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 real effort level orchestra runs at. The xhigh ceiling is the most thorough the
    /// API documents; orchestra adds the fan-out consent on top, not a hidden higher level.
    public static let resolvedOrchestraEffort = "xhigh"

    /// Whether `effort` selects orchestra (case-insensitive, whitespace-tolerant). Accepts the
    /// legacy "ultracode" token too, so the rename can't strand an already-stored selection.
    public static func isOrchestra(_ effort: String?) -> Bool {
        let normalized = effort?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        return normalized == effortSentinel || normalized == legacyEffortSentinel
    }

    /// 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: `orchestra` resolves to `xhigh`; everything
    /// else passes through untouched. One-way — the resolved value never flows back into
    /// `Session.effort`, so the UI keeps showing "Orchestra".
    public static func resolvedEffort(_ effort: String?) -> String? {
        isOrchestra(effort) ? resolvedOrchestraEffort : effort
    }

    /// 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
    /// non-blocking spawn + `nucleic_supervise` drive loop plus the verification patterns to reach
    /// for. 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. `nucleic_subagent` is non-blocking (it returns a `worker_id` immediately),
    /// so the supervisor must actively drive the run with `nucleic_supervise` rather than assume a
    /// spawn call blocks until the worker is done.
    public static let consentPrompt = """
        Orchestra (orchestration mode) is on. Your role this session is SUPERVISOR, not \
        implementer: plan the work, 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.

        Delegate, don't do. On a substantive turn, scout the task yourself, decompose it into \
        independent subtasks sized to the problem's natural structure, and dispatch each as a \
        worker with `nucleic_subagent` (a short task label plus complete, self-contained \
        instructions). Spawning is non-blocking: each call returns a `worker_id` immediately \
        while the worker runs. Do the hands-on work yourself only when a worker genuinely cannot \
        — a one-line reply, or a step no subagent can perform. Never do work a worker could do \
        just because it seems quick.

        Drive the workers with the supervise loop. After dispatching, call `nucleic_supervise` — \
        it blocks until workers report events and returns them along with how many are still \
        running. Handle each event: answer a worker's question with `nucleic_reply_to_worker` \
        (the worker_id plus your reply); read a completed worker's output and re-dispatch \
        follow-on work between phases; note failures and retry or adjust. Keep calling \
        `nucleic_supervise` until no workers remain running. When a decision genuinely needs the \
        human — a product choice, an ambiguous requirement, an irreversible action — ask them \
        directly with `AskUserQuestion`; you are the only agent that can reach the user, and \
        workers route such 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_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 fan-outs). 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`: 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")
    }
}
