name: 4. Build Trio
run-name: Build Trio (${{ github.ref_name }})
on:
  workflow_dispatch:
  schedule:
    # Check for updates every Sunday
    #   Later logic builds if there are updates or if it is the 2nd Sunday of the month
    - cron: "43 6 * * 0" # Sunday at UTC 06:43

env:
  GH_PAT: ${{ secrets.GH_PAT }}
  UPSTREAM_REPO: nightscout/Trio
  UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
  TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync

jobs:
  # use a single runner for these sequential steps
  check_status:
    runs-on: ubuntu-latest
    name: Check status to decide whether to build
    permissions:
      contents: write
    outputs:
      NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
      IS_SECOND_IN_MONTH: ${{ steps.date-check.outputs.is_second_instance }}

    # Check GH_PAT, sync repository, check day in month
    steps:

      - name: Access
        id: workflow-permission
        run: |
          # Validate Access Token
          
          # Ensure that gh exit codes are handled when output is piped.
          set -o pipefail
          
          # Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
          GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
          GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
          
          # Validate Access Token (GH_PAT)
          if [ -z "$GH_PAT" ]; then
            failed=true
            echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
          else
            if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
              provides_scopes=true
              echo "The GH_PAT secret is a structurally valid classic token."
            elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
              echo "The GH_PAT secret is a structurally valid fine-grained token."
            else
              unknown_format=true
              echo "The GH_PAT secret does not have a known token format."
            fi
            
            # Attempt to capture the x-oauth-scopes scopes of the token.
            if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then
              failed=true
              if [ $unknown_format ]; then
                echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again."
              else
                echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again."
              fi
            elif [[ $scopes =~ workflow ]]; then
              echo "The GH_PAT secret has repo and workflow permissions."
              echo "has_permission=true" >> $GITHUB_OUTPUT
            elif [[ $scopes =~ repo ]]; then
              echo "The GH_PAT secret has repo (but not workflow) permissions."
            elif [ $provides_scopes ]; then
              failed=true
              if [ -z "$scopes" ]; then
                echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
              else
                echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
              fi
              echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again."
            else
              echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present."
              echo "has_permission=true" >> $GITHUB_OUTPUT
            fi
          fi
          
          # Exit unsuccessfully if secret validation failed.
          if [ $failed ]; then
            exit 2
          fi

      - name: Checkout target repo
        if: |
          steps.workflow-permission.outputs.has_permission == 'true' &&
          (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GH_PAT }}

      # This syncs any target branch to upstream branch of the same name
      - name: Sync upstream changes
        if: | # do not run the upstream sync action on the upstream repository
          steps.workflow-permission.outputs.has_permission == 'true' &&
          vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'nightscout'
        id: sync
        uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
        with:
          target_sync_branch: ${{ env.TARGET_BRANCH }}
          shallow_since: 6 months ago
          target_repo_token: ${{ secrets.GH_PAT }}
          upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
          upstream_sync_repo: ${{ env.UPSTREAM_REPO }}

      # Display a sample message based on the sync output var 'has_new_commits'
      - name: New commits found
        if: |
          steps.workflow-permission.outputs.has_permission == 'true' &&
          vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
        run: echo "New commits were found to sync."

      - name: No new commits
        if: |
          steps.workflow-permission.outputs.has_permission == 'true' &&
          vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
        run: echo "There were no new commits."

      - name: Show value of 'has_new_commits'
        if: steps.workflow-permission.outputs.has_permission == 'true' && vars.SCHEDULED_SYNC != 'false'
        run: |
          echo ${{ steps.sync.outputs.has_new_commits }}
          echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT

      - name: Show scheduled build configuration message
        if: steps.workflow-permission.outputs.has_permission != 'true'
        run: |
          echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
          echo "You have not yet configured the scheduled sync and build for Trio's browser build." >> $GITHUB_STEP_SUMMARY
          echo "Synchronizing your fork of <code>Trio</code> with the upstream repository <code>nightscout/Trio</code> will be skipped." >> $GITHUB_STEP_SUMMARY
          echo "If you want to enable automatic builds and updates for your Trio, please follow the instructions \
              under the following path <code>Trio/fastlane/testflight.md</code>." >> $GITHUB_STEP_SUMMARY

      # Set a logic flag if this is the second instance of this day-of-week in this month
      - name: Check if this is the second time this day-of-week happens this month
        id: date-check
        run: |
          DAY_OF_MONTH=$(date +%-d)
          WEEK_OF_MONTH=$(( ($(date +%-d) - 1) / 7 + 1 ))
          if [[ $WEEK_OF_MONTH -eq 2 ]]; then
            echo "is_second_instance=true" >> "$GITHUB_OUTPUT"
          else
            echo "is_second_instance=false" >> "$GITHUB_OUTPUT"
          fi

  # Checks if Distribution certificate is present and valid, optionally nukes and
  # creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
  # only run if a build is planned
  check_certs:
      needs: [check_status]
      name: Check certificates
      uses: ./.github/workflows/create_certs.yml
      secrets: inherit
      if: |
        github.event_name == 'workflow_dispatch' ||
          (vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
          (vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )

  # Builds Trio
  build:
    name: Build
    needs: [check_certs, check_status]
    runs-on: macos-15
    permissions:
      contents: write
    if:
      | # builds with manual start; if scheduled: once a month or when new commits are found
      github.event_name == 'workflow_dispatch' ||
        (vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
        (vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
    steps:
      - name: Select Xcode version
        run: "sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer"
      
      - name: Checkout Repo for building
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GH_PAT }}
          submodules: recursive
          ref: ${{ env.TARGET_BRANCH }}

      # Customize Trio: Use patches or download and apply patches from GitHub
      - name: Customize Trio
        run: |

          # Trio workspace patches
          # -applies any patches located in the Trio/patches/ directory
          if $(ls ./patches/* &> /dev/null); then
          git apply ./patches/* --allow-empty -v --whitespace=fix
          fi

          # Download and apply Trio patches from GitHub:
          # Template for customizing Trio code (as opposed to submodule code)
          # Remove the "#" sign from the beginning of the line below to activate
          #   and then replace the alphanumeric string with your SHA, this SHA is NOT valid
          #curl https://github.com/nightscout/Trio/commit/d206432b024279ef710df462b20bd464cd9682d4.patch | git apply -v --whitespace=fix

          # Download and apply Submodule patches from GitHub:
          # Template for customizing submodules (you must edit the submodule name)
          # This example is for G7SensorKit showing you can apply multiple commits, in the proper order
          # Remove the "#" sign from the beginning of the lines below to activate
          # This example applies 3 commits from the scan-fix folder; valid only when these are not already in Trio
          #curl https://github.com/loopandlearn/G7SensorKit/commit/ba44beb3d1491c453f4f438443c3f8ba29146ab3.patch | git apply --directory=G7SensorKit -v --whitespace=fix
          #curl https://github.com/loopandlearn/G7SensorKit/commit/d86ac8e9cd523d1267587dd70c96597125eef7ab.patch | git apply --directory=G7SensorKit -v --whitespace=fix
          #curl https://github.com/loopandlearn/G7SensorKit/commit/205054e7537723c2aec58d807634b4853f687244.patch | git apply --directory=G7SensorKit -v --whitespace=fix

          # Add patches for additional customization by following the templates above,
          # and make sure to specify the submodule by setting "--directory=(submodule_name)".
          # Several patches may be added per submodule.
          # Adding comments (#) is strongly recommended to easily tell the individual patches apart.

      # Patch Fastlane Match to not print tables
      - name: Patch Match Tables
        run: |
          TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb
          if [ -f "$TABLE_PRINTER_PATH" ]; then
            sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH"
          else
            echo "table_printer.rb not found"
            exit 1
          fi

      # Install project dependencies
      - name: Install Project Dependencies
        run: bundle install

      # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
      - name: Sync clock
        run: sudo sntp -sS time.windows.com

      # Build signed Trio IPA file
      - name: Fastlane Build & Archive
        run: bundle exec fastlane build_trio
        env:
          TEAMID: ${{ secrets.TEAMID }}
          GH_PAT: ${{ secrets.GH_PAT }}
          FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
          FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
          FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

      # Upload to TestFlight
      - name: Fastlane upload to TestFlight
        run: bundle exec fastlane release
        env:
          TEAMID: ${{ secrets.TEAMID }}
          GH_PAT: ${{ secrets.GH_PAT }}
          FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
          FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
          FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

      # Upload Build artifacts
      - name: Upload build log, IPA and Symbol artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: |
            artifacts
            buildlog
