import Foundation
#if arch(arm64)
import Virtualization
#endif

/// Carries a value across an isolation boundary the compiler can't prove safe. Used to hand a
/// freshly-created, not-yet-shared non-`Sendable` VZ object (e.g. `VZMacOSRestoreImage`) out of a
/// framework completion handler through a `CheckedContinuation` — the object is created inside the
/// callback and touched nowhere else until it lands on the engine actor, so the transfer is a
/// disconnected hand-off despite the type lacking a `Sendable` conformance.
public struct UncheckedSendableBox<T>: @unchecked Sendable {
    public let value: T
    public init(value: T) { self.value = value }
}

/// On-disk layout of one macOS **VM bundle** — the golden base or a per-session clone. Mirrors the
/// bundle Apple's "Running macOS in a virtual machine on Apple silicon" sample uses: the platform
/// identity (`HardwareModel` + `MachineIdentifier`), the writable NVRAM (`AuxiliaryStorage`), and the
/// system disk (`Disk.img`). `MACAddress` (our addition) pins the clone's NAT MAC so its DHCP lease is
/// findable; `bundle.json` records how it was built.
public struct MacVMBundle: Sendable, Equatable {
    public let root: URL
    public init(root: URL) { self.root = root }

    // macOS-guest artifacts (Mac platform identity + NVRAM).
    public var hardwareModelURL: URL { root.appendingPathComponent("HardwareModel") }
    public var machineIdentifierURL: URL { root.appendingPathComponent("MachineIdentifier") }
    public var auxiliaryStorageURL: URL { root.appendingPathComponent("AuxiliaryStorage") }
    /// The system disk — the macOS install, or (for a Linux guest) the ext4 root filesystem.
    public var diskImageURL: URL { root.appendingPathComponent("Disk.img") }
    public var macAddressURL: URL { root.appendingPathComponent("MACAddress") }
    public var metadataURL: URL { root.appendingPathComponent("bundle.json") }
    /// Host-only MDM CA/configuration/device identity. Contains private keys; never copied into clones.
    public var mdmStateURL: URL { root.appendingPathComponent("mdm-state.json") }
    /// Captures and metadata retained when an expected MDM approval screen cannot be recognized.
    ///
    /// Deliberately a **sibling** of the bundle, not a child. Diagnostics written inside the bundle
    /// are destroyed by the very next "Delete & reinstall" or clean rebuild — which is exactly what
    /// happens after a failed enrollment, so the evidence for the failure being investigated was
    /// routinely deleted by the first step taken to investigate it. Keyed by bundle name so several
    /// bases don't collide.
    public var mdmDiagnosticsURL: URL {
        diagnosticsRoot.appendingPathComponent("MDM", isDirectory: true)
    }
    /// Host-captured STATUS and log files from a failed guest provisioning phase. Outside the bundle
    /// for the same reason as ``mdmDiagnosticsURL``.
    public var provisioningDiagnosticsURL: URL {
        diagnosticsRoot.appendingPathComponent("Provisioning", isDirectory: true)
    }
    /// `<bundle-parent>/Diagnostics/<bundle-name>` — outlives the bundle it describes.
    private var diagnosticsRoot: URL {
        root.deletingLastPathComponent()
            .appendingPathComponent("Diagnostics", isDirectory: true)
            .appendingPathComponent(root.lastPathComponent, isDirectory: true)
    }
    /// The saved runtime state (full guest RAM + device state) written by a **suspend-to-disk**
    /// (`VZVirtualMachine.saveMachineState(to:)`), consumed by the next boot's
    /// `restoreMachineState(from:)`. Present only while the VM is suspended to disk; deleted the
    /// instant it's restored (the writable disk diverges once the guest runs again) or the clone is
    /// removed. OS-agnostic: a macOS and a Linux clone both use the same filename.
    public var savedStateURL: URL { root.appendingPathComponent("SavedState.vzvmsave") }

    // Linux-guest artifacts (`VZLinuxBootLoader` boots an external kernel + initrd; there is no
    // in-disk bootloader). The kernel is an *uncompressed* arm64 `Image`; the cmdline is persisted so
    // a clone boots with the exact command line the base was provisioned under.
    public var kernelURL: URL { root.appendingPathComponent("vmlinux") }
    public var initrdURL: URL { root.appendingPathComponent("initrd.img") }
    public var cmdlineURL: URL { root.appendingPathComponent("cmdline") }

    /// True when every artifact needed to boot a **macOS** guest is present.
    public var isComplete: Bool { isComplete(for: .macOS) }

    /// True when every artifact needed to boot `os` is present. A macOS guest needs the Mac platform
    /// identity + NVRAM + disk; a Linux guest needs the kernel + initrd + rootfs disk.
    public func isComplete(for os: GuestOS) -> Bool {
        let fm = FileManager.default
        let required: [URL]
        switch os {
        case .macOS:
            required = [hardwareModelURL, machineIdentifierURL, auxiliaryStorageURL, diskImageURL]
        case .linux:
            required = [kernelURL, initrdURL, diskImageURL]
        }
        return required.allSatisfy { fm.fileExists(atPath: $0.path) }
    }
}

extension MacVMEngine {
    // MARK: - Base bundle resolution

    /// Resolve the golden base bundle to clone from, without ever kicking off the (heavy, interactive)
    /// build implicitly. Order: a user-supplied **prebuilt** bundle if configured and complete, else
    /// the engine's own built base if present. When neither exists, throw `.baseImageMissing` with a
    /// pointer to the explicit build path — the ~14 GB install + toolchain provisioning is a deliberate
    /// one-time action (Settings ▸ Virtual Machines ▸ Build base image, `macvm-spike`, or
    /// `scripts/build-macos-base.sh`), never a surprise on an agent's first turn.
    func ensureBaseBundle(for os: GuestOS = .macOS) async throws -> MacVMBundle {
        if os == .linux { return try ensureLinuxBaseBundle() }
        if let prebuilt = MacVMSettings.basePrebuiltPath {
            let bundle = MacVMBundle(root: URL(fileURLWithPath: prebuilt))
            if bundle.isComplete { return bundle }
            throw MacVMError.baseImageMissing(
                "the configured prebuilt base at \(prebuilt) is missing its VM artifacts")
        }
        let built = MacVMBundle(root: builtBaseDir)
        if built.isComplete {
            // An engine-built base is cloneable only when its current-recipe control plane and
            // mandatory MDM policy landed. This duplicates the publication metadata gate at the last
            // responsible moment, so a missed launch migration or manually damaged bundle cannot cut
            // an unreachable/prompting clone. User-supplied prebuilt bundles bypass this managed-base
            // gate above; every engine-built bundle, including a pre-metadata one, must migrate.
            // Either policy lane satisfies admission, mirroring `baseIsUsable`
            // (docs/MACOS_VM_POLICY_ALTERNATIVE.md): a Lane B (datastore) base carries verified TCC
            // grants and has no host MDM identity artifact; a Lane A (MDM) base needs enrollment +
            // profiles AND the persisted host MDM identity on disk.
            let status = readBaseStatus(built)
            // The control-plane flags alone are not enough: a prior recipe could set every one of
            // them while still publishing an unusable Xcode (v12 did exactly that). Clone only the
            // current recipe. Launch-time reprovisioning runs asynchronously, so this is also the
            // last-responsible-moment gate that prevents a session racing ahead of that migration.
            let recipeCurrent =
                status.provisioningRecipe == Self.macOSProvisioningRecipe
            let admissible = recipeCurrent
                && (Self.baseStatusReadyForRecipe(status, lane: .datastore)
                    || (Self.baseStatusReadyForRecipe(status, lane: .mdm)
                        && FileManager.default.fileExists(atPath: built.mdmStateURL.path)))
            if !admissible {
                if baseIsBusy {
                    throw MacVMError.baseBusy(
                        "the base image is being (re)provisioned right now — retry once it finishes")
                }
                throw MacVMError.baseImageMissing(
                    "the engine-built macOS base is stale or missing a required account, verified "
                        + "Xcode/SwiftUI toolchain, agent, User-Approved MDM enrollment, or policy "
                        + "profile. It will be re-provisioned automatically at the next app launch, "
                        + "or run Settings ▸ Virtual Machines ▸ Build base image now.")
            }
            return built
        }
        throw MacVMError.baseImageMissing(
            "no base macOS image has been built yet. Build it once via Settings ▸ Virtual Machines ▸ "
                + "Build base image, or `scripts/build-macos-base.sh`.")
    }

    /// The installed guest macOS version of the current base bundle (read from `bundle.json`), for the
    /// settings panel. `nil` when no base exists, or when a prebuilt bundle was produced out-of-band
    /// without version metadata.
    public func baseImageOSVersion() -> String? { currentBaseStatus().osVersion }

    /// The current base bundle's provisioning status (installed / exec-ready / computer-use-ready),
    /// for the settings panel. Prefers a configured prebuilt bundle, else the engine-built base;
    /// all-false when neither is complete.
    public func currentBaseStatus() -> MacVMBaseStatus {
        let bundle = MacVMSettings.basePrebuiltPath.map { MacVMBundle(root: URL(fileURLWithPath: $0)) }
            ?? MacVMBundle(root: builtBaseDir)
        guard bundle.isComplete else { return MacVMBaseStatus() }
        var status = readBaseStatus(bundle)
        status.installed = true  // a complete bundle is installed even if it predates the metadata
        return status
    }

    /// Delete the engine-built golden base so the next ``buildBaseImage`` reinstalls macOS **from
    /// scratch** (the full install path, not the reentrant re-provision). Removes the base bundle and,
    /// when `includingRestoreImages` is set, the cached `.ipsw` too — so the restore image is also
    /// re-downloaded. Refuses while the base is busy (a build in flight or a Recovery window open)
    /// **unless** `force` is set, so it can't accidentally yank the disk out from under a running
    /// install. A configured *prebuilt* base is left untouched (Nucleic didn't create it); this only
    /// clears `builtBaseDir`.
    /// Forcibly abandon whatever is holding the base and release every claim on it, so a delete or a
    /// fresh build can proceed immediately.
    ///
    /// The escape hatch for a wedged pass. A provisioning step that hangs — `awaitAgentReady` spinning
    /// for its full 300 s because the in-guest agent never came up, or an MDM approval loop waiting on a
    /// screen that never appears — leaves `baseBuilding` set, and a failure held under
    /// ``MacVMSettings/holdBaseOnMDMFailure`` pins the base for the life of the process. Both made
    /// "Delete base image" refuse, so the only way out was quitting the app.
    ///
    /// This powers off every held diagnostic guest, drops the busy flags, and marks the in-flight pass
    /// cancelled so it cannot write the bundle back as it unwinds. It does **not** wait for that
    /// unwinding: the pass is blocked on a guest that is now gone, so its next command fails and it
    /// throws on its own. Returns a human-readable summary of what was released, for the panel to show.
    @discardableResult
    public func forceCancelBaseWork() async -> String {
        var released: [String] = []
        #if arch(arm64)
        if !heldDiagnosticInstances.isEmpty {
            released.append(
                "\(heldDiagnosticInstances.count) held diagnostic guest"
                    + (heldDiagnosticInstances.count == 1 ? "" : "s"))
            let held = heldDiagnosticInstances
            heldDiagnosticInstances.removeAll()
            for instance in held { await instance.stop() }
        }
        #endif
        if baseBuilding { released.append("an in-flight base build/provision pass") }
        if baseAppInstalling { released.append("a base app/package injection") }

        // Set before clearing the flags: a pass unwinding concurrently must see the cancellation, not a
        // window where it looks like an ordinary run.
        baseWorkCancelled = true
        baseBuilding = false
        baseAppInstalling = false
        baseBuildGuest = nil
        baseObserverRequested = false
        baseProgress = nil
        syncBackgroundActivity()

        guard !released.isEmpty else { return "Nothing was holding the base image." }
        return "Force-released " + released.joined(separator: " and ") + "."
    }

    /// Reset the force-cancel latch the way a new deliberate pass does. Test-only seam: the real
    /// clearers are the `baseBuilding = true` / `baseAppInstalling = true` sites, which all need a VM.
    func clearBaseWorkCancelledForTesting() { baseWorkCancelled = false }

    /// Delete the base. `force` skips the busy check and calls ``forceCancelBaseWork()`` first, which is
    /// what the settings panel always does — a delete the operator explicitly asked for should not be
    /// refused because a hung pass never cleared its flag.
    public func deleteBaseImage(includingRestoreImages: Bool, force: Bool = false) async throws {
        if force {
            await forceCancelBaseWork()
        }
        guard force || !baseIsBusy else {
            throw MacVMError.baseBusy(
                baseBuilding
                    ? "a base image build is in progress — wait for it to finish, then delete."
                    : "the base is open in a Recovery window — close it first.")
        }
        // Before wiping the bundle (and its `bundle.json`), stash the apps/packages the user had
        // installed into it so the next build restores them into the replacement base — an OS-version
        // update is a delete-then-reinstall, and the on-disk record is gone by the time that build runs
        // (docs/MACOS_VM.md §9.1). The current "Included apps" / "Common packages" settings are re-staged
        // regardless; this preserves the extras that had dropped off those lists.
        let outgoing = readBaseStatus(MacVMBundle(root: builtBaseDir))
        MacVMSettings.setPendingBaseCarry(
            apps: outgoing.installedAppPaths, packages: outgoing.installedPackageIDs)

        let fm = FileManager.default
        try? fm.removeItem(at: builtBaseDir)
        if includingRestoreImages { try? fm.removeItem(at: restoreDir) }
        baseProgress = nil
    }

    /// Drop a terminal `.ready` progress once the build that published it has finished.
    ///
    /// ``baseProgress`` doubles as the settings panel's "a build is in flight" signal, but a *successful*
    /// build's last act is to publish `.ready` — which then stuck around for the life of the process.
    /// The panel kept rendering the build checklist ("Base macOS image ready") instead of the settled
    /// row, so the trailing "…" menu — the only way to rebuild or delete the base — never appeared.
    /// Called from each build's `defer`, so the flag and the progress clear together.
    func clearTerminalBaseProgress() {
        if baseProgress?.phase == .ready { baseProgress = nil }
    }

    /// Read the base's provisioning status from `bundle.json` (all-false when absent/unreadable).
    func readBaseStatus(_ bundle: MacVMBundle) -> MacVMBaseStatus {
        guard let data = try? Data(contentsOf: bundle.metadataURL),
            let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
        else { return MacVMBaseStatus() }
        return MacVMBaseStatus(json: json)
    }

    /// Persist the base's provisioning status to `bundle.json`.
    ///
    /// A no-op once ``forceCancelBaseWork()`` has fired: the cancelled pass is still unwinding and would
    /// otherwise re-create `bundle.json` inside a bundle the operator just deleted, resurrecting a
    /// half-built base that the panel then reports as installed.
    func writeBaseStatus(_ bundle: MacVMBundle, _ status: MacVMBaseStatus) {
        guard !baseWorkCancelled else { return }
        if let data = try? JSONSerialization.data(withJSONObject: status.dictionary) {
            try? data.write(to: bundle.metadataURL)
        }
    }

    // MARK: - Cloning

    /// Produce a per-session clone of `base`: copy the small identity + NVRAM files, copy-on-write the
    /// large system disk (instant + space-efficient on APFS), and assign a fresh NAT MAC. Returns the
    /// assigned MAC. The clone is what the session actually boots — the base stays pristine.
    func cloneBase(_ base: MacVMBundle, to dest: MacVMBundle, os: GuestOS = .macOS) throws -> String {
        let fm = FileManager.default
        do {
            try? fm.removeItem(at: dest.root)
            try fm.createDirectory(at: dest.root, withIntermediateDirectories: true)
            switch os {
            case .macOS:
                try fm.copyItem(at: base.hardwareModelURL, to: dest.hardwareModelURL)
                try fm.copyItem(at: base.machineIdentifierURL, to: dest.machineIdentifierURL)
                // Aux storage (NVRAM) must be per-VM writable, so each clone gets its own copy.
                try fm.copyItem(at: base.auxiliaryStorageURL, to: dest.auxiliaryStorageURL)
            case .linux:
                // No Mac platform identity/NVRAM. The kernel + initrd + cmdline are read-only and
                // small, so copy them straight through (the clone boots the same kernel as the base).
                try fm.copyItem(at: base.kernelURL, to: dest.kernelURL)
                try fm.copyItem(at: base.initrdURL, to: dest.initrdURL)
                if fm.fileExists(atPath: base.cmdlineURL.path) {
                    try fm.copyItem(at: base.cmdlineURL, to: dest.cmdlineURL)
                }
            }
            // The multi-GB system disk (macOS install / Linux rootfs): clone copy-on-write via
            // clonefile(2). Falls back to a full copy on a non-APFS volume (clonefile returns non-zero).
            if clonefile(base.diskImageURL.path, dest.diskImageURL.path, 0) != 0 {
                try fm.copyItem(at: base.diskImageURL, to: dest.diskImageURL)
            }
        } catch {
            throw MacVMError.cloneFailed(String(describing: error))
        }
        let mac = Self.randomMAC()
        try? mac.write(to: dest.macAddressURL, atomically: true, encoding: .utf8)
        return mac
    }

}

#if arch(arm64)
extension MacVMEngine {
    // MARK: - VM configuration builder

    /// Assemble the `VZVirtualMachineConfiguration` for a bundle: reconstruct the persisted Mac
    /// platform (hardware model + machine identifier + this bundle's aux storage), clamp CPU/memory to
    /// the framework's allowed range, and attach the standard device set. A display, keyboard, and
    /// trackpad are included so the *same* config can be shown in a window while provisioning the base
    /// (Setup Assistant needs a display); clones run headless and simply never present the view.
    ///
    /// `nonisolated static` on purpose: it reads only the (Sendable) bundle files and constructs VZ
    /// objects — no engine-actor state — so both the actor and the main-actor GUI window (the Recovery
    /// helper) can build a config from the same code without crossing isolation.
    public nonisolated static func makeConfiguration(
        bundle: MacVMBundle, os: GuestOS = .macOS, cpus: Int, memoryGiB: Int, mac: String,
        mounts: [MacVMSpec.Mount]
    ) throws -> VZVirtualMachineConfiguration {
        switch os {
        case .macOS:
            return try makeMacConfiguration(
                bundle: bundle, cpus: cpus, memoryGiB: memoryGiB, mac: mac, mounts: mounts)
        case .linux:
            return try makeLinuxConfiguration(
                bundle: bundle, cpus: cpus, memoryGiB: memoryGiB, mac: mac, mounts: mounts)
        }
    }

    /// Assemble the config for a **macOS** guest (the original ``makeConfiguration`` body): the Mac
    /// platform + `VZMacOSBootLoader` + `VZMac*` graphics/keyboard/trackpad.
    public nonisolated static func makeMacConfiguration(
        bundle: MacVMBundle, cpus: Int, memoryGiB: Int, mac: String, mounts: [MacVMSpec.Mount]
    ) throws -> VZVirtualMachineConfiguration {
        let config = VZVirtualMachineConfiguration()
        config.platform = try loadMacPlatform(bundle: bundle)
        config.bootLoader = VZMacOSBootLoader()

        config.cpuCount = Self.clampCPU(cpus)
        config.memorySize = Self.clampMemory(UInt64(memoryGiB) * 1024 * 1024 * 1024)

        // System disk (this bundle's writable Disk.img).
        let attachment = try VZDiskImageStorageDeviceAttachment(url: bundle.diskImageURL, readOnly: false)
        config.storageDevices = [VZVirtioBlockDeviceConfiguration(attachment: attachment)]

        // NAT networking — kept **only for the guest's own outbound internet** (brew / npm / xcodebuild
        // dependencies). It is NOT part of the host↔guest control plane anymore: that is 100% vsock (no
        // SSH, no DHCP-lease discovery, no incoming-connection/local-network permission surface). NAT
        // needs no restricted entitlement — deliberately NOT bridged (`com.apple.vm.networking` would
        // get an ad-hoc-signed binary killed at launch; see signing/spike.entitlements). The pinned MAC
        // is still used for the best-effort IP shown in the settings panel.
        let net = VZVirtioNetworkDeviceConfiguration()
        net.macAddress = VZMACAddress(string: mac) ?? VZMACAddress.randomLocallyAdministered()
        net.attachment = VZNATNetworkDeviceAttachment()
        config.networkDevices = [net]

        // vsock channel for the native in-guest agent (docs/MACOS_VM_NATIVE_AGENT.md §3) — the **sole**
        // host↔guest channel: the host reaches the agent via VZVirtioSocketDevice.connect(toPort:) for
        // exec, computer-use, and readiness. Exactly one socket device per VM (the framework's limit);
        // harmless on the clean install during a base build, before the agent is installed.
        config.socketDevices = [VZVirtioSocketDeviceConfiguration()]

        config.graphicsDevices = [Self.makeGraphics()]
        config.keyboards = [VZMacKeyboardConfiguration()]
        config.pointingDevices = [VZMacTrackpadConfiguration()]
        config.entropyDevices = [VZVirtioEntropyDeviceConfiguration()]
        config.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()]

        // Shared host directories → virtiofs. Shares that recreate their host path use one custom-
        // tagged single-directory device apiece; once the guest agent is ready, `mount_virtiofs`
        // attaches each device directly at its original absolute host path. That produces a real mount
        // at `/Users/…`, rather than the old `/Users/…` symlink to `/Volumes/My Shared Files/…`.
        // Opted-out/auxiliary shares retain Apple's automount behavior under `/Volumes`.
        if !mounts.isEmpty {
            var devices: [VZDirectorySharingDeviceConfiguration] = []
            var automountShares: [String: VZSharedDirectory] = [:]
            for (index, m) in mounts.enumerated() {
                let directory = VZSharedDirectory(
                    url: URL(fileURLWithPath: m.host), readOnly: m.readOnly)
                if directMacOSMount(m) {
                    let fs = VZVirtioFileSystemDeviceConfiguration(tag: macOSShareTag(index: index))
                    fs.share = VZSingleDirectoryShare(directory: directory)
                    devices.append(fs)
                } else {
                    automountShares[m.name] = directory
                }
            }
            if !automountShares.isEmpty {
                let fs = VZVirtioFileSystemDeviceConfiguration(
                    tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag)
                fs.share = VZMultipleDirectoryShare(directories: automountShares)
                devices.append(fs)
            }
            config.directorySharingDevices = devices
        }

        try config.validate()
        return config
    }

    /// Reconstruct the Mac platform from a bundle's persisted identity + this bundle's aux storage.
    nonisolated private static func loadMacPlatform(bundle: MacVMBundle) throws -> VZMacPlatformConfiguration {
        let platform = VZMacPlatformConfiguration()
        platform.auxiliaryStorage = VZMacAuxiliaryStorage(url: bundle.auxiliaryStorageURL)

        guard let hwData = try? Data(contentsOf: bundle.hardwareModelURL),
            let hardwareModel = VZMacHardwareModel(dataRepresentation: hwData)
        else { throw MacVMError.startFailed("unreadable hardware model in bundle") }
        guard hardwareModel.isSupported else {
            throw MacVMError.startFailed("this Mac can't run the base image's hardware model")
        }
        platform.hardwareModel = hardwareModel

        guard let idData = try? Data(contentsOf: bundle.machineIdentifierURL),
            let machineIdentifier = VZMacMachineIdentifier(dataRepresentation: idData)
        else { throw MacVMError.startFailed("unreadable machine identifier in bundle") }
        platform.machineIdentifier = machineIdentifier
        return platform
    }

    static func makeGraphics() -> VZMacGraphicsDeviceConfiguration {
        let g = VZMacGraphicsDeviceConfiguration()
        // A 3840×2400 panel at ~220 PPI (real-Retina density) so the guest renders "looks like
        // 1920×1200" @ 2×, matching a live Retina Mac rather than the coarse 1× a low-PPI panel
        // forces. The *logical* desktop stays 1920×1200 pts — the same workspace the 1×/80-PPI
        // panel gave — so window layouts are unaffected. Deliberately not a smaller panel at high
        // PPI: that would keep 2× but halve the logical desktop to 960×600, too small for the main
        // window's minimum, reintroducing the squeezed/overlapping layout. The host-side
        // computer-use surface is decoupled from this (its VZVirtualMachineView is a fixed
        // 1920×1200-pt view whose captures normalize to `MacVMComputerSurface.fbWidth/Height`), so
        // agent screenshots and click mapping stay 1:1 at 1920×1200 regardless of this density.
        g.displays = [
            VZMacGraphicsDisplayConfiguration(widthInPixels: 3840, heightInPixels: 2400, pixelsPerInch: 220)
        ]
        return g
    }

    static func clampCPU(_ requested: Int) -> Int {
        var n = requested
        n = max(n, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
        n = min(n, VZVirtualMachineConfiguration.maximumAllowedCPUCount)
        // Never claim more than the host has, less one core for the host itself.
        n = min(n, max(1, ProcessInfo.processInfo.processorCount - 1))
        return max(1, n)
    }

    static func clampMemory(_ requested: UInt64) -> UInt64 {
        var m = requested
        m = max(m, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
        m = min(m, VZVirtualMachineConfiguration.maximumAllowedMemorySize)
        return m
    }

    // MARK: - Base install (one-time)

    /// Build the golden base bundle: resolve a macOS restore image (a configured local `.ipsw`, or the
    /// latest supported fetched over the network), install macOS into a fresh bundle, and record
    /// metadata. This installs a *clean* macOS — the guest then sits at Setup Assistant. Turning it into
    /// a usable base (an `agent` account, Remote Login on, the dev toolchain, Nucleic's SSH key) is the
    /// **provisioning** step, driven from a display session + `scripts/provision-macos-guest.sh`
    /// (see docs/MACOS_VM.md); Apple provides no unattended-install path for macOS guests outside MDM.
    ///
    /// Long-running (a ~14 GB download on first use, then a multi-minute install); `progress` is
    /// reported through ``currentBaseProgress()``. Intended to be invoked explicitly (settings action /
    /// spike / script), never lazily on an agent turn.
    public func buildBaseImage(localRestoreImagePath: String? = nil) async throws {
        guard Self.isSupported else {
            throw MacVMError.unavailable(Self.unsupportedReason ?? "requires Apple silicon")
        }
        // Serialize base builds: two concurrent ones share the `base.building` temp path and would
        // delete each other's work-in-progress. The check+set is atomic on the actor (no await
        // between).
        guard !baseIsBusy else {
            throw MacVMError.installFailed("a base image build is already in progress")
        }
        baseBuilding = true
        baseWorkCancelled = false  // a new, deliberate pass — re-enable bundle writes
        baseBuildGuest = .macOS
        // Hold the anti-nap assertion for the whole build/provision pass: the base VM booted below never
        // enters `live`, so without this an auto-rebuild that fires at launch (e.g. after an update,
        // while the app is still backgrounded) would be App-Napped, freezing the main-queue provisioning
        // VM + its HID and wedging the build at "Waiting for the guest desktop". Released in the defer.
        syncBackgroundActivity()
        defer {
            baseBuilding = false
            baseBuildGuest = nil
            clearTerminalBaseProgress()
            syncBackgroundActivity()
        }
        let fm = FileManager.default

        // Reentrancy: if the base is already installed + account-provisioned, skip the multi-GB
        // install and rerun the recipe-v8 minimal-agent → MDM → full-provisioning sequence. This
        // updates policy/agent identity without consuming declarative first boot again.
        let existingBase = MacVMBundle(root: builtBaseDir)
        if existingBase.isComplete, MacVMSettings.basePrebuiltPath == nil,
            readBaseStatus(existingBase).accountProvisioned
        {
            // Reentrant re-provision: the account already exists, so this is a normal boot (no
            // declarative first-boot options). Carry this base's own recorded apps/packages forward so
            // re-provisioning re-stages anything the user installed here that's since been dropped from
            // the live Settings list. (A carry stash from an aborted delete is folded in too, then
            // cleared once the pass succeeds.)
            let status = readBaseStatus(existingBase)
            try await provisionAndFinalize(
                bundle: existingBase, base: status, declarativeFirstBoot: false,
                carryAppPaths: Self.orderedUnion(
                    status.installedAppPaths, MacVMSettings.pendingBaseCarryApps),
                carryPackageIDs: Self.orderedUnion(
                    status.installedPackageIDs, MacVMSettings.pendingBaseCarryPackages))
            MacVMSettings.clearPendingBaseCarry()
            return
        }
        // If a base is installed but has no key-authorized `agent` account, the macOS-27 declarative
        // first-boot slot (the only unattended way to create it) was already consumed — there's no
        // in-place fix. Rather than dumping the user out to docs, fall through to a clean reinstall
        // below: it re-runs install + declarative first boot end-to-end and carries this base's
        // apps/packages forward (`priorBase`, further down). The IPSW is cached, so it's a reinstall,
        // not a re-download.
        if existingBase.isComplete, MacVMSettings.basePrebuiltPath == nil {
            baseProgress = MacVMBaseProgress(
                phase: .installing, fraction: nil,
                detail: "Reinstalling — the previous install had no agent account")
        }

        try fm.createDirectory(at: restoreDir, withIntermediateDirectories: true)

        let restoreURL = try await resolveRestoreImage(localPath: localRestoreImagePath)
        let restoreImage = try await Self.loadRestoreImage(at: restoreURL)
        let osv = restoreImage.operatingSystemVersion
        let guestVersion = "\(osv.majorVersion).\(osv.minorVersion).\(osv.patchVersion)"
        guard let requirements = restoreImage.mostFeaturefulSupportedConfiguration,
            requirements.hardwareModel.isSupported
        else {
            // The host's Virtualization framework can't run this image's hardware model — almost always
            // because the guest macOS is newer than the host (a macOS N guest needs a macOS ≥N host).
            let h = ProcessInfo.processInfo.operatingSystemVersion
            throw MacVMError.installFailed(
                "this restore image (macOS \(guestVersion)) can't run on this host "
                    + "(macOS \(h.majorVersion).\(h.minorVersion).\(h.patchVersion)). A macOS "
                    + "\(osv.majorVersion) guest requires a macOS \(osv.majorVersion)+ host — update the "
                    + "host, or pin an older restore image (Settings ▸ Virtual Machines).")
        }

        baseProgress = MacVMBaseProgress(phase: .installing, fraction: 0)
        // Build into a temp bundle, then publish atomically so an interrupted install never leaves a
        // half-built base that `ensureBaseBundle` would treat as usable.
        let tmpBundle = MacVMBundle(
            root: storageRoot.appendingPathComponent("base.building", isDirectory: true))
        try? fm.removeItem(at: tmpBundle.root)
        try fm.createDirectory(at: tmpBundle.root, withIntermediateDirectories: true)

        // Set inside the install do-block once the base is published; the provisioning pass then runs
        // AFTER the catch, so its (non-install) errors aren't wrapped with the install-failure hint.
        // `declarativeFirstBoot` is true for a macOS-27 guest: the provisioning boot IS the first boot
        // after restore, so it carries `VZMacGuestProvisioningOptions` to create the `agent` account.
        var provisionTarget:
            (bundle: MacVMBundle, status: MacVMBaseStatus, declarativeFirstBoot: Bool,
             carryApps: [String], carryPackages: [String])?

        do {
            try createInstallPlatform(into: tmpBundle, requirements: requirements)
            try Self.createDiskImage(
                at: tmpBundle.diskImageURL,
                sizeBytes: UInt64(MacVMSettings.baseDiskGiB) * 1024 * 1024 * 1024)

            // The install MAC is reused for the (macOS 27+) first-boot provisioning pass below —
            // the DHCP lease keyed to it is how that pass finds the guest's IP.
            let installMAC = Self.randomMAC()
            let config = try Self.makeConfiguration(
                bundle: tmpBundle,
                cpus: max(requirements.minimumSupportedCPUCount, MacVMSettings.defaultVMCPUs),
                memoryGiB: MacVMSettings.defaultVMMemoryGiB,
                mac: installMAC, mounts: [])

            let instance = MacVMInstance(configuration: config, label: "base-install")
            try await instance.installMacOS(restoreImageURL: restoreURL) { [weak self] fraction in
                Task { await self?.setBaseProgress(.init(phase: .installing, fraction: fraction)) }
            }
            await instance.stop()

            // This build REPLACES whatever base was at `builtBaseDir`. Recover the apps/packages the
            // user had installed into the old base so the provisioning pass below re-installs them into
            // the new base automatically — even ones since dropped from the live Settings list
            // (docs/MACOS_VM.md §9.1). Source: the old bundle's `bundle.json` if it's still on disk
            // (an in-place reinstall), unioned with the carry stash written by `deleteBaseImage` (the
            // OS-version-update path, where the bundle was already removed). Empty on a first-ever build.
            let priorBase = readBaseStatus(MacVMBundle(root: builtBaseDir))
            let carryApps = Self.orderedUnion(
                priorBase.installedAppPaths, MacVMSettings.pendingBaseCarryApps)
            let carryPackages = Self.orderedUnion(
                priorBase.installedPackageIDs, MacVMSettings.pendingBaseCarryPackages)

            // Publish the clean install FIRST (so a later provisioning failure can't discard the
            // multi-GB install), then provision it in place. `accountProvisioned` starts false and is
            // set true by the provisioning pass once the declarative first boot has created the account.
            let installedStatus = MacVMBaseStatus(
                installed: true, accountProvisioned: false,
                osVersion: guestVersion, buildVersion: restoreImage.buildVersion)
            writeBaseStatus(tmpBundle, installedStatus)
            try? fm.removeItem(at: builtBaseDir)
            try fm.moveItem(at: tmpBundle.root, to: builtBaseDir)

            if #available(macOS 27.0, *), osv.majorVersion >= 27 {
                // macOS 27 guest: the provisioning boot is the first boot after restore and carries
                // `VZMacGuestProvisioningOptions` to create the `agent` account + auto-login (no Remote
                // Login — the control plane is vsock). See `installMinimalAgent` (§4.4).
                provisionTarget = (
                    MacVMBundle(root: builtBaseDir), installedStatus, true, carryApps, carryPackages)
            } else {
                // Unsupported (pre-27) guest: no unattended account-creation path — declarative
                // provisioning is macOS 27+, and we don't drive Setup Assistant. Leave the installed
                // base for the manual path (§4.2).
                baseProgress = MacVMBaseProgress(phase: .ready, fraction: 1)
            }
        } catch {
            try? fm.removeItem(at: tmpBundle.root)
            baseProgress = nil
            if let e = error as? MacVMError { throw e }
            // A newer-than-host guest can pass the hardware-model check yet still fail to install:
            // `VZMacOSInstaller` can't install a guest newer than the host. Add an actionable hint.
            let hostMajor = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
            var hint = ""
            if osv.majorVersion > hostMajor {
                hint = " — note: installing a macOS \(osv.majorVersion) guest from a macOS \(hostMajor) "
                    + "host is not supported: the guest can't be newer than the host, and macOS 27 is "
                    + "required. Build the base on a host running at least macOS \(osv.majorVersion) "
                    + "(Settings ▸ Virtual Machines)."
            }
            throw MacVMError.installFailed(String(describing: error) + hint)
        }

        // The clean install is published; now create the account (declarative first boot) and provision
        // the toolchain + native agent — all host-side (HID bootstrap) + VirtioFS, no network.
        if let target = provisionTarget {
            try await provisionAndFinalize(
                bundle: target.bundle, base: target.status,
                declarativeFirstBoot: target.declarativeFirstBoot,
                carryAppPaths: target.carryApps, carryPackageIDs: target.carryPackages)
            // The replacement base's `bundle.json` now records the carried set, so the delete-time
            // stash has done its job — clear it (a provisioning failure above leaves it for a retry).
            MacVMSettings.clearPendingBaseCarry()
        }
    }

    func setBaseProgress(_ p: MacVMBaseProgress) { baseProgress = p }

    /// Create a fresh install-time platform: a new aux storage bound to the restore image's hardware
    /// model, a new machine identifier, and both persisted into the bundle for later reload.
    private func createInstallPlatform(
        into bundle: MacVMBundle, requirements: VZMacOSConfigurationRequirements
    ) throws {
        let platform = VZMacPlatformConfiguration()
        let aux = try VZMacAuxiliaryStorage(
            creatingStorageAt: bundle.auxiliaryStorageURL,
            hardwareModel: requirements.hardwareModel, options: [])
        platform.auxiliaryStorage = aux
        platform.hardwareModel = requirements.hardwareModel
        platform.machineIdentifier = VZMacMachineIdentifier()
        try platform.hardwareModel.dataRepresentation.write(to: bundle.hardwareModelURL)
        try platform.machineIdentifier.dataRepresentation.write(to: bundle.machineIdentifierURL)
    }

    /// Resolve the restore image to a local `.ipsw` path, choosing the guest macOS version (up to what
    /// the host supports — a macOS N guest needs a macOS ≥N host). Precedence:
    ///   1. an explicit local `.ipsw` (`localPath` arg or `restoreImagePath` setting) — pins a version;
    ///   2. a configured remote `.ipsw` URL (`restoreImageURL` setting) — pins a version, downloaded;
    ///   3. `VZMacOSRestoreImage.latestSupported` — the newest the host can run (macOS 27 on a 27 host).
    ///
    /// Downloads are cached **keyed by the IPSW's filename** (which encodes the version/build), NOT a
    /// fixed `latest.ipsw`. That's the fix that lets the fetch path move up to macOS 27: when Apple
    /// publishes a newer version, `latestSupported.url`'s filename changes, so a fresh image is fetched
    /// instead of a stale one being reused forever.
    private func resolveRestoreImage(localPath: String?) async throws -> URL {
        if let localPath = localPath ?? MacVMSettings.restoreImagePath {
            let url = URL(fileURLWithPath: localPath)
            guard FileManager.default.fileExists(atPath: url.path) else {
                throw MacVMError.installFailed("restore image not found at \(localPath)")
            }
            return url
        }
        if let urlString = MacVMSettings.restoreImageURL, let remote = URL(string: urlString) {
            return try await downloadRestoreImageIfNeeded(from: remote)
        }
        // Resolve the latest image's metadata first (a cheap network call) so the cache key tracks the
        // actual image identity.
        baseProgress = MacVMBaseProgress(phase: .downloadingRestoreImage, fraction: 0)
        let latest = try await Self.fetchLatestRestoreImage()
        return try await downloadRestoreImageIfNeeded(from: latest.url)
    }

    /// Download `remote` into the version-keyed cache (`restore/<ipsw-filename>`) unless already present.
    private func downloadRestoreImageIfNeeded(from remote: URL) async throws -> URL {
        let fm = FileManager.default
        try fm.createDirectory(at: restoreDir, withIntermediateDirectories: true)
        let cached = restoreDir.appendingPathComponent(Self.restoreCacheName(for: remote))
        if fm.fileExists(atPath: cached.path) { return cached }
        baseProgress = MacVMBaseProgress(phase: .downloadingRestoreImage, fraction: 0)
        try await downloadFile(from: remote, to: cached) { [weak self] fraction in
            Task { await self?.setBaseProgress(.init(phase: .downloadingRestoreImage, fraction: fraction)) }
        }
        return cached
    }

    /// A stable, filesystem-safe cache filename for a restore-image URL: the IPSW's own filename (which
    /// encodes the macOS version/build, e.g. `UniversalMac_26.0_25A...ipsw`), so different versions land
    /// in different cache files and re-selecting the same version reuses the download.
    static func restoreCacheName(for url: URL) -> String {
        var leaf = url.lastPathComponent
        if leaf.isEmpty || leaf == "/" { leaf = "restore" }
        // Sanitize any stray path separators / query artifacts.
        leaf = leaf.map { ($0 == "/" || $0 == ":" || $0 == "?") ? "_" : $0 }.reduce(into: "") { $0.append($1) }
        return leaf.hasSuffix(".ipsw") ? leaf : leaf + ".ipsw"
    }

    /// Retains a KVO observation across an async operation. `NSKeyValueObservation` stops firing the
    /// moment it deinits, so a progress observation must be held until the download completes — a bare
    /// `withExtendedLifetime` wouldn't span the async gap.
    private final class ProgressRetainer: @unchecked Sendable {
        var observation: NSKeyValueObservation?
    }

    /// Download a URL to `dest`, reporting `\.fractionCompleted` as it streams (the restore image is
    /// ~14 GB, so a determinate bar matters). Internal (not private) so the Linux base build reuses it
    /// for the kernel / initrd / rootfs downloads.
    func downloadFile(
        from url: URL, to dest: URL, onFraction: @escaping @Sendable (Double) -> Void
    ) async throws {
        let retainer = ProgressRetainer()
        try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
            let task = URLSession.shared.downloadTask(with: url) { tmp, _, error in
                retainer.observation?.invalidate()  // keeps `retainer` (hence the observation) alive to here
                if let error { cont.resume(throwing: error); return }
                guard let tmp else {
                    cont.resume(throwing: MacVMError.installFailed("download produced no file"))
                    return
                }
                do {
                    try? FileManager.default.removeItem(at: dest)
                    try FileManager.default.moveItem(at: tmp, to: dest)
                    cont.resume()
                } catch { cont.resume(throwing: error) }
            }
            retainer.observation = task.progress.observe(\.fractionCompleted, options: [.new]) { p, _ in
                onFraction(p.fractionCompleted)
            }
            task.resume()
        }
    }

    // MARK: - Restore-image API bridges (completion-handler → async)

    static func fetchLatestRestoreImage() async throws -> VZMacOSRestoreImage {
        // `VZMacOSRestoreImage` isn't Sendable; box it to cross the completion-handler → continuation
        // boundary (it's freshly minted here and untouched until it reaches the actor — a safe hand-off).
        let box: UncheckedSendableBox<VZMacOSRestoreImage> = try await withCheckedThrowingContinuation {
            cont in
            VZMacOSRestoreImage.fetchLatestSupported { result in
                switch result {
                case .success(let image): cont.resume(returning: UncheckedSendableBox(value: image))
                case .failure(let error): cont.resume(throwing: error)
                }
            }
        }
        return box.value
    }

    static func loadRestoreImage(at url: URL) async throws -> VZMacOSRestoreImage {
        let box: UncheckedSendableBox<VZMacOSRestoreImage> = try await withCheckedThrowingContinuation {
            cont in
            VZMacOSRestoreImage.load(from: url) { result in
                switch result {
                case .success(let image): cont.resume(returning: UncheckedSendableBox(value: image))
                case .failure(let error): cont.resume(throwing: error)
                }
            }
        }
        return box.value
    }

    /// Create a sparse disk image file of `sizeBytes` (grows on demand on APFS).
    static func createDiskImage(at url: URL, sizeBytes: UInt64) throws {
        let fm = FileManager.default
        if fm.fileExists(atPath: url.path) { return }
        guard fm.createFile(atPath: url.path, contents: nil) else {
            throw MacVMError.installFailed("could not create disk image at \(url.path)")
        }
        let fh = try FileHandle(forWritingTo: url)
        defer { try? fh.close() }
        try fh.truncate(atOffset: sizeBytes)
    }
}

extension MacVMInstance {
    /// Install macOS into this instance's VM from a restore `.ipsw`, reporting install progress. Runs
    /// the `VZMacOSInstaller` on the VM queue (as required) and resumes when it finishes or errors.
    func installMacOS(
        restoreImageURL: URL, progress: @escaping @Sendable (Double) -> Void
    ) async throws {
        try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
            queue.async { [self] in
                let installer = VZMacOSInstaller(
                    virtualMachine: vm, restoringFromImageAt: restoreImageURL)
                let observation = installer.progress.observe(\.fractionCompleted, options: [.new]) { p, _ in
                    progress(p.fractionCompleted)
                }
                installer.install { result in
                    observation.invalidate()
                    switch result {
                    case .success: cont.resume()
                    case .failure(let error): cont.resume(throwing: error)
                    }
                }
            }
        }
    }
}
#endif
