import Foundation
import NucleicProtocol

/// Presentation for Nucleic's own sandbox-escape tools — the macOS/Linux VM pair, their
/// computer-use siblings, and `linux_container` — plus the shell tool, whose wire name is
/// still Claude Code's `Bash` but which runs nash (the Nucleic agent shell, see `docs/NASH.md`)
/// on every surface we control. The rename lives here rather than at the match sites so the
/// wire vocabulary stays exactly what the backends emit and only the *presentation* moves.
///
/// These arrive over MCP under their wire names (`mcp__nucleic__mac_vm_exec`, …), which is
/// what the transcript and the working-row would otherwise print verbatim: a double-underscored
/// identifier that reads as plumbing rather than as "the agent is building on a Mac". Every
/// surface that shows one of these calls — the status line, the tool card headline, the
/// tool-block summary lines — routes its label through here so the same call reads the same way
/// everywhere, and so a new tool is named in exactly one place.
///
/// `host_exec` is deliberately absent: it already has bespoke phrasing and a structured card of
/// its own (``HostExecToolCard``), and folding it in here would fight that.
public enum SandboxToolDisplay {

    /// The MCP prefix every Nucleic tool is advertised under. Transcripts from backends that
    /// record the qualified name carry it; ones that record the bare tool name don't, so every
    /// lookup below matches on the stripped form.
    private static let mcpPrefix = "mcp__nucleic__"

    /// The bare tool name — `mcp__nucleic__mac_vm_exec` → `mac_vm_exec` — so callers can pass
    /// whichever form their backend recorded.
    public static func bareName(_ toolName: String) -> String {
        toolName.hasPrefix(mcpPrefix) ? String(toolName.dropFirst(mcpPrefix.count)) : toolName
    }

    /// Whether this is one of the tools this file speaks for. Callers use it to decide between
    /// the pretty path and their existing generic fallback.
    public static func handles(_ toolName: String) -> Bool {
        label(for: toolName) != nil
    }

    // MARK: - Names

    /// The human label that replaces the raw tool name in a card headline — "macOS VM",
    /// "Linux container". Nil for anything this file doesn't speak for, so callers fall back
    /// to the tool name unchanged.
    public static func label(for toolName: String) -> String? {
        switch bareName(toolName) {
        // The wire name is Claude Code's, but the shell behind it is nash.
        case "Bash":                                            "Nash"
        case "BashOutput":                                      "Nash output"
        case MCPApprovalServer.macVMExecToolName:               "macOS VM"
        case MCPApprovalServer.macVMControlToolName:            "macOS VM"
        case MCPApprovalServer.macVMComputerToolName,
             MCPApprovalServer.macVMComputerBatchToolName:      "macOS VM screen"
        case MCPApprovalServer.macVMClearNotificationsToolName: "macOS VM notifications"
        case MCPApprovalServer.macVMOperatorToolName:           "macOS VM handoff"
        case MCPApprovalServer.linuxVMExecToolName:             "Linux VM"
        case MCPApprovalServer.linuxVMControlToolName:          "Linux VM"
        case MCPApprovalServer.linuxVMComputerToolName,
             MCPApprovalServer.linuxVMComputerBatchToolName:    "Linux VM screen"
        case MCPApprovalServer.linuxContainerToolName:          "Linux container"
        default:                                                 nil
        }
    }

    /// The tool name for an *approval* surface, where identity is what the user is deciding on:
    /// the pretty label when we have one, and otherwise the tool name with the `mcp__nucleic__`
    /// plumbing prefix dropped. Unlike ``label(for:)`` this never returns nil — every gated tool
    /// gets a name — and it never invents one, so a tool this file doesn't know still reads as
    /// its own identifier rather than as something friendlier than it is. Callers should keep
    /// the fully-qualified name reachable (a tooltip) so the exact tool is never in doubt.
    public static func gateLabel(for toolName: String) -> String {
        label(for: toolName) ?? bareName(toolName)
    }

    /// The present-progressive working-row line — "Running on a macOS VM…". Nil for tools this
    /// file doesn't speak for, so the caller keeps its own "Running <name>…" fallback.
    public static func gerund(for toolName: String) -> String? {
        switch bareName(toolName) {
        case MCPApprovalServer.macVMExecToolName:               "Running on a macOS VM…"
        case MCPApprovalServer.linuxVMExecToolName:             "Running on a Linux VM…"
        case MCPApprovalServer.linuxContainerToolName:          "Running in a Linux container…"
        case MCPApprovalServer.macVMComputerToolName,
             MCPApprovalServer.macVMComputerBatchToolName:      "Operating a macOS VM…"
        case MCPApprovalServer.linuxVMComputerToolName,
             MCPApprovalServer.linuxVMComputerBatchToolName:    "Operating a Linux VM…"
        case MCPApprovalServer.macVMClearNotificationsToolName: "Clearing VM notifications…"
        case MCPApprovalServer.macVMOperatorToolName:           "Waiting for you to drive the VM…"
        case MCPApprovalServer.macVMControlToolName:            "Managing the macOS VM…"
        case MCPApprovalServer.linuxVMControlToolName:          "Managing the Linux VM…"
        default:                                                 nil
        }
    }

    /// The past-tense verb + bucket key for a run of these calls in a tool-block headline —
    /// the sandbox arm of `ConversationIntelligence.familyKind`. Nil leaves the family alone.
    public static func family(for toolName: String) -> (key: String, verb: String)? {
        switch bareName(toolName) {
        case MCPApprovalServer.macVMExecToolName:               ("macvmexec", "Ran on a macOS VM")
        case MCPApprovalServer.linuxVMExecToolName:             ("linuxvmexec", "Ran on a Linux VM")
        case MCPApprovalServer.linuxContainerToolName:          ("linuxcontainer", "Used a Linux container")
        case MCPApprovalServer.macVMComputerToolName,
             MCPApprovalServer.macVMComputerBatchToolName:      ("macvmcomputer", "Operated a macOS VM")
        case MCPApprovalServer.linuxVMComputerToolName,
             MCPApprovalServer.linuxVMComputerBatchToolName:    ("linuxvmcomputer", "Operated a Linux VM")
        case MCPApprovalServer.macVMClearNotificationsToolName: ("macvmnotifications", "Cleared VM notifications")
        case MCPApprovalServer.macVMOperatorToolName:           ("macvmoperator", "Asked you to drive the VM")
        case MCPApprovalServer.macVMControlToolName:            ("macvmcontrol", "Managed the macOS VM")
        case MCPApprovalServer.linuxVMControlToolName:          ("linuxvmcontrol", "Managed the Linux VM")
        default:                                                 nil
        }
    }

    /// SF Symbol for the tool, so the glyph carries the same distinction the label does — a
    /// screen for computer-use, a box for a container, a power switch for a lifecycle op.
    public static func icon(for toolName: String) -> String? {
        switch bareName(toolName) {
        case MCPApprovalServer.macVMExecToolName:               "macwindow"
        case MCPApprovalServer.linuxVMExecToolName:             "server.rack"
        case MCPApprovalServer.linuxContainerToolName:          "shippingbox"
        case MCPApprovalServer.macVMComputerToolName,
             MCPApprovalServer.macVMComputerBatchToolName,
             MCPApprovalServer.linuxVMComputerToolName,
             MCPApprovalServer.linuxVMComputerBatchToolName:    "cursorarrow.motionlines"
        case MCPApprovalServer.macVMClearNotificationsToolName: "bell.slash"
        case MCPApprovalServer.macVMOperatorToolName:           "hand.raised"
        case MCPApprovalServer.macVMControlToolName,
             MCPApprovalServer.linuxVMControlToolName:          "power"
        default:                                                 nil
        }
    }

    // MARK: - Bodies

    /// The one-line rendering of a call's arguments, parsed per tool rather than dumped as
    /// JSON: a shell command reads as `$ swift build`, a computer-use action as
    /// `left click (120, 400)`, a container op as `exec — npm test`. Nil when the tool isn't
    /// one of ours or the input carries nothing worth showing (e.g. `mac_vm_clear_notifications`,
    /// which takes no arguments), in which case the caller falls back to its generic detail.
    public static func detail(for toolName: String, input: JSONValue) -> String? {
        func arg(_ key: String) -> String? {
            guard let value = input[key]?.stringValue else { return nil }
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            return trimmed.isEmpty ? nil : trimmed
        }
        switch bareName(toolName) {
        // The command itself, prompt-prefixed the way the host card renders it — a `$` reads as
        // "this is a shell line" without spending width on the word "command".
        case MCPApprovalServer.macVMExecToolName, MCPApprovalServer.linuxVMExecToolName:
            return arg("command").map { "$ \(singleLine($0))" }

        case MCPApprovalServer.macVMComputerToolName, MCPApprovalServer.linuxVMComputerToolName:
            return computerAction(input)

        case MCPApprovalServer.macVMComputerBatchToolName,
             MCPApprovalServer.linuxVMComputerBatchToolName:
            return batchSummary(input)

        case MCPApprovalServer.linuxContainerToolName:
            return containerOp(input)

        case MCPApprovalServer.macVMControlToolName, MCPApprovalServer.linuxVMControlToolName:
            return arg("op").map(controlOpPhrase)

        // The instructions are the whole point of the hand-off — they're what the user is being
        // asked to do — so they, not the reason, are the line.
        case MCPApprovalServer.macVMOperatorToolName:
            return arg("instructions").map(singleLine)

        // Takes no arguments: nothing to print beside the label.
        case MCPApprovalServer.macVMClearNotificationsToolName:
            return nil

        default:
            return nil
        }
    }

    /// The same parsed body as ``detail(for:input:)``, minus the `$` shell prompt — for a
    /// tool-block summary line, where the family's verb ("Ran on a macOS VM") already supplies
    /// the framing the prompt glyph would otherwise duplicate.
    public static func summaryItem(for toolName: String, input: JSONValue) -> String? {
        guard let detail = detail(for: toolName, input: input) else { return nil }
        return detail.hasPrefix("$ ") ? String(detail.dropFirst(2)) : detail
    }

    /// One computer-use step as prose — "left click (120, 400)", "type “hello”", "launch Xcode".
    /// Shared by the single-action tool and each step of the batch tool, which take the same
    /// fields. Falls back to the bare action verb when the step carries no useful argument, and
    /// to nil only when there's no action at all (a malformed call keeps the generic row).
    public static func computerAction(_ step: JSONValue) -> String? {
        func arg(_ key: String) -> String? {
            guard let value = step[key]?.stringValue else { return nil }
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            return trimmed.isEmpty ? nil : trimmed
        }
        guard let action = arg("action") else { return nil }
        let point: String? = {
            guard let x = step["x"]?.intValue, let y = step["y"]?.intValue else { return nil }
            return "(\(x), \(y))"
        }()
        let verb = actionVerb(action)
        switch action {
        case "screenshot", "cursor_position", "ax_dump":
            return verb
        case "left_click", "right_click", "double_click", "mouse_move", "left_click_drag",
             "ax_element_at":
            return [verb, point].compactMap { $0 }.joined(separator: " ")
        case "type":
            return arg("text").map { "\(verb) “\(clip(singleLine($0)))”" } ?? verb
        case "key":
            return arg("text").map { "\(verb) \(clip($0))" } ?? verb
        case "launch_app":
            return arg("text").map { "\(verb) \(clip($0))" } ?? verb
        // The starting page is the informative half of an `open_browser` with a `url` — "open chrome"
        // says far less than the address it lands on.
        case "open_browser":
            let target = arg("url") ?? arg("text")
            return target.map { "\(verb) \(clip($0))" } ?? verb
        case "scroll":
            let direction = arg("scroll_direction")
            let amount = step["scroll_amount"]?.intValue.map { String($0) }
            return ([verb, direction, amount].compactMap { $0 }).joined(separator: " ")
        case "wait":
            return step["duration_ms"]?.intValue.map { "\(verb) \($0)ms" } ?? verb
        // The AX verbs act on an element handle from the last dump, so the handle — and, for a
        // write, the value going into it — is the informative part.
        case "ax_press", "ax_focus":
            return arg("ref").map { "\(verb) \(clip($0))" } ?? verb
        case "ax_set_value":
            let ref = arg("ref")
            let value = arg("value").map { "“\(clip(singleLine($0)))”" }
            return ([verb, ref, value].compactMap { $0 }).joined(separator: " ")
        default:
            // An action we don't know yet still reads better de-underscored than raw.
            return ([verb, point].compactMap { $0 }).joined(separator: " ")
        }
    }

    /// "5 steps — left click, type, key…" for a batch call: the count (the thing that makes a
    /// batch a batch) plus the leading verbs, so the row says what the sequence does without
    /// unrolling every step's coordinates.
    private static func batchSummary(_ input: JSONValue) -> String? {
        guard let steps = input["steps"]?.arrayValue, !steps.isEmpty else { return nil }
        let count = "\(steps.count) step\(steps.count == 1 ? "" : "s")"
        var verbs: [String] = []
        for step in steps.prefix(3) {
            guard let action = step["action"]?.stringValue else { continue }
            verbs.append(actionVerb(action))
        }
        guard !verbs.isEmpty else { return count }
        let tail = steps.count > verbs.count ? "…" : ""
        return "\(count) — \(verbs.joined(separator: ", "))\(tail)"
    }

    /// A `linux_container` call as "op — argument": the lifecycle verb plus whichever field
    /// that op actually uses (the command for `exec`, the image or label for `create`, the
    /// container name for `stop`/`remove`). `list` takes nothing, so it stands alone.
    private static func containerOp(_ input: JSONValue) -> String? {
        func arg(_ key: String) -> String? {
            guard let value = input[key]?.stringValue else { return nil }
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            return trimmed.isEmpty ? nil : trimmed
        }
        guard let op = arg("op") else { return nil }
        let subject: String? = switch op {
        case "exec":   arg("command").map { "$ \(singleLine($0))" }
        case "create": arg("image") ?? arg("name")
        default:       arg("name")
        }
        guard let subject else { return op }
        return "\(op) — \(subject)"
    }

    /// A VM lifecycle op as a phrase rather than a bare verb, so "suspend" reads as an action
    /// taken on the VM and "status" doesn't look like a state label.
    private static func controlOpPhrase(_ op: String) -> String {
        switch op {
        case "status":  "check status"
        case "stop":    "power off"
        case "suspend": "suspend"
        case "resume":  "resume"
        case "restart": "restart"
        case "kill":    "delete and reset"
        default:        op
        }
    }

    /// `left_click` → "left click": computer-use action names are snake_case wire identifiers,
    /// and the underscores are the main thing that makes them look like plumbing. The `ax_`
    /// prefix expands to "accessibility" since the abbreviation means nothing to a reader.
    private static func actionVerb(_ action: String) -> String {
        switch action {
        case "ax_dump":       return "accessibility dump"
        case "ax_element_at": return "accessibility element at"
        case "ax_press":      return "press"
        case "ax_set_value":  return "set"
        case "ax_focus":      return "focus"
        default:              return action.replacingOccurrences(of: "_", with: " ")
        }
    }

    /// Collapses a multi-line argument onto one line — a heredoc or a multi-line `type` payload
    /// would otherwise blow out a row sized for a single line.
    private static func singleLine(_ text: String) -> String {
        text.split(whereSeparator: \.isNewline)
            .map { $0.trimmingCharacters(in: .whitespaces) }
            .joined(separator: " ⏎ ")
    }

    /// Keeps an inline argument short enough that the label beside it stays readable. The row
    /// itself truncates too, but that would eat the whole line for a long payload.
    private static func clip(_ text: String, limit: Int = 40) -> String {
        text.count <= limit ? text : String(text.prefix(limit)) + "…"
    }
}
