name: Release macOS app (notarized, self-hosted)
run-name: notarized release (self-hosted) ${{ inputs.tag }}

on:
  workflow_dispatch:
    inputs:
      tag:
        description: "Existing Git tag to release (e.g. v0.4.6)"
        required: true
        type: string
      use_self_hosted:
        description: "Allow self-hosted runner usage (requires trusted release context)"
        required: true
        default: false
        type: boolean

permissions:
  actions: read
  contents: write
  issues: write

jobs:
  release:
    if: ${{ inputs.use_self_hosted == true }}
    timeout-minutes: 180
    runs-on: [self-hosted, macOS]
    environment: self-hosted-release
    concurrency:
      group: release-${{ inputs.tag }}
      cancel-in-progress: true

    steps:
      - name: Validate trusted self-hosted request
        env:
          TAG_NAME: ${{ inputs.tag }}
          REPO: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          set -euo pipefail
          SERVER_HOST="${SERVER_URL#https://}"
          git init
          REMOTE_URL="https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/${REPO}.git"
          if git remote get-url origin >/dev/null 2>&1; then
            git remote set-url origin "$REMOTE_URL"
          else
            git remote add origin "$REMOTE_URL"
          fi
          git fetch --force --depth=1 origin "+refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}"
          git fetch --force --depth=1 origin "+refs/heads/main:refs/remotes/origin/main"
          TAG_SHA="$(git rev-list -n1 "refs/tags/${TAG_NAME}")"
          MAIN_SHA="$(git rev-list -n1 "refs/remotes/origin/main")"
          echo "Tag SHA: ${TAG_SHA}"
          echo "Main SHA: ${MAIN_SHA}"
          if [[ "${TAG_SHA}" != "${MAIN_SHA}" ]]; then
            echo "Self-hosted releases are only allowed for tags that point to origin/main HEAD." >&2
            exit 1
          fi

      - name: Checkout tag
        env:
          TAG_NAME: ${{ inputs.tag }}
          REPO: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          set -euo pipefail
          SERVER_HOST="${SERVER_URL#https://}"
          if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
            # Keep self-hosted runners deterministic by wiping any prior workspace state.
            git reset --hard || true
            git clean -fdx || true
          else
            git init
          fi
          REMOTE_URL="https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/${REPO}.git"
          if git remote get-url origin >/dev/null 2>&1; then
            git remote set-url origin "$REMOTE_URL"
          else
            git remote add origin "$REMOTE_URL"
          fi
          git fetch --depth=1 origin "refs/tags/${TAG_NAME}"
          git checkout -f FETCH_HEAD

      - name: Normalize shell script line endings
        run: |
          set -euo pipefail
          find scripts -type f -name "*.sh" -exec perl -pi -e 's/\r$//' {} +
          chmod +x scripts/ci/select_xcode17.sh

      - name: Select/verify Xcode 17+
        run: |
          set -euo pipefail
          if [[ ! -x scripts/ci/select_xcode17.sh ]]; then
            echo "Missing scripts/ci/select_xcode17.sh in checked-out tag. Production notarized release requires Xcode 17+ selection." >&2
            exit 1
          fi
          bash scripts/ci/select_xcode17.sh

      - name: Import signing certificate
        env:
          MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
          MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          set -euo pipefail
          KEYCHAIN="$RUNNER_TEMP/build.keychain-db"
          ORIGINAL_LIST="$RUNNER_TEMP/original-user-keychains.txt"
          ORIGINAL_DEFAULT="$RUNNER_TEMP/original-default-keychain.txt"
          ORIGINAL_LOGIN="$RUNNER_TEMP/original-login-keychain.txt"

          echo "$MACOS_CERT_P12" | base64 --decode > cert.p12

          security list-keychains -d user > "$ORIGINAL_LIST" || true
          security default-keychain -d user > "$ORIGINAL_DEFAULT" || true
          security login-keychain -d user > "$ORIGINAL_LOGIN" || true

          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
          security import cert.p12 -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign
          EXISTING_KEYCHAINS=()
          while IFS= read -r keychain_path; do
            [[ -n "$keychain_path" ]] && EXISTING_KEYCHAINS+=("$keychain_path")
          done < <(security list-keychains -d user | sed -E 's/^[[:space:]]*"?(.*)"?[[:space:]]*$/\1/' | awk 'NF')
          if (( ${#EXISTING_KEYCHAINS[@]} > 0 )); then
            security list-keychains -d user -s "$KEYCHAIN" "${EXISTING_KEYCHAINS[@]}"
          else
            security list-keychains -d user -s "$KEYCHAIN"
          fi

      - name: Build archive
        env:
          TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          ARCHIVE_PATH: ${{ runner.temp }}/NeonVisionEditor.xcarchive
        run: |
          set -euo pipefail
          xcodebuild \
            -project "Neon Vision Editor.xcodeproj" \
            -scheme "Neon Vision Editor" \
            -configuration Release \
            -destination "generic/platform=macOS" \
            -archivePath "$ARCHIVE_PATH" \
            CODE_SIGN_STYLE=Manual \
            DEVELOPMENT_TEAM="$TEAM_ID" \
            CODE_SIGN_IDENTITY="Developer ID Application" \
            archive

      - name: Export app
        env:
          TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          ARCHIVE_PATH: ${{ runner.temp }}/NeonVisionEditor.xcarchive
          EXPORT_PATH: ${{ runner.temp }}/export
        run: |
          set -euo pipefail
          mkdir -p "$EXPORT_PATH"

          cat > export.plist <<EOF
          <?xml version="1.0" encoding="UTF-8"?>
          <plist version="1.0">
          <dict>
            <key>method</key><string>developer-id</string>
            <key>teamID</key><string>${TEAM_ID}</string>
          </dict>
          </plist>
          EOF

          xcodebuild -exportArchive \
            -archivePath "$ARCHIVE_PATH" \
            -exportPath "$EXPORT_PATH" \
            -exportOptionsPlist export.plist

      - name: Verify app icon payload
        env:
          EXPORT_PATH: ${{ runner.temp }}/export
          REQUIRE_ICONSTACK: "1"
        run: |
          set -euo pipefail
          APP="$EXPORT_PATH/Neon Vision Editor.app"
          scripts/ci/verify_icon_payload.sh "$APP"

      - name: Notarize
        env:
          EXPORT_PATH: ${{ runner.temp }}/export
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
        run: |
          set -euo pipefail
          APP="$EXPORT_PATH/Neon Vision Editor.app"

          ditto -c -k --keepParent "$APP" submit.zip

          xcrun notarytool submit submit.zip \
            --apple-id "$APPLE_ID" \
            --team-id "$APPLE_TEAM_ID" \
            --password "$APPLE_APP_SPECIFIC_PASSWORD" \
            --wait

          xcrun stapler staple "$APP"

      - name: Zip notarized app
        env:
          EXPORT_PATH: ${{ runner.temp }}/export
        run: |
          set -euo pipefail
          ditto -c -k --keepParent "$EXPORT_PATH/Neon Vision Editor.app" Neon.Vision.Editor.app.zip

      - name: Create DMG release asset
        env:
          EXPORT_PATH: ${{ runner.temp }}/export
        run: |
          set -euo pipefail
          DMG_STAGE="$(mktemp -d)"
          cp -R "$EXPORT_PATH/Neon Vision Editor.app" "$DMG_STAGE/"
          ln -s /Applications "$DMG_STAGE/Applications"
          hdiutil create \
            -volname "Neon Vision Editor" \
            -srcfolder "$DMG_STAGE" \
            -ov \
            -format UDZO \
            Neon.Vision.Editor.app.dmg
          rm -rf "$DMG_STAGE"

      - name: Extract changelog section
        env:
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          ./scripts/extract_changelog_section.sh CHANGELOG.md "$TAG_NAME" > release-notes.md

      - name: Validate release docs are in sync
        env:
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          if grep -nE "^- TODO\r?$" release-notes.md >/dev/null; then
            echo "CHANGELOG section for ${TAG_NAME} still contains TODO entries." >&2
            exit 1
          fi
          grep -nE "^> Latest release: \\*\\*${TAG_NAME}\\*\\*\r?$" README.md >/dev/null
          grep -nE "^- Latest release: \\*\\*${TAG_NAME}\\*\\*\r?$" README.md >/dev/null
          grep -nE "^\\| .*\\(https://github\\.com/h3pdesign/Neon-Vision-Editor/releases/tag/${TAG_NAME}\\) \\|" README.md >/dev/null

      - name: Create or Update GitHub Release
        id: publish_release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail

          if gh release view "$TAG_NAME" >/dev/null 2>&1; then
            gh release upload "$TAG_NAME" Neon.Vision.Editor.app.zip Neon.Vision.Editor.app.dmg --clobber
            gh release edit "$TAG_NAME" --title "Neon Vision Editor $TAG_NAME" --notes-file release-notes.md
          else
            gh release create "$TAG_NAME" Neon.Vision.Editor.app.zip Neon.Vision.Editor.app.dmg \
              --title "Neon Vision Editor $TAG_NAME" \
              --notes-file release-notes.md
          fi

      - name: Verify published release asset payload
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG_NAME: ${{ inputs.tag }}
          REQUIRE_ICONSTACK: "1"
        run: |
          set -euo pipefail
          WORK_DIR="$(mktemp -d)"
          MOUNT_POINT=""
          cleanup_mount() {
            if [[ -n "${MOUNT_POINT}" ]]; then
              hdiutil detach "${MOUNT_POINT}" -quiet || true
            fi
            rm -rf "$WORK_DIR"
          }
          trap cleanup_mount EXIT
          gh release download "$TAG_NAME" -p Neon.Vision.Editor.app.zip -p Neon.Vision.Editor.app.dmg -D "$WORK_DIR"
          ditto -x -k "$WORK_DIR/Neon.Vision.Editor.app.zip" "$WORK_DIR/extracted"
          /usr/bin/codesign --verify --deep --strict "$WORK_DIR/extracted/Neon Vision Editor.app"
          scripts/ci/verify_icon_payload.sh "$WORK_DIR/extracted/Neon Vision Editor.app"
          MOUNT_POINT="$WORK_DIR/dmg-mount"
          mkdir -p "$MOUNT_POINT"
          hdiutil attach "$WORK_DIR/Neon.Vision.Editor.app.dmg" -nobrowse -mountpoint "$MOUNT_POINT" -quiet
          if [[ ! -d "$MOUNT_POINT/Neon Vision Editor.app" ]]; then
            echo "Mounted DMG does not contain app bundle." >&2
            exit 1
          fi
          /usr/bin/codesign --verify --deep --strict "$MOUNT_POINT/Neon Vision Editor.app"
          scripts/ci/verify_icon_payload.sh "$MOUNT_POINT/Neon Vision Editor.app"

      - name: Roll back broken published release asset
        if: ${{ failure() && steps.publish_release.outcome == 'success' }}
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          gh release delete-asset "$TAG_NAME" "Neon.Vision.Editor.app.zip" -y || true
          gh release delete-asset "$TAG_NAME" "Neon.Vision.Editor.app.dmg" -y || true
          gh release edit "$TAG_NAME" --draft true || true
          echo "Rolled back release asset and marked release as draft due to post-publish verification failure."

      - name: Create release-failure alert issue
        if: ${{ failure() }}
        continue-on-error: true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          gh issue create \
            --title "Release failure: ${TAG_NAME} (run ${GITHUB_RUN_ID})" \
            --body "Automated release failed for ${TAG_NAME}. Run URL: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
            --label "release-alert"

      - name: Trigger homebrew-tap update
        env:
          GH_TOKEN: ${{ secrets.TAP_BOT_TOKEN }}
          TAG_NAME: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          echo "Waiting for release API consistency for ${TAG_NAME}..."
          for attempt in {1..12}; do
            if gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}" >/dev/null 2>&1; then
              echo "Release tag ${TAG_NAME} is visible (attempt ${attempt})."
              break
            fi
            if [[ "$attempt" -eq 12 ]]; then
              echo "Release tag ${TAG_NAME} still not visible after retries; dispatching anyway." >&2
              break
            fi
            sleep 5
          done

          dispatch_homebrew() {
            gh api repos/h3pdesign/homebrew-tap/dispatches \
              -f event_type=notarized_release \
              -f client_payload[tag]="$TAG_NAME"
          }

          wait_for_tap_run() {
            local start_ts="$1"
            local run_id=""
            for poll in {1..24}; do
              run_id="$(gh run list -R h3pdesign/homebrew-tap \
                --workflow update-cask.yml \
                --event repository_dispatch \
                --limit 20 \
                --json databaseId,displayTitle,createdAt \
                --jq ".[] | select(.displayTitle == \"notarized_release\") | select(.createdAt >= \"${start_ts}\") | .databaseId" | head -n1 || true)"
              if [[ -n "${run_id}" ]]; then
                echo "${run_id}"
                return 0
              fi
              sleep 5
            done
            return 1
          }

          wait_for_tap_completion() {
            local run_id="$1"
            gh run watch -R h3pdesign/homebrew-tap "${run_id}" || true
            local conclusion
            conclusion="$(gh run view -R h3pdesign/homebrew-tap "${run_id}" --json conclusion --jq .conclusion || true)"
            if [[ "${conclusion}" == "success" ]]; then
              return 0
            fi
            echo "homebrew-tap run ${run_id} concluded with '${conclusion:-unknown}'." >&2
            return 1
          }

          for dispatch_attempt in {1..3}; do
            dispatch_start="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
            if ! dispatch_homebrew; then
              echo "homebrew-tap dispatch failed on attempt ${dispatch_attempt}." >&2
              sleep $((dispatch_attempt * 5))
              continue
            fi
            echo "homebrew-tap dispatch accepted (attempt ${dispatch_attempt})."

            run_id="$(wait_for_tap_run "${dispatch_start}" || true)"
            if [[ -z "${run_id}" ]]; then
              echo "No homebrew-tap run appeared after dispatch attempt ${dispatch_attempt}." >&2
              sleep $((dispatch_attempt * 5))
              continue
            fi

            if wait_for_tap_completion "${run_id}"; then
              echo "homebrew-tap run ${run_id} succeeded."
              exit 0
            fi

            if [[ "${dispatch_attempt}" -lt 3 ]]; then
              sleep $((dispatch_attempt * 10))
            fi
          done

          echo "Failed to complete homebrew-tap update after retries." >&2
          exit 1

      - name: Restore user keychain state
        if: ${{ always() }}
        run: |
          set -euo pipefail
          KEYCHAIN="$RUNNER_TEMP/build.keychain-db"
          ORIGINAL_LIST="$RUNNER_TEMP/original-user-keychains.txt"
          ORIGINAL_DEFAULT="$RUNNER_TEMP/original-default-keychain.txt"
          ORIGINAL_LOGIN="$RUNNER_TEMP/original-login-keychain.txt"

          if [[ -f "$ORIGINAL_LIST" ]]; then
            RESTORE_LIST=()
            while IFS= read -r keychain_path; do
              [[ -n "$keychain_path" ]] && RESTORE_LIST+=("$keychain_path")
            done < <(sed -E 's/^[[:space:]]*"?(.*)"?[[:space:]]*$/\1/' "$ORIGINAL_LIST" | awk 'NF')
            if (( ${#RESTORE_LIST[@]} > 0 )); then
              security list-keychains -d user -s "${RESTORE_LIST[@]}" || true
            fi
          fi

          if [[ -f "$ORIGINAL_DEFAULT" ]]; then
            DEFAULT_KEYCHAIN="$(sed -E 's/^[[:space:]]*"?(.*)"?[[:space:]]*$/\1/' "$ORIGINAL_DEFAULT" | head -n1)"
            if [[ -n "${DEFAULT_KEYCHAIN:-}" ]]; then
              security default-keychain -d user -s "$DEFAULT_KEYCHAIN" || true
            fi
          fi

          if [[ -f "$ORIGINAL_LOGIN" ]]; then
            LOGIN_KEYCHAIN="$(sed -E 's/^[[:space:]]*"?(.*)"?[[:space:]]*$/\1/' "$ORIGINAL_LOGIN" | head -n1)"
            if [[ -n "${LOGIN_KEYCHAIN:-}" ]]; then
              security login-keychain -d user -s "$LOGIN_KEYCHAIN" || true
            fi
          fi

          security delete-keychain "$KEYCHAIN" || true
          rm -f cert.p12
