name: Build Single Popular App

env:
  NODE_VERSION: "22"
  PNPM_VERSION: "10.26.2"

on:
  workflow_dispatch:
    inputs:
      name:
        description: "App Name"
        required: true
        default: "twitter"
      title:
        description: "App Title"
        required: true
        default: "Twitter"
      name_zh:
        description: "App Name in Chinese"
        required: true
        default: "推特"
      url:
        description: "App URL"
        required: true
        default: "https://twitter.com/"
      icon:
        description: "App Icon"
        required: false
      new_window:
        description: "Allow sites to open new windows"
        required: false
        default: false
      incognito:
        description: "Enable incognito mode"
        required: false
        default: false
      width:
        description: "Window width"
        required: false
        default: 1200
      height:
        description: "Window height"
        required: false
        default: 780
  workflow_call:
    inputs:
      name:
        description: "App Name"
        type: string
        required: true
        default: "twitter"
      title:
        description: "App Title"
        required: true
        type: string
        default: "Twitter"
      name_zh:
        description: "App Name in Chinese"
        required: true
        type: string
        default: "推特"
      url:
        description: "App URL"
        required: true
        type: string
        default: "https://twitter.com/"
      icon:
        description: "App Icon"
        type: string
        required: false
      new_window:
        description: "Allow sites to open new windows"
        type: boolean
        required: false
        default: false
      incognito:
        description: "Enable incognito mode"
        type: boolean
        required: false
        default: false
      width:
        description: "Window width"
        type: number
        required: false
        default: 1200
      height:
        description: "Window height"
        type: number
        required: false
        default: 780
    secrets:
      PAKE_SIGNING_IDENTITY:
        required: false
      PAKE_CERTIFICATE_P12:
        required: false
      PAKE_CERTIFICATE_PASSWORD:
        required: false
      PAKE_NOTARIZE_APPLE_ID:
        required: false
      PAKE_NOTARIZE_TEAM_ID:
        required: false
      PAKE_NOTARIZE_PASSWORD:
        required: false

jobs:
  build:
    name: ${{ inputs.title }} (${{ matrix.build }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        build: [linux, macos, windows]
        include:
          - build: linux
            os: ubuntu-latest
            rust: stable
          - build: windows
            os: windows-latest
            rust: stable-x86_64-msvc
          - build: macos
            os: macos-latest
            rust: stable
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

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

      - name: Setup Node.js Environment
        uses: ./.github/actions/setup-env
        with:
          mode: build

      - name: Download CLI Artifact
        if: github.event_name != 'workflow_dispatch'
        uses: actions/download-artifact@v7
        with:
          name: pake-cli-dist
          path: dist

      - name: Build CLI (workflow_dispatch)
        if: github.event_name == 'workflow_dispatch'
        run: pnpm run cli:build

      - name: Setup mold linker
        if: matrix.os == 'ubuntu-latest'
        uses: rui314/setup-mold@v1

      - name: Prepare macOS signing
        if: matrix.os == 'macos-latest'
        env:
          PAKE_SIGNING_IDENTITY: ${{ secrets.PAKE_SIGNING_IDENTITY }}
          PAKE_CERTIFICATE_P12: ${{ secrets.PAKE_CERTIFICATE_P12 }}
          PAKE_CERTIFICATE_PASSWORD: ${{ secrets.PAKE_CERTIFICATE_PASSWORD }}
        run: |
          set -euo pipefail

          SIGNING_IDENTITY="${PAKE_SIGNING_IDENTITY:-}"

          if [ -n "${PAKE_CERTIFICATE_P12:-}" ] && [ -n "${PAKE_CERTIFICATE_PASSWORD:-}" ]; then
            echo "Importing macOS signing certificate..."
            KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
            KEYCHAIN_PASSWORD="$(openssl rand -base64 16)"

            security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
            security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
            security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

            echo "$PAKE_CERTIFICATE_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
            security import "$RUNNER_TEMP/certificate.p12" -P "$PAKE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
            security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
            security list-keychain -d user -s "$KEYCHAIN_PATH"

            if [ -z "$SIGNING_IDENTITY" ]; then
              SIGNING_IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | sed -n 's/.*"\(Developer ID Application:.*\)".*/\1/p' | head -n 1)"
            fi
          else
            echo "No certificate secret configured, fallback to ad-hoc signing."
          fi

          if [ -z "$SIGNING_IDENTITY" ]; then
            SIGNING_IDENTITY="-"
          fi

          SIGNING_IDENTITY="$SIGNING_IDENTITY" node <<'NODE'
          const fs = require('fs');
          const file = 'src-tauri/tauri.macos.conf.json';
          const config = JSON.parse(fs.readFileSync(file, 'utf8'));
          config.bundle ??= {};
          config.bundle.macOS ??= {};
          config.bundle.macOS.signingIdentity = process.env.SIGNING_IDENTITY || '-';
          fs.writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
          NODE

          echo "Using signing identity: $SIGNING_IDENTITY"

      - name: Build for Linux
        if: matrix.os == 'ubuntu-latest'
        timeout-minutes: 20
        run: |
          ARGS=("${{ inputs.url }}" "--name" "${{ inputs.name }}")

          # Auto-detect local icon or use provided icon
          if [ -n "${{ inputs.icon }}" ]; then
            ARGS+=("--icon" "${{ inputs.icon }}")
          elif [ -f "src-tauri/png/${{ inputs.name }}_512.png" ]; then
            ARGS+=("--icon" "src-tauri/png/${{ inputs.name }}_512.png")
          fi

          if [ "${{ inputs.new_window }}" = "true" ]; then
            ARGS+=("--new-window")
          fi

          if [ "${{ inputs.incognito }}" = "true" ]; then
            ARGS+=("--incognito")
          fi

          ARGS+=("--width" "${{ inputs.width }}")
          ARGS+=("--height" "${{ inputs.height }}")

          # Build once with multiple targets (faster than separate builds)
          node dist/cli.js "${ARGS[@]}" --targets deb,appimage

          mkdir -p output/linux

          # The CLI copies files to project root and removes them from bundle directory
          # Check project root first, then fallback to bundle directory
          if [ -f "${{ inputs.name }}.deb" ]; then
            mv "${{ inputs.name }}.deb" output/linux/${{inputs.title}}_`arch`.deb
          elif [ -n "$(ls src-tauri/target/release/bundle/deb/*.deb 2>/dev/null)" ]; then
            mv src-tauri/target/release/bundle/deb/*.deb output/linux/${{inputs.title}}_`arch`.deb
          else
            echo "Error: No DEB file found"
            find . -name "*.deb" -type f
            exit 1
          fi

          if [ -f "${{ inputs.name }}.AppImage" ]; then
            mv "${{ inputs.name }}.AppImage" output/linux/"${{inputs.title}}"_`arch`.AppImage
          elif [ -n "$(ls src-tauri/target/release/bundle/appimage/*.AppImage 2>/dev/null)" ]; then
            mv src-tauri/target/release/bundle/appimage/*.AppImage output/linux/"${{inputs.title}}"_`arch`.AppImage
          else
            echo "Error: No AppImage file found"
            find . -name "*.AppImage" -type f
            exit 1
          fi

      - name: Build for macOS
        if: matrix.os == 'macos-latest'
        timeout-minutes: 25
        env:
          TAURI_BUNDLER_DMG_IGNORE_CI: "true"
        run: |
          # Use title as app product name on macOS to preserve display casing in .app
          ARGS=("${{ inputs.url }}" "--name" "${{ inputs.title }}" "--hide-title-bar")

          # Auto-detect local icon or use provided icon
          if [ -n "${{ inputs.icon }}" ]; then
            ARGS+=("--icon" "${{ inputs.icon }}")
          elif [ -f "src-tauri/icons/${{ inputs.name }}.icns" ]; then
            ARGS+=("--icon" "src-tauri/icons/${{ inputs.name }}.icns")
          fi

          if [ "${{ inputs.new_window }}" = "true" ]; then
            ARGS+=("--new-window")
          fi

          if [ "${{ inputs.incognito }}" = "true" ]; then
            ARGS+=("--incognito")
          fi

          ARGS+=("--width" "${{ inputs.width }}")
          ARGS+=("--height" "${{ inputs.height }}")

          node dist/cli.js "${ARGS[@]}" --targets universal --multi-arch

          mkdir -p output/macos

          # The CLI copies the DMG to project root and removes it from bundle directory
          # Check project root first, then fallback to bundle directory
          if [ -f "${{ inputs.title }}.dmg" ]; then
            mv "${{ inputs.title }}.dmg" output/macos/"${{inputs.title}}".dmg
          elif [ -f "${{ inputs.name }}.dmg" ]; then
            mv "${{ inputs.name }}.dmg" output/macos/"${{inputs.title}}".dmg
          elif [ -n "$(ls src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg 2>/dev/null)" ]; then
            mv src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg output/macos/"${{inputs.title}}".dmg
          else
            echo "Error: No DMG file found"
            echo "Searched locations:"
            echo "  - ${{ inputs.name }}.dmg (project root)"
            echo "  - src-tauri/target/universal-apple-darwin/release/bundle/dmg/"
            find . -name "*.dmg" -type f
            exit 1
          fi

      - name: Notarize macOS DMG
        if: matrix.os == 'macos-latest'
        env:
          PAKE_NOTARIZE_APPLE_ID: ${{ secrets.PAKE_NOTARIZE_APPLE_ID }}
          PAKE_NOTARIZE_TEAM_ID: ${{ secrets.PAKE_NOTARIZE_TEAM_ID }}
          PAKE_NOTARIZE_PASSWORD: ${{ secrets.PAKE_NOTARIZE_PASSWORD }}
        run: |
          set -euo pipefail

          if [ -z "${PAKE_NOTARIZE_APPLE_ID:-}" ] || [ -z "${PAKE_NOTARIZE_TEAM_ID:-}" ] || [ -z "${PAKE_NOTARIZE_PASSWORD:-}" ]; then
            echo "Notarization secrets not configured, skipping notarization."
            exit 0
          fi

          DMG_PATH="output/macos/${{inputs.title}}.dmg"
          if [ ! -f "$DMG_PATH" ]; then
            echo "Error: DMG file not found at $DMG_PATH"
            exit 1
          fi

          echo "Submitting DMG to Apple notarization service..."
          xcrun notarytool submit "$DMG_PATH" \
            --apple-id "$PAKE_NOTARIZE_APPLE_ID" \
            --team-id "$PAKE_NOTARIZE_TEAM_ID" \
            --password "$PAKE_NOTARIZE_PASSWORD" \
            --wait

          echo "Stapling notarization ticket..."
          xcrun stapler staple "$DMG_PATH"

      - name: Build for Windows
        if: matrix.os == 'windows-latest'
        timeout-minutes: 20
        run: |
          # Use title as app product name on Windows to preserve display casing
          $args = "${{ inputs.url }}", "--name", "${{ inputs.title }}"

          # Auto-detect local icon or use provided icon
          if ("${{ inputs.icon }}" -ne "") {
            $args += "--icon", "${{ inputs.icon }}"
          } elseif (Test-Path "src-tauri\png\${{ inputs.name }}_256.ico") {
            $args += "--icon", "src-tauri\png\${{ inputs.name }}_256.ico"
          }

          if ("${{ inputs.new_window }}" -eq "true") {
            $args += "--new-window"
          }

          if ("${{ inputs.incognito }}" -eq "true") {
            $args += "--incognito"
          }

          $args += "--width", "${{ inputs.width }}"
          $args += "--height", "${{ inputs.height }}"

          $args += "--targets", "x64"

          node dist/cli.js $args

          New-Item -Path "output\windows" -ItemType Directory -Force

          # The CLI copies the MSI to project root and removes it from bundle directory
          # Check project root first, then fallback to bundle directories
          $projectRootMsi = "${{ inputs.title }}.msi"
          $legacyProjectRootMsi = "${{ inputs.name }}.msi"

          if (Test-Path $projectRootMsi) {
            Move-Item -Path $projectRootMsi -Destination "output\windows\${{inputs.title}}_x64.msi"
          } elseif (Test-Path $legacyProjectRootMsi) {
            Move-Item -Path $legacyProjectRootMsi -Destination "output\windows\${{inputs.title}}_x64.msi"
          } else {
            # Check architecture-specific path (x64 builds)
            $msiFiles = Get-ChildItem -Path "src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi" -ErrorAction SilentlyContinue

            # Fallback to generic path
            if (-not $msiFiles) {
              $msiFiles = Get-ChildItem -Path "src-tauri\target\release\bundle\msi\*.msi" -ErrorAction SilentlyContinue
            }

            if ($msiFiles) {
              Move-Item -Path $msiFiles[0].FullName -Destination "output\windows\${{inputs.title}}_x64.msi"
            } else {
              Write-Error "No MSI files found in expected locations"
              Write-Host "Searched paths:"
              Write-Host "  - $projectRootMsi (project root)"
              Write-Host "  - $legacyProjectRootMsi (project root)"
              Write-Host "  - src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\"
              Write-Host "  - src-tauri\target\release\bundle\msi\"
              Write-Host "`nAll MSI files in target directory:"
              Get-ChildItem -Path "src-tauri\target\" -Recurse -Name "*.msi" | Write-Host
              exit 1
            }
          }

          git checkout -- src-tauri/Cargo.lock

      - name: Upload artifacts
        uses: actions/upload-artifact@v6
        with:
          name: ${{ inputs.title }}-${{ matrix.build }}
          path: output/*/*.*
          retention-days: 3

      - name: Upload to release (Linux)
        uses: ncipollo/release-action@v1 # cspell:disable-line
        if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/')
        with:
          allowUpdates: true
          omitBody: true
          omitName: true
          artifacts: "output/linux/*.deb,output/linux/*.AppImage"
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload to release (macOS)
        uses: ncipollo/release-action@v1 # cspell:disable-line
        if: matrix.os == 'macos-latest' && startsWith(github.ref, 'refs/tags/')
        with:
          allowUpdates: true
          omitBody: true
          omitName: true
          artifacts: "output/macos/*.dmg"
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload to release (Windows)
        uses: ncipollo/release-action@v1 # cspell:disable-line
        if: matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/')
        with:
          allowUpdates: true
          omitBody: true
          omitName: true
          artifacts: "output/windows/*.msi"
          token: ${{ secrets.GITHUB_TOKEN }}
