name: Notify Telegram

# tauri-action creates the release with the default GITHUB_TOKEN, and GitHub
# Actions deliberately suppresses `release: published` events for releases
# made by GITHUB_TOKEN (to prevent recursive workflow chains). So we can't
# listen for `release: published` — it will never fire on stable releases.
#
# Instead, chain off the Release workflow via `workflow_run`, the same way
# `publish-repos.yml` does. `workflow_dispatch` is kept so a missed
# announcement can be replayed by hand.
on:
  workflow_dispatch:
    inputs:
      tag:
        description: "Release tag to announce (e.g. v0.23.0). Leave empty for latest stable."
        required: false
        type: string
  workflow_run:
    workflows: ["Release"]
    types:
      - completed

permissions:
  contents: read
  models: read

jobs:
  notify:
    if: >
      github.repository == 'zhom/donutbrowser' &&
      (github.event_name == 'workflow_dispatch' ||
       github.event.workflow_run.conclusion == 'success')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: main
          fetch-depth: 0

      - name: Resolve release tag
        id: tag
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          INPUT_TAG: ${{ inputs.tag }}
          # `head_branch` of a workflow_run trigger is attacker-influenceable
          # (anyone with push to a tag can choose its name), so we pass it via
          # env and validate before use rather than splicing it into the
          # shell script literally. See CodeQL actions/code-injection.
          EVENT_NAME: ${{ github.event_name }}
          WORKFLOW_RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
          REPO: ${{ github.repository }}
        run: |
          if [[ -n "${INPUT_TAG:-}" ]]; then
            TAG="${INPUT_TAG}"
          elif [[ "${EVENT_NAME}" == "workflow_run" ]]; then
            # The Release workflow runs on `push: tags: v*` so head_branch
            # of the triggering run is the tag name. Reject anything that
            # isn't a plain tag-shaped string to keep this resistant to
            # shell metacharacters injected via a crafted ref name.
            if [[ ! "${WORKFLOW_RUN_HEAD_BRANCH}" =~ ^[A-Za-z0-9._/-]+$ ]]; then
              echo "::error::Refusing tag with unexpected characters: ${WORKFLOW_RUN_HEAD_BRANCH}"
              exit 1
            fi
            TAG="${WORKFLOW_RUN_HEAD_BRANCH}"
          else
            TAG=$(gh release view --repo "${REPO}" --json tagName -q .tagName)
          fi
          echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
          echo "Resolved tag: ${TAG}"

      - name: Skip pre-releases / missing releases
        id: gate
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG: ${{ steps.tag.outputs.tag }}
        run: |
          # Tag like `nightly-…` or `nightly` is never an announceable
          # stable release. Short-circuit before hitting the API.
          if [[ "${TAG}" == nightly* ]]; then
            echo "Tag '${TAG}' is a rolling/nightly build, skipping Telegram post."
            echo "skip=true" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          # Only stable semver tags vX.Y.Z are eligible. Reject anything
          # with a pre-release suffix (`-rc1`, `-beta`, etc.).
          if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "Tag '${TAG}' is not a stable semver tag, skipping."
            echo "skip=true" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          # Confirm the release exists and isn't marked prerelease in the
          # GitHub UI — guards against someone manually flipping the flag.
          RELEASE_JSON=$(gh release view "${TAG}" --repo "${{ github.repository }}" --json isPrerelease,tagName 2>/dev/null || echo "")
          if [[ -z "${RELEASE_JSON}" ]]; then
            echo "Release ${TAG} not found via gh — skipping."
            echo "skip=true" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          IS_PRE=$(jq -r .isPrerelease <<< "${RELEASE_JSON}")
          if [[ "${IS_PRE}" == "true" ]]; then
            echo "Release ${TAG} is marked prerelease, skipping."
            echo "skip=true" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          echo "skip=false" >> "$GITHUB_OUTPUT"

      - name: Collect commits between previous tag and current tag
        id: commits
        if: steps.gate.outputs.skip != 'true'
        env:
          TAG: ${{ steps.tag.outputs.tag }}
        run: |
          PREV_TAG=$(git tag --sort=-version:refname \
            | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
            | grep -v "^${TAG}$" \
            | head -n 1)
          if [ -z "$PREV_TAG" ]; then
            PREV_TAG=$(git rev-list --max-parents=0 HEAD)
          fi
          git log --pretty=format:"- %s (%h)" "${PREV_TAG}..${TAG}" --no-merges > commits.txt
          echo "previous-tag=${PREV_TAG}" >> "$GITHUB_OUTPUT"
          echo "Collected $(wc -l < commits.txt) commits between ${PREV_TAG} and ${TAG}."

      - name: Generate summary with AI
        id: ai
        if: steps.gate.outputs.skip != 'true'
        uses: actions/ai-inference@a7805884c80886efc241e94a5351df715968a0ad # v2.1.1
        with:
          prompt-file: .github/prompts/telegram-release-summary.prompt.yml
          input: |
            version: ${{ steps.tag.outputs.tag }}
          file_input: |
            commits: ./commits.txt
          max-tokens: 1024

      - name: Post release announcement to Telegram
        if: steps.gate.outputs.skip != 'true'
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
          TAG: ${{ steps.tag.outputs.tag }}
          REPO: ${{ github.repository }}
          AI_RESPONSE_FILE: ${{ steps.ai.outputs.response-file }}
          AI_RESPONSE: ${{ steps.ai.outputs.response }}
        run: |
          if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
            echo "::warning::TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set — skipping Telegram notification."
            exit 0
          fi

          # Prefer the file output — `response` can be truncated for longer summaries.
          if [ -n "$AI_RESPONSE_FILE" ] && [ -f "$AI_RESPONSE_FILE" ]; then
            SUMMARY=$(cat "$AI_RESPONSE_FILE")
          else
            SUMMARY="$AI_RESPONSE"
          fi

          if [ -z "${SUMMARY//[[:space:]]/}" ]; then
            echo "::error::AI summary is empty"
            exit 1
          fi

          # HTML-escape the AI summary before injecting into Telegram HTML mode —
          # commit messages can legitimately contain `<`, `>`, `&` and the AI may echo them.
          ESCAPED_CHANGES=$(printf '%s' "$SUMMARY" \
            | python3 -c "import html, sys; sys.stdout.write(html.escape(sys.stdin.read()))")

          VERSION="${TAG}"
          VERSION_NUM="${TAG#v}"
          RELEASE_URL="https://github.com/${REPO}/releases/tag/${VERSION}"
          DL="https://github.com/${REPO}/releases/download/${VERSION}"

          # Build the API payload in one jq pass — keeps every literal
          # newline, every angle bracket, and every quote correctly escaped
          # for both shell and JSON.
          PAYLOAD=$(jq -n \
            --arg chat_id "$TELEGRAM_CHAT_ID" \
            --arg version "$VERSION" \
            --arg changes "$ESCAPED_CHANGES" \
            --arg dl "$DL" \
            --arg vnum "$VERSION_NUM" \
            --arg release_url "$RELEASE_URL" \
            '{
              chat_id: $chat_id,
              parse_mode: "HTML",
              disable_web_page_preview: true,
              text: (
                "<b>Donut Browser " + $version + " released</b>\n\n" +
                $changes + "\n" +
                "<b>Download</b>\n" +
                "<a href=\"" + $dl + "/Donut_" + $vnum + "_aarch64.dmg\">macOS (Apple Silicon)</a> · " +
                "<a href=\"" + $dl + "/Donut_" + $vnum + "_x64.dmg\">macOS (Intel)</a>\n" +
                "<a href=\"" + $dl + "/Donut_" + $vnum + "_x64-setup.exe\">Windows x64</a> · " +
                "<a href=\"" + $dl + "/Donut_" + $vnum + "_amd64.AppImage\">Linux x64</a>\n\n" +
                "<a href=\"" + $release_url + "\">Full release notes</a>"
              )
            }')

          # Use --fail-with-body so we surface Telegram's error JSON on 4xx/5xx
          # instead of just a curl exit code.
          RESPONSE=$(curl -sSL --fail-with-body \
            -H "Content-Type: application/json" \
            -d "$PAYLOAD" \
            "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage") \
            || { echo "::error::Telegram API call failed"; echo "$RESPONSE"; exit 1; }

          if [ "$(jq -r .ok <<< "$RESPONSE")" != "true" ]; then
            echo "::error::Telegram API rejected the message:"
            jq . <<< "$RESPONSE"
            exit 1
          fi

          echo "Posted to Telegram (message_id $(jq -r .result.message_id <<< "$RESPONSE"))"
