name: Changelog

on:
  pull_request:
    branches: [main]
    types: [opened, synchronize]

permissions: {}

jobs:
  update-changelog:
    if: >-
      github.event.pull_request.user.login != 'github-actions[bot]'
      && github.event.pull_request.user.login != 'dependabot[bot]'
      && !startsWith(github.head_ref, 'chore/bump-version-to-')
    runs-on: ubuntu-latest
    permissions:
      contents: write
      models: read
      pull-requests: read
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ github.head_ref }}
          token: ${{ secrets.PAT }}
          fetch-depth: 0

      - name: Check if last commit is from bot
        id: check
        run: |
          LAST_AUTHOR=$(git log -1 --format='%an')
          if [ "$LAST_AUTHOR" = "github-actions[bot]" ]; then
            echo "skip=true" >> "$GITHUB_OUTPUT"
          else
            echo "skip=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Get PR info
        if: steps.check.outputs.skip == 'false'
        id: pr
        env:
          GH_TOKEN: ${{ github.token }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_TITLE: ${{ github.event.pull_request.title }}
        run: |
          FILES=$(gh pr diff "$PR_NUMBER" --name-only)
          COMMITS=$(gh pr view "$PR_NUMBER" --json commits --jq '.commits[].messageHeadline' | head -20)

          {
            echo "files<<FILES_EOF"
            echo "$FILES"
            echo "FILES_EOF"
          } >> "$GITHUB_OUTPUT"

          {
            echo "commits<<COMMITS_EOF"
            echo "$COMMITS"
            echo "COMMITS_EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Extract existing changelog entry
        if: steps.check.outputs.skip == 'false'
        id: existing
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
        shell: python
        run: |
          import os

          pr_number = os.environ["PR_NUMBER"]
          marker = f"<!-- PR #{pr_number} -->"

          with open("CHANGELOG.md") as f:
              content = f.read()

          existing = ""
          if content.count(marker) >= 2:
              start = content.index(marker) + len(marker)
              end = content.index(marker, start)
              existing = content[start:end].strip()

          with open(os.environ["GITHUB_OUTPUT"], "a") as f:
              f.write(f"entry<<EXISTING_EOF\n{existing}\nEXISTING_EOF\n")

      - name: Generate changelog entry with AI
        if: steps.check.outputs.skip == 'false'
        id: ai
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_TITLE: ${{ github.event.pull_request.title }}
          PR_FILES: ${{ steps.pr.outputs.files }}
          PR_COMMITS: ${{ steps.pr.outputs.commits }}
          EXISTING_ENTRY: ${{ steps.existing.outputs.entry }}
        run: |
          PROMPT=$(cat <<'PROMPT_END'
          You are a changelog writer for "dature" — a type-safe configuration loader for Python dataclasses.

          You will receive the PR context AND an existing changelog entry (if any).
          Your job:
          1. PRESERVE all existing bullet points — do not remove any.
          2. You may rephrase existing entries for clarity or consistency, but keep the same meaning.
          3. Add new bullet points for changes not yet covered by existing entries.
          4. Remove duplicate or redundant entries (keep the better-worded one).
          5. Group entries under markdown headings (### Features, ### Improvements, ### Fixes, ### Docs) if there are 4+ entries.

          Format: each line starts with "- " followed by a short description in past tense.
          Do not include PR numbers, commit hashes, or author names.
          Only output the changelog entry, nothing else.
          PROMPT_END
          )

          BODY=$(jq -n \
            --arg prompt "$PROMPT" \
            --arg title "$PR_TITLE" \
            --arg commits "$PR_COMMITS" \
            --arg files "$PR_FILES" \
            --arg existing "$EXISTING_ENTRY" \
            '{
              "model": "openai/gpt-4o-mini",
              "messages": [
                {"role": "system", "content": $prompt},
                {"role": "user", "content": ("PR title: " + $title + "\n\nCommits:\n" + $commits + "\n\nChanged files:\n" + $files + "\n\nExisting changelog entry:\n" + $existing)}
              ],
              "temperature": 0.3
            }')

          RESPONSE=$(curl -s "https://models.github.ai/inference/chat/completions" \
            -H "Content-Type: application/json" \
            -H "Authorization: Bearer $GITHUB_TOKEN" \
            -d "$BODY")

          ENTRY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // empty')

          if [ -z "$ENTRY" ]; then
            echo "AI generation failed, falling back to PR title"
            ENTRY="- $PR_TITLE"
          fi

          {
            echo "entry<<ENTRY_EOF"
            echo "$ENTRY"
            echo "ENTRY_EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Update CHANGELOG.md
        if: steps.check.outputs.skip == 'false'
        env:
          ENTRY: ${{ steps.ai.outputs.entry }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        shell: python
        run: |
          import os

          pr_number = os.environ["PR_NUMBER"]
          entry = os.environ["ENTRY"]
          marker = f"<!-- PR #{pr_number} -->"

          with open("CHANGELOG.md") as f:
              content = f.read()

          # Remove previous entry for this PR (between markers)
          while content.count(marker) >= 2:
              start = content.index(marker)
              end = content.index(marker, start + len(marker)) + len(marker)
              # Remove the block including trailing newline
              content = content[:start] + content[end:].lstrip("\n")

          # Remove orphaned marker if odd number remained
          content = content.replace(marker + "\n", "").replace(marker, "")

          # Insert new entry after "## Unreleased"
          heading = "## Unreleased"
          insertion = f"{marker}\n{entry}\n{marker}"
          content = content.replace(heading, f"{heading}\n\n{insertion}", 1)

          with open("CHANGELOG.md", "w") as f:
              f.write(content)

      - name: Commit and push
        if: steps.check.outputs.skip == 'false'
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add CHANGELOG.md
          git diff --cached --quiet && exit 0
          git commit -m "changelog: update for PR #$PR_NUMBER"
          git push

  release-changelog:
    if: startsWith(github.head_ref, 'chore/bump-version-to-')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ github.head_ref }}
          token: ${{ secrets.PAT }}

      - name: Extract version from branch name
        id: version
        run: |
          VERSION="${GITHUB_HEAD_REF#chore/bump-version-to-}"
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"

      - name: Check if already released
        id: check
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          if grep -q "^## $VERSION" CHANGELOG.md; then
            echo "skip=true" >> "$GITHUB_OUTPUT"
          else
            echo "skip=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Move Unreleased to version section
        if: steps.check.outputs.skip == 'false'
        env:
          VERSION: ${{ steps.version.outputs.version }}
        shell: python
        run: |
          import os
          import re

          version = os.environ["VERSION"]

          with open("CHANGELOG.md") as f:
              content = f.read()

          unreleased_heading = "## Unreleased"
          idx = content.index(unreleased_heading)
          after_heading = idx + len(unreleased_heading)

          # Find the next ## heading (or end of file)
          next_section = re.search(r"^## ", content[after_heading:], re.MULTILINE)
          if next_section:
              end = after_heading + next_section.start()
          else:
              end = len(content)

          raw = content[after_heading:end].strip()

          # Remove PR markers
          raw = re.sub(r"<!-- PR #\d+ -->\n?", "", raw).strip()

          if not raw:
              print("No entries to move, skipping")
              with open(os.environ["GITHUB_OUTPUT"], "a") as f:
                  f.write("skip=true\n")
              raise SystemExit(0)

          # Merge duplicate ### sections
          sections: dict[str, list[str]] = {}
          current_heading = ""
          for line in raw.splitlines():
              if line.startswith("### "):
                  current_heading = line
                  if current_heading not in sections:
                      sections[current_heading] = []
              elif line.strip():
                  sections.setdefault(current_heading, []).append(line)

          parts: list[str] = []
          for heading, lines in sections.items():
              if heading:
                  parts.append(heading)
              parts.extend(lines)
              parts.append("")
          entries = "\n".join(parts).strip()

          # Rebuild: Unreleased (empty) + new version section + rest
          new_content = (
              content[:after_heading]
              + "\n\n"
              + f"## {version}\n\n"
              + entries
              + "\n\n"
              + content[end:].lstrip("\n")
          )

          with open("CHANGELOG.md", "w") as f:
              f.write(new_content)

      - name: Commit and push
        if: steps.check.outputs.skip == 'false'
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add CHANGELOG.md
          git diff --cached --quiet && exit 0
          git commit -m "changelog: release $VERSION"
          git push
