import AppIntents
import NucleicProtocol

// MARK: - Decision option (§6 enums)

/// The decision an approval intent can carry, mapped from the wire `Decision` (`Approval.swift`).
/// The `allowAlways` cases are the safe subset (session / this-tool); pattern scopes and any allow
/// on a destructive request are handled by the risk gate, not offered here.
enum ApprovalDecisionOption: String, AppEnum {
    case allow
    case deny
    case allowAlwaysSession
    case allowAlwaysTool

    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Decision")
    static let caseDisplayRepresentations: [ApprovalDecisionOption: DisplayRepresentation] = [
        .allow: "Allow",
        .deny: "Deny",
        .allowAlwaysSession: "Always Allow (this session)",
        .allowAlwaysTool: "Always Allow (this tool)",
    ]

    /// Whether this decision grants the request (everything but `deny`) — the half the risk gate
    /// forbids inline on a high-risk approval.
    var isAllow: Bool { self != .deny }

    /// The wire `Decision` this option resolves to.
    func decision() -> Decision {
        switch self {
        case .allow: .allow(updatedInput: nil)
        case .deny: .deny(reason: nil)
        case .allowAlwaysSession: .allowAlways(.session)
        case .allowAlwaysTool: .allowAlways(.toolName)
        }
    }
}

// MARK: - Approval entity (§4.6)

/// A pending approval exposed to Shortcuts/Siri. Backed by `RemoteStore.openApprovals`, which the
/// phone holds in full (id, risk, title) only for the *subscribed* session — so today this surfaces
/// the approvals of the session you're looking at. (A global pending-approvals feed would need the
/// host to carry the top approval's id/risk on the wire summary; see §4.1 / the Live Activity note.)
struct ApprovalEntity: AppEntity, Identifiable {
    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Approval")
    static let defaultQuery = ApprovalEntityQuery()

    /// `ApprovalID.rawValue`.
    var id: String
    /// `SessionID.rawValue` of the owning session — routes the decision to the right Mac.
    var sessionID: String
    var toolName: String
    var requestTitle: String
    /// `Risk.rawValue`, kept for display; the gate uses `isHighRisk`.
    var riskLabel: String
    /// Destructive / network / host-exec — never allowed inline (§3.3).
    var isHighRisk: Bool

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(toolName): \(requestTitle)",
            subtitle: "\(riskLabel)")
    }
}

extension ApprovalEntity {
    init(_ r: ApprovalRequest) {
        self.init(
            id: r.id.rawValue,
            sessionID: r.sessionID.rawValue,
            toolName: r.toolName,
            requestTitle: r.title,
            riskLabel: r.risk.label,
            isHighRisk: r.risk.isHigh)
    }
}

struct ApprovalEntityQuery: EntityQuery {
    @MainActor
    func entities(for identifiers: [ApprovalEntity.ID]) async throws -> [ApprovalEntity] {
        let wanted = Set(identifiers)
        return RemoteStore.shared.openApprovals
            .filter { wanted.contains($0.id.rawValue) }
            .map(ApprovalEntity.init)
    }

    @MainActor
    func suggestedEntities() async throws -> [ApprovalEntity] {
        RemoteStore.shared.openApprovals.map(ApprovalEntity.init)
    }
}

// MARK: - Answer an approval (§4.1, the flagship)

/// Allow or deny what an agent is asking to do. The risk gate (§3.3) is enforced here so an intent
/// can never be a softer approval path than the UI: a high-risk request (destructive / network /
/// host-exec) is never granted inline — it routes to the app's guarded card. Deny always goes
/// straight through. "Already resolved elsewhere" is a friendly no-op (§3.4), not an error, because
/// the host de-dupes a lost first-responder race.
struct AnswerApprovalIntent: AppIntent {
    static let title: LocalizedStringResource = "Answer Approval"
    static let description = IntentDescription(
        "Allow or deny what a Nucleic agent is asking to do.")

    @Parameter(title: "Approval")
    var approval: ApprovalEntity

    @Parameter(title: "Decision", default: .allow)
    var decision: ApprovalDecisionOption

    init() {}
    init(approval: ApprovalEntity, decision: ApprovalDecisionOption) {
        self.approval = approval
        self.decision = decision
    }

    @MainActor
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let store = RemoteStore.shared
        guard store.isPaired else { throw IntentError.notPaired }
        let approvalID = ApprovalID(rawValue: approval.id)
        let sessionID = SessionID(rawValue: approval.sessionID)

        // §3.3 — high-risk can't be granted inline; route to the app's biometric-gated card.
        if approval.isHighRisk, decision.isAllow {
            store.route(to: sessionID)
            throw IntentError.needsAppConfirmation
        }

        guard await store.awaitLiveConnection() else { throw IntentError.macUnreachable }
        store.respondToApproval(id: approvalID, sessionID: sessionID, decision: decision.decision())
        return .result(dialog: decision.isAllow ? "Allowed." : "Denied.")
    }

    static var parameterSummary: some ParameterSummary {
        Summary("\(\.$decision) \(\.$approval)")
    }
}
