import Foundation

/// "Orchestra" — Nucleic's orchestration mode, modeled on Claude Code's ultracode mode.
///
/// Orchestra is *not* an API effort level. It pairs the documented `xhigh` effort with
/// standing consent for the agent to fan work out through Nucleic-managed worker sessions.
/// The user selects it in the effort menu like any other level, but it never reaches a
/// backend verbatim:
///
/// - `resolvedEffort` maps the `orchestra` sentinel to its real effort, `xhigh`, so the
///   `claude --effort` (and Codex) flag only ever sees a level the API accepts.
/// - `combinedSystemPrompt` rides the standing-consent text in on `--append-system-prompt`.
///   The CLI doesn't persist that flag, so `SessionController` re-applies it every turn —
///   which is exactly the per-turn "mode reminder" the orchestration design calls for, so
///   no separate refresher counter is needed here.
///
/// Keeping the sentinel, the effort mapping, and the consent text in one place lets the UI,
/// the core, and the tests agree on what "orchestra" means.
public enum OrchestrationMode {
    /// The sentinel stored in `Session.effort` when the user picks Orchestra. Chosen so it
    /// never collides with a real API effort level and is obvious in the persisted store.
    public static let effortSentinel = "orchestra"

    /// The mode's former sentinel, from before it was renamed from "Ultracode". Still
    /// recognized so a session persisted (or in flight from an older remote) under the old
    /// name keeps resolving to the mode rather than being sent to a backend verbatim.
    public static let legacyEffortSentinel = "ultracode"

    /// The effort orchestra runs at when the sentinel carries no qualifier — the level every
    /// Orchestra session used before the supervisor's effort became selectable, so an unqualified
    /// (or legacy) stored selection keeps behaving exactly as it did.
    public static let resolvedOrchestraEffort = "xhigh"

    /// Separates the sentinel from the supervisor's chosen effort: `"orchestra:max"`. The mode is
    /// still Orchestra — only the underlying API effort differs — so the qualifier rides on the
    /// sentinel rather than becoming a second `Session` column: every existing reader keeps
    /// working (`isOrchestra` is true, the UI still shows "Orchestra"), and the choice persists
    /// and transfers with the session for free. `xhigh` is stored bare, so the common case
    /// produces exactly the string it always did.
    public static let effortQualifierSeparator: Character = ":"

    /// The value to store in `Session.effort` for a supervisor running at `effort`. Falls back to
    /// the bare sentinel for a nil/unknown effort or for `xhigh` (the unqualified default).
    public static func qualifiedEffortSentinel(_ effort: String?) -> String {
        guard let normalized = effort?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
            normalized != resolvedOrchestraEffort,
            EffortLadder.efforts.contains(normalized) || normalized == EffortLadder.proEffort
        else { return effortSentinel }
        return "\(effortSentinel)\(effortQualifierSeparator)\(normalized)"
    }

    /// The sentinel and its optional effort qualifier, or nil when `effort` isn't an Orchestra
    /// selection at all. Case-insensitive and whitespace-tolerant, like the raw comparison it
    /// replaced.
    private static func parse(_ effort: String?) -> (base: String, qualifier: String?)? {
        guard let normalized = effort?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
            !normalized.isEmpty
        else { return nil }
        let parts = normalized.split(
            separator: effortQualifierSeparator, maxSplits: 1, omittingEmptySubsequences: false)
        let base = String(parts[0])
        guard base == effortSentinel || base == legacyEffortSentinel else { return nil }
        let qualifier = parts.count > 1 ? String(parts[1]) : nil
        return (base, (qualifier?.isEmpty ?? true) ? nil : qualifier)
    }

    /// Whether `effort` selects orchestra (case-insensitive, whitespace-tolerant). Accepts the
    /// legacy "ultracode" token too, so the rename can't strand an already-stored selection, and
    /// the qualified form (`"orchestra:max"`), so choosing a supervisor effort never reads as
    /// "not Orchestra" — which would drop the mode's tools and prompt.
    public static func isOrchestra(_ effort: String?) -> Bool {
        parse(effort) != nil
    }

    /// Which side of an Orchestra session a run is: the user-started chat is the `supervisor` (it
    /// dispatches and drives workers); a machine-spawned `nucleic_subagent` session is a `worker`
    /// (it executes one subtask and reports back). Selects which standing prompt rides in on
    /// `--append-system-prompt` — the supervisor gets ``consentPrompt``, the worker
    /// ``workerBriefing``. `nil` (the default) means neither: an ordinary, non-Orchestra run.
    public enum Role: Sendable, Equatable {
        case supervisor
        case worker
    }

    /// The effort actually handed to a backend: an Orchestra selection resolves to its qualifier
    /// (`"orchestra:max"` → `max`) or to `xhigh` when unqualified; everything else passes through
    /// untouched. One-way — the resolved value never flows back into `Session.effort`, so the UI
    /// keeps showing "Orchestra". An unrecognized qualifier resolves to `xhigh` rather than being
    /// forwarded, so a hand-edited or future-versioned store value can't reach a backend as a
    /// bogus `--effort`.
    public static func resolvedEffort(_ effort: String?) -> String? {
        guard let parsed = parse(effort) else { return effort }
        guard let qualifier = parsed.qualifier,
            EffortLadder.efforts.contains(qualifier) || qualifier == EffortLadder.proEffort
        else { return resolvedOrchestraEffort }
        return qualifier
    }

    // MARK: - Worker model selection

    /// The sentinel stored in the worker-model setting for **Intelligent**: instead of pinning one
    /// SKU for every worker, classify each worker's task and let `IntelligenceRouter` pick the model
    /// (and effort) that suits it. Deliberately not a real SKU — `BackendID.forModel` doesn't know
    /// it, so it must be resolved to a concrete model before any spawn (see
    /// `AppStore.orchestraWorkerSelection`); it must never reach a backend as `--model`.
    public static let intelligentWorkerModel = "nucleic-intelligent"

    /// The model a routed worker falls back to when the router can't resolve one — no provider
    /// probed yet at launch, or every candidate is quota-blocked. Matches the fixed worker default
    /// Orchestra shipped with, so the fallback is the behavior users already knew.
    public static let intelligentWorkerFallbackModel = "claude-sonnet-5"

    /// Whether the worker-model setting selects Intelligent (classifier-chosen) rather than a SKU.
    public static func isIntelligentWorkerModel(_ model: String?) -> Bool {
        model?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == intelligentWorkerModel
    }

    /// The standing prompt appended for the **supervisor** while Orchestra is on. It frames the
    /// agent's role — delegate, don't do — grants blanket fan-out consent, and lays out the full loop:
    /// clarify intent, submit a batched delegation plan, drive the workers, act on each batch, re-plan.
    /// Re-sent every turn by the caller, so it doubles as the mode's recurring reminder.
    ///
    /// Load-bearing details: Orchestra routes worker creation through Nucleic's MCP tools so the app
    /// can manage the worker lifecycle and enforce the configured model, and the model's most common
    /// failure is to *narrate* a fan-out in prose without emitting a real tool call — so the prompt
    /// names both directly. Dispatch is non-blocking (a plan returns worker ids immediately), so the
    /// supervisor must actively drive the run with `nucleic_supervise` rather than assume dispatch
    /// blocks until the work is done. `nucleic_ask_user` is named rather than `AskUserQuestion` because
    /// it is the one question tool present on every provider — a Codex or Grok supervisor has no
    /// built-in equivalent.
    public static let consentPrompt = """
        Orchestra (orchestration mode) is on. Your role this session is SUPERVISOR, not \
        implementer: turn the user's request into a plan, delegate every substantive task to \
        Nucleic-managed worker subagents, drive them to completion, and synthesize their results — \
        do not write code, edit files, or carry out the work yourself. Standing consent: you may \
        fan work out to parallel workers through Nucleic's tools on every substantive task without \
        asking first. Optimize for the most exhaustive, correct answer rather than the fastest one.

        Start from the user's request and plan for delegation. Read what they asked for, scout \
        only as much as you need to decompose it (a quick look yourself, or a first explore \
        batch), and decide the approach. If something genuinely material is ambiguous — what they \
        actually want, scope, a product or architectural choice with real consequences — ask them \
        with `nucleic_ask_user` BEFORE you commit to a plan: it puts multiple-choice questions in \
        front of the human and blocks for their answer. Ask once, up front, about the things that \
        would change the plan; don't interrogate them about details you can settle yourself or \
        with a sensible default, and don't stall a clear request to confirm it.

        Then submit the plan with `nucleic_delegate_plan`. One call carries `plan` (your approach \
        in prose, which the user sees) and `batches`: an ordered list of groups, each with a \
        `name`, a `purpose`, and its `tasks` — every task a short label plus complete, \
        self-contained instructions, since the worker sees only that text. Nucleic spawns a \
        worker session per task immediately and returns their ids; the user's worker cap queues \
        anything beyond it, so decompose along the problem's natural structure rather than to a \
        number. Use `nucleic_subagent` for a one-off follow-up worker.

        Use batches for what you can act on early. A batch is a group whose results stand on \
        their own — typically explore/research/audit work whose findings shape what you dispatch \
        next, kept separate from the implementation or verification it informs. Wait for one with \
        `nucleic_supervise` {batch:"name"}: it blocks until that whole group has reported, while \
        the other batches keep running, so you can read its results, re-plan, and dispatch \
        follow-on work with another `nucleic_delegate_plan` without waiting for everything. \
        Called with no argument it returns on the next event from any worker.

        Drive the loop until the work is done. `nucleic_supervise` returns the events plus \
        `remaining_running` and per-batch progress. Handle every event: answer a worker's \
        question with `nucleic_reply_to_worker` (its worker_id plus your reply) — relay the \
        user's own decision when you needed one; read completed workers' output and dispatch what \
        it implies; note failures and retry or adjust. Keep going — supervise, act, plan, \
        delegate — until no workers remain running and every result is incorporated, then \
        synthesize the outcome for the user yourself. You are the only agent that can reach the \
        human; workers route their questions up to you.

        Make the fan-out real, not narrated. Never say you are "delegating", "spawning workers", \
        or working "in parallel" unless you have actually issued the corresponding \
        `nucleic_delegate_plan` / `nucleic_subagent` calls this turn. Do not use native Task/Agent \
        spawn tools; Nucleic-managed workers are required so the app can observe their lifecycle \
        and enforce the user's worker-model choice. If a turn is conversational or trivial enough \
        that workers would not help, say so plainly and handle it solo; do not announce an \
        orchestration you are not performing.

        Lean on the quality patterns that fit: adversarial verification (a second wave that \
        tries to refute the first wave's findings against the source), a completeness critic \
        (one worker hunting for what the others missed), and multi-phase sequencing (understand, \
        design, implement, review as separate batches). The fan-out multiplies token usage, so \
        keep each subtask scoped to a distinct concern rather than splitting per line or file.
        """

    /// The standing prompt appended for a **worker** subagent. It tells the worker what it is (one
    /// leaf of a supervisor's fan-out), how to return its result (a single self-contained final
    /// reply — that reply *is* what the supervisor reads), and how to reach back up: workers can't
    /// talk to the human, so anything needing a decision or unblocking goes to the supervisor via
    /// `nucleic_ask_supervisor`, never `AskUserQuestion`. Delivered on `--append-system-prompt` at
    /// worker creation, the worker-side counterpart of ``consentPrompt``.
    public static let workerBriefing = """
        You are a Nucleic-managed worker subagent in an Orchestra (orchestration) session. A \
        supervisor agent decomposed a larger task and dispatched this subtask to you; other \
        workers may be running in parallel on sibling subtasks. Carry out the task described in \
        your prompt completely and autonomously, then return a single, self-contained final reply \
        that stands on its own as your result — the supervisor reads that final reply, not your \
        intermediate steps, so put everything it needs there.

        If you need a decision, missing context, or clarification, or you hit a blocker or \
        discover something that may change the plan, call `nucleic_ask_supervisor` with your \
        question instead of guessing or stopping — it pauses you until the supervisor (who may \
        relay to the human) answers, then you continue. Do not use `AskUserQuestion` or \
        `nucleic_ask_user`: you cannot reach the user directly, and the supervisor is your point of \
        contact — route anything user-facing through `nucleic_ask_supervisor`. For minor choices, \
        prefer proceeding on a reasonable assumption and noting it in your reply; reserve questions \
        for things that would meaningfully change your output.
        """

    /// The system prompt appended for a run: the caller's environment guidance (sandbox build
    /// instructions, etc.) plus, for an Orchestra run, the standing prompt for its `role` — the
    /// supervisor's ``consentPrompt`` or the worker's ``workerBriefing``. Returns `nil` when nothing
    /// applies so the backend flag is omitted entirely; joins the parts with a blank line when both
    /// are present, so the standing prompt reads as its own paragraph.
    public static func combinedSystemPrompt(environment: String?, role: Role?) -> String? {
        let orchestraPrompt: String?
        switch role {
        case .supervisor: orchestraPrompt = consentPrompt
        case .worker: orchestraPrompt = workerBriefing
        case nil: orchestraPrompt = nil
        }
        let parts = [environment, orchestraPrompt]
            .compactMap { $0 }
            .filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
        return parts.isEmpty ? nil : parts.joined(separator: "\n\n")
    }
}
