import Foundation
import NucleicProtocol
#if canImport(FoundationModels)
import FoundationModels
#endif

/// The phone-side executor for delegated intelligence work (docs/COVALENCE_RUNNER.md §5,
/// item 6): a runner host pushes `HostMsg.intelligenceRequest` at this device — the mesh AFM
/// queue only ever sends it the lower tiers (`completion`/`background`), which don't need
/// desktop tok/s — and this renders the shared `IntelligenceDelegate` template on the local
/// Apple Foundation Models and answers `ClientMsg.intelligenceResult` with the same id.
///
/// Generations run one at a time through ``SerialGate`` (the phone's Neural Engine is a single
/// resource, same reasoning as the Mac's `AFMRequestQueue`), and every failure — model off,
/// unavailable, unknown kind — answers with `error` set so the runner falls back to heuristics
/// immediately instead of waiting out its deadline.
enum PhoneIntelligenceExecutor {
    /// Whether this device can execute at all (Foundation Models exist on this OS). Gates
    /// advertising `canProvideIntelligence`; live availability (Apple Intelligence enabled,
    /// model downloaded) is re-checked per request.
    static var isSupported: Bool {
        #if canImport(FoundationModels)
        if #available(iOS 26, *) { return true }
        #endif
        return false
    }

    /// The worker profile advertised in the hello: mobile class (only the lower priority
    /// tiers land here) with the SoC name for the queue's power bias — an M-series iPad
    /// outranks an A-series iPhone.
    static var profile: IntelligenceWorkerProfile? {
        guard isSupported else { return nil }
        return IntelligenceWorkerProfile(deviceClass: .mobile, chip: chipName)
    }

    /// The SoC brand string ("Apple A18 Pro", "Apple M4"), falling back to the hardware model
    /// identifier ("iPhone17,1" — unranked but still telling) when the sysctl is unreadable.
    static var chipName: String? {
        sysctlString("machdep.cpu.brand_string") ?? sysctlString("hw.machine")
    }

    private static func sysctlString(_ name: String) -> String? {
        var size = 0
        guard sysctlbyname(name, nil, &size, nil, 0) == 0, size > 0 else { return nil }
        var buffer = [CChar](repeating: 0, count: size)
        guard sysctlbyname(name, &buffer, &size, nil, 0) == 0 else { return nil }
        let value = String(cString: buffer).trimmingCharacters(in: .whitespaces)
        return value.isEmpty ? nil : value
    }

    /// Run one delegated request end to end. Never throws — every failure mode answers with
    /// `error` set, correlated by the request id.
    static func execute(_ request: WireIntelligenceRequest) async -> WireIntelligenceResult {
        guard let job = IntelligenceDelegate.job(for: request) else {
            return WireIntelligenceResult(
                id: request.id, error: "unsupported kind: \(request.kind.rawValue)")
        }
        #if canImport(FoundationModels)
        if #available(iOS 26, *) {
            let model = SystemLanguageModel.default
            guard model.isAvailable else {
                return WireIntelligenceResult(id: request.id, error: "model unavailable")
            }
            // Bound the total wait (queue + generation) by the request's deadline: the host
            // has already timed the job out by then, and a wedged `respond` must not park
            // every later `execute` behind the stall — the deadline path answers an error and
            // moves on. (An uncancellable stuck generation itself can't be reclaimed; it is
            // abandoned in the background.)
            let deadline = request.deadlineSeconds ?? 60
            let text = await raceAgainstDeadline(seconds: deadline) {
                await gate.run {
                    // Strip the tool-calling envelope the macOS 27 / iOS 27 model wraps free-form
                    // replies in before it goes on the wire — the host parses this text with the
                    // same line-oriented heuristics as a local generation, so it must arrive
                    // already unwrapped. A reply that was nothing but scaffolding yields nil, which
                    // answers `error` below and drops the host straight to its heuristic.
                    ModelOutput.usableText(
                        try? await LanguageModelSession(model: model, instructions: job.instructions)
                            .respond(to: job.prompt).content)
                }
            }
            if let text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                return WireIntelligenceResult(id: request.id, outputs: [text])
            }
            return WireIntelligenceResult(id: request.id, error: "generation failed")
        }
        #endif
        return WireIntelligenceResult(id: request.id, error: "model unavailable")
    }

    private static let gate = SerialGate()

    /// First-resume race between `operation` and a deadline: whichever finishes first answers;
    /// the loser's resume is dropped by the once-latch. A task group can't express this — its
    /// scope waits for ALL children, so an uncancellable straggler would still block the exit.
    private static func raceAgainstDeadline(
        seconds: Double, _ operation: @escaping @Sendable () async -> String?
    ) async -> String? {
        let once = FirstResume()
        return await withCheckedContinuation { continuation in
            Task {
                let value = await operation()
                if once.take() { continuation.resume(returning: value) }
            }
            Task {
                try? await Task.sleep(for: .seconds(seconds))
                if once.take() { continuation.resume(returning: nil) }
            }
        }
    }
}

/// A thread-safe "fire once" latch so the deadline race resumes its continuation exactly once.
private final class FirstResume: @unchecked Sendable {
    private let lock = NSLock()
    private var done = false
    func take() -> Bool {
        lock.lock()
        defer { lock.unlock() }
        if done { return false }
        done = true
        return true
    }
}

/// A minimal FIFO gate: chains each operation behind the previous one so delegated
/// generations never contend for the Neural Engine (an actor alone doesn't serialize across
/// its suspension points).
private actor SerialGate {
    private var tail: Task<Void, Never>?

    func run<T: Sendable>(_ operation: @escaping @Sendable () async -> T) async -> T {
        let previous = tail
        let task = Task<T, Never> {
            await previous?.value
            return await operation()
        }
        tail = Task { _ = await task.value }
        return await task.value
    }
}
