name: Release

on:
  workflow_dispatch:
    inputs:
      tag:
        description: "Release tag (e.g. v0.1.0)"
        required: true
  push:
    tags:
      - "v*"

# Required to create the GitHub Release and attach artifacts.
permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: athen-linux-x86_64
            updater_target: linux-x86_64
            artifact_globs: |
              target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
              target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
              target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
              target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage.sig
          - platform: macos-latest
            target: aarch64-apple-darwin
            artifact_name: athen-macos-aarch64
            updater_target: darwin-aarch64
            artifact_globs: |
              target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
              target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz
              target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz.sig
          # Intel Mac (macos-13) is dropped from the v0.1.x matrix: GitHub's
          # free-tier Intel runners are scarce (3+ hour queues observed). Re-add
          # once code signing is in place and we can justify a self-hosted or
          # paid macOS-13 runner.
          - platform: windows-latest
            target: x86_64-pc-windows-msvc
            artifact_name: athen-windows-x86_64
            updater_target: windows-x86_64
            artifact_globs: |
              target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi
              target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi.sig
              target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
              target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig

    name: Build (${{ matrix.platform }})
    runs-on: ${{ matrix.platform }}

    steps:
      - uses: actions/checkout@v4

      - name: Install Linux dependencies
        if: startsWith(matrix.platform, 'ubuntu')
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libwebkit2gtk-4.1-dev \
            libgtk-3-dev \
            libsoup-3.0-dev \
            libappindicator3-dev \
            librsvg2-dev \
            cmake \
            nasm \
            rpm

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: release-${{ matrix.target }}
          # The Windows post-step intermittently fails because vctip.exe
          # holds files open while tar is archiving. The build itself has
          # already succeeded by then — don't poison the matrix outcome
          # over a flaky cache save.
          save-if: ${{ runner.os != 'Windows' }}

      - name: Install Tauri CLI
        # Pin the major to keep CI deterministic across patch releases.
        run: cargo install tauri-cli --version "^2.0" --locked

      - name: Fetch nushell sidecar (Unix)
        if: runner.os != 'Windows'
        env:
          TARGET_TRIPLE: ${{ matrix.target }}
        run: bash scripts/fetch-nushell.sh

      - name: Fetch nushell sidecar (Windows)
        if: runner.os == 'Windows'
        env:
          TARGET_TRIPLE: ${{ matrix.target }}
        run: pwsh scripts/fetch-nushell.ps1

      - name: Build Tauri app
        # `cargo tauri build` resolves config relative to the cwd, so run
        # it from the crate that owns tauri.conf.json. The shared workspace
        # `target/` directory is still at the repo root, which is what the
        # artifact globs above expect.
        working-directory: crates/athen-app
        env:
          # Updater signing keys. When unset, the bundler skips .sig generation;
          # manifest assembly downstream will then skip this platform.
          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
        run: cargo tauri build --target ${{ matrix.target }}

      # Upload to workflow artifacts (90-day retention) so the run is
      # debuggable even if the Release step below is skipped or fails.
      - name: Upload workflow artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact_name }}
          path: ${{ matrix.artifact_globs }}
          if-no-files-found: warn

      # Attach the same bundles to the GitHub Release page so users can
      # actually download them. Only fires on tag pushes; manual dispatch
      # still produces workflow artifacts but doesn't auto-publish.
      - name: Publish to GitHub Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: ${{ matrix.artifact_globs }}
          fail_on_unmatched_files: false
          draft: true # Review the release notes before publishing.
          generate_release_notes: true

  publish-manifest:
    name: Publish updater manifest
    needs: build
    # Run even if some matrix legs fail (e.g. flaky cache-save post-step on
    # Windows). The python script below already skips per-platform when the
    # signature/payload artifacts are missing, so a partial manifest is the
    # graceful failure mode rather than no manifest at all.
    if: always() && startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - name: Download all build artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - name: Generate latest.json
        env:
          TAG: ${{ github.ref_name }}
          REPO: ${{ github.repository }}
        run: |
          set -euo pipefail
          python3 <<'PY'
          import json, os, glob, datetime, sys

          tag = os.environ["TAG"]
          repo = os.environ["REPO"]
          version = tag.lstrip("v")
          base = f"https://github.com/{repo}/releases/download/{tag}"

          # (signature_file_glob, payload_file_glob, updater_target)
          # The updater downloads `payload`, verifies against `signature`.
          plans = [
              ("artifacts/athen-linux-x86_64/**/*.AppImage.sig",
               "artifacts/athen-linux-x86_64/**/*.AppImage",
               "linux-x86_64"),
              ("artifacts/athen-macos-aarch64/**/*.app.tar.gz.sig",
               "artifacts/athen-macos-aarch64/**/*.app.tar.gz",
               "darwin-aarch64"),
              # Windows: prefer NSIS .exe over MSI when both signed bundles exist.
              ("artifacts/athen-windows-x86_64/**/*-setup.exe.sig",
               "artifacts/athen-windows-x86_64/**/*-setup.exe",
               "windows-x86_64"),
          ]

          platforms = {}
          for sig_glob, bin_glob, target in plans:
              sigs = glob.glob(sig_glob, recursive=True)
              bins = glob.glob(bin_glob, recursive=True)
              if not sigs or not bins:
                  print(f"skip {target}: signature or payload missing "
                        f"(sigs={sigs}, bins={bins})", file=sys.stderr)
                  continue
              with open(sigs[0]) as f:
                  signature = f.read().strip()
              filename = os.path.basename(bins[0])
              platforms[target] = {
                  "signature": signature,
                  "url": f"{base}/{filename}",
              }

          if not platforms:
              print("no signed updater bundles found — manifest will be empty",
                    file=sys.stderr)

          manifest = {
              "version": version,
              "notes": f"See https://github.com/{repo}/releases/tag/{tag}",
              "pub_date": datetime.datetime.utcnow().strftime(
                  "%Y-%m-%dT%H:%M:%SZ"),
              "platforms": platforms,
          }

          with open("latest.json", "w") as f:
              json.dump(manifest, f, indent=2)
          print("wrote latest.json")
          print(json.dumps(manifest, indent=2))
          PY

      - name: Attach latest.json to release
        uses: softprops/action-gh-release@v2
        with:
          files: latest.json
          fail_on_unmatched_files: true
          draft: true
