// swift-tools-version: 6.2
import Foundation
import PackageDescription

// Build channel, selected at manifest-evaluation time via the NUCLEIC_CHANNEL
// environment variable (dev | canary | beta | rc | stable); defaults to `dev`. It drives two
// things in lockstep:
//   • the app executable's PRODUCT name — which becomes the on-disk binary and
//     therefore the OS process name (Activity Monitor / `ps`):
//        dev -> nucleic-local   canary -> nucleic-canary   beta -> nucleic-beta
//        rc -> nucleic-rc   stable -> nucleic
//   • a compile-time define (`NUCLEIC_STABLE/RC/BETA/CANARY/DEV`) read by NucleicApp for its build
//     identity + warning banner (red local / yellow canary / blue beta / gold release-candidate /
//     none stable), and by NucleicCore's `ContainerManager` to suffix non-release container names.
// Branch mapping (see BUILD.md): dev builds from `dev`, canary from `canary`, beta from `staging`,
// rc from `rc`, stable from `main`.
// TailscaleKit (the embedded tsnet node behind the Tailnet sync transport) has no SwiftPM
// distribution — it's an Xcode framework built from a pinned libtailscale commit by
// scripts/build-tailscalekit.sh into third_party/TailscaleKit/ (untracked, ~100 MB). The
// binary target is declared only when the artifact exists so a fresh clone builds without
// Go/Xcode legwork; NucleicTailnet compiles either way (`#if canImport(TailscaleKit)`) and
// reports "not built in" at runtime when the framework is absent.
let tailscaleKitAvailable = FileManager.default.fileExists(
    atPath: Context.packageDirectory + "/third_party/TailscaleKit/TailscaleKit.xcframework")

let nucleicChannel = (Context.environment["NUCLEIC_CHANNEL"] ?? "dev").lowercased()
let (appProductName, channelDefine): (String, String) = {
    switch nucleicChannel {
    case "stable", "prod", "release": return ("nucleic", "NUCLEIC_STABLE")
    case "rc", "candidate": return ("nucleic-rc", "NUCLEIC_RC")
    case "beta": return ("nucleic-beta", "NUCLEIC_BETA")
    case "canary": return ("nucleic-canary", "NUCLEIC_CANARY")
    default: return ("nucleic-local", "NUCLEIC_DEV")
    }
}()

let package = Package(
    name: "Nucleic",
    // NucleicProtocol (the shared sync wire layer) must build for iOS too — that's what the
    // NucleicRemote iPhone client links. The host-only targets (NucleicCore/App/spike) use
    // macOS-only APIs and are simply never built for iOS.
    //
    // macOS floor is 26 (Tahoe): the container sandbox is built directly on Apple's
    // `containerization` framework, whose in-process vmnet networking requires macOS 26 + Apple
    // silicon. `containerization` is a dependency of NucleicCore ONLY (never of the iOS-linked
    // NucleicProtocol), so the iOS build resolves the package graph but never compiles it.
    //
    // iOS floor is 18.1: TailscaleKit ships with an 18.1 minimum (its `state()` methods use
    // the Failure-typed `any AsyncSequence` existential, an iOS 18 Swift-runtime feature), and
    // SwiftPM has no per-target floors, so the iOS-linked targets rise with it. Every device
    // that runs iOS 17 also runs 18.1, so this drops no hardware — only stale installs.
    platforms: [.macOS(.v26), .iOS("18.1")],
    products: [
        .library(name: "NucleicProtocol", targets: ["NucleicProtocol"]),
        // The Tailnet transport (embedded tsnet node + fd-backed FrameChannel). A separate
        // product because the iPhone client links it directly (NucleicCore is host-only).
        .library(name: "NucleicTailnet", targets: ["NucleicTailnet"]),
        .library(name: "NucleicCore", targets: ["NucleicCore"]),
        // Product (binary / process) name varies by channel — see `appProductName`.
        .executable(name: appProductName, targets: ["NucleicApp"]),
        .executable(name: "nucleic-spike", targets: ["nucleic-spike"]),
        .executable(name: "fake-claude", targets: ["fake-claude"]),
        .executable(name: "fake-grok", targets: ["fake-grok"]),
        .executable(name: "container-spike", targets: ["container-spike"]),
        .executable(name: "macvm-spike", targets: ["macvm-spike"]),
    ],
    dependencies: [
        .package(url: "https://github.com/groue/GRDB.swift.git", from: "7.0.0"),
        .package(url: "https://github.com/migueldeicaza/SwiftTerm.git", from: "1.2.0"),
        // Apple's containerization framework — the in-process runtime the `container` CLI is built
        // on. **Vendored** (third_party/containerization) at upstream commit 6b7b42ca rather than
        // pulled from github, because we carry a small patch: `LinuxContainer.Configuration` forwards
        // `vmExtensions` into `VMConfiguration.extensions` so we can attach a memory-balloon device
        // (see third_party/containerization/PATCHES.md). The package is pre-1.0 (source stability
        // holds only within a minor version); to bump, re-vendor the new commit and re-apply the
        // patch. All framework calls are centralized in `ContainerEngine`/`MemoryBalloon`.
        .package(path: "third_party/containerization"),
        // Sparkle — in-app auto-update for the directly-distributed (non-App-Store) macOS
        // builds. macOS-only and used ONLY by the NucleicApp executable target, so the iOS
        // resolution of the package graph never compiles it. Updates are delivered as signed,
        // notarized DMGs described by a per-channel appcast (see BUILD.md "Auto-update").
        .package(url: "https://github.com/sparkle-project/Sparkle", from: "2.6.0"),
    ],
    targets: [
        // Pure, platform-agnostic: identifiers, the AgentEvent model, the sync wire
        // protocol (ClientMsg/HostMsg), CBOR framing, and the Noise SecureChannel.
        // No GRDB / Process / Network — compiles for macOS and iOS alike.
        .target(name: "NucleicProtocol"),
        // The Tailnet sync transport (SYNC_PROTOCOL §3): an embedded tsnet node
        // (TailscaleKit) plus the fd-backed FrameChannel both sides speak over it. Compiles
        // for macOS (host listener) and iOS (phone dialer) alike; when the TailscaleKit
        // artifact is absent the module still builds and `TailnetSupport.isBuiltIn` is false.
        .target(
            name: "NucleicTailnet",
            dependencies: ["NucleicProtocol"] + (tailscaleKitAvailable ? ["TailscaleKit"] : [])),
        .target(
            name: "NucleicCore",
            dependencies: [
                "NucleicProtocol",
                "NucleicTailnet",
                .product(name: "GRDB", package: "GRDB.swift"),
                // In-process container runtime (host-only). EXT4 unpacking + vmnet networking are
                // reached transitively through `Containerization`; `ContainerizationOCI` provides
                // the `User`/`Platform` types used directly by `ContainerEngine`.
                .product(name: "Containerization", package: "containerization"),
                .product(name: "ContainerizationOCI", package: "containerization"),
                // For `AddressAllocator` — named in the `VZInstanceExtension.configureVZ` signature
                // our `MemoryBalloon` conforms to (it lives here, not in `Containerization`).
                .product(name: "ContainerizationExtras", package: "containerization"),
            ],
            // The build channel reaches the container-naming code here: `ContainerManager` appends a
            // per-channel suffix (e.g. `-beta`, `-local`) to non-release container names so a beta /
            // local-dev build's containers don't collide with a release's on the shared on-disk
            // container store (ContainerEngine.defaultStorageRoot). Same define NucleicApp's banner uses.
            swiftSettings: [.define(channelDefine)]),
        .executableTarget(
            name: "NucleicApp",
            dependencies: [
                "NucleicCore",
                .product(name: "SwiftTerm", package: "SwiftTerm"),
                // Auto-update for direct (non-App-Store) distribution. macOS-only.
                .product(name: "Sparkle", package: "Sparkle"),
            ],
            swiftSettings: [.define(channelDefine)],
            // FoundationModels ships in macOS 26, but its Swift ABI is still shifting between the
            // 26/27 beta seeds: a symbol present in the SDK we build against (e.g.
            // `Generable.promptRepresentation`, emitted by the `@Generable` guided-generation code
            // in AppleIntelligence.swift) can be ABSENT from an older seed's framework. Strong-linked
            // (the Swift autolink default), that makes dyld abort BEFORE main() on any Mac whose seed
            // lacks the symbol — "Symbol not found … FoundationModels", EXC_CRASH / Namespace DYLD —
            // even though every use is already gated behind `#available` + `model.isAvailable`, since
            // those runtime guards never get to run. Weak-linking binds a missing symbol to NULL at
            // load instead of aborting, so the app always launches and the guards do their job (the
            // soft-AI features fall back to their heuristics). Verify the flag took with
            // `otool -l <binary> | grep -A3 FoundationModels` → expect LC_LOAD_WEAK_DYLIB.
            linkerSettings: [
                .unsafeFlags(["-Xlinker", "-weak_framework", "-Xlinker", "FoundationModels"]),
            ],
            plugins: ["EmbedGitCommit"]),
        // Prebuild plugin: stamps the built-from commit into a generated `GitCommit.swift`
        // at build time, so the hash is baked into the binary (correct even on a
        // packaged beta / rc / stable archive, where git isn't present at runtime).
        .plugin(name: "EmbedGitCommit", capability: .buildTool()),
        .executableTarget(name: "nucleic-spike", dependencies: ["NucleicCore"]),
        .executableTarget(name: "fake-claude", dependencies: ["NucleicCore"]),
        .executableTarget(name: "fake-grok", dependencies: ["NucleicCore"]),
        // Standalone de-risking spike for the container rework: drives the real `ContainerEngine`
        // end-to-end — pulls an image from a registry, boots the VM, execs a process, and streams
        // its stdout through `ContainerizedProcessHandle` (the agent's path). Run signed with
        // signing/spike.entitlements; not shipped.
        .executableTarget(
            name: "container-spike",
            dependencies: [
                "NucleicCore",
                .product(name: "Containerization", package: "containerization"),
                .product(name: "ContainerizationOCI", package: "containerization"),
            ]),
        // Standalone de-risking spike for the macOS-VM rework: drives the real `MacVMEngine`
        // end-to-end on Apple's `Virtualization` framework — clone the golden base, boot the guest,
        // `ssh` a command in, and stream stdout (the `mac_vm_exec` agent path); `NUCLEIC_MACVM_BUILD_BASE=1`
        // runs the one-time macOS install instead. Run signed with signing/spike.entitlements; not shipped.
        .executableTarget(name: "macvm-spike", dependencies: ["NucleicCore"]),
        // The iPhone app's transcript projection (pure Foundation + NucleicProtocol model
        // folding), compiled here as a host-testable library. The iOS app target compiles these
        // same sources directly via its file-system-synchronized group; this target exists so the
        // incremental projector's prefix-equivalence suite (a required safety net —
        // docs/TRANSCRIPT_INCREMENTAL_PROJECTION.md) runs under plain `swift test`, where the
        // Xcode project has no test target. `sources` is an explicit allowlist: the folder's
        // other files are SwiftUI views this library must not build.
        .target(
            name: "NucleicRemoteProjection",
            dependencies: ["NucleicProtocol"],
            path: "ios/NucleicRemote/NucleicRemote/Views/Transcript",
            // The folder's other files are SwiftUI views compiled only by the Xcode iOS app
            // target; excluding them keeps this host-testable library building just the two
            // pure-projection sources and silences SwiftPM's "unhandled files" warning. Add
            // any new sibling view here (or it re-triggers the warning).
            exclude: [
                "AskUserQuestionResultCard.swift",
                "GitCommitCard.swift",
                "HostCommandSummary.swift",
                "HostExecCard.swift",
                "MarkdownText.swift",
                "ToolGroupRow.swift",
            ],
            sources: ["TranscriptProjection.swift", "IncrementalTranscriptProjection.swift"]),
        .testTarget(
            name: "NucleicRemoteProjectionTests",
            dependencies: ["NucleicRemoteProjection", "NucleicProtocol"]),
        .testTarget(
            name: "NucleicProtocolTests",
            dependencies: ["NucleicProtocol"]
        ),
        .testTarget(
            name: "NucleicCoreTests",
            dependencies: ["NucleicCore", "NucleicTailnet"],
            resources: [.copy("Fixtures")],
            // The TailscaleKit binary framework is staged at the build-products root, which
            // executables reach via their default rpath but .xctest bundles do not (their
            // rpaths point at PackageFrameworks + the bundle's own Frameworks dir). Walk the
            // three levels from Contents/MacOS/<binary> back up to the products dir.
            linkerSettings: [.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../.."])]
        ),
    ] + (tailscaleKitAvailable
        ? [.binaryTarget(name: "TailscaleKit", path: "third_party/TailscaleKit/TailscaleKit.xcframework")]
        : [])
)

