name: Build and Release
on:
  push:
    branches:
      - main
    tags:
      - "v*.*.*"
  pull_request:
    types:
      - opened
      - labeled
      - synchronize
      - reopened

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && (github.event.action != 'labeled' || github.event.label.name == 'run-pipeline') && github.event.pull_request.number || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' && (github.event.action != 'labeled' || github.event.label.name == 'run-pipeline') }}

env:
  RETENTION_INTERMEDIATE_ASSETS: 1
  RETENTION_RELEASE_ASSETS: 30
  FLATPAK_REPOSITORY: MindWorkAI/Flatpak
  FLATPAK_WORKFLOW: flatpak.yml
  FLATPAK_YQ_VERSION: 4.44.6
  FLATPAK_YQ_SHA256: 0c2b24e645b57d8e7c0566d18643a6d4f5580feeea3878127354a46f2a1e4598
  FLATPAK_UV_VERSION: 0.11.28
  FLATPAK_UV_SHA256: e490a6464492183c5d4534a5527fb4440f7f2bb2f228162ad7e4afe076dc0224
  FLATPAK_FREEDESKTOP_VERSION: "25.08"
    
jobs:
  determine_run_mode:
    name: Determine run mode
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      is_release: ${{ steps.determine.outputs.is_release }}
      is_main_push: ${{ steps.determine.outputs.is_main_push }}
      is_labeled_pr: ${{ steps.determine.outputs.is_labeled_pr }}
      is_pr_build: ${{ steps.determine.outputs.is_pr_build }}
      is_internal_pr: ${{ steps.determine.outputs.is_internal_pr }}
      build_enabled: ${{ steps.determine.outputs.build_enabled }}
      artifact_retention_days: ${{ steps.determine.outputs.artifact_retention_days }}
      skip_reason: ${{ steps.determine.outputs.skip_reason }}

    steps:
      - name: Determine run mode
        id: determine
        env:
          EVENT_NAME: ${{ github.event_name }}
          PR_ACTION: ${{ github.event.action }}
          ACTION_LABEL_NAME: ${{ github.event.label.name }}
          REF: ${{ github.ref }}
          PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }}
          PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
          REPOSITORY: ${{ github.repository }}
        run: |
          is_release=false
          is_main_push=false
          is_labeled_pr=false
          is_pr_build=false
          is_internal_pr=false
          build_enabled=false
          artifact_retention_days=0
          skip_reason="Build disabled: event did not match main push, release tag, or labeled internal PR."

          if [[ "$EVENT_NAME" == "pull_request" && "$PR_HEAD_REPO" == "$REPOSITORY" ]]; then
            is_internal_pr=true
          fi

          has_run_pipeline_label=false
          if [[ " $PR_LABELS " == *" run-pipeline "* ]]; then
            has_run_pipeline_label=true
          fi

          if [[ "$REF" == refs/tags/v* ]]; then
            is_release=true
            build_enabled=true
            artifact_retention_days=${{ env.RETENTION_INTERMEDIATE_ASSETS }}
            skip_reason=""
          elif [[ "$EVENT_NAME" == "push" && "$REF" == "refs/heads/main" ]]; then
            is_main_push=true
            build_enabled=true
            artifact_retention_days=7
            skip_reason=""
          elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" == "labeled" && "$ACTION_LABEL_NAME" == "run-pipeline" ]]; then
            is_labeled_pr=true
            is_pr_build=true
            build_enabled=true
            artifact_retention_days=3
            skip_reason=""
          elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" != "labeled" && "$has_run_pipeline_label" == "true" ]]; then
            is_labeled_pr=true
            is_pr_build=true
            build_enabled=true
            artifact_retention_days=3
            skip_reason=""
          elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" == "labeled" ]]; then
            skip_reason="Build disabled: label '${ACTION_LABEL_NAME}' is not 'run-pipeline'."
          elif [[ "$EVENT_NAME" == "pull_request" && "$has_run_pipeline_label" != "true" ]]; then
            skip_reason="Build disabled: PR does not have the required 'run-pipeline' label."
          fi

          echo "is_release=${is_release}" >> "$GITHUB_OUTPUT"
          echo "is_main_push=${is_main_push}" >> "$GITHUB_OUTPUT"
          echo "is_labeled_pr=${is_labeled_pr}" >> "$GITHUB_OUTPUT"
          echo "is_pr_build=${is_pr_build}" >> "$GITHUB_OUTPUT"
          echo "is_internal_pr=${is_internal_pr}" >> "$GITHUB_OUTPUT"
          echo "build_enabled=${build_enabled}" >> "$GITHUB_OUTPUT"
          echo "artifact_retention_days=${artifact_retention_days}" >> "$GITHUB_OUTPUT"
          echo "skip_reason=${skip_reason}" >> "$GITHUB_OUTPUT"

      - name: Log run mode
        env:
          EVENT_NAME: ${{ github.event_name }}
          REF: ${{ github.ref }}
          PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ', ') }}
          PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
          REPOSITORY: ${{ github.repository }}
          IS_RELEASE: ${{ steps.determine.outputs.is_release }}
          IS_MAIN_PUSH: ${{ steps.determine.outputs.is_main_push }}
          IS_LABELED_PR: ${{ steps.determine.outputs.is_labeled_pr }}
          IS_PR_BUILD: ${{ steps.determine.outputs.is_pr_build }}
          IS_INTERNAL_PR: ${{ steps.determine.outputs.is_internal_pr }}
          BUILD_ENABLED: ${{ steps.determine.outputs.build_enabled }}
          ARTIFACT_RETENTION_DAYS: ${{ steps.determine.outputs.artifact_retention_days }}
          SKIP_REASON: ${{ steps.determine.outputs.skip_reason }}
        run: |
          echo "event_name: ${EVENT_NAME}"
          echo "ref: ${REF}"
          echo "repository: ${REPOSITORY}"
          echo "pr_head_repo: ${PR_HEAD_REPO}"
          echo "pr_labels: ${PR_LABELS}"
          echo "is_release: ${IS_RELEASE}"
          echo "is_main_push: ${IS_MAIN_PUSH}"
          echo "is_labeled_pr: ${IS_LABELED_PR}"
          echo "is_pr_build: ${IS_PR_BUILD}"
          echo "is_internal_pr: ${IS_INTERNAL_PR}"
          echo "build_enabled: ${BUILD_ENABLED}"
          echo "artifact_retention_days: ${ARTIFACT_RETENTION_DAYS}"
          echo "skip_reason: ${SKIP_REASON}"

          {
            echo "### Run Mode"
            echo ""
            echo "| Key | Value |"
            echo "| --- | --- |"
            echo "| event_name | ${EVENT_NAME} |"
            echo "| ref | ${REF} |"
            echo "| repository | ${REPOSITORY} |"
            echo "| pr_head_repo | ${PR_HEAD_REPO} |"
            echo "| pr_labels | ${PR_LABELS} |"
            echo "| is_release | ${IS_RELEASE} |"
            echo "| is_main_push | ${IS_MAIN_PUSH} |"
            echo "| is_labeled_pr | ${IS_LABELED_PR} |"
            echo "| is_pr_build | ${IS_PR_BUILD} |"
            echo "| is_internal_pr | ${IS_INTERNAL_PR} |"
            echo "| build_enabled | ${BUILD_ENABLED} |"
            echo "| artifact_retention_days | ${ARTIFACT_RETENTION_DAYS} |"
            echo "| skip_reason | ${SKIP_REASON} |"
          } >> "$GITHUB_STEP_SUMMARY"

  read_metadata:
    name: Read metadata
    runs-on: ubuntu-latest
    needs: determine_run_mode
    if: needs.determine_run_mode.outputs.build_enabled == 'true'
    permissions:
      contents: read
    outputs:
      formatted_version: ${{ steps.format_metadata.outputs.formatted_version }}
      formatted_build_time: ${{ steps.format_metadata.outputs.formatted_build_time }}
      changelog: ${{ steps.read_changelog.outputs.changelog }}
      version: ${{ steps.format_metadata.outputs.version }}
      source_commit: ${{ steps.format_metadata.outputs.source_commit }}
      pdfium_chromium_revision: ${{ steps.format_metadata.outputs.pdfium_chromium_revision }}

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

      - name: Read and format metadata
        id: format_metadata
        run: |
          # Read the first two lines of the metadata file:
          version=$(sed -n '1p' metadata.txt)
          build_time=$(sed -n '2p' metadata.txt)
          pdfium_full_version=$(sed -n '11p' metadata.txt)
          pdfium_chromium_revision=$(echo "$pdfium_full_version" | cut -d'.' -f3)
          source_commit=$(git rev-parse HEAD)

          # Format the version:
          formatted_version="v${version}"

          # Format the build time according to RFC 3339:
          formatted_build_time=$(date -d "${build_time}" --utc +'%Y-%m-%dT%H:%M:%SZ')

          # Log the formatted metadata:
          echo "Formatted version: '${formatted_version}'"
          echo "Formatted build time: '${formatted_build_time}'"
          echo "Source commit: '${source_commit}'"
          echo "PDFium Chromium revision: '${pdfium_chromium_revision}'"

          # Set the outputs:
          echo "formatted_version=${formatted_version}" >> "$GITHUB_OUTPUT"
          echo "FORMATTED_VERSION=${formatted_version}" >> $GITHUB_ENV
          echo "formatted_build_time=${formatted_build_time}" >> "$GITHUB_OUTPUT"
          echo "version=${version}" >> "$GITHUB_OUTPUT"
          echo "source_commit=${source_commit}" >> "$GITHUB_OUTPUT"
          echo "pdfium_chromium_revision=${pdfium_chromium_revision}" >> "$GITHUB_OUTPUT"
          
      - name: Check tag vs. metadata version
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          # Ensure, that the tag matches the version in the metadata file:
          if [ "${GITHUB_REF}" != "refs/tags/${FORMATTED_VERSION}" ]; then
              echo "Tag '${GITHUB_REF}' does not match the version in the metadata file '${FORMATTED_VERSION}'"
              exit 1
          else
              echo "Tag '${GITHUB_REF}' matches the version in the metadata file '${FORMATTED_VERSION}'"
          fi

      - name: Read changelog
        id: read_changelog
        if: needs.determine_run_mode.outputs.is_release == 'true'
        run: |
          # Ensure, that the matching changelog file for the current version exists:
          if [ ! -f "app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md" ]; then
              echo "Changelog file 'app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md' not found"
              exit 1
          else
              echo "Changelog file '${FORMATTED_VERSION}.md' found"
          fi
          
          # Read the changelog file:
          changelog=$(cat "app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md")

          # Set the output:
          echo "changelog<<EOOOF" >> "$GITHUB_OUTPUT"
          echo "${changelog}" >> "$GITHUB_OUTPUT"
          echo "EOOOF" >> "$GITHUB_OUTPUT"

  sync_flatpak_repo:
    name: Sync Flatpak repo
    runs-on: ubuntu-latest
    needs: [determine_run_mode, read_metadata]
    if: needs.determine_run_mode.outputs.is_release == 'true'
    permissions:
      contents: read
    outputs:
      flatpak_commit: ${{ steps.sync.outputs.flatpak_commit }}

    env:
      AI_STUDIO_TAG: ${{ needs.read_metadata.outputs.formatted_version }}
      AI_STUDIO_COMMIT: ${{ needs.read_metadata.outputs.source_commit }}
      PDFIUM_CHROMIUM_REVISION: ${{ needs.read_metadata.outputs.pdfium_chromium_revision }}

    steps:
      - name: Checkout AI Studio release metadata
        uses: actions/checkout@v4
        with:
          ref: ${{ env.AI_STUDIO_COMMIT }}
          path: ai-studio
          sparse-checkout: |
            metadata.txt
            runtime/packaging/linux/org.mindworkai.AIStudio.desktop
            runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml
          sparse-checkout-cone-mode: false

      - name: Checkout Flatpak repository
        uses: actions/checkout@v4
        with:
          repository: ${{ env.FLATPAK_REPOSITORY }}
          token: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
          ref: main
          path: flatpak
          fetch-depth: 0
          persist-credentials: false

      - name: Install Flatpak sync tools and SDK
        run: |
          set -euo pipefail

          sudo apt-get update
          sudo apt-get install --no-install-recommends -y flatpak

          flatpak remote-add \
            --user \
            --if-not-exists \
            flathub \
            https://flathub.org/repo/flathub.flatpakrepo
          flatpak install \
            --user \
            --noninteractive \
            -y \
            flathub \
            "org.freedesktop.Sdk//${FLATPAK_FREEDESKTOP_VERSION}" \
            "org.freedesktop.Sdk.Extension.dotnet9//${FLATPAK_FREEDESKTOP_VERSION}"

          tools_dir="$RUNNER_TEMP/flatpak-sync-tools"
          mkdir -p "$tools_dir"

          curl -fsSL \
            -o "$tools_dir/yq" \
            "https://github.com/mikefarah/yq/releases/download/v${FLATPAK_YQ_VERSION}/yq_linux_amd64"
          echo "${FLATPAK_YQ_SHA256}  ${tools_dir}/yq" | sha256sum --check --strict
          chmod +x "$tools_dir/yq"

          uv_archive="$RUNNER_TEMP/uv-x86_64-unknown-linux-gnu.tar.gz"
          curl -fsSL \
            -o "$uv_archive" \
            "https://github.com/astral-sh/uv/releases/download/${FLATPAK_UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz"
          echo "${FLATPAK_UV_SHA256}  ${uv_archive}" | sha256sum --check --strict
          tar -xzf "$uv_archive" -C "$tools_dir"
          install -m 0755 "$tools_dir/uv-x86_64-unknown-linux-gnu/uv" "$tools_dir/uv"

          echo "$tools_dir" >> "$GITHUB_PATH"
          "$tools_dir/uv" --version
          "$tools_dir/yq" --version

      - name: Update Flatpak release sources
        working-directory: flatpak
        env:
          PDFIUM_X64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-x64.tgz
          PDFIUM_ARM64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-arm64.tgz
        run: |
          set -euo pipefail

          release_version=$(sed -n '1p' ../ai-studio/metadata.txt)
          release_timestamp=$(sed -n '2p' ../ai-studio/metadata.txt)
          release_date=${release_timestamp%% *}

          test "$release_version" = "${AI_STUDIO_TAG#v}"
          [[ "$release_timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]][0-9]{2}:[0-9]{2}:[0-9]{2}[[:space:]]UTC$ ]]
          test -s ../ai-studio/runtime/packaging/linux/org.mindworkai.AIStudio.desktop
          python3 ./update-metainfo.py \
            --check \
            --metainfo ../ai-studio/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml \
            "$release_version" \
            "$release_date"

          pdfium_base_url="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_CHROMIUM_REVISION}"
          pdfium_x64_url="${pdfium_base_url}/pdfium-linux-x64.tgz"
          pdfium_arm64_url="${pdfium_base_url}/pdfium-linux-arm64.tgz"

          curl -fsSL -o "$PDFIUM_X64_ARCHIVE" "$pdfium_x64_url"
          curl -fsSL -o "$PDFIUM_ARM64_ARCHIVE" "$pdfium_arm64_url"

          export PDFIUM_X64_URL="$pdfium_x64_url"
          export PDFIUM_ARM64_URL="$pdfium_arm64_url"
          export PDFIUM_X64_SHA256
          export PDFIUM_ARM64_SHA256
          PDFIUM_X64_SHA256=$(sha256sum "$PDFIUM_X64_ARCHIVE" | awk '{print $1}')
          PDFIUM_ARM64_SHA256=$(sha256sum "$PDFIUM_ARM64_ARCHIVE" | awk '{print $1}')

          yq -i '
            (.modules[] | select(.name == "mind-work-ai-studio").sources[0].tag) = strenv(AI_STUDIO_TAG) |
            (.modules[] | select(.name == "mind-work-ai-studio").sources[0].commit) = strenv(AI_STUDIO_COMMIT) |
            (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url) = strenv(PDFIUM_X64_URL) |
            (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256) = strenv(PDFIUM_X64_SHA256) |
            (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url) = strenv(PDFIUM_ARM64_URL) |
            (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256) = strenv(PDFIUM_ARM64_SHA256)
          ' org.mindworkai.AIStudio.yml

          ./update-dependencies

          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].tag' org.mindworkai.AIStudio.yml)" = "$AI_STUDIO_TAG"
          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].commit' org.mindworkai.AIStudio.yml)" = "$AI_STUDIO_COMMIT"
          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url' org.mindworkai.AIStudio.yml)" = "$PDFIUM_X64_URL"
          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256' org.mindworkai.AIStudio.yml)" = "$PDFIUM_X64_SHA256"
          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url' org.mindworkai.AIStudio.yml)" = "$PDFIUM_ARM64_URL"
          test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256' org.mindworkai.AIStudio.yml)" = "$PDFIUM_ARM64_SHA256"

          for generated_source in cargo-sources.json dotnet-sources.json tauri-cli-sources.json; do
            test -s "$generated_source"
            jq -e 'type == "array" and length > 0' "$generated_source" > /dev/null
          done

          git diff --check
          git diff --stat

      - name: Commit and merge Flatpak sync
        id: sync
        working-directory: flatpak
        env:
          GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
        run: |
          set -euo pipefail

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

          branch="sync/ai-studio-${AI_STUDIO_TAG}"
          git checkout -B "$branch"
          git add org.mindworkai.AIStudio.yml cargo-sources.json dotnet-sources.json tauri-cli-sources.json

          if git diff --cached --quiet; then
            echo "Flatpak repository is already synced for ${AI_STUDIO_TAG}."
            flatpak_commit=$(git rev-parse HEAD)
            remote_main=$(git ls-remote origin refs/heads/main | awk '{print $1}')
            if [ "$remote_main" != "$flatpak_commit" ]; then
              echo "Flatpak main advanced from ${flatpak_commit} to ${remote_main} during synchronization."
              exit 1
            fi

            echo "flatpak_commit=${flatpak_commit}" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          git commit -m "Sync AI Studio ${AI_STUDIO_TAG}"
          sync_commit=$(git rev-parse HEAD)

          basic_auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w 0)
          git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
          trap 'git config --local --unset-all http.https://github.com/.extraheader || true' EXIT

          remote_branch_sha=$(git ls-remote --heads origin "$branch" | awk '{print $1}')
          if [ -n "$remote_branch_sha" ]; then
            git push --force-with-lease="refs/heads/${branch}:${remote_branch_sha}" origin "HEAD:${branch}"
          else
            git push origin "HEAD:${branch}"
          fi

          pr_number=$(gh pr list \
            --repo "$FLATPAK_REPOSITORY" \
            --head "$branch" \
            --state open \
            --json number \
            --jq '.[0].number // empty')

          if [ -z "$pr_number" ]; then
            pr_url=$(gh pr create \
              --repo "$FLATPAK_REPOSITORY" \
              --base main \
              --head "$branch" \
              --title "Sync AI Studio ${AI_STUDIO_TAG}" \
              --body "Synchronizes the Flatpak manifest and generated dependency sources for MindWork AI Studio ${AI_STUDIO_TAG}.")
            pr_number="${pr_url##*/}"
          fi

          mergeable="UNKNOWN"
          for attempt in {1..30}; do
            pr_state=$(gh pr view "$pr_number" --repo "$FLATPAK_REPOSITORY" --json mergeable,mergeStateStatus)
            mergeable=$(echo "$pr_state" | jq -r '.mergeable')
            merge_state_status=$(echo "$pr_state" | jq -r '.mergeStateStatus')

            echo "PR #${pr_number}: mergeable=${mergeable}, mergeStateStatus=${merge_state_status}"

            if [ "$mergeable" = "MERGEABLE" ]; then
              break
            fi

            if [ "$mergeable" = "CONFLICTING" ]; then
              echo "Flatpak sync PR #${pr_number} has merge conflicts."
              exit 1
            fi

            sleep 5
          done

          if [ "$mergeable" != "MERGEABLE" ]; then
            echo "Timed out waiting for Flatpak sync PR #${pr_number} to become mergeable."
            exit 1
          fi

          gh pr merge "$pr_number" \
            --repo "$FLATPAK_REPOSITORY" \
            --squash \
            --delete-branch \
            --match-head-commit "$sync_commit"

          for attempt in {1..120}; do
            pr_state=$(gh pr view "$pr_number" \
              --repo "$FLATPAK_REPOSITORY" \
              --json state,mergedAt,mergeCommit)
            state=$(echo "$pr_state" | jq -r '.state')
            merged_at=$(echo "$pr_state" | jq -r '.mergedAt // empty')
            merge_commit=$(echo "$pr_state" | jq -r '.mergeCommit.oid // empty')

            echo "PR #${pr_number}: state=${state}, mergedAt=${merged_at:-pending}, mergeCommit=${merge_commit:-pending}"

            if [ -n "$merged_at" ] && [ -n "$merge_commit" ]; then
              echo "flatpak_commit=${merge_commit}" >> "$GITHUB_OUTPUT"
              exit 0
            fi

            if [ "$state" = "CLOSED" ]; then
              echo "Flatpak sync PR #${pr_number} was closed without being merged."
              exit 1
            fi

            sleep 5
          done

          echo "Timed out waiting for Flatpak sync PR #${pr_number} to be merged."
          exit 1

  collect_flatpak_artifacts:
    name: Collect Flatpak artifacts
    runs-on: ubuntu-latest
    needs: [determine_run_mode, read_metadata, sync_flatpak_repo]
    if: needs.determine_run_mode.outputs.is_release == 'true'
    permissions:
      contents: read

    env:
      FLATPAK_COMMIT: ${{ needs.sync_flatpak_repo.outputs.flatpak_commit }}

    steps:
      - name: Dispatch and wait for Flatpak build
        id: flatpak_run
        env:
          GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
        run: |
          set -euo pipefail

          find_run_id() {
            local created_after="$1"
            local runs

            runs=$(gh run list \
              --repo "$FLATPAK_REPOSITORY" \
              --workflow "$FLATPAK_WORKFLOW" \
              --branch main \
              --commit "$FLATPAK_COMMIT" \
              --limit 20 \
              --json databaseId,event,headSha,createdAt)

            echo "$runs" | jq -r \
              --arg commit "$FLATPAK_COMMIT" \
              --arg created_after "$created_after" \
              '[.[] | select(.headSha == $commit and .event == "workflow_dispatch" and .createdAt >= $created_after)][0].databaseId // empty'
          }

          validate_required_artifacts() {
            local run_id="$1"
            local artifacts
            local expected_name
            local match_count
            local custom_count

            artifacts=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100")
            custom_count=$(echo "$artifacts" | jq \
              '[.artifacts[] | select(.name | startswith("MindWork AI Studio Flatpak ("))] | length')

            if [ "$custom_count" -ne 2 ]; then
              echo "Flatpak run ${run_id} contains ${custom_count} release artifacts; expected 2."
              return 1
            fi

            for expected_name in \
              "MindWork AI Studio Flatpak (x86_64)" \
              "MindWork AI Studio Flatpak (aarch64)"; do
              match_count=$(echo "$artifacts" | jq \
                --arg name "$expected_name" \
                '[.artifacts[] | select(.name == $name and .expired == false)] | length')

              if [ "$match_count" -ne 1 ]; then
                echo "Flatpak run ${run_id} does not contain one active '${expected_name}' artifact."
                return 1
              fi
            done
          }

          wait_for_run() {
            local run_id="$1"
            local run_json
            local status
            local conclusion
            local head_sha
            local url

            for attempt in {1..180}; do
              run_json=$(gh run view "$run_id" \
                --repo "$FLATPAK_REPOSITORY" \
                --json status,conclusion,headSha,url)
              status=$(echo "$run_json" | jq -r '.status')
              conclusion=$(echo "$run_json" | jq -r '.conclusion // empty')
              head_sha=$(echo "$run_json" | jq -r '.headSha')
              url=$(echo "$run_json" | jq -r '.url')

              echo "Flatpak run ${run_id}: status=${status}, conclusion=${conclusion:-pending}, url=${url}"

              if [ "$head_sha" != "$FLATPAK_COMMIT" ]; then
                echo "Flatpak run ${run_id} targets '${head_sha}', expected '${FLATPAK_COMMIT}'."
                return 1
              fi

              if [ "$status" = "completed" ]; then
                if [ "$conclusion" = "success" ] && validate_required_artifacts "$run_id"; then
                  return 0
                fi

                echo "Flatpak run ${run_id} completed without usable release artifacts."
                return 1
              fi

              sleep 20
            done

            echo "Timed out waiting for Flatpak run ${run_id}."
            return 2
          }

          current_main=$(gh api "repos/${FLATPAK_REPOSITORY}/commits/main" --jq .sha)
          if [ "$current_main" != "$FLATPAK_COMMIT" ]; then
            echo "Flatpak main advanced from ${FLATPAK_COMMIT} to ${current_main} before the build could be dispatched."
            exit 1
          fi

          dispatch_started_at=$(date --utc +'%Y-%m-%dT%H:%M:%SZ')
          gh workflow run "$FLATPAK_WORKFLOW" \
            --repo "$FLATPAK_REPOSITORY" \
            --ref main \
            -f "artifact_retention_days=${RETENTION_INTERMEDIATE_ASSETS}"

          run_id=""
          for attempt in {1..15}; do
            run_id=$(find_run_id "$dispatch_started_at")
            if [ -n "$run_id" ]; then
              break
            fi

            echo "Waiting for the dispatched Flatpak workflow on commit ${FLATPAK_COMMIT}..."
            sleep 20
          done

          if [ -z "$run_id" ]; then
            echo "Timed out waiting for a Flatpak workflow to start on commit ${FLATPAK_COMMIT}."
            exit 1
          fi

          set +e
          wait_for_run "$run_id"
          wait_result=$?
          set -e

          if [ "$wait_result" -eq 0 ]; then
            echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          if [ "$wait_result" -eq 2 ]; then
            exit 1
          fi

          echo "Re-running Flatpak run ${run_id} once."
          previous_attempt=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}" --jq .run_attempt)
          gh run rerun "$run_id" --repo "$FLATPAK_REPOSITORY"

          rerun_started=false
          for attempt in {1..60}; do
            rerun_state=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}")
            current_attempt=$(echo "$rerun_state" | jq -r '.run_attempt')
            current_status=$(echo "$rerun_state" | jq -r '.status')

            if [ "$current_attempt" -gt "$previous_attempt" ] || [ "$current_status" != "completed" ]; then
              rerun_started=true
              break
            fi

            sleep 2
          done

          if [ "$rerun_started" != "true" ]; then
            echo "Timed out waiting for Flatpak run ${run_id} to start its retry."
            exit 1
          fi

          if ! wait_for_run "$run_id"; then
            echo "Flatpak run ${run_id} did not succeed after one retry."
            exit 1
          fi

          echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"

      - name: Download Flatpak artifacts
        env:
          GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
          RUN_ID: ${{ steps.flatpak_run.outputs.run_id }}
        run: |
          set -euo pipefail

          mkdir -p flatpak-artifacts
          gh run download "$RUN_ID" \
            --repo "$FLATPAK_REPOSITORY" \
            --name "MindWork AI Studio Flatpak (x86_64)" \
            --name "MindWork AI Studio Flatpak (aarch64)" \
            --dir flatpak-artifacts

          expected_files=(
            "MindWork AI Studio_x86_64.flatpak"
            "MindWork AI Studio Plugin Pandoc_x86_64.flatpak"
            "MindWork AI Studio_aarch64.flatpak"
            "MindWork AI Studio Plugin Pandoc_aarch64.flatpak"
          )

          flatpak_count=$(find flatpak-artifacts -type f -name '*.flatpak' | wc -l | tr -d ' ')
          if [ "$flatpak_count" -ne 4 ]; then
            echo "Downloaded ${flatpak_count} Flatpak files; expected 4."
            find flatpak-artifacts -type f -print
            exit 1
          fi

          for expected_file in "${expected_files[@]}"; do
            match_count=$(find flatpak-artifacts -type f -name "$expected_file" | wc -l | tr -d ' ')
            if [ "$match_count" -ne 1 ]; then
              echo "Expected exactly one '${expected_file}', found ${match_count}."
              exit 1
            fi

            matched_file=$(find flatpak-artifacts -type f -name "$expected_file" -print -quit)
            test -s "$matched_file"
          done

          find flatpak-artifacts -type f -name '*.flatpak' -print

      - name: Upload Flatpak artifacts
        uses: actions/upload-artifact@v4
        with:
          name: MindWork AI Studio Flatpak Release
          path: flatpak-artifacts/**/*.flatpak
          if-no-files-found: error
          overwrite: true
          retention-days: ${{ env.RETENTION_INTERMEDIATE_ASSETS }}

  build_main:
    name: Build app (${{ matrix.dotnet_runtime }})
    needs: [determine_run_mode, read_metadata]
    if: needs.determine_run_mode.outputs.build_enabled == 'true'
    permissions:
      contents: read
    
    strategy:
      fail-fast: true
      matrix:
        include:
          - platform: 'macos-latest' # for ARM-based macOS (M1 and above)
            rust_target: 'aarch64-apple-darwin'
            dotnet_runtime: 'osx-arm64'
            dotnet_name_postfix: '-aarch64-apple-darwin'
            tauri_bundle: 'dmg,app,updater'
            tauri_bundle_pr: 'dmg'

          - platform: 'macos-latest' # for Intel-based macOS
            rust_target: 'x86_64-apple-darwin'
            dotnet_runtime: 'osx-x64'
            dotnet_name_postfix: '-x86_64-apple-darwin'
            tauri_bundle: 'dmg,app,updater'
            tauri_bundle_pr: 'dmg'

          - platform: 'ubuntu-22.04' # for x86-based Linux
            rust_target: 'x86_64-unknown-linux-gnu'
            dotnet_runtime: 'linux-x64'
            dotnet_name_postfix: '-x86_64-unknown-linux-gnu'
            tauri_bundle: 'appimage,updater'
            tauri_bundle_pr: 'appimage'
            
          - platform: 'ubuntu-22.04-arm' # for ARM-based Linux
            rust_target: 'aarch64-unknown-linux-gnu'
            dotnet_runtime: 'linux-arm64'
            dotnet_name_postfix: '-aarch64-unknown-linux-gnu'
            tauri_bundle: 'appimage,updater'
            tauri_bundle_pr: 'appimage'

          - platform: 'windows-latest' # for x86-based Windows
            rust_target: 'x86_64-pc-windows-msvc'
            dotnet_runtime: 'win-x64'
            dotnet_name_postfix: '-x86_64-pc-windows-msvc.exe'
            tauri_bundle: 'nsis,updater'
            tauri_bundle_pr: 'nsis'

          - platform: 'windows-latest' # for ARM-based Windows
            rust_target: 'aarch64-pc-windows-msvc'
            dotnet_runtime: 'win-arm64'
            dotnet_name_postfix: '-aarch64-pc-windows-msvc.exe'
            tauri_bundle: 'nsis,updater'
            tauri_bundle_pr: 'nsis'
    
    runs-on: ${{ matrix.platform }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          lfs: false
          
      - name: Read and format metadata (Unix)
        if: matrix.platform != 'windows-latest'
        run: |
          # Read the lines of the metadata file:
          app_version=$(sed -n '1p' metadata.txt)
          build_time=$(sed -n '2p' metadata.txt)
          build_number=$(sed -n '3p' metadata.txt)
          
          # Next line is the .NET SDK version.
          # The format is '8.0.205 (commit 3e1383b780)'.
          # We extract only the version number, though:
          dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
          
          # Next line is the .NET runtime version.
          # The format is '8.0.5 (commit 087e15321b)'.
          # We extract only the version number, though:
          dotnet_runtime_version=$(sed -n '5p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
          
          # Next line is the Rust version.
          # The format is '1.78.0 (commit 9b00956e5)'.
          # We extract only the version number, though:
          rust_version=$(sed -n '6p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
          
          # Next line is the MudBlazor version:
          mud_blazor_version=$(sed -n '7p' metadata.txt)
          
          # Next line is the Tauri version:
          tauri_version=$(sed -n '8p' metadata.txt)

          # Format the app version:
          formatted_app_version="v${app_version}"
          
          # Set the architecture:
          if sed --version 2>/dev/null | grep -q GNU; then
            sed -i "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt
          else
            sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt
          fi
          
          # Next line is the necessary PDFium version.
          # The format is '137.0.7123.0'. What we need
          # is the '7123' part:
          pdfium_version=$(sed -n '11p' metadata.txt)
          pdfium_version=$(echo $pdfium_version | cut -d'.' -f3)
          
          # Next line is the vector store version:
          vector_store_version="$(sed -n '12p' metadata.txt)"
          
          # Write the metadata to the environment:
          echo "APP_VERSION=${app_version}" >> $GITHUB_ENV
          echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV
          echo "BUILD_TIME=${build_time}" >> $GITHUB_ENV
          echo "BUILD_NUMBER=${build_number}" >> $GITHUB_ENV
          echo "DOTNET_SDK_VERSION=${dotnet_sdk_version}" >> $GITHUB_ENV
          echo "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $GITHUB_ENV
          echo "RUST_VERSION=${rust_version}" >> $GITHUB_ENV
          echo "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $GITHUB_ENV
          echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV
          echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV
          echo "PDFIUM_VERSION=${pdfium_version}" >> $GITHUB_ENV
          echo "VECTOR_STORE_VERSION=${vector_store_version}" >> $GITHUB_ENV

          # Log the metadata:
          echo "App version: '${formatted_app_version}'"
          echo "Build time: '${build_time}'"
          echo "Build number: '${build_number}'"
          echo ".NET SDK version: '${dotnet_sdk_version}'"
          echo ".NET runtime version: '${dotnet_runtime_version}'"
          echo "Rust version: '${rust_version}'"
          echo "MudBlazor version: '${mud_blazor_version}'"
          echo "Tauri version: '${tauri_version}'"
          echo "Architecture: '${{ matrix.dotnet_runtime }}'"
          echo "PDFium version: '${pdfium_version}'"
          echo "Vector store version: '${vector_store_version}'"
          
      - name: Read and format metadata (Windows)
        if: matrix.platform == 'windows-latest'
        run: |
          # Read the lines of the metadata file:
          $metadata = Get-Content metadata.txt
          
          $app_version = $metadata[0]
          $build_time = $metadata[1]
          $build_number = $metadata[2]
          
          # Next line is the .NET SDK version.
          # The format is '8.0.205 (commit 3e1383b780)'.
          # We extract only the version number, though:
          $dotnet_sdk_version = ($metadata[3] -match '(\d+\.\d+\.\d+)') | Out-Null; $dotnet_sdk_version = $matches[1]
          
          # Next line is the .NET runtime version.
          # The format is '8.0.5 (commit 087e15321b)'.
          # We extract only the version number, though:
          $dotnet_runtime_version = ($metadata[4] -match '(\d+\.\d+\.\d+)') | Out-Null; $dotnet_runtime_version = $matches[1]
          
          # Next line is the Rust version.
          # The format is '1.78.0 (commit 9b00956e5)'.
          # We extract only the version number, though:
          $rust_version = ($metadata[5] -match '(\d+\.\d+\.\d+)') | Out-Null; $rust_version = $matches[1]
          
          $mud_blazor_version = $metadata[6]
          $tauri_version = $metadata[7]

          # Format the app version:
          $formatted_app_version = "v${app_version}"
          
          # Set the architecture:
          $metadata[9] = "${{ matrix.dotnet_runtime }}"
          
          # Write the changed metadata back to the file:
          Set-Content -Path metadata.txt -Value $metadata
          
          # Next line is the necessary PDFium version.
          # The format is '137.0.7123.0'. What we need
          # is the '7123' part:
          $pdfium_version = $metadata[10]
          $pdfium_version = $pdfium_version.Split('.')[2]
          
          # Next line is the vector store version:
          $vector_store_version = $metadata[11]
          
          # Write the metadata to the environment:
          Write-Output "APP_VERSION=${app_version}" >> $env:GITHUB_ENV
          Write-Output "FORMATTED_APP_VERSION=${formatted_app_version}" >> $env:GITHUB_ENV
          Write-Output "BUILD_TIME=${build_time}" >> $env:GITHUB_ENV
          Write-Output "BUILD_NUMBER=${build_number}" >> $env:GITHUB_ENV
          Write-Output "DOTNET_SDK_VERSION=${dotnet_sdk_version}" >> $env:GITHUB_ENV
          Write-Output "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $env:GITHUB_ENV
          Write-Output "RUST_VERSION=${rust_version}" >> $env:GITHUB_ENV
          Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV
          Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV
          Write-Output "PDFIUM_VERSION=${pdfium_version}" >> $env:GITHUB_ENV
          Write-Output "VECTOR_STORE_VERSION=${vector_store_version}" >> $env:GITHUB_ENV
          
          # Log the metadata:
          Write-Output "App version: '${formatted_app_version}'"
          Write-Output "Build time: '${build_time}'"
          Write-Output "Build number: '${build_number}'"
          Write-Output ".NET SDK version: '${dotnet_sdk_version}'"
          Write-Output ".NET runtime version: '${dotnet_runtime_version}'"
          Write-Output "Rust version: '${rust_version}'"
          Write-Output "MudBlazor version: '${mud_blazor_version}'"
          Write-Output "Tauri version: '${tauri_version}'"
          Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'"
          Write-Output "PDFium version: '${pdfium_version}'"
          Write-Output "Vector store version: '${vector_store_version}'"

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
          cache: true
          cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json'

      - name: Deploy PDFium (Unix)
        if: matrix.platform != 'windows-latest'
        env:
          PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }}
          DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
        run: |
          set -e

          # Target directory:
          TLIB_DIR="runtime/resources/libraries"
          mkdir -p "$TLIB_DIR"

          case "${DOTNET_RUNTIME}" in
            linux-x64)
              PDFIUM_FILE="linux-x64.tgz"
              LIB_SOURCE="lib/libpdfium.so"
              LIB_TARGET="libpdfium.so"
              ;;
            linux-arm64)
              PDFIUM_FILE="linux-arm64.tgz"
              LIB_SOURCE="lib/libpdfium.so"
              LIB_TARGET="libpdfium.so"
              ;;
            osx-x64)
              PDFIUM_FILE="mac-x64.tgz"
              LIB_SOURCE="lib/libpdfium.dylib"
              LIB_TARGET="libpdfium.dylib"
              ;;
            osx-arm64)
              PDFIUM_FILE="mac-arm64.tgz"
              LIB_SOURCE="lib/libpdfium.dylib"
              LIB_TARGET="libpdfium.dylib"
              ;;
            *)
              echo "Unknown platform: ${DOTNET_RUNTIME}"
              exit 1
              ;;
          esac

          PDFIUM_URL="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_VERSION}/pdfium-${PDFIUM_FILE}"

          echo "Download PDFium $PDFIUM_URL ..."
          TMP=$(mktemp -d)
          ARCHIVE="${TMP}/pdfium.tgz"

          curl -fsSL -o "$ARCHIVE" "$PDFIUM_URL"

          echo "Extracting PDFium ..."
          tar xzf "$ARCHIVE" -C "$TMP"
          SRC="${TMP}/${LIB_SOURCE}"

          if [ ! -f "$SRC" ]; then
            echo "Was not able to find PDFium source: $SRC"
            exit 1
          fi

          echo "Copy PDFium from ${LIB_TARGET} to ${TLIB_DIR}/"
          cp -f "$SRC" "$TLIB_DIR/$LIB_TARGET"

          echo "Cleaning up ..."
          rm -fr "$TMP"
          
      - name: Deploy PDFium (Windows)
        if: matrix.platform == 'windows-latest'
        env:
          PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }}
          DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
        run: |
          $TLIB_DIR = "runtime\resources\libraries"
          New-Item -ItemType Directory -Force -Path $TLIB_DIR | Out-Null
      
          switch ($env:DOTNET_RUNTIME) {
            "win-x64" {
              $PDFIUM_FILE = "win-x64.tgz"
              $LIB_SOURCE = "bin\pdfium.dll"
              $LIB_TARGET = "pdfium.dll"
            }
            "win-arm64" {
              $PDFIUM_FILE = "win-arm64.tgz"
              $LIB_SOURCE = "bin\pdfium.dll"
              $LIB_TARGET = "pdfium.dll"
            }
            default {
              Write-Error "Unknown platform: $($env:DOTNET_RUNTIME)"
              exit 1
            }
          }
      
          $PDFIUM_URL = "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F$($env:PDFIUM_VERSION)/pdfium-$PDFIUM_FILE"
          Write-Host "Download $PDFIUM_URL ..."

          # Create a unique temporary directory (not just a file)
          $TMP = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
          New-Item -ItemType Directory -Path $TMP -Force | Out-Null
          $ARCHIVE = Join-Path $TMP "pdfium.tgz"
      
          Invoke-WebRequest -Uri $PDFIUM_URL -OutFile $ARCHIVE
      
          Write-Host "Extracting PDFium ..."
          tar -xzf $ARCHIVE -C $TMP
      
          $SRC = Join-Path $TMP $LIB_SOURCE
          if (!(Test-Path $SRC)) {
            Write-Error "Cannot find PDFium source: $SRC"
            exit 1
          }
      
          $DEST = Join-Path $TLIB_DIR $LIB_TARGET
          Copy-Item -Path $SRC -Destination $DEST -Force
      
          Write-Host "Cleaning up ..."
          Remove-Item $ARCHIVE -Force -ErrorAction SilentlyContinue

          # Try to remove the temporary directory, but ignore errors if files are still in use
          try {
            Remove-Item $TMP -Recurse -Force -ErrorAction Stop
            Write-Host "Successfully cleaned up temporary directory: $TMP"
          } catch {
            Write-Warning "Could not fully clean up temporary directory: $TMP. This is usually harmless as Windows will clean it up later. Error: $($_.Exception.Message)"
          }
      - name: Build .NET project
        run: |
          cd "app/MindWork AI Studio"
          dotnet publish --configuration release --runtime ${{ matrix.dotnet_runtime }} --disable-build-servers --force --output ../../publish/dotnet
          
      - name: Move & rename .NET artifact (Unix)
        if: matrix.platform != 'windows-latest'
        run: |
          mkdir -p "app/MindWork AI Studio/bin/dist"
          cd publish/dotnet
          mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}"
          
      - name: Move & rename .NET artifact (Windows)
        if: matrix.platform == 'windows-latest'
        run: |
          New-Item -ItemType Directory -Path "app\MindWork AI Studio\bin\dist" -Force
          cd publish/dotnet
          mv mindworkAIStudio.exe "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}"
          
      - name: Cache Rust
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/git/db/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            runtime/target
            
          key: target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}
            
      - name: Setup Rust (stable)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ env.RUST_VERSION }}
          targets: ${{ matrix.rust_target }}

      - name: Cache Tauri CLI
        uses: actions/cache@v4
        with:
          path: ~/.cargo-tauri-cli
          key: tauri-cli-v2-${{ runner.os }}-${{ runner.arch }}
      
      - name: Setup dependencies (Ubuntu-specific, x86)
        if: matrix.platform == 'ubuntu-22.04' && contains(matrix.rust_target, 'x86_64')
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 xdg-utils gstreamer1.0-plugins-base gstreamer1.0-plugins-good
          
      - name: Setup dependencies (Ubuntu-specific, ARM)
        if: matrix.platform == 'ubuntu-22.04-arm' && contains(matrix.rust_target, 'aarch64')
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 xdg-utils gstreamer1.0-plugins-base gstreamer1.0-plugins-good
          
      - name: Setup Tauri (Unix)
        if: matrix.platform != 'windows-latest'
        run: |
          echo "$HOME/.cargo-tauri-cli/bin" >> "$GITHUB_PATH"
          export PATH="$HOME/.cargo-tauri-cli/bin:$PATH"

          if ! cargo tauri --version 2>/dev/null | grep -Eq '^tauri-cli 2\.'; then
            cargo install tauri-cli --version "^2.11.0" --locked --force --root "$HOME/.cargo-tauri-cli"
          else
            echo "Tauri CLI v2 is already installed"
          fi
          
      - name: Setup Tauri (Windows)
        if: matrix.platform == 'windows-latest'
        run: |
          "$env:USERPROFILE\.cargo-tauri-cli\bin" >> $env:GITHUB_PATH
          $env:PATH = "$env:USERPROFILE\.cargo-tauri-cli\bin;$env:PATH"

          $tauriVersion = cargo tauri --version 2>$null
          if (-not $tauriVersion -or $tauriVersion -notmatch '^tauri-cli 2\.') {
            cargo install tauri-cli --version "^2.11.0" --locked --force --root "$env:USERPROFILE\.cargo-tauri-cli"
          } else {
            Write-Output "Tauri CLI v2 is already installed"
          }
          
      - name: Delete previous artifact, which may exist due to caching (macOS)
        if: startsWith(matrix.platform, 'macos')
        run: |
          dmg_dir="runtime/target/${{ matrix.rust_target }}/release/bundle/dmg"
          macos_dir="runtime/target/${{ matrix.rust_target }}/release/bundle/macos"

          if [ -d "$dmg_dir" ]; then
            find "$dmg_dir" -maxdepth 1 -name 'MindWork AI Studio_*.dmg' -delete
          fi

          if [ -d "$macos_dir" ]; then
            find "$macos_dir" -maxdepth 1 -name '*.app' -exec rm -rf {} +
            find "$macos_dir" -maxdepth 1 -name '*.app.tar.gz*' -delete
          fi

      - name: Delete previous artifact, which may exist due to caching (Windows - MSI)
        if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'msi')
        run: |
          rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio_*.msi" -ErrorAction SilentlyContinue
          rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio*msi.zip*" -ErrorAction SilentlyContinue
          
      - name: Delete previous artifact, which may exist due to caching (Windows - NSIS)
        if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'nsis')
        run: |
          rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio_*.exe" -ErrorAction SilentlyContinue
          rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio*nsis.zip*" -ErrorAction SilentlyContinue
          
      - name: Delete previous artifact, which may exist due to caching (Linux - AppImage)
        if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'appimage')
        run: |
          rm -f runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage
          rm -f runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage.tar.gz*
      
      - name: Build Tauri project (Unix)
        if: matrix.platform != 'windows-latest'
        env:
          PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }}
          PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }}
        run: |
          bundles="${{ matrix.tauri_bundle }}"
          tauri_config_args=()

          if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" = "true" ]; then
            echo "Running PR test build without updater bundle signing"
            bundles="${{ matrix.tauri_bundle_pr }}"
            tauri_config_args=(--config '{"bundle":{"createUpdaterArtifacts":false}}')
          else
            export TAURI_SIGNING_PRIVATE_KEY="$PRIVATE_PUBLISH_KEY"
            export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$PRIVATE_PUBLISH_KEY_PASSWORD"
          fi

          cd runtime
          cargo tauri build --target ${{ matrix.rust_target }} --bundles "$bundles" "${tauri_config_args[@]}"

          if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" = "true" ]; then
            updater_artifact_count=$(find target/${{ matrix.rust_target }}/release/bundle -type f \( -name '*.app.tar.gz*' -o -name '*.AppImage.tar.gz*' -o -name '*nsis.zip*' \) | wc -l)

            if [ "$updater_artifact_count" -ne 0 ]; then
              echo "PR builds must not generate updater artifacts."
              find target/${{ matrix.rust_target }}/release/bundle -type f \( -name '*.app.tar.gz*' -o -name '*.AppImage.tar.gz*' -o -name '*nsis.zip*' \)
              exit 1
            fi
          fi

          if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" != "true" ] && [[ "${{ matrix.platform }}" == macos* ]]; then
            app_update_archive_count=$(find target/${{ matrix.rust_target }}/release/bundle/macos -maxdepth 1 -name '*.app.tar.gz' | wc -l)
            app_update_signature_count=$(find target/${{ matrix.rust_target }}/release/bundle/macos -maxdepth 1 -name '*.app.tar.gz.sig' | wc -l)

            if [ "$app_update_archive_count" -eq 0 ] || [ "$app_update_signature_count" -eq 0 ]; then
              echo "Expected macOS updater artifacts were not generated."
              exit 1
            fi
          fi
          
      - name: Build Tauri project (Windows)
        if: matrix.platform == 'windows-latest'
        env:
          PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }}
          PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }}
        run: |
          $bundles = "${{ matrix.tauri_bundle }}"
          $tauriConfigArgs = @()

          if ("${{ needs.determine_run_mode.outputs.is_pr_build }}" -eq "true") {
            Write-Output "Running PR test build without updater bundle signing"
            $bundles = "${{ matrix.tauri_bundle_pr }}"
            $tauriConfigArgs = @("--config", '{"bundle":{"createUpdaterArtifacts":false}}')
          } else {
            $env:TAURI_SIGNING_PRIVATE_KEY="$env:PRIVATE_PUBLISH_KEY"
            $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$env:PRIVATE_PUBLISH_KEY_PASSWORD"
          }

          cd runtime
          cargo tauri build --target ${{ matrix.rust_target }} --bundles $bundles @tauriConfigArgs

          if ("${{ needs.determine_run_mode.outputs.is_pr_build }}" -eq "true") {
            $updaterArtifacts = Get-ChildItem -Path "target/${{ matrix.rust_target }}/release/bundle" -Recurse -File -Include "*.app.tar.gz*", "*.AppImage.tar.gz*", "*nsis.zip*" -ErrorAction SilentlyContinue

            if ($updaterArtifacts.Count -ne 0) {
              Write-Error "PR builds must not generate updater artifacts."
              $updaterArtifacts | ForEach-Object { Write-Error $_.FullName }
              exit 1
            }
          }

      - name: Upload artifact (macOS)
        if: startsWith(matrix.platform, 'macos')
        uses: actions/upload-artifact@v4
        with:
          name: MindWork AI Studio (macOS ${{ matrix.dotnet_runtime }})
          path: |
            runtime/target/${{ matrix.rust_target }}/release/bundle/dmg/MindWork AI Studio_*.dmg
            runtime/target/${{ matrix.rust_target }}/release/bundle/macos/*.app.tar.gz*
          if-no-files-found: error
          retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }}
          
      - name: Upload artifact (Windows - MSI)
        if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'msi')
        uses: actions/upload-artifact@v4
        with:
          name: MindWork AI Studio (Windows - MSI ${{ matrix.dotnet_runtime }})
          path: |
            runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio_*.msi
            runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio*msi.zip*
          if-no-files-found: error
          retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }}
          
      - name: Upload artifact (Windows - NSIS)
        if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'nsis')
        uses: actions/upload-artifact@v4
        with:
          name: MindWork AI Studio (Windows - NSIS ${{ matrix.dotnet_runtime }})
          path: |
            runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio_*.exe
            runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio*nsis.zip*
          if-no-files-found: error
          retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }}
          
      - name: Upload artifact (Linux - AppImage)
        if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'appimage')
        uses: actions/upload-artifact@v4
        with:
          name: MindWork AI Studio (Linux - AppImage ${{ matrix.dotnet_runtime }})
          path: |
            runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage
            runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage.tar.gz*
          if-no-files-found: error
          retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }}
        
  create_release:
    name: Prepare & create release
    runs-on: ubuntu-latest
    needs: [build_main, collect_flatpak_artifacts, read_metadata]
    if: startsWith(github.ref, 'refs/tags/v')
    permissions: {}
    steps:
      - name: Create artifact directory
        run: mkdir -p $GITHUB_WORKSPACE/artifacts
        
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: ${{ github.workspace }}/artifacts
          merge-multiple: false
          
      - name: Display structure of previously artifacts
        run: ls -Rlhat $GITHUB_WORKSPACE/artifacts
        
      - name: Prepare release assets
        env:
          VERSION: ${{ needs.read_metadata.outputs.version }}

        run: |
          set -euo pipefail

          RELEASE_DIR="$GITHUB_WORKSPACE/release/assets"
          declare -A release_asset_sources

          # Ensure the release directory exists:
          mkdir -p "$RELEASE_DIR"

          # Find and process files in the artifacts directory:
          while IFS= read -r -d '' FILE; do

            if [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz.sig" ]]; then
              TARGET_NAME="MindWork AI Studio_x64.app.tar.gz.sig"
            elif [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz" ]]; then
              TARGET_NAME="MindWork AI Studio_x64.app.tar.gz"
            elif [[ "$FILE" == *"osx-arm64"* && "$FILE" == *".tar.gz.sig" ]]; then
              TARGET_NAME="MindWork AI Studio_aarch64.app.tar.gz.sig"
            elif [[ "$FILE" == *"osx-arm64"* && "$FILE" == *".tar.gz" ]]; then
              TARGET_NAME="MindWork AI Studio_aarch64.app.tar.gz"
            else
              TARGET_NAME="$(basename "$FILE")"
              TARGET_NAME=$(echo "$TARGET_NAME" | sed "s/_${VERSION}//")
            fi

            if [ -n "${release_asset_sources[$TARGET_NAME]+x}" ]; then
              echo "Duplicate release asset name '${TARGET_NAME}':"
              echo "  ${release_asset_sources[$TARGET_NAME]}"
              echo "  ${FILE}"
              exit 1
            fi

            release_asset_sources[$TARGET_NAME]="$FILE"

            cp "$FILE" "${RELEASE_DIR}/${TARGET_NAME}"
          done < <(find "$GITHUB_WORKSPACE/artifacts" -type f -print0)

          expected_flatpaks=(
            "MindWork AI Studio_x86_64.flatpak"
            "MindWork AI Studio Plugin Pandoc_x86_64.flatpak"
            "MindWork AI Studio_aarch64.flatpak"
            "MindWork AI Studio Plugin Pandoc_aarch64.flatpak"
          )

          flatpak_count=$(find "$RELEASE_DIR" -maxdepth 1 -type f -name '*.flatpak' | wc -l | tr -d ' ')
          if [ "$flatpak_count" -ne 4 ]; then
            echo "Prepared ${flatpak_count} Flatpak release assets; expected 4."
            exit 1
          fi

          for expected_flatpak in "${expected_flatpaks[@]}"; do
            test -s "${RELEASE_DIR}/${expected_flatpak}"
          done

          # Display the structure of the release directory:
          ls -Rlhat $GITHUB_WORKSPACE/release/assets
          
      - name: Create .update directory
        run: mkdir -p $GITHUB_WORKSPACE/.updates
          
      - name: Build platform JSON for latest.json file
        env:
          FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
        
        run: |
          # Here, we create the JSON object:
          platforms_json=$(jq -n '{}')
          
          # Display the structure of the release directory:
          ls -Rlhat $GITHUB_WORKSPACE/release/assets
          
          # Iterate over all signature files:
          while IFS= read -r -d '' sig_file; do
          
            echo "Processing signature file '$sig_file':"
          
            #
            # Next, we determine the "update platform".
            # We store the result in the $platform variable.
            # 
            # We derive the platform from the signature file name:
            # - platform=darwin-aarch64 when path contains 'aarch64-apple-darwin'
            # - platform=darwin-x86_64 when path contains 'x86_64-apple-darwin'
            # - platform=linux-x86_64 when path contains 'x86_64-unknown-linux-'
            # - platform=linux-aarch64 when path contains 'aarch64-unknown-linux-'
            # - platform=windows-x86_64 when path contains 'x86_64-pc-windows-'
            # - platform=windows-aarch64 when path contains 'aarch64-pc-windows-'
            #
            if [[ "$sig_file" == *"aarch64.app"* ]]; then
              platform="darwin-aarch64"
            elif [[ "$sig_file" == *"x64.app"* ]]; then
              platform="darwin-x86_64"
            elif [[ "$sig_file" == *"amd64.AppImage"* ]]; then
              platform="linux-x86_64"
            elif [[ "$sig_file" == *"aarch64.AppImage"* ]]; then
              platform="linux-aarch64"
            elif [[ "$sig_file" == *"x64-setup"* ]]; then
              platform="windows-x86_64"
            elif [[ "$sig_file" == *"arm64-setup"* ]]; then
              platform="windows-aarch64"
            else
              echo "Platform not recognized: '$sig_file'"
              exit 1
            fi
          
            # Read the signature:
            signature=$(cat "$sig_file")
        
            # Extract the artifact name and create the URL:
            artifact_name=$(basename "$sig_file" .sig)
            
            # Replaces spaces by '.':
            encoded_artifact_name=$(echo "$artifact_name" | sed 's/ /./g')
      
            # Create the URL:
            url="https://github.com/MindWorkAI/AI-Studio/releases/download/$FORMATTED_VERSION/$encoded_artifact_name"
        
            # Build the JSON object:
            platforms_json=$(echo "$platforms_json" | jq --arg platform "$platform" --arg signature "$signature" --arg url "$url" '.[$platform] = {"signature": $signature, "url": $url}')
            
          done < <(find $GITHUB_WORKSPACE/release/assets -type f -name '*.sig' -print0)
          
          # Write the JSON object to a temporary file:
          echo "$platforms_json" > $GITHUB_WORKSPACE/.updates/platforms.json

      - name: Create latest.json
        env:
          FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
          FORMATTED_BUILD_TIME: ${{ needs.read_metadata.outputs.formatted_build_time }}
          CHANGELOG: ${{ needs.read_metadata.outputs.changelog }}
          
        run: |
          # Read the platforms JSON, which was created in the previous step:
          platforms=$(cat $GITHUB_WORKSPACE/.updates/platforms.json)

          # Create the latest.json file via jq so the changelog is escaped as valid JSON.
          jq -n \
            --arg version "$FORMATTED_VERSION" \
            --arg notes "$CHANGELOG" \
            --arg pub_date "$FORMATTED_BUILD_TIME" \
            --argjson platforms "$platforms" \
            '{
              version: $version,
              notes: $notes,
              pub_date: $pub_date,
              platforms: $platforms
            }' > $GITHUB_WORKSPACE/release/assets/latest.json

      - name: Validate latest.json
        env:
          CHANGELOG: ${{ needs.read_metadata.outputs.changelog }}

        run: |
          # Ensure the generated file is valid JSON and the changelog round-trips unchanged.
          jq -e . $GITHUB_WORKSPACE/release/assets/latest.json > /dev/null

          generated_notes=$(jq -r '.notes' $GITHUB_WORKSPACE/release/assets/latest.json)
          if [[ "$generated_notes" != "$CHANGELOG" ]]; then
            echo "The generated notes field does not match the changelog input."
            exit 1
          fi

          for platform in darwin-aarch64 darwin-x86_64 linux-aarch64 linux-x86_64 windows-aarch64 windows-x86_64; do
            if ! jq -e --arg platform "$platform" '.platforms[$platform]' $GITHUB_WORKSPACE/release/assets/latest.json > /dev/null; then
              echo "The generated latest.json is missing platform '$platform'."
              exit 1
            fi
          done

      - name: Show all release assets
        run: ls -Rlhat $GITHUB_WORKSPACE/release/assets

      - name: Display the content of latest.json
        run: cat $GITHUB_WORKSPACE/release/assets/latest.json
        
      - name: Upload release assets
        uses: actions/upload-artifact@v4
        env:
          FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
          
        with:
          name: MindWork AI Studio ${{ env.FORMATTED_VERSION }} Release
          path: release/assets/
          if-no-files-found: error
          retention-days: ${{ env.RETENTION_RELEASE_ASSETS }}
          
  publish_release:
      name: Publish release
      runs-on: ubuntu-latest
      needs: [read_metadata, create_release]
      if: startsWith(github.ref, 'refs/tags/v')
      
      permissions:
        contents: write
      
      env:
        FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
        CHANGELOG: ${{ needs.read_metadata.outputs.changelog }}
      
      steps:
          - name: Create release folder
            run: mkdir -p release/assets
          
          - name: Download release assets
            uses: actions/download-artifact@v4  
            with:
                name: MindWork AI Studio ${{ env.FORMATTED_VERSION }} Release
                path: release/assets
                
          - name: Display the content of the release folder
            run: ls -Rlhat release/assets
            
          - name: Scan for threats
            id: virus_total
            uses: crazy-max/ghaction-virustotal@v4
            with:
              vt_api_key: ${{ secrets.VIRUS_TOTAL_KEY }}
              files: release/assets/*
              request_rate: 4
              vt_monitor: false
              github_token: ${{ secrets.GITHUB_TOKEN }}
              update_release_body: true
              
          - name: Append scan results to changelog
            run: |
              changelog=$(cat <<'EOOOOOOOF'
              ${{ env.CHANGELOG }}
              EOOOOOOOF
              )
              
              links="${{ steps.virus_total.outputs.analysis }}"

              # Create a temporary file for the changelog:
              temp_changelog=$(mktemp)

              # Add the new Markdown section:
              echo -e "$changelog" > $temp_changelog
              echo -e "\n\n## Virus scans" >> $temp_changelog
              
              # Split the analysis output by comma:
              IFS=',' read -ra analysis_array <<< "$links"

              # Append each file and link to the changelog:
              for item in "${analysis_array[@]}"; do
                
                # Get the part before the first '=':
                filename="${item%%=*}"
                filename=$(echo $filename | xargs)
              
                # Extract the base name of the file
                base_filename=$(basename "$filename")
              
                # Ignore the latest.json file:
                if [[ "$base_filename" == "latest.json" ]]; then
                  continue
                fi
              
                # Get the part after the first '=':
                link="${item#*=}"
                link=$(echo $link | xargs)
              
                # Append this scan result to the changelog:
                echo "- [$base_filename]($link)" >> $temp_changelog
              done
              
              # Export the modified changelog (using HEREDOC syntax for multi-line support):
              echo "CHANGELOG<<EOOOF" >> $GITHUB_ENV
              cat $temp_changelog >> $GITHUB_ENV
              echo "EOOOF" >> $GITHUB_ENV
            
          - name: Create release
            uses: softprops/action-gh-release@v2
            with:
              prerelease: true
              draft: false
              make_latest: false
              body: ${{ env.CHANGELOG }}
              name: "Release ${{ env.FORMATTED_VERSION }}"
              fail_on_unmatched_files: true
              files: |
                release/assets/*
