import Foundation

/// `Decision` → Codex app-server approval reply mapping (ADAPTERS §2.4,
/// BACKEND_PROTOCOL §4.1). Codex answers approvals as JSON-RPC server→client
/// requests; we reply with a `result` keyed by the same id.
///
/// Two reply shapes exist:
///  - command / file-change approvals → a `ReviewDecision`-style `{ "decision": … }`
///    where the enum is `accept | acceptForSession | decline | cancel`
///    (CONFIRMED from `openai/codex` source at commit `7a19b14`, ADAPTERS §2.4);
///  - permission approvals → `{ "permissions": <granted subset>, "scope": … }`,
///    NOT a decision (ADAPTERS §2.4, confirmed from `v2/permissions.rs`).
///
/// Codex has no arbitrary command rewrite (`canModifyToolInput = false`), so a
/// `Decision.allow`'s `updatedInput` is intentionally ignored here.
public enum CodexDecisionMapping {
    /// The decision-enum reply for a command-execution / file-change approval.
    /// Returns the raw string the app-server expects under `result.decision`.
    public static func reviewDecision(for decision: Decision) -> String {
        switch decision {
        case .allow:
            return "accept"
        case .allowAlways:
            // Codex's only "always" scope is the session (`acceptForSession`);
            // any `AlwaysScope` maps to it. The cross-session always-rule cache
            // (ApprovalCoordinator) still handles finer scopes above the adapter.
            return "acceptForSession"
        case .deny:
            return "decline"
        case .cancelRun:
            return "cancel"
        }
    }

    /// The full `result` object for a command/file approval reply.
    public static func reviewReply(for decision: Decision) -> JSONValue {
        .object(["decision": .string(reviewDecision(for: decision))])
    }

    /// The legacy v1 `ReviewDecision` enum (`approved`/`approved_for_session`/`denied`/`abort`,
    /// snake_case), used by the deprecated `execCommandApproval`/`applyPatchApproval` server
    /// requests. We only target v2, but answer v1 defensively so a differently-configured
    /// server never hangs on us.
    public static func reviewDecisionV1(for decision: Decision) -> String {
        switch decision {
        case .allow: return "approved"
        case .allowAlways: return "approved_for_session"
        case .deny: return "denied"
        case .cancelRun: return "abort"
        }
    }

    public static func reviewReplyV1(for decision: Decision) -> JSONValue {
        .object(["decision": .string(reviewDecisionV1(for: decision))])
    }

    /// The `result` object for a `item/permissions/requestApproval` reply.
    /// We can't express a partial permission subset through `Decision`, so an
    /// allow grants exactly what was requested (`requestedPermissions`, echoed
    /// back) and a deny/cancel grants nothing. `scope` is `session` for an
    /// "allow always", else `turn`.
    public static func permissionsReply(
        for decision: Decision, requestedPermissions: JSONValue
    ) -> JSONValue {
        switch decision {
        case .allow:
            return .object(["permissions": requestedPermissions, "scope": .string("turn")])
        case .allowAlways:
            return .object(["permissions": requestedPermissions, "scope": .string("session")])
        case .deny, .cancelRun:
            return .object(["permissions": .object([:]), "scope": .string("turn")])
        }
    }
}
