name: Windows

# The Windows-port CI (docs/WINDOWS_PORT.md §13 M0, §14). Three legs:
#
#   protocol (REQUIRED)  — Swift toolchain pinned by windows/props/swift-version.txt on
#     hosted x64 (windows-2025) + ARM64 (windows-11-arm): build NucleicProtocol and run
#     NucleicProtocolTests. Runs with NUCLEIC_WINDOWS_PROTOCOL_ONLY=1 so `swift test`
#     builds only the wire layer — NucleicCore's Windows shims land with §12 items 7–9.
#
#   core — the same toolchain WITHOUT the flag, driving the full Windows manifest
#     (NucleicCore + NucleicCarbonTests + nucleic-hostd + NucleicProtocolC). Two tiers,
#     because the legs are proven to different depths (docs/WINDOWS_PORT.md §12 item 9):
#       * the x64 `swift build --target NucleicCore` step is REQUIRED — it builds green on
#         Windows 11 amd64 as of the item-9 shims, so a regression must turn CI red;
#       * ARM64 is still `continue-on-error` (no ARM64 hardware has run it), and
#         NucleicCarbonTests is a per-step probe on both arches, because `swift test`
#         builds EVERY declared target — nucleic-hostd, NucleicProtocolC, nucleic-smoke —
#         and those are still §12 items 10+ work. That step's log is what remains of the
#         M0 "corelibs/toolchain gap list". Both tiers flip to required at M2.
#
#   broker (REQUIRED) — the C# nucleic-brokerd contract tests against FakeWslc
#     (windows/Nucleic.sln, no wslc NuGet — that path needs -p:UseWslc=true and the M1
#     spike hardware).
#
# The container e2e suite (M2) needs nested virtualization → the self-hosted runner (D11),
# added later behind a runs-on label; hosted runners cover everything above.
#
# WHERE THESE RUN (D11). Every `runs-on` below reads a repository variable with the hosted
# runner as its fallback, so moving a leg on-prem is a settings change, not a PR:
#
#   WINDOWS_X64_RUNNER    → label of the on-prem x64 box   (unset ⇒ windows-2025)
#   WINDOWS_ARM64_RUNNER  → label of an on-prem ARM64 box  (unset ⇒ windows-11-arm)
#
# Set them to a UNIQUE label you assigned that runner (e.g. nucleic-win-x64), not to the
# generic `self-hosted`. Clearing a variable fails the leg straight back to GitHub-hosted,
# which is the escape hatch when the box is down. Self-hosted minutes are never billed —
# hosted Windows minutes bill at 2x, which is what makes this leg the expensive one.
# On-prem machine setup: docs/WINDOWS_PORT.md §14.1.

on:
  push:
    branches: [main, dev]
    paths:
      - Package.swift
      - Package.resolved
      - Sources/**
      - Tests/**
      - windows/**
      - ios/NucleicRemote/NucleicRemote/Views/Transcript/**
      - .github/workflows/windows.yml
  pull_request:
    paths:
      - Package.swift
      - Package.resolved
      - Sources/**
      - Tests/**
      - windows/**
      - ios/NucleicRemote/NucleicRemote/Views/Transcript/**
      - .github/workflows/windows.yml
  workflow_dispatch:

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

jobs:
  swift-version:
    name: Resolve pinned Swift
    runs-on: ubuntu-latest
    outputs:
      swift-version: ${{ steps.pin.outputs.swift-version }}
      swift-build: ${{ steps.pin.outputs.swift-build }}
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: windows/props/swift-version.txt
          sparse-checkout-cone-mode: false
      - id: pin
        run: |
          # windows/props/swift-version.txt holds e.g. "swift-6.3.3-RELEASE". gha-setup-swift
          # composes the download URL as
          #   download.swift.org/<swift-version>/windows10/swift-<swift-build>/...
          # so it wants swift-version=swift-6.3.3-release and swift-build=6.3.3-RELEASE
          # (no "swift-" prefix — the action adds it).
          #
          # The pin has a FLOOR set by the runner's MSVC: the VS 2026 STL (14.5x) hard-errors
          # (STL1000) on clang older than 20, and Swift 6.2.x bundles clang 19. 6.3.x bundles
          # clang 21. Keep this at or above the Linux pin in scripts/build-nucleicd-linux.sh.
          TAG="$(tr -d '[:space:]' < windows/props/swift-version.txt)"
          echo "swift-version=$(echo "$TAG" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
          echo "swift-build=${TAG#swift-}" >> "$GITHUB_OUTPUT"

  protocol:
    name: Protocol tests (${{ matrix.arch }})
    needs: swift-version
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x64
            runner: ${{ vars.WINDOWS_X64_RUNNER || 'windows-2025' }}
            build_arch: amd64
          - arch: arm64
            runner: ${{ vars.WINDOWS_ARM64_RUNNER || 'windows-11-arm' }}
            build_arch: arm64
    runs-on: ${{ matrix.runner }}
    env:
      # Narrow the manifest to the wire layer (see Package.swift): `swift test` builds every
      # declared target, and NucleicCore doesn't compile on Windows yet.
      NUCLEIC_WINDOWS_PROTOCOL_ONLY: "1"
    steps:
      - name: Git line-ending + longpath config
        # Agents run in Linux containers — the repo must stay LF (docs/WINDOWS_PORT.md §4.4);
        # and SwiftPM checkouts nest deep enough to hit MAX_PATH without longpaths.
        run: |
          git config --global core.autocrlf false
          git config --global core.longpaths true
      - uses: actions/checkout@v4
      - uses: compnerd/gha-setup-swift@v0.4.0
        with:
          swift-version: ${{ needs.swift-version.outputs.swift-version }}
          swift-build: ${{ needs.swift-version.outputs.swift-build }}
          build_arch: ${{ matrix.build_arch }}
          cache: true
      - name: Build NucleicProtocol
        run: swift build --target NucleicProtocol
      - name: Run NucleicProtocolTests
        # /FORCE:MULTIPLE is TEMPORARY — the CCryptoBoringSSL / CNIOBoringSSL
        # `p_thread_callback_boringssl` collision (docs/WINDOWS_PORT.md §14.1.1). It bites here
        # even under NUCLEIC_WINDOWS_PROTOCOL_ONLY, because NucleicProtocol itself links both
        # Crypto and NIOSSL on Windows. Drop it when one copy prefixes the symbol.
        run: swift test --filter NucleicProtocolTests -Xlinker "/FORCE:MULTIPLE"

  core:
    name: Full core (${{ matrix.arch }})
    needs: swift-version
    # x64 is required (the NucleicCore build is green there); ARM64 has never run, so it
    # stays advisory until a box confirms it. See the header note.
    continue-on-error: ${{ matrix.experimental }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x64
            runner: ${{ vars.WINDOWS_X64_RUNNER || 'windows-2025' }}
            build_arch: amd64
            vcpkg_triplet: x64-windows-static-md-nucleic
            experimental: false
          - arch: arm64
            runner: ${{ vars.WINDOWS_ARM64_RUNNER || 'windows-11-arm' }}
            build_arch: arm64
            vcpkg_triplet: arm64-windows-static-md-nucleic
            experimental: true
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Git line-ending + longpath config
        run: |
          git config --global core.autocrlf false
          git config --global core.longpaths true
      - uses: actions/checkout@v4
      - uses: compnerd/gha-setup-swift@v0.4.0
        with:
          swift-version: ${{ needs.swift-version.outputs.swift-version }}
          swift-build: ${{ needs.swift-version.outputs.swift-build }}
          build_arch: ${{ matrix.build_arch }}
          cache: true
      - name: Provision SQLite for GRDB
        # GRDB's GRDBSQLite is a .systemLibrary declaring only an apt provider, so nothing
        # supplies sqlite3.h / sqlite3.lib on Windows — hence "including GRDB/SQLite linkage"
        # in the §13 M0 exit criteria. The -static-md triplet links SQLite statically against
        # the DYNAMIC CRT, matching what the Swift toolchain uses: the CRT agrees and there's
        # no DLL to stage beside the test binaries. Stock vcpkg FEATURES are enough — nothing
        # here uses FTS or rtree — but the stock BUILD is not: it compiles the snapshot API out,
        # which GRDB's system-SQLite path needs, hence the overlay triplet below.
        shell: pwsh
        run: |
          # Hosted images ship vcpkg and set VCPKG_INSTALLATION_ROOT; a self-hosted box does
          # not. Fall back to a clone under RUNNER_TOOL_CACHE, which survives between jobs,
          # so the bootstrap is a one-time cost on the on-prem runner rather than per-run.
          $vcpkg = $env:VCPKG_INSTALLATION_ROOT
          if (-not $vcpkg -or -not (Test-Path (Join-Path $vcpkg 'vcpkg.exe'))) {
            $vcpkg = Join-Path $env:RUNNER_TOOL_CACHE 'vcpkg'
            if (-not (Test-Path (Join-Path $vcpkg '.git'))) {
              git clone --depth 1 https://github.com/microsoft/vcpkg $vcpkg
              if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
            }
            if (-not (Test-Path (Join-Path $vcpkg 'vcpkg.exe'))) {
              & (Join-Path $vcpkg 'bootstrap-vcpkg.bat') -disableMetrics
              if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
            }
          }
          # Overlay triplet = stock -static-md + SQLITE_ENABLE_SNAPSHOT, which GRDB's
          # system-SQLite path needs and the stock port compiles out (§14.2).
          & (Join-Path $vcpkg 'vcpkg.exe') install sqlite3:${{ matrix.vcpkg_triplet }} "--overlay-triplets=$env:GITHUB_WORKSPACE\windows\vcpkg-triplets"
          if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
          $root = Join-Path $vcpkg 'installed\${{ matrix.vcpkg_triplet }}'
          # Deliberately NOT %INCLUDE%/%LIB%: clang's MSVC driver stops auto-detecting the
          # VC + Windows SDK header set as soon as %INCLUDE% names one existing directory,
          # so appending here could silently strip the system headers. Pass them per-build
          # as ordinary -I / -LIBPATH instead.
          "SQLITE_INCLUDE=$root\include" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
          "SQLITE_LIB=$root\lib" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
      - name: Build NucleicCore
        # `/LIBPATH:`, not `-LIBPATH:` — a leading `-L` is eaten by the driver's GNU-style
        # library-path flag and the linker ends up searching `IBPATH:C:\…` (§14.2).
        run: swift build --target NucleicCore -Xcc "-I$env:SQLITE_INCLUDE" -Xlinker "/LIBPATH:$env:SQLITE_LIB"
      - name: Run NucleicCarbonTests (gap probe)
        # Advisory on BOTH arches, independently of the job tier: `swift test` builds every
        # declared target, so this covers nucleic-hostd / NucleicProtocolC / nucleic-smoke —
        # §12 items 10+ — not just the suite it names. Flips to required with those items.
        continue-on-error: true
        # /FORCE:MULTIPLE as in the protocol job — see §14.1.1.
        run: swift test --filter NucleicCarbonTests -Xcc "-I$env:SQLITE_INCLUDE" -Xlinker "/LIBPATH:$env:SQLITE_LIB" -Xlinker "/FORCE:MULTIPLE"

  broker:
    name: Broker contract tests
    runs-on: ${{ vars.WINDOWS_X64_RUNNER || 'windows-latest' }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "9.0.x"
      - name: Test windows/Nucleic.sln
        run: dotnet test windows/Nucleic.sln --nologo
