name: CI

on:
  push:
    branches:
      - main
    tags:
      - v*
  pull_request:

permissions:
  contents: read

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false

      - name: Install Rust
        run: rustup toolchain install stable --profile minimal --component clippy,rustfmt

      - name: Check formatting
        run: cargo fmt --check

      - name: Lint
        run: cargo clippy --all-targets -- -D warnings

      - name: Test
        run: cargo test --locked

  plan:
    name: Plan release
    needs:
      - test
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    outputs:
      val: ${{ steps.plan.outputs.manifest }}
      tag: ${{ github.ref_name }}
    env:
      GH_TOKEN: ${{ github.token }}
      TAG: ${{ github.ref_name }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411  # v2.19.4
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
          ref: ${{ github.ref_name }}

      - name: Install dist
        shell: bash
        run: |
          set -euo pipefail
          curl --proto '=https' --tlsv1.2 -LsSf \
            "https://github.com/axodotdev/cargo-dist/releases/download/v0.32.0/cargo-dist-installer.sh" |
            sh

      - name: Cache dist
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/dist

      - id: plan
        name: Plan release
        run: |
          set -euo pipefail
          dist host --steps=create --tag "$TAG" --output-format=json > plan-dist-manifest.json
          cat plan-dist-manifest.json
          {
            printf 'manifest='
            jq -c . plan-dist-manifest.json
          } >> "$GITHUB_OUTPUT"

      - name: Upload dist manifest
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: artifacts-plan-dist-manifest
          path: plan-dist-manifest.json

  build-local-artifacts:
    name: Build local artifacts (${{ join(matrix.targets, ', ') }})
    needs:
      - plan
    if: ${{ startsWith(github.ref, 'refs/tags/v') && fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null }}
    strategy:
      fail-fast: false
      matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
    runs-on: ${{ matrix.runner }}
    container: ${{ matrix.container && matrix.container.image || null }}
    permissions:
      contents: read
    env:
      BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
      DIST_ARGS: ${{ matrix.dist_args }}
      GH_TOKEN: ${{ github.token }}
      TAG: ${{ needs.plan.outputs.tag }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411  # v2.19.4
        with:
          egress-policy: audit

      - name: Enable Windows long paths
        run: git config --global core.longpaths true

      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
          ref: ${{ needs.plan.outputs.tag }}
          submodules: recursive

      - name: Install Rust in container
        if: ${{ matrix.container }}
        run: |
          set -euo pipefail
          if ! command -v cargo > /dev/null 2>&1; then
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
          fi

      - name: Install dist
        if: ${{ runner.os != 'Windows' }}
        shell: bash
        run: |
          set -euo pipefail
          curl --proto '=https' --tlsv1.2 -LsSf \
            "https://github.com/axodotdev/cargo-dist/releases/download/v0.32.0/cargo-dist-installer.sh" |
            sh

      - name: Install dist
        if: ${{ runner.os == 'Windows' }}
        shell: pwsh
        run: irm https://github.com/axodotdev/cargo-dist/releases/download/v0.32.0/cargo-dist-installer.ps1 | iex

      - name: Fetch local artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true

      - name: Build artifacts
        if: ${{ runner.os != 'Windows' }}
        shell: bash
        run: |
          set -euo pipefail
          read -r -a dist_args <<< "$DIST_ARGS"
          dist build --tag "$TAG" --print=linkage --output-format=json "${dist_args[@]}" > dist-manifest.json

      - name: Build artifacts
        if: ${{ runner.os == 'Windows' }}
        shell: pwsh
        run: |
          $distArgs = $env:DIST_ARGS -split ' '
          dist build --tag "$env:TAG" --print=linkage --output-format=json @distArgs > dist-manifest.json

      - id: cargo-dist
        name: Collect artifacts
        shell: bash
        run: |
          set -euo pipefail
          {
            echo "paths<<EOF"
            dist print-upload-files-from-manifest --manifest dist-manifest.json
            echo "EOF"
          } >> "$GITHUB_OUTPUT"
          cp dist-manifest.json "$BUILD_MANIFEST_NAME"

      - name: Upload artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: artifacts-build-local-${{ join(matrix.targets, '_') }}
          path: |
            ${{ steps.cargo-dist.outputs.paths }}
            ${{ env.BUILD_MANIFEST_NAME }}

  build-global-artifacts:
    name: Build global artifacts
    needs:
      - plan
      - build-local-artifacts
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    runs-on: ubuntu-22.04
    permissions:
      contents: read
    env:
      BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
      GH_TOKEN: ${{ github.token }}
      TAG: ${{ needs.plan.outputs.tag }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411  # v2.19.4
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
          ref: ${{ needs.plan.outputs.tag }}
          submodules: recursive

      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/

      - name: Make dist executable
        run: chmod +x ~/.cargo/bin/dist

      - name: Fetch local artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true

      - id: cargo-dist
        name: Build artifacts
        shell: bash
        run: |
          set -euo pipefail
          dist build --tag "$TAG" --output-format=json "--artifacts=global" > dist-manifest.json
          {
            echo "paths<<EOF"
            jq --raw-output ".upload_files[]" dist-manifest.json
            echo "EOF"
          } >> "$GITHUB_OUTPUT"
          cp dist-manifest.json "$BUILD_MANIFEST_NAME"

      - name: Upload artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: artifacts-build-global
          path: |
            ${{ steps.cargo-dist.outputs.paths }}
            ${{ env.BUILD_MANIFEST_NAME }}

  publish-crate:
    name: Publish crate
    needs:
      - plan
      - build-local-artifacts
      - build-global-artifacts
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    runs-on: ubuntu-latest
    environment: release
    permissions:
      contents: read
      id-token: write
    env:
      TAG: ${{ needs.plan.outputs.tag }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411  # v2.19.4
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
          ref: ${{ needs.plan.outputs.tag }}

      - name: Install Rust
        run: rustup toolchain install stable --profile minimal

      - name: Verify tag version
        run: |
          set -euo pipefail
          version="${TAG#v}"
          manifest_version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
          if [ "$version" = "$manifest_version" ]; then
            exit 0
          fi
          echo "tag ${TAG} does not match Cargo.toml version ${manifest_version}" >&2
          exit 1

      - name: Authenticate with crates.io
        id: crates-io-auth
        uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe  # v1.0.4

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
        run: cargo publish --locked

  host:
    name: Host release
    needs:
      - plan
      - build-local-artifacts
      - build-global-artifacts
      - publish-crate
    if: ${{ always() && startsWith(github.ref, 'refs/tags/v') && needs.plan.result == 'success' && needs.publish-crate.result == 'success' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    env:
      GH_TOKEN: ${{ github.token }}
      RELEASE_COMMIT: ${{ github.sha }}
      TAG: ${{ needs.plan.outputs.tag }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411  # v2.19.4
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
          ref: ${{ needs.plan.outputs.tag }}
          submodules: recursive

      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/

      - name: Make dist executable
        run: chmod +x ~/.cargo/bin/dist

      - name: Fetch artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true

      - id: host
        name: Host artifacts
        shell: bash
        run: |
          set -euo pipefail
          dist host --tag "$TAG" --steps=upload --steps=release --output-format=json > dist-manifest.json
          cat dist-manifest.json
          {
            printf 'manifest='
            jq -c . dist-manifest.json
          } >> "$GITHUB_OUTPUT"

      - name: Upload dist manifest
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: artifacts-dist-manifest
          path: dist-manifest.json

      - name: Download GitHub artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true

      - name: Cleanup manifests
        run: rm -f artifacts/*-dist-manifest.json

      - name: Create GitHub Release
        env:
          ANNOUNCEMENT_BODY: ${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}
          ANNOUNCEMENT_TITLE: ${{ fromJson(steps.host.outputs.manifest).announcement_title }}
          PRERELEASE_FLAG: ${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}
        run: |
          set -euo pipefail
          prerelease_args=()
          if [ -n "$PRERELEASE_FLAG" ]; then
            prerelease_args=("$PRERELEASE_FLAG")
          fi
          echo "$ANNOUNCEMENT_BODY" > "$RUNNER_TEMP/notes.txt"
          gh release create "$TAG" \
            --target "$RELEASE_COMMIT" \
            "${prerelease_args[@]}" \
            --title "$ANNOUNCEMENT_TITLE" \
            --notes-file "$RUNNER_TEMP/notes.txt" \
            artifacts/*
