name: Build & Release

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

permissions:
  contents: write

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: macos-latest
            args: '--target aarch64-apple-darwin'
            rust_target: aarch64-apple-darwin
          - platform: ubuntu-22.04
            args: ''
            rust_target: ''
          # GitHub-hosted ARM Linux runner (free for public repos
          # since 2024). Native build — no cross-compile of webkit2gtk.
          - platform: ubuntu-22.04-arm
            args: ''
            rust_target: ''
          - platform: windows-latest
            args: ''
            rust_target: ''

    runs-on: ${{ matrix.platform }}
    env:
      # GitHub-hosted runners occasionally hit crates.io HTTP/2 stream
      # resets while downloading large dependency graphs. Cargo retries plus
      # HTTP/1.1 transport make release builds less flaky across the matrix.
      CARGO_HTTP_MULTIPLEXING: "false"
      CARGO_NET_RETRY: "5"
      CARGO_HTTP_TIMEOUT: "60"

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.rust_target }}

      - name: Rust cache
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: src-tauri

      - name: Install protoc (macOS)
        if: startsWith(matrix.platform, 'macos-')
        run: brew install protobuf

      - name: Install dependencies (Ubuntu)
        if: startsWith(matrix.platform, 'ubuntu-22.04')
        run: |
          sudo apt-get update
          # xdg-utils provides /usr/bin/xdg-open, which Tauri's
          # AppImage bundler embeds into the produced AppImage.
          # Pre-installed on the x86_64 runner image but NOT on
          # the ARM64 image — list it explicitly so both arches
          # bundle cleanly regardless of future image drift.
          sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf protobuf-compiler xdg-utils

      # The repo ships pre-downloaded PDFium binaries under
      # src-tauri/pdfium/ for every supported architecture
      # (libpdfium.so = Linux x86_64, libpdfium-arm64.so = Linux aarch64,
      # libpdfium.dylib = macOS arm64, pdfium.dll = Windows).
      # For architectures whose bundled filename must
      # stay `libpdfium.*`, swap the matching binary into place before cargo
      # runs. We do NOT pull from bblanchon/pdfium-binaries during CI — that
      # download has historically failed often enough that committing the
      # binaries is the maintenance-friendly path.
      - name: Verify PDFium binary checksums
        if: matrix.platform != 'windows-latest'
        run: shasum -a 256 -c src-tauri/pdfium/SHA256SUMS

      # rust_target is empty for both Ubuntu rows, so platform is the
      # only stable discriminator for the Linux ARM swap.
      - name: Use ARM64 pdfium binary (Ubuntu ARM only)
        if: matrix.platform == 'ubuntu-22.04-arm'
        run: |
          cp src-tauri/pdfium/libpdfium-arm64.so src-tauri/pdfium/libpdfium.so
          file src-tauri/pdfium/libpdfium.so

      - name: Install protoc (Windows)
        if: matrix.platform == 'windows-latest'
        uses: arduino/setup-protoc@v3
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install frontend dependencies
        run: npm install

      - name: Prepare MCP server resources
        run: |
          npm --prefix mcp-server ci
          npm run mcp:build

      - name: Build Tauri app
        uses: tauri-apps/tauri-action@v0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        with:
          # Tag-triggered runs publish a real GitHub Release.
          # Manual (workflow_dispatch) runs leave tagName/releaseName
          # empty so tauri-action skips the release-upload step and
          # just produces bundle artifacts — useful for testing a
          # branch build without polluting the Releases page.
          tagName: ${{ github.event_name == 'push' && github.ref_name || '' }}
          releaseName: ${{ github.event_name == 'push' && format('LLM Wiki {0}', github.ref_name) || '' }}
          releaseBody: 'See the assets below for download links.'
          releaseDraft: false
          prerelease: false
          args: ${{ matrix.args }}

      - name: Package Windows portable zip
        if: matrix.platform == 'windows-latest'
        shell: pwsh
        run: |
          $version = node -p "require('./package.json').version"
          .github/scripts/package-windows-portable.ps1 -Version $version

      - name: Attach Windows portable zip to release
        if: github.event_name == 'push' && matrix.platform == 'windows-latest'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        shell: pwsh
        run: |
          gh release upload "${{ github.ref_name }}" dist-portable/*.zip --clobber

      # On workflow_dispatch, no release is created, so the bundles
      # would otherwise be discarded with the runner. Upload them as
      # workflow artifacts so the maintainer can `gh run download`
      # the .msi / .exe / .dmg / .deb to test locally. Skipped on
      # tag pushes since the release page already has them.
      - name: Upload bundles as workflow artifacts (manual runs only)
        if: github.event_name == 'workflow_dispatch'
        uses: actions/upload-artifact@v4
        with:
          name: bundle-${{ matrix.platform }}
          # Glob covers both targeted (e.g.
          # target/aarch64-apple-darwin/release/...) and default
          # (target/release/...) build paths.
          path: |
            src-tauri/target/**/release/bundle/msi/*.msi
            src-tauri/target/**/release/bundle/nsis/*.exe
            src-tauri/target/**/release/bundle/dmg/*.dmg
            src-tauri/target/**/release/bundle/deb/*.deb
            src-tauri/target/**/release/bundle/appimage/*.AppImage
            dist-portable/*.zip
          if-no-files-found: warn
          retention-days: 14

  package-extension:
    name: Package browser extension
    needs: build
    # Browser extension is only published as part of an actual
    # tagged release; manual builds don't need it.
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Sync extension manifest version and zip
        run: |
          # Pull version from package.json so we have a single source of
          # truth. Chrome's manifest requires numeric-only version
          # (e.g. 0.3.5), which matches the repo's semver convention.
          APP_VERSION=$(node -p "require('./package.json').version")
          node -e "
            const fs = require('fs');
            const p = 'extension/manifest.json';
            const m = JSON.parse(fs.readFileSync(p, 'utf-8'));
            m.version = '${APP_VERSION}';
            fs.writeFileSync(p, JSON.stringify(m, null, 2) + '\n');
          "
          mkdir -p dist-extension
          (cd extension && zip -r "../dist-extension/llm-wiki-extension-${APP_VERSION}.zip" . -x "*.DS_Store")
          ls -la dist-extension

      - name: Attach extension zip to release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload "${{ github.ref_name }}" dist-extension/*.zip --clobber
