import Foundation

// MARK: - Approvals (BACKEND_PROTOCOL §4)

public enum Risk: String, Sendable, Codable {
    case readOnly, write, execute, network, destructive, unknown
    /// Run a command on the host machine, outside the sandbox container (HOST_EXEC).
    /// The highest-trust gate: always surfaced, never auto-approved.
    case hostExec
}

public enum AlwaysScope: String, Sendable, Codable {
    /// This exact tool + input, this session.
    case session
    /// Any call to this tool, this session.
    case toolName
    /// Tool + argument pattern (e.g. Bash(git *)).
    case toolNameWithPattern
}

public struct ApprovalRequest: Sendable, Codable, Equatable, Identifiable {
    public let id: ApprovalID
    public let sessionID: SessionID
    public let toolCallID: String?
    public let toolName: String
    public let input: JSONValue
    public let title: String
    public let risk: Risk
    public let suggested: Decision?
    public let createdAt: Date

    public init(
        id: ApprovalID,
        sessionID: SessionID,
        toolCallID: String?,
        toolName: String,
        input: JSONValue,
        title: String,
        risk: Risk,
        suggested: Decision? = nil,
        createdAt: Date
    ) {
        self.id = id
        self.sessionID = sessionID
        self.toolCallID = toolCallID
        self.toolName = toolName
        self.input = input
        self.title = title
        self.risk = risk
        self.suggested = suggested
        self.createdAt = createdAt
    }
}

public enum Decision: Sendable, Equatable {
    case allow(updatedInput: JSONValue? = nil)
    case allowAlways(AlwaysScope)
    case deny(reason: String?)
    /// Deny + stop the whole run.
    case cancelRun
}

extension Decision: Codable {
    private enum CodingKeys: String, CodingKey {
        case type, updatedInput, scope, reason
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "allow":
            self = .allow(updatedInput: try container.decodeIfPresent(JSONValue.self, forKey: .updatedInput))
        case "allowAlways":
            self = .allowAlways(try container.decode(AlwaysScope.self, forKey: .scope))
        case "deny":
            self = .deny(reason: try container.decodeIfPresent(String.self, forKey: .reason))
        case "cancelRun":
            self = .cancelRun
        default:
            throw DecodingError.dataCorruptedError(
                forKey: .type, in: container, debugDescription: "Unknown decision type \(type)")
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .allow(let updatedInput):
            try container.encode("allow", forKey: .type)
            try container.encodeIfPresent(updatedInput, forKey: .updatedInput)
        case .allowAlways(let scope):
            try container.encode("allowAlways", forKey: .type)
            try container.encode(scope, forKey: .scope)
        case .deny(let reason):
            try container.encode("deny", forKey: .type)
            try container.encodeIfPresent(reason, forKey: .reason)
        case .cancelRun:
            try container.encode("cancelRun", forKey: .type)
        }
    }
}

public struct ApprovalResolved: Sendable, Codable, Equatable {
    public let id: ApprovalID
    public let decision: Decision
    /// "mac-ui" | "iphone:<device>" | "always-rule" — first responder wins.
    public let decidedBy: String
    public let decidedAt: Date

    public init(id: ApprovalID, decision: Decision, decidedBy: String, decidedAt: Date) {
        self.id = id
        self.decision = decision
        self.decidedBy = decidedBy
        self.decidedAt = decidedAt
    }
}
