import Foundation
import Testing

@testable import NucleicCore

/// `ExitPlanMode` parsing: the agent presents the plan it wrote (Markdown) under `plan`, plus an
/// optional `planFilePath`. The transcript + approval cards render that Markdown instead of the
/// escaped-JSON blob the generic tool row would show, so the parser must pull the plan out and
/// tolerate a missing/empty file path.
@Suite struct ExitPlanModeTests {
    @Test func parsesPlanAndFilePath() {
        let input: JSONValue = [
            "plan": "# Plan\n\n- Step one\n- Step two",
            "planFilePath": "/tmp/plans/dapper-singing-reef.md",
        ]
        let plan = ExitPlanMode.plan(from: input)
        #expect(plan?.markdown == "# Plan\n\n- Step one\n- Step two")
        #expect(plan?.filePath == "/tmp/plans/dapper-singing-reef.md")
    }

    @Test func planWithoutFilePathParses() {
        let plan = ExitPlanMode.plan(from: ["plan": "Just do it."])
        #expect(plan?.markdown == "Just do it.")
        #expect(plan?.filePath == nil)
    }

    @Test func emptyFilePathIsNil() {
        let plan = ExitPlanMode.plan(from: ["plan": "Do the thing.", "planFilePath": ""])
        #expect(plan?.filePath == nil)
    }

    @Test func missingOrBlankPlanReturnsNil() {
        #expect(ExitPlanMode.plan(from: ["command": "ls"]) == nil)
        #expect(ExitPlanMode.plan(from: ["plan": ""]) == nil)
        #expect(ExitPlanMode.plan(from: ["plan": "   \n  "]) == nil)
    }

    @Test func toolNameMatchesClaudeWire() {
        #expect(ExitPlanMode.toolName == "ExitPlanMode")
    }

    @Test func explicitDecisionToolsBypassAutoApproval() {
        #expect(NucleicApprovalPolicy.requiresExplicitUserDecision(toolName: ExitPlanMode.toolName))
        #expect(NucleicApprovalPolicy.requiresExplicitUserDecision(toolName: AskUserQuestion.toolName))
        #expect(!NucleicApprovalPolicy.requiresExplicitUserDecision(toolName: "Read"))
        #expect(!NucleicApprovalPolicy.shouldAutoApprove(
            autoApprove: true, risk: .readOnly, requiresExplicitUserDecision: true))
    }

    @Test func rejectionKeepsClaudeInPlanMode() {
        #expect(ExitPlanMode.rejectionReason.contains("Stay in plan mode"))
    }

    @Test func revisionCarriesTrimmedFeedbackAndKeepsPlanning() {
        let reason = ExitPlanMode.revisionReason(feedback: "  Add rollback steps. \n")
        #expect(reason?.contains("Add rollback steps.") == true)
        #expect(reason?.contains("Stay in plan mode") == true)
        #expect(ExitPlanMode.revisionReason(feedback: "  \n ") == nil)
    }
}
