import Foundation

/// Decoding for Claude Code's built-in `ExitPlanMode` tool.
///
/// Lives in NucleicProtocol (not NucleicCore) so both the Mac app and the iOS remote — which
/// links only NucleicProtocol — can parse the plan, and each renders its own card (Mac
/// `ExitPlanModeCard`, iOS `ExitPlanModeCard`).
///
/// When the agent finishes planning it calls `ExitPlanMode` to present the plan it wrote and
/// ask the user's approval to start coding. The tool input carries the plan as a Markdown
/// string under `plan`, plus an optional `planFilePath` pointing at the plan file the CLI saved
/// on disk. The generic tool card renders that as escaped JSON with an unread Markdown blob, so
/// we parse it out here and render the plan as formatted Markdown instead. The plan lives on the
/// call *input*, so it's available the moment the call streams in — before, and regardless of,
/// the user's approval.
public enum ExitPlanMode {
    public static let toolName = "ExitPlanMode"

    /// A plain rejection keeps Claude in plan mode instead of authorizing the transition to
    /// implementation. This is intentionally more specific than the generic permission denial:
    /// the tool result is Claude's signal to continue planning, not to treat the whole run as
    /// cancelled.
    public static let rejectionReason =
        "The user rejected this plan. Stay in plan mode and ask what they would like changed."

    public struct Plan: Sendable, Equatable {
        /// The plan body, Markdown.
        public let markdown: String
        /// The plan file on disk, when the call carried one (`planFilePath`); nil otherwise.
        public let filePath: String?

        public init(markdown: String, filePath: String?) {
            self.markdown = markdown
            self.filePath = filePath
        }
    }

    /// Parse the tool input into a plan, or `nil` when the payload carries no non-empty `plan`
    /// string (i.e. it isn't an `ExitPlanMode` call we can render). Callers should also gate on
    /// `call.name == toolName` so a stray `plan` key on some other tool can't be mistaken for one.
    public static func plan(from input: JSONValue) -> Plan? {
        guard let markdown = input["plan"]?.stringValue,
              !markdown.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        else { return nil }
        let path = input["planFilePath"]?.stringValue
        return Plan(markdown: markdown, filePath: path.flatMap { $0.isEmpty ? nil : $0 })
    }

    /// Turn free-form plan feedback into the denial message returned to Claude's permission hook.
    /// Denying `ExitPlanMode` is how a first-party-style revision keeps the agent in plan mode;
    /// carrying the feedback in the denial gives Claude the changes it should make before asking
    /// for approval again. Blank feedback cannot be submitted as a revision.
    public static func revisionReason(feedback: String) -> String? {
        let feedback = feedback.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !feedback.isEmpty else { return nil }
        return """
            The user requested changes before approving the plan:

            \(feedback)

            Stay in plan mode, revise the plan to address this feedback, and present it for approval again.
            """
    }
}
