name: Desktop Release

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      release_tag:
        description: 'Existing release tag in semver format, e.g. v3.2.12'
        required: true
        type: string

concurrency:
  group: desktop-release-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-windows:
    runs-on: windows-latest
    permissions:
      contents: read
    env:
      RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Resolve release ref
        shell: bash
        run: |
          if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
            [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
            git fetch --tags --force
            git rev-parse "${RELEASE_TAG}" >/dev/null
            git checkout "${RELEASE_TAG}"
          fi

      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
          cache: 'pip'

      - uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: |
            apps/dsa-web/package-lock.json
            apps/dsa-desktop/package-lock.json

      - name: Sync desktop app version from release tag
        shell: bash
        run: |
          set -euo pipefail
          [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
          export VERSION="${RELEASE_TAG#v}"
          cd apps/dsa-desktop
          npm version "${VERSION}" --no-git-tag-version --allow-same-version
          node -e "const pkg = require('./package.json'); if (pkg.version !== process.env.VERSION) throw new Error('package version mismatch: ' + pkg.version); console.log('desktop version:', pkg.version);"

      - name: Build desktop package (Windows)
        shell: pwsh
        env:
          DSA_SKIP_DEVMODE_CHECK: 'true'
        run: |
          powershell -ExecutionPolicy Bypass -File scripts/build-all.ps1

      - name: Prepare release artifact (Windows)
        shell: pwsh
        run: |
          $expectedInstallerName = "daily-stock-analysis-windows-installer-$env:RELEASE_TAG.exe"
          $installerPath = Join-Path 'apps/dsa-desktop/dist' $expectedInstallerName
          $installerExe = Get-Item -Path $installerPath -ErrorAction SilentlyContinue
          if (-not $installerExe) {
            throw "No expected NSIS installer found under apps/dsa-desktop/dist: $expectedInstallerName"
          }
          $winUnpacked = 'apps/dsa-desktop/dist/win-unpacked'
          if (-not (Test-Path $winUnpacked)) {
            throw 'No win-unpacked directory found under apps/dsa-desktop/dist.'
          }
          New-Item -ItemType Directory -Path 'dist/release-assets' -Force | Out-Null
          $exeTarget = "dist/release-assets/$expectedInstallerName"
          $zipTarget = "dist/release-assets/daily-stock-analysis-windows-noinstall-$env:RELEASE_TAG.zip"
          Copy-Item -Path $installerExe.FullName -Destination $exeTarget -Force
          Compress-Archive -Path $winUnpacked -DestinationPath $zipTarget -CompressionLevel Optimal -Force
          Get-ChildItem -Path 'apps/dsa-desktop/dist' -File |
            Where-Object { $_.Name -eq 'latest.yml' -or $_.Name -like '*.blockmap' } |
            ForEach-Object {
              Copy-Item -Path $_.FullName -Destination "dist/release-assets/$($_.Name)" -Force
            }
          if (-not (Test-Path 'dist/release-assets/latest.yml')) {
            throw 'No latest.yml found under dist/release-assets.'
          }
          $expectedVersion = $env:RELEASE_TAG.TrimStart('v')
          $versionPattern = '^version:\s*[''"]?' + [regex]::Escape($expectedVersion) + '[''"]?\s*$'
          if (-not (Select-String -Path 'dist/release-assets/latest.yml' -Pattern $versionPattern)) {
            throw "latest.yml version does not match release tag $env:RELEASE_TAG."
          }
          $latestText = Get-Content -Path 'dist/release-assets/latest.yml' -Raw
          $pathMatch = [regex]::Match($latestText, '(?m)^\s*path:\s*[''"]?([^''"\r\n]+)[''"]?\s*$')
          if (-not $pathMatch.Success) {
            throw 'latest.yml missing path field.'
          }
          $latestInstallerPath = $pathMatch.Groups[1].Value.Trim()
          if ($latestInstallerPath -ne $expectedInstallerName) {
            throw "latest.yml path ($latestInstallerPath) does not match release installer $expectedInstallerName."
          }
          $urlMatch = [regex]::Match($latestText, '(?m)^\s*-\s*url:\s*[''"]?([^''"\r\n]+)[''"]?\s*$')
          if ($urlMatch.Success -and $urlMatch.Groups[1].Value.Trim() -ne $expectedInstallerName) {
            throw "latest.yml file url ($($urlMatch.Groups[1].Value.Trim())) does not match release installer $expectedInstallerName."
          }
          $expectedBlockmapName = "$expectedInstallerName.blockmap"
          if (-not (Get-ChildItem -Path 'dist/release-assets' -Filter $expectedBlockmapName -File)) {
            throw "No matching blockmap found under dist/release-assets: $expectedBlockmapName"
          }
          Get-ChildItem -Path 'dist/release-assets' -File | Sort-Object Name | Format-Table Name, Length

      - uses: actions/upload-artifact@v6
        with:
          name: desktop-windows-${{ env.RELEASE_TAG }}
          path: dist/release-assets/*
          if-no-files-found: error

  build-macos:
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x64
            runner: macos-15-intel
          - arch: arm64
            runner: macos-15
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
    env:
      RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Resolve release ref
        shell: bash
        run: |
          if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
            [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
            git fetch --tags --force
            git rev-parse "${RELEASE_TAG}" >/dev/null
            git checkout "${RELEASE_TAG}"
          fi

      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
          cache: 'pip'

      - uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: |
            apps/dsa-web/package-lock.json
            apps/dsa-desktop/package-lock.json

      - name: Sync desktop app version from release tag
        shell: bash
        run: |
          set -euo pipefail
          [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
          export VERSION="${RELEASE_TAG#v}"
          cd apps/dsa-desktop
          npm version "${VERSION}" --no-git-tag-version --allow-same-version
          node -e "const pkg = require('./package.json'); if (pkg.version !== process.env.VERSION) throw new Error('package version mismatch: ' + pkg.version); console.log('desktop version:', pkg.version);"

      - name: Cache Electron binaries
        uses: actions/cache@v5
        with:
          path: ~/Library/Caches/electron
          key: electron-macos-${{ matrix.arch }}-${{ hashFiles('apps/dsa-desktop/package-lock.json') }}
          restore-keys: |
            electron-macos-${{ matrix.arch }}-

      - name: Disable Spotlight indexing (prevents hdiutil Resource busy)
        run: sudo mdutil -a -i off

      - name: Build desktop package (macOS)
        env:
          DSA_MAC_ARCH: ${{ matrix.arch }}
        run: bash scripts/build-all-macos.sh

      - name: Prepare release artifact (macOS)
        shell: bash
        run: |
          set -euo pipefail
          dmg_file="$(ls -t apps/dsa-desktop/dist/*${{ matrix.arch }}*.dmg 2>/dev/null | head -n 1 || true)"
          if [[ -z "${dmg_file}" ]]; then
            dmg_file="$(ls -t apps/dsa-desktop/dist/*.dmg | head -n 1)"
          fi
          test -n "$dmg_file"
          mkdir -p dist/release-assets
          cp "$dmg_file" "dist/release-assets/daily-stock-analysis-macos-${{ matrix.arch }}-${RELEASE_TAG}.dmg"

      - uses: actions/upload-artifact@v6
        with:
          name: desktop-macos-${{ matrix.arch }}-${{ env.RELEASE_TAG }}
          path: dist/release-assets/*.dmg
          if-no-files-found: error

  publish-release:
    runs-on: ubuntu-latest
    needs: [build-windows, build-macos]
    permissions:
      contents: write
    env:
      RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
    steps:
      - uses: actions/checkout@v5

      - name: Validate release tag
        run: |
          [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]

      - uses: actions/download-artifact@v7
        with:
          pattern: desktop-*
          path: dist/release-assets
          merge-multiple: true

      - name: Show release assets
        run: ls -lah dist/release-assets

      - name: Publish GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.RELEASE_TAG }}
          generate_release_notes: false
          files: dist/release-assets/*
          fail_on_unmatched_files: true
