name: Build & Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

permissions:
  contents: write

jobs:
  build:
    strategy:
      matrix:
        include:
          - os: windows-latest
            artifact: PDFApps-Windows
          - os: ubuntu-22.04
            artifact: PDFApps-Linux
          - os: macos-latest
            artifact: PDFApps-macOS

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Set up Python
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: '3.14'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      # ── Windows ─────────────────────────────────────────────────
      - name: Build (Windows)
        if: runner.os == 'Windows'
        run: |
          python -m PyInstaller --noconfirm pdfapps.spec
          python -m PyInstaller --noconfirm uninstaller.spec
          python -m PyInstaller --noconfirm installer.spec

      - name: Upload (Windows)
        if: runner.os == 'Windows'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ matrix.artifact }}
          path: |
            dist/PDFApps.exe
            dist/PDFAppsSetup.exe
            dist/PDFAppsUninstall.exe

      # ── Linux ───────────────────────────────────────────────────
      - name: Install Linux system deps
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y libegl1 libxkbcommon0 libxcb-cursor0

      - name: Build (Linux)
        if: runner.os == 'Linux'
        run: |
          python -m PyInstaller --noconfirm pdfapps.spec
          chmod +x dist/PDFApps
          tar -czf dist/PDFApps-Linux.tar.gz -C dist PDFApps

      - name: Upload (Linux)
        if: runner.os == 'Linux'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ matrix.artifact }}
          path: dist/PDFApps-Linux.tar.gz

      # ── macOS ───────────────────────────────────────────────────
      - name: Free macOS disk space
        if: runner.os == 'macOS'
        # macos-latest ships with Xcode (~16 GB) which our pure-Python
        # build doesn't use. Removing it plus dropping APFS local
        # snapshots frees enough disk for hdiutil to create the DMG.
        # We keep CommandLineTools because PyInstaller calls
        # lipo / codesign / otool to slice and re-sign Mach-O binaries
        # — without those, the build dies with "lipo command ...
        # failed with error code 1".
        run: |
          echo "── before cleanup ──"
          df -h /
          sudo rm -rf /Applications/Xcode*.app
          # Default xcode-select path points at the (now gone) Xcode
          # bundle; redirect to CommandLineTools so /usr/bin/lipo and
          # friends keep resolving.
          sudo xcode-select -s /Library/Developer/CommandLineTools || true
          # Without this, the bytes freed by `rm -rf Xcode` are still
          # reserved by Time Machine local snapshots and df doesn't
          # actually drop.
          sudo tmutil deletelocalsnapshots / || true
          echo "── after cleanup ──"
          df -h /

      - name: Build (macOS)
        if: runner.os == 'macOS'
        run: |
          echo "── before PyInstaller ──"
          df -h
          python -m PyInstaller --noconfirm pdfapps.spec
          echo "── after PyInstaller ──"
          df -h
          du -sh dist/PDFApps.app
          # PyInstaller intermediates (~3-7 GB in build/ + the
          # bootloader cache) and the loose Unix exe under dist/ are
          # not needed for the DMG — only dist/PDFApps.app is.
          rm -rf build/
          rm -rf "$HOME/Library/Application Support/pyinstaller"
          rm -f dist/PDFApps
          # Create DMG with the .app bundle and an Applications
          # shortcut. `mv` (not cp -R) — we don't need to keep the
          # source .app after the DMG is built.
          mkdir -p dist/dmg
          mv dist/PDFApps.app dist/dmg/
          ln -s /Applications dist/dmg/Applications
          du -sh dist/dmg
          # The previous `hdiutil create -srcfolder` (no -size)
          # auto-estimated the intermediate sparse image too tight and
          # died with "No space left on device" while *copying into
          # the new DMG* — i.e. the runner had 83 GB free, but the
          # mounted image at /Volumes/PDFApps/ ran out of bytes mid-
          # copy. The .app has thousands of tiny files (Qt
          # frameworks, qtawesome fonts, etc.) so HFS+ slack space
          # blows past the auto-estimate. Force the intermediate to
          # 1 GB; UDZO compression makes the final .dmg much smaller.
          hdiutil create -volname "PDFApps" \
            -srcfolder dist/dmg \
            -ov -format UDZO \
            -size 1g \
            dist/PDFApps-macOS.dmg
          echo "── after DMG ──"
          df -h
          ls -lh dist/PDFApps-macOS.dmg

      - name: Upload (macOS)
        if: runner.os == 'macOS'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ matrix.artifact }}
          path: dist/PDFApps-macOS.dmg

  appimage:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Set up Python
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: '3.10'
      - name: Install AppImage build deps
        run: |
          sudo apt-get update
          sudo apt-get install -y libfuse2 libegl1 libxkbcommon0 libxcb-cursor0 wget
      - name: Build AppImage
        run: |
          chmod +x appimage/build.sh
          ./appimage/build.sh
      - name: Upload AppImage
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: PDFApps-AppImage
          path: PDFApps-*.AppImage

  release:
    needs: [build, appimage]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
        with:
          client-id: ${{ secrets.AP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - name: Generate release notes
        id: notes
        run: |
          TAG="${GITHUB_REF_NAME}"
          PREV=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | sed -n '2p')
          if [ -z "$PREV" ]; then PREV=$(git rev-list --max-parents=0 HEAD); fi

          echo "Generating notes from $PREV to $TAG"

          # Collect conventional commit messages, skip chore/ci/deps/merge
          FEATURES=""
          FIXES=""
          PERF=""
          OTHER=""

          while IFS= read -r line; do
            # Skip empty, merge, chore, ci, deps, bump, dependabot
            echo "$line" | grep -qiE '^(chore|ci|build|Merge|docs\(deps\))' && continue
            echo "$line" | grep -qiE 'dependabot|bump.*from.*to' && continue
            echo "$line" | grep -qi '^$' && continue

            # Clean up: remove scope prefix for display, capitalize
            clean=$(echo "$line" | sed -E 's/^(feat|fix|perf|a11y|docs)(\([^)]*\))?:\s*//')
            clean="$(echo "${clean:0:1}" | tr '[:lower:]' '[:upper:]')${clean:1}"

            if echo "$line" | grep -qE '^feat'; then
              FEATURES="${FEATURES}- ${clean}\n"
            elif echo "$line" | grep -qE '^fix'; then
              FIXES="${FIXES}- ${clean}\n"
            elif echo "$line" | grep -qE '^perf'; then
              PERF="${PERF}- ${clean}\n"
            elif echo "$line" | grep -qE '^a11y'; then
              FIXES="${FIXES}- ${clean}\n"
            elif echo "$line" | grep -qE '^docs'; then
              : # skip docs-only commits
            else
              OTHER="${OTHER}- ${clean}\n"
            fi
          done <<< "$(git log --pretty=format:'%s' "${PREV}..${TAG}" --no-merges)"

          BODY=""
          if [ -n "$FEATURES" ]; then
            BODY="${BODY}## New features\n${FEATURES}\n"
          fi
          if [ -n "$PERF" ]; then
            BODY="${BODY}## Performance\n${PERF}\n"
          fi
          if [ -n "$FIXES" ]; then
            BODY="${BODY}## Fixes & improvements\n${FIXES}\n"
          fi
          if [ -n "$OTHER" ]; then
            BODY="${BODY}## Other\n${OTHER}\n"
          fi

          if [ -z "$BODY" ]; then
            BODY="Bug fixes and improvements."
          fi

          # Write to file (multiline output)
          printf '%b' "$BODY" > release_notes.md
          echo "Generated release notes:"
          cat release_notes.md

      - name: Download all artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: artifacts

      - name: Append SHA256 checksums to release notes
        run: |
          # The in-app auto-updater parses lines of the form
          # "<64-hex>  <filename>" from the release body and refuses to
          # run an update whose asset hash is not published. Any artifact
          # that is uploaded below MUST also appear here.
          echo "" >> release_notes.md
          echo "## Checksums (SHA256)" >> release_notes.md
          for f in artifacts/PDFApps-Windows/PDFAppsSetup.exe \
                   artifacts/PDFApps-Windows/PDFApps.exe \
                   artifacts/PDFApps-Linux/PDFApps-Linux.tar.gz \
                   artifacts/PDFApps-macOS/PDFApps-macOS.dmg \
                   artifacts/PDFApps-AppImage/PDFApps-*.AppImage; do
            for match in $f; do
              if [ -f "$match" ]; then
                sha=$(sha256sum "$match" | awk '{print $1}')
                name=$(basename "$match")
                echo "${sha}  ${name}" >> release_notes.md
              fi
            done
          done
          echo "Final release notes:"
          cat release_notes.md

      - name: Create Release
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v2
        with:
          token: ${{ steps.app-token.outputs.token }}
          body_path: release_notes.md
          files: |
            artifacts/PDFApps-Windows/PDFAppsSetup.exe
            artifacts/PDFApps-Windows/PDFApps.exe
            artifacts/PDFApps-Linux/PDFApps-Linux.tar.gz
            artifacts/PDFApps-macOS/PDFApps-macOS.dmg
            artifacts/PDFApps-AppImage/PDFApps-*.AppImage
