import Foundation
// For `unsetenv` below: on Windows that name is NucleicProtocol's POSIX stand-in, not libc's.
// (Every other file here gets NucleicProtocol via a type it names; this one otherwise wouldn't.)
import NucleicProtocol

/// Shared bring-up for the headless hosts — `nucleicd` (the Covalence runner,
/// docs/COVALENCE_RUNNER.md §9) and `nucleic-hostd` (the Windows host,
/// docs/WINDOWS_PORT.md §11): the cockpit's object graph with no UI attached. Each host keeps
/// its own personality (runner: mesh `.cloud` kind, credential vault, pool heartbeats;
/// hostd: single-instance mutex, rendezvous file, broker supervision) and calls these three
/// steps for the parts that must never drift between them.
public enum NucleicHeadless {
    /// A daemon launched by launchd/a supervisor inherits the same blank-API-key hazard the
    /// GUI app scrubs: Claude/Grok/ACP CLIs prefer the env var over their logins, so a blank
    /// inherited key would 401 every session. Purge blanks; real keys stay.
    public static func scrubBlankAPIKeys(
        environment: [String: String] = ProcessInfo.processInfo.environment
    ) {
        for key in [
            "ANTHROPIC_API_KEY", "XAI_API_KEY", "OPENCODE_API_KEY", "CURSOR_API_KEY",
            "HERMES_API_KEY",
        ] where SessionController.isBlankAPIKey(environment[key]) {
            unsetenv(key)
        }
    }

    /// The Windows host must inject its wslc broker engine — there is no defaultable sandbox
    /// mechanism on that platform (docs/WINDOWS_PORT.md §3.2).
    public struct MissingSandboxEngine: Error, CustomStringConvertible {
        public let description =
            "a headless host on this platform must inject a SandboxEngine (none is defaultable)"
    }

    /// The cockpit's object graph (NucleicApp.init), headless: same stores, same backend
    /// factory, no AppKit, no `AppleIntelligenceProvider` — intelligence stays on the Core
    /// default (heuristics) until the caller installs a provider (runner: delegated/mesh,
    /// hostd: Windows Foundry via the broker).
    ///
    /// `sandboxEngine` picks the container mechanism: macOS defaults to the in-process
    /// `ContainerEngine`; Windows callers MUST pass the wslc engine; Linux ignores it (the
    /// stub `ContainerManager` is the Linux story — the cloud container IS the sandbox).
    ///
    /// The new-chat *defaults* (`ModelCatalog.stored*`) live in the App layer and don't apply
    /// headlessly: a headless host receives model/effort explicitly in each dispatched
    /// `StartChatRequest`, so `AppStore`'s own field defaults are the right fallback and the
    /// phone's picker uses its built-in list (an empty `Welcome.modelCatalog` decodes to that
    /// fallback by design). Nothing to seed here.
    @MainActor
    public static func makeStore(
        support: URL, sandboxEngine: (any SandboxEngine)? = nil
    ) throws -> AppStore {
        let database = try GRDBMetadataStore(
            path: support.appendingPathComponent("nucleic.sqlite").path)
        let worktrees = GitWorktreeManager()
        let transcriptsDir = support.appendingPathComponent("sessions", isDirectory: true)
        let approvalServerRegistry = ApprovalServerRegistry()
        let onSharedContainerRemoved: @Sendable (String) async -> Void = { name in
            await approvalServerRegistry.teardown(containerName: name)
        }
        #if os(Windows)
        guard let sandboxEngine else { throw MissingSandboxEngine() }
        let containerManager = ContainerManager(
            engine: sandboxEngine, onSharedContainerRemoved: onSharedContainerRemoved)
        #elseif canImport(Containerization)
        let containerManager = ContainerManager(
            engine: sandboxEngine ?? ContainerEngine(),
            onSharedContainerRemoved: onSharedContainerRemoved)
        #else
        _ = sandboxEngine  // Linux: the stub manager has no engine seam (see doc above)
        let containerManager = ContainerManager(onSharedContainerRemoved: onSharedContainerRemoved)
        #endif
        let macVMManager = MacVMManager()
        let conflictCoordinator = ConflictCoordinator()
        return AppStore(
            database: database, worktrees: worktrees, transcriptsDir: transcriptsDir,
            containerManager: containerManager,
            // Remote agent sign-in (docs/REMOTE_AGENT_LOGIN.md): a headless host brokers the
            // OAuth flows with NO local browser or loopback — the initiating device presents the
            // consent URL and captures the redirect, so no `openURL`/`promptForCode` is injected.
            // This is what lets a phone credential a fresh runner before any Mac holds a login;
            // the landed files then ride the existing per-turn seeding + mirror-back sweep.
            claudeCredentialBroker: ClaudeCredentialBroker(containerManager: containerManager),
            codexCredentialBroker: CodexCredentialBroker(),
            macVMManager: macVMManager,
            conflictCoordinator: conflictCoordinator
        ) { session in
            switch session.backend {
            case .codex:
                return CodexAppServerBackend(
                    containerManager: containerManager,
                    macVMManager: macVMManager,
                    conflictCoordinator: conflictCoordinator,
                    approvalServerRegistry: approvalServerRegistry)
            case .codexExec:
                return CodexExecBackend(
                    containerManager: containerManager,
                    macVMManager: macVMManager,
                    conflictCoordinator: conflictCoordinator,
                    approvalServerRegistry: approvalServerRegistry)
            case .claudeCode:
                return ClaudeCodeBackend(
                    configuration: .init(closeStdinAfterPrompt: true),
                    containerManager: containerManager,
                    macVMManager: macVMManager,
                    conflictCoordinator: conflictCoordinator,
                    approvalServerRegistry: approvalServerRegistry)
            case .grok, .opencode, .openclaw, .hermes, .cursorAgent, .acp:
                let agent = ACPAgent.forBackend(session.backend) ?? .grok
                return ACPBackend(
                    configuration: .init(agent: agent),
                    conflictCoordinator: conflictCoordinator,
                    containerManager: containerManager,
                    macVMManager: macVMManager,
                    approvalServerRegistry: approvalServerRegistry)
            }
        }
    }

    /// The same startup choreography as the cockpit's `.task`, minus UI concerns: arbitration,
    /// persisted projects/sessions/todos, scratch-dir GC, interrupted-transfer recovery. Run
    /// after `makeStore` and any host-personality configuration, before the sync server starts.
    @MainActor
    public static func loadPersistentState(_ store: AppStore) async {
        await store.activateConflictArbitration()
        await store.loadProjects()
        await store.loadSessions()
        store.sweepOrphanedScratchDirs()
        await store.recoverInterruptedTransfers()
        await store.loadTodos()
        // Headless hosts have no NucleicApp launch task to start lifecycle maintenance.
        // Keep the archived-worktree one-day grace period enforceable on runners and
        // Windows hosts too (the call is idempotent if startup is retried).
        store.startAutoArchivePolling()
    }
}
