# Release workflow - builds and publishes fresh-editor for all platforms
# Replaces cargo-dist with direct cargo builds

name: Release
permissions:
  contents: write
  id-token: write

on:
  push:
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+*'

env:
  CARGO_TERM_COLOR: always

jobs:
  # Extract version and determine if we're publishing
  plan:
    runs-on: ubuntu-22.04
    outputs:
      version: ${{ steps.version.outputs.version }}
      tag: ${{ steps.version.outputs.tag }}
      publishing: ${{ steps.version.outputs.publishing }}
      is_prerelease: ${{ steps.version.outputs.is_prerelease }}
    steps:
      - uses: actions/checkout@v6
      - name: Extract version info
        id: version
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            # For PRs, get version from Cargo.toml
            VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
            TAG=""
            PUBLISHING="false"
          else
            # For tags, extract from ref
            TAG="${{ github.ref_name }}"
            VERSION="${TAG#v}"
            PUBLISHING="true"
          fi

          # Check if prerelease (contains -, like 0.1.0-beta.1)
          if [[ "$VERSION" == *-* ]]; then
            IS_PRERELEASE="true"
          else
            IS_PRERELEASE="false"
          fi

          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "publishing=$PUBLISHING" >> "$GITHUB_OUTPUT"
          echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"

          echo "Version: $VERSION"
          echo "Tag: $TAG"
          echo "Publishing: $PUBLISHING"
          echo "Is prerelease: $IS_PRERELEASE"

  # Test Nix build
  test-nix-build:
    needs: plan
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v6
        with:
          submodules: recursive

      - name: Install Nix
        uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable

      - name: Build with Nix
        run: |
          echo "=== Building fresh with nix build ==="
          nix build .#fresh --print-build-logs

          echo ""
          echo "=== Verifying binary ==="
          ./result/bin/fresh --version

          echo ""
          echo "=== Nix build test PASSED ==="

  # Build binaries for each platform
  build:
    needs: plan
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-22.04
            archive_ext: tar.xz
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-22.04
            archive_ext: tar.xz
            cross: true
          - target: x86_64-apple-darwin
            os: macos-15
            archive_ext: tar.xz
          - target: aarch64-apple-darwin
            os: macos-latest
            archive_ext: tar.xz
          - target: x86_64-pc-windows-msvc
            os: windows-2022
            archive_ext: zip
          - target: aarch64-pc-windows-msvc
            os: windows-11-arm
            archive_ext: zip
    runs-on: ${{ matrix.os }}
    env:
      VERSION: ${{ needs.plan.outputs.version }}
    steps:
      - uses: actions/checkout@v6
        with:
          submodules: recursive

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

      # rust-toolchain.toml overrides the targets parameter above, so we need to explicitly add it
      - name: Add target
        run: rustup target add ${{ matrix.target }}

      - name: Install cross-compilation tools (Linux ARM)
        if: matrix.cross && matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu

      - name: Build release binary
        shell: bash
        run: |
          if [ "${{ matrix.cross }}" = "true" ] && [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
            export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
          fi
          # Static link the C runtime on Windows MSVC to avoid vcruntime DLL dependency
          if [[ "${{ matrix.target }}" == *-pc-windows-msvc ]]; then
            export RUSTFLAGS="-C target-feature=+crt-static"
          fi
          # Default features include embed-plugins; plugins (and themes via build.rs)
          # are compiled into the binary, so we ship a single self-contained executable.
          cargo build --release --target ${{ matrix.target }}

      - name: Create archive (Unix)
        if: runner.os != 'Windows'
        run: |
          ARCHIVE_NAME="fresh-editor-${{ matrix.target }}"
          mkdir -p "${ARCHIVE_NAME}"
          cp target/${{ matrix.target }}/release/fresh "${ARCHIVE_NAME}/"
          cp README.md LICENSE CHANGELOG.md "${ARCHIVE_NAME}/"
          # Plugins and themes are compiled into the binary via embed-plugins
          # and build.rs BUILTIN_THEMES, so we ship just the binary + docs.

          # Include icons and desktop file for Linux targets
          if [[ "${{ matrix.target }}" == *-linux-* ]]; then
            cp -r docs/icons/linux/hicolor "${ARCHIVE_NAME}/icons"
            cp crates/fresh-editor/resources/fresh.desktop "${ARCHIVE_NAME}/fresh.desktop"
          fi

          # Generic self-update receipt for raw-archive installs. Wrapper
          # channels (npm, Homebrew, AUR-bin, …) overwrite this with their own.
          sh scripts/write-install-receipt.sh tarball "${ARCHIVE_NAME}/install-receipt.toml" \
            target=${{ matrix.target }} \
            asset=fresh-editor-${{ matrix.target }}.${{ matrix.archive_ext }}

          if [ "${{ matrix.archive_ext }}" = "tar.xz" ]; then
            tar -cJvf "${ARCHIVE_NAME}.tar.xz" "${ARCHIVE_NAME}"
            sha256sum "${ARCHIVE_NAME}.tar.xz" > "${ARCHIVE_NAME}.tar.xz.sha256"
          else
            tar -czvf "${ARCHIVE_NAME}.tar.gz" "${ARCHIVE_NAME}"
            sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
          fi

      - name: Create archive (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $ARCHIVE_NAME = "fresh-editor-${{ matrix.target }}"
          New-Item -ItemType Directory -Path $ARCHIVE_NAME -Force
          Copy-Item "target/${{ matrix.target }}/release/fresh.exe" "$ARCHIVE_NAME/"
          Copy-Item "README.md", "LICENSE", "CHANGELOG.md" "$ARCHIVE_NAME/"
          # Plugins and themes are compiled into the binary; no on-disk copy needed.

          # Generic self-update receipt for raw-archive installs (a winget/scoop
          # wrapper would ship its own; a plain unzip self-updates). Built as an
          # indented array (a here-string's terminator can't be indented, which
          # would break this YAML block scalar).
          $target = "${{ matrix.target }}"
          $lines = @(
            'schema = 1',
            'channel = "tarball"',
            'package_name = "fresh-editor"',
            'managed = false',
            'self_update = true',
            '',
            '[hints]',
            "target = `"$target`"",
            "asset = `"fresh-editor-$target.zip`""
          )
          $lines | Out-File -Encoding ASCII "$ARCHIVE_NAME/install-receipt.toml"

          Compress-Archive -Path "$ARCHIVE_NAME/*" -DestinationPath "$ARCHIVE_NAME.zip"
          $hash = (Get-FileHash "$ARCHIVE_NAME.zip" -Algorithm SHA256).Hash.ToLower()
          "$hash  $ARCHIVE_NAME.zip" | Out-File -Encoding ASCII "$ARCHIVE_NAME.zip.sha256"

      - name: Upload artifacts
        uses: actions/upload-artifact@v6
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            fresh-editor-${{ matrix.target }}.${{ matrix.archive_ext }}
            fresh-editor-${{ matrix.target }}.${{ matrix.archive_ext }}.sha256

  # Build FreeBSD binaries
  build-freebsd:
    needs: plan
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/freebsd-builds.yml
    secrets: inherit

  # Build musl static binaries (with plugins)
  build-musl:
    needs: plan
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/musl-builds.yml
    secrets: inherit

  # Build the minimal-size static binary (min-size profile, trimmed features)
  build-min-size:
    needs: plan
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/min-size-build.yml
    secrets: inherit

  # Build Linux packages (.deb, .rpm) - builds from source, can run in parallel
  build-linux-packages:
    needs: [plan]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/linux-packages.yml
    secrets: inherit

  # Build Flatpak bundle
  build-flatpak:
    needs: [plan, build]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/flatpak-build.yml
    with:
      version: ${{ needs.plan.outputs.version }}
    secrets: inherit

  # Build GUI variants (native window)
  build-gui:
    needs: [plan]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/gui-builds.yml
    with:
      version: ${{ needs.plan.outputs.version }}
    secrets: inherit

  # Build AppImage
  build-appimage:
    needs: [plan, build]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    uses: ./.github/workflows/appimage-build.yml
    with:
      version: ${{ needs.plan.outputs.version }}
    secrets: inherit

  # Build npm package
  build-npm-package:
    needs: [plan, build]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    runs-on: ubuntu-22.04
    env:
      VERSION: ${{ needs.plan.outputs.version }}
    steps:
      - uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24.x'

      - name: Download all build artifacts
        uses: actions/download-artifact@v7
        with:
          pattern: artifacts-*
          path: artifacts/
          merge-multiple: true

      - name: List downloaded artifacts
        run: ls -la artifacts/

      - name: Create npm package
        run: |
          # Copy JS files from npm-package template
          mkdir -p dist-npm
          cp crates/fresh-editor/npm-package/*.js dist-npm/

          # Create package.json from template with version
          sed "s/VERSION_PLACEHOLDER/${VERSION}/g" crates/fresh-editor/npm-package/package.json.template > dist-npm/package.json

          # Copy supporting files. Plugins/themes are embedded in the binary.
          cp README.md LICENSE CHANGELOG.md dist-npm/

          # Create the tarball
          cd dist-npm
          npm pack
          mv *.tgz ../fresh-editor-npm-package.tar.gz
          cd ..

      - name: Upload npm package artifact
        uses: actions/upload-artifact@v6
        with:
          name: artifacts-npm-package
          path: fresh-editor-npm-package.tar.gz

  # Build Homebrew formula
  build-homebrew-formula:
    needs: [plan, build]
    if: ${{ needs.plan.outputs.publishing == 'true' }}
    runs-on: ubuntu-22.04
    env:
      VERSION: ${{ needs.plan.outputs.version }}
    steps:
      - uses: actions/checkout@v6

      - name: Download artifacts for checksums
        uses: actions/download-artifact@v7
        with:
          pattern: artifacts-*
          path: artifacts/
          merge-multiple: true

      - name: Generate Homebrew formula
        run: |
          # Read checksums
          X86_DARWIN_SHA=$(cat artifacts/fresh-editor-x86_64-apple-darwin.tar.xz.sha256 | awk '{print $1}')
          ARM_DARWIN_SHA=$(cat artifacts/fresh-editor-aarch64-apple-darwin.tar.xz.sha256 | awk '{print $1}')
          X86_LINUX_SHA=$(cat artifacts/fresh-editor-x86_64-unknown-linux-gnu.tar.xz.sha256 | awk '{print $1}')
          ARM_LINUX_SHA=$(cat artifacts/fresh-editor-aarch64-unknown-linux-gnu.tar.xz.sha256 | awk '{print $1}')

          cat > fresh.rb << FORMULA
          class Fresh < Formula
            desc "A modern terminal-based text editor with plugin support"
            homepage "https://github.com/sinelaw/fresh"
            version "${VERSION}"
            license "Apache-2.0"

            on_macos do
              on_intel do
                url "https://github.com/sinelaw/fresh/releases/download/v${VERSION}/fresh-editor-x86_64-apple-darwin.tar.xz"
                sha256 "${X86_DARWIN_SHA}"
              end
              on_arm do
                url "https://github.com/sinelaw/fresh/releases/download/v${VERSION}/fresh-editor-aarch64-apple-darwin.tar.xz"
                sha256 "${ARM_DARWIN_SHA}"
              end
            end

            on_linux do
              on_intel do
                url "https://github.com/sinelaw/fresh/releases/download/v${VERSION}/fresh-editor-x86_64-unknown-linux-gnu.tar.xz"
                sha256 "${X86_LINUX_SHA}"
              end
              on_arm do
                url "https://github.com/sinelaw/fresh/releases/download/v${VERSION}/fresh-editor-aarch64-unknown-linux-gnu.tar.xz"
                sha256 "${ARM_LINUX_SHA}"
              end
            end

            def install
              # Plugins and themes are compiled into the binary; the tarball
              # ships only the binary + docs.
              bin.install "fresh"
              # Provenance receipt so the editor knows Homebrew installed it and
              # defers updates to \`brew upgrade\`. Overrides the generic tarball
              # receipt that ships inside the archive.
              (prefix/"share/fresh").mkpath
              (prefix/"share/fresh/install-receipt.toml").write <<~RECEIPT
                schema = 1
                channel = "homebrew"
                version = "#{version}"
                package_name = "fresh-editor"
                managed = true
                self_update = false

                [hints]
                formula = "fresh"
                tap = "sinelaw/homebrew-fresh"
              RECEIPT
            end

            test do
              system "#{bin}/fresh", "--version"
            end
          end
          FORMULA

          cat fresh.rb

      - name: Upload Homebrew formula
        uses: actions/upload-artifact@v6
        with:
          name: artifacts-homebrew-formula
          path: fresh.rb

  # Create GitHub Release
  # Only runs if ALL needed jobs succeed - uses !cancelled() to run after all jobs complete,
  # then !failure() ensures none failed
  release:
    needs: [plan, build, build-freebsd, build-musl, build-min-size, build-linux-packages, build-flatpak, build-appimage, build-gui, build-npm-package, build-homebrew-formula, test-nix-build]
    if: ${{ !cancelled() && !failure() && needs.plan.outputs.publishing == 'true' }}
    runs-on: ubuntu-22.04
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      VERSION: ${{ needs.plan.outputs.version }}
      TAG: ${{ needs.plan.outputs.tag }}
    steps:
      - uses: actions/checkout@v6

      - name: Download all artifacts
        uses: actions/download-artifact@v7
        with:
          pattern: artifacts-*
          path: artifacts/
          merge-multiple: true

      - name: List artifacts
        run: ls -la artifacts/

      - name: Create source tarball
        run: |
          # Create a stable source tarball (GitHub's auto-generated archives are not stable)
          TARBALL_NAME="fresh-editor-${VERSION}-source.tar.gz"

          # Create tarball from the current checkout (which is at the tagged commit)
          # Use git archive to get a clean export without .git directory
          git archive --format=tar.gz --prefix="fresh-${VERSION}/" -o "artifacts/${TARBALL_NAME}" HEAD

          # Generate checksum
          cd artifacts
          sha256sum "${TARBALL_NAME}" > "${TARBALL_NAME}.sha256"
          cd ..

          echo "Created source tarball:"
          ls -la "artifacts/${TARBALL_NAME}"
          cat "artifacts/${TARBALL_NAME}.sha256"

      - name: Extract changelog for this version
        id: changelog
        run: |
          # Extract the changelog section for this version
          # CHANGELOG format: ## 0.1.53 (no brackets), sections end with ---
          VERSION="${{ needs.plan.outputs.version }}"
          awk -v ver="$VERSION" '
            /^## [0-9]/ {
              if (found) exit
              if ($2 == ver) found=1
            }
            /^---$/ { if (found) exit }
            found { print }
          ' CHANGELOG.md > release_notes.md || echo "Release ${VERSION}" > release_notes.md

          # If empty, use a default message
          if [ ! -s release_notes.md ]; then
            echo "Release ${VERSION}" > release_notes.md
          fi

          cat release_notes.md

      - name: Create GitHub Release
        run: |
          PRERELEASE_FLAG=""
          if [ "${{ needs.plan.outputs.is_prerelease }}" = "true" ]; then
            PRERELEASE_FLAG="--prerelease"
          fi

          gh release create "$TAG" \
            --target "${{ github.sha }}" \
            $PRERELEASE_FLAG \
            --title "fresh-editor ${VERSION}" \
            --notes-file release_notes.md \
            artifacts/*.tar.xz \
            artifacts/*.tar.xz.sha256 \
            artifacts/*.tar.gz \
            artifacts/*.tar.gz.sha256 \
            artifacts/*.zip \
            artifacts/*.zip.sha256 \
            artifacts/*.deb \
            artifacts/*.deb.sha256 \
            artifacts/*.rpm \
            artifacts/*.rpm.sha256 \
            artifacts/*.flatpak \
            artifacts/*.flatpak.sha256 \
            artifacts/*.AppImage \
            artifacts/*.AppImage.sha256 \
            artifacts/*.pkg \
            artifacts/*.pkg.sha256 \
            artifacts/fresh-gui-*.exe \
            artifacts/fresh-gui-*.exe.sha256 \
            artifacts/fresh.rb \
            2>/dev/null || true

  # Publish to Homebrew tap
  publish-homebrew:
    needs: [plan, release]
    if: ${{ needs.plan.outputs.publishing == 'true' && needs.plan.outputs.is_prerelease != 'true' }}
    runs-on: ubuntu-22.04
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      VERSION: ${{ needs.plan.outputs.version }}
    steps:
      - name: Download Homebrew formula
        uses: actions/download-artifact@v7
        with:
          name: artifacts-homebrew-formula
          path: formula/

      - uses: actions/checkout@v6
        with:
          repository: sinelaw/homebrew-fresh
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          path: tap

      - name: Update Homebrew tap
        run: |
          cp formula/fresh.rb tap/Formula/fresh.rb
          cd tap
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add Formula/fresh.rb
          git commit -m "fresh ${VERSION}" || echo "No changes to commit"
          git push

  # Publish to npm
  publish-npm:
    needs: [plan, release]
    if: ${{ needs.plan.outputs.publishing == 'true' && needs.plan.outputs.is_prerelease != 'true' }}
    runs-on: ubuntu-22.04
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v6

      - name: Determine version
        id: version
        run: |
          if [ -n "${{ needs.plan.outputs.version }}" ]; then
            VERSION="${{ needs.plan.outputs.version }}"
          else
            VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
          fi
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "Publishing npm package for version: $VERSION"

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24'

      - name: Update npm
        run: npm install -g npm@latest

      - name: Create npm package
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          # Copy JS files from npm-package template
          mkdir -p dist-npm
          cp crates/fresh-editor/npm-package/*.js dist-npm/

          # Create package.json from template with version
          sed "s/VERSION_PLACEHOLDER/${VERSION}/g" crates/fresh-editor/npm-package/package.json.template > dist-npm/package.json

          # Copy supporting files. Plugins/themes are embedded in the binary.
          cp README.md LICENSE CHANGELOG.md dist-npm/

          # Create the tarball
          cd dist-npm
          npm pack
          mv *.tgz ../fresh-editor-npm-package.tar.gz
          cd ..

          echo "Created npm package:"
          ls -la fresh-editor-npm-package.tar.gz

      - name: Publish to npm with provenance (OIDC)
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          echo "Publishing npm package for version ${VERSION}"
          npm publish --access public --provenance ./fresh-editor-npm-package.tar.gz

  # Publish to AUR
  publish-aur:
    needs: [plan, release]
    if: ${{ needs.plan.outputs.publishing == 'true' && needs.plan.outputs.is_prerelease != 'true' }}
    uses: ./.github/workflows/aur-publish.yml
    with:
      version: ${{ needs.plan.outputs.version }}
    secrets: inherit

  # Publish to crates.io
  publish-cargo:
    needs: [plan, release]
    if: ${{ needs.plan.outputs.publishing == 'true' && needs.plan.outputs.is_prerelease != 'true' }}
    uses: ./.github/workflows/cargo-publish.yml
    secrets: inherit

  # Publish to winget-pkgs
  publish-winget:
    needs: [plan, release]
    if: ${{ needs.plan.outputs.publishing == 'true' && needs.plan.outputs.is_prerelease != 'true' }}
    uses: ./.github/workflows/winget-publish.yml
    with:
      version: ${{ needs.plan.outputs.version }}
    secrets: inherit
