name: Release

on:
  release:
    types: [published]
  workflow_dispatch:

jobs:
  build-appimage:
    runs-on: ubuntu-22.04
    permissions:
      contents: write

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

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install dependencies
        run: |
          sudo apt-get update -q
          sudo apt-get install -y \
            libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \
            libxkbcommon-dev libwayland-dev libfontconfig1-dev libfreetype6-dev \
            librsvg2-bin fuse libfuse2

      - name: Build
        run: cargo build --release

      - name: Prepare AppDir
        run: |
          mkdir -p AppDir/usr/bin
          mkdir -p AppDir/usr/share/applications
          mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
          mkdir -p AppDir/usr/share/metainfo
          cp target/release/OpenCADStudio AppDir/usr/bin/OpenCADStudio
          cp packaging/OpenCADStudio.desktop AppDir/usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop
          cp packaging/io.github.HakanSeven12.OpenCadStudio.metainfo.xml AppDir/usr/share/metainfo/
          rsvg-convert -w 256 -h 256 assets/logo.svg \
            -o AppDir/usr/share/icons/hicolor/256x256/apps/io.github.HakanSeven12.OpenCadStudio.png

      - name: Download linuxdeploy
        run: |
          wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
          chmod +x linuxdeploy-x86_64.AppImage

      - name: Build AppImage
        env:
          APPIMAGE_EXTRACT_AND_RUN: 1
        run: |
          ./linuxdeploy-x86_64.AppImage \
            --appdir AppDir \
            --desktop-file AppDir/usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop \
            --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/io.github.HakanSeven12.OpenCadStudio.png \
            --output appimage
          mv Open*CAD*Studio*.AppImage OpenCADStudio.AppImage

      - name: Upload AppImage to release
        if: github.event_name == 'release'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          ASSET=OpenCADStudio-${{ github.ref_name }}-linux-x86_64.AppImage
          mv OpenCADStudio.AppImage "$ASSET"
          sha256sum "$ASSET" > "$ASSET.sha256"
          gh release upload ${{ github.ref_name }} "$ASSET" "$ASSET.sha256" --clobber --repo ${{ github.repository }}

  build-windows:
    runs-on: windows-latest
    permissions:
      contents: write

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

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Convert SVG icon to ICO
        shell: pwsh
        run: |
          # ImageMagick is pre-installed on windows-latest. The
          # auto-resize generates every size Explorer / Add-Remove
          # Programs / the Start Menu shortcut may request from a
          # single source SVG. Must run BEFORE the build so build.rs
          # can embed the icon into the .exe (issue #107).
          magick assets/logo.svg `
            -define icon:auto-resize=16,24,32,48,64,128,256 `
            packaging/windows/AppIcon.ico

      - name: Build
        run: cargo build --release

      - name: Sign executable (Azure Trusted Signing)
        # Sign the bare .exe *before* the MSI is built so the copy packed
        # into the installer carries the signature too. AZURE_CLIENT_SECRET
        # authenticates the App Registration; the remaining AZURE_* secrets
        # select the Trusted Signing account / certificate profile.
        uses: azure/artifact-signing-action@v2
        with:
          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
          endpoint: ${{ secrets.AZURE_ENDPOINT }}
          signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
          certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}
          files: ${{ github.workspace }}\target\release\OpenCADStudio.exe
          file-digest: SHA256
          timestamp-rfc3161: http://timestamp.acs.microsoft.com
          timestamp-digest: SHA256

      - name: Build MSI installer
        shell: pwsh
        run: |
          $version = "${{ github.ref_name }}" -replace '^v', ''
          # workflow_dispatch runs use the branch name as ref_name, which
          # is not a valid MSI ProductVersion — fall back to 0.0.0 so test
          # builds still produce an installer.
          if ($version -notmatch '^\d+(\.\d+){0,3}$') { $version = "0.0.0" }
          # WiX Toolset 3.x is pre-installed on windows-latest; $env:WIX
          # points to the install root. candle compiles .wxs → .wixobj
          # and light links it into the final .msi.
          $candle = Join-Path $env:WIX "bin\candle.exe"
          $light  = Join-Path $env:WIX "bin\light.exe"
          # Resolve to absolute paths and pass each `-d<name>=<value>`
          # as one quoted argument. PowerShell's native-command argument
          # passing strips unquoted `-d…=…` strings on the `=` sign,
          # which would otherwise hand candle an empty Source variable
          # and `target\release\OpenCADStudio.exe` as a stray input
          # filename.
          $exePath  = (Resolve-Path target\release\OpenCADStudio.exe).Path
          $iconPath = (Resolve-Path packaging\windows\AppIcon.ico).Path
          # GPL-3 license RTF shown on the installer's EULA page.
          $licensePath = (Resolve-Path packaging\windows\License.rtf).Path
          # main.wxs holds the package/feature; ui.wxs holds the installer UI
          # and finish-screen launch checkbox. Both compile together.
          $mainWxs  = (Resolve-Path packaging\windows\main.wxs).Path
          $uiWxs     = (Resolve-Path packaging\windows\ui.wxs).Path
          # candle writes one .wixobj per source into this directory (the
          # trailing slash tells it -out is a folder, not a single file).
          $objDir   = Join-Path $PWD "packaging\windows\"
          $msiPath  = Join-Path $PWD "OpenCADStudio.msi"
          & $candle -arch x64 `
            "-dVersion=$version" `
            "-dSource=$exePath" `
            "-dIcon=$iconPath" `
            "-dLicense=$licensePath" `
            $mainWxs $uiWxs `
            -out $objDir
          if ($LASTEXITCODE -ne 0) { throw "candle failed" }
          $mainObj = Join-Path $objDir "main.wixobj"
          $uiObj   = Join-Path $objDir "ui.wixobj"
          & $light $mainObj $uiObj -ext WixUIExtension -out $msiPath
          if ($LASTEXITCODE -ne 0) { throw "light failed" }

      - name: Sign MSI installer (Azure Trusted Signing)
        # The installer itself must be signed separately — light produces a
        # fresh, unsigned .msi even though the .exe inside is already signed.
        uses: azure/artifact-signing-action@v2
        with:
          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
          endpoint: ${{ secrets.AZURE_ENDPOINT }}
          signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
          certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}
          files: ${{ github.workspace }}\OpenCADStudio.msi
          file-digest: SHA256
          timestamp-rfc3161: http://timestamp.acs.microsoft.com
          timestamp-digest: SHA256

      - name: Upload artifacts to release
        if: github.event_name == 'release'
        shell: pwsh
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          $tag = "${{ github.ref_name }}"
          # Ship both the bare .exe (portable, no install) and the
          # .msi (installer with Start Menu shortcut + .dwg / .dxf
          # file association). The `-portable` / `-installer` suffix
          # makes the distinction obvious on the release page.
          $exe = "OpenCADStudio-$tag-windows-x86_64-portable.exe"
          $msi = "OpenCADStudio-$tag-windows-x86_64-installer.msi"
          Move-Item target\release\OpenCADStudio.exe $exe
          Move-Item OpenCADStudio.msi $msi
          # SHA256 sidecars so downloads can be verified.
          (Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower() + "  $exe" | Out-File -Encoding ascii "$exe.sha256"
          (Get-FileHash -Algorithm SHA256 $msi).Hash.ToLower() + "  $msi" | Out-File -Encoding ascii "$msi.sha256"
          gh release upload $tag `
            $exe "$exe.sha256" `
            $msi "$msi.sha256" `
            --clobber --repo ${{ github.repository }}

  build-macos:
    runs-on: macos-14    # Apple Silicon (arm64) only
    permissions:
      contents: write

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

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-darwin

      - name: Install librsvg (for rsvg-convert)
        env:
          HOMEBREW_NO_AUTO_UPDATE: "1"
        run: brew install librsvg

      - name: Build
        run: cargo build --release --target aarch64-apple-darwin

      - name: Build .icns from SVG
        run: |
          mkdir -p OpenCADStudio.iconset
          # Render all the sizes Apple expects in an .icns.
          for SIZE in 16 32 64 128 256 512 1024; do
            rsvg-convert -w $SIZE -h $SIZE assets/logo.svg -o OpenCADStudio.iconset/icon_${SIZE}x${SIZE}.png
          done
          # @2x retina variants (half-size base name)
          for BASE in 16 32 128 256 512; do
            DOUBLE=$((BASE * 2))
            cp OpenCADStudio.iconset/icon_${DOUBLE}x${DOUBLE}.png OpenCADStudio.iconset/icon_${BASE}x${BASE}@2x.png
          done
          iconutil -c icns OpenCADStudio.iconset -o AppIcon.icns

      - name: Assemble .app bundle
        run: |
          APP=OpenCADStudio.app
          rm -rf "$APP"
          mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
          cp target/aarch64-apple-darwin/release/OpenCADStudio "$APP/Contents/MacOS/OpenCADStudio"
          chmod +x "$APP/Contents/MacOS/OpenCADStudio"
          cp AppIcon.icns "$APP/Contents/Resources/AppIcon.icns"
          # Substitute version into Info.plist.
          VERSION="${{ github.ref_name }}"
          VERSION="${VERSION#v}"
          [ -z "$VERSION" ] && VERSION="0.0.0"
          sed "s/__VERSION__/$VERSION/g" packaging/Info.plist > "$APP/Contents/Info.plist"

      - name: Ad-hoc code-sign the bundle
        run: |
          # No paid Apple Developer ID is available, so the app cannot be
          # notarised. An *ad-hoc* signature (identity "-") at least gives the
          # bundle a valid self-signature: this avoids the harshest Gatekeeper
          # verdict ("app is damaged, Move to Trash") on a quarantined download
          # and is mandatory anyway for arm64 binaries to execute. Sign the
          # whole bundle deeply, then verify.
          codesign --force --deep --sign - --timestamp=none OpenCADStudio.app
          codesign --verify --strict --verbose=2 OpenCADStudio.app

      - name: Create .dmg
        run: |
          # hdiutil intermittently fails with "Resource busy" on the hosted
          # runner (a stale mount or background indexer holding the image).
          # Detach any leftover volume and retry a few times before giving up.
          set +e
          for attempt in 1 2 3 4 5; do
            hdiutil detach "/Volumes/Open CAD Studio" >/dev/null 2>&1 || true
            rm -f OpenCADStudio.dmg
            hdiutil create \
              -volname "Open CAD Studio" \
              -srcfolder OpenCADStudio.app \
              -ov -format UDZO \
              OpenCADStudio.dmg && break
            echo "hdiutil create failed (attempt $attempt) — retrying in 5s"
            sleep 5
          done
          set -e
          test -f OpenCADStudio.dmg

      - name: Upload .dmg to release
        if: github.event_name == 'release'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          ASSET=OpenCADStudio-${{ github.ref_name }}-macos-arm64.dmg
          mv OpenCADStudio.dmg "$ASSET"
          shasum -a 256 "$ASSET" > "$ASSET.sha256"
          gh release upload ${{ github.ref_name }} "$ASSET" "$ASSET.sha256" --clobber --repo ${{ github.repository }}

      - name: Emit Homebrew cask sha256
        if: github.event_name == 'release'
        run: |
          # Print the values needed to bump the Homebrew cask
          # (packaging/homebrew/open-cad-studio.rb) for this release, so the
          # tap can be updated with a copy-paste instead of computing the
          # digest by hand.
          VERSION="${{ github.ref_name }}"
          VERSION="${VERSION#v}"
          SHA=$(shasum -a 256 OpenCADStudio-${{ github.ref_name }}-macos-arm64.dmg | awk '{print $1}')
          {
            echo "## Homebrew cask bump"
            echo "Update \`packaging/homebrew/open-cad-studio.rb\`:"
            echo '```ruby'
            echo "  version \"$VERSION\""
            echo "  sha256 \"$SHA\""
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"
