import SwiftUI
import NucleicProtocol

/// The defining interaction (UX_IOS §5): answer an approval. High-risk (destructive/network)
/// still routes through the app (never the notification banner) so approval is a deliberate,
/// in-context tap. First-responder-wins: if the Mac (or another phone) answers first, the
/// host's `approvalResolved` removes this card.
struct ApprovalCardView: View {
    @EnvironmentObject var store: RemoteStore
    let approval: ApprovalRequest
    /// The transcript's on-screen height, so the card can cap itself and scroll its detail rather
    /// than growing past the screen and pushing Allow/Deny out of reach. 0 until measured.
    var availableHeight: CGFloat = 0

    @State private var showAlwaysMenu = false

    private var allowEnabled: Bool { store.connectivity.isLive }

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(spacing: 6) {
                Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.orange)
                Text("Permission requested").font(.subheadline.weight(.semibold))
            }
            HStack(spacing: 6) {
                Text(approval.toolName).font(.caption.weight(.bold))
                Text(approval.risk.label)
                    .font(.caption2.weight(.semibold))
                    .padding(.horizontal, 6).padding(.vertical, 2)
                    .background(approval.risk.color.opacity(0.2), in: Capsule())
                    .foregroundStyle(approval.risk.color)
            }

            // The detail (commit card, host breakdown + command, or plain text) scrolls inside a
            // bounded region so a giant command can't grow the card off-screen; the buttons below
            // stay pinned and always tappable. Rendered only when there's detail to show.
            if hasDetail {
                BoundedCardScroll(maxHeight: middleMaxHeight) {
                    VStack(alignment: .leading, spacing: 10) { detailContent }
                }
            }

            HStack(spacing: 10) {
                Button(role: .destructive) {
                    store.respond(approval, .deny(reason: nil))
                } label: {
                    Text("Deny").frame(maxWidth: .infinity)
                }
                .buttonStyle(.bordered)
                // Hardware-keyboard shortcuts for the defining interaction: Esc denies, Return
                // allows (the approval bar replaces the composer, so Return is unclaimed here).
                .keyboardShortcut(.cancelAction)

                Button {
                    store.respond(approval, .allow())
                } label: {
                    Text("Allow").frame(maxWidth: .infinity)
                }
                .buttonStyle(.borderedProminent)
                .disabled(!allowEnabled)
                .keyboardShortcut(.defaultAction)
            }

            // A destructive action (rm, force-push, reset --hard, …) offers no remembered
            // allow: every one must be a deliberate, one-off approval, never granted in a
            // way that lets the next one through unseen. Mirrors the Mac.
            if approval.risk != .destructive, !store.capabilities.allowAlwaysScopes.isEmpty {
                Menu("Allow always…") {
                    ForEach(store.capabilities.allowAlwaysScopes, id: \.self) { scope in
                        Button(alwaysLabel(scope)) {
                            store.respond(approval, .allowAlways(scope))
                        }
                        .disabled(!allowEnabled)
                    }
                }
                .font(.caption)
            }
        }
        .padding()
        .glassSurface(cornerRadius: 20)
        // A hard ceiling on the whole card: even if the chrome estimate is off, the card can never
        // exceed the screen. `nil` (before the height is measured) leaves it unconstrained.
        .frame(maxHeight: availableHeight > 0 ? availableHeight - 12 : nil, alignment: .bottom)
    }

    // MARK: - Detail

    /// The kinds of detail an approval carries, resolved once so the body and `hasDetail` agree.
    /// A git commit reads as a structured commit card (subject + Markdown body); a host_exec gate
    /// lays out its parsed breakdown + exact command; every other tool shows its untruncated detail.
    private enum Detail {
        case commit(GitCommitSummary.Commit, command: String)
        case host(HostCommandSummary.Summary, command: String)
        case text(String)
        case none
    }

    private var detail: Detail {
        if let command = approval.input["command"]?.stringValue,
           let commit = GitCommitSummary.parse(command) {
            return .commit(commit, command: command)
        }
        if let command = approval.input["command"]?.stringValue,
           let parsed = HostCommandSummary.summary(for: command),
           approval.toolName == HostCommandSummary.hostExecToolName
             || parsed.invocations.count > 1 || parsed.isDestructive {
            return .host(parsed, command: command)
        }
        let text = approval.input.approvalDetail
        return text.isEmpty ? .none : .text(text)
    }

    private var hasDetail: Bool {
        if case .none = detail { return false }
        return true
    }

    @ViewBuilder private var detailContent: some View {
        switch detail {
        case .commit(let commit, let command):
            GitCommitCard(commit: commit, rawCommand: command)
        case .host(let parsed, let command):
            HostCommandBreakdown(summary: parsed)
            Text("Exact command").font(.caption2.weight(.semibold)).foregroundStyle(.secondary)
            ApprovalDetailBox(text: Self.truncatedForDisplay(command))
        case .text(let text):
            ApprovalDetailBox(text: Self.truncatedForDisplay(text))
        case .none:
            EmptyView()
        }
    }

    /// The height the scrolling detail may take before it starts scrolling — the screen minus a
    /// generous reserve for the header, risk tag, buttons, and "Allow always…" menu, so those
    /// always have room. Falls back to a roomy default before the height is measured.
    private var middleMaxHeight: CGFloat {
        let base = availableHeight > 0 ? availableHeight - 12 : 640
        return max(140, base - 220)
    }

    /// A very long command/detail is capped for display so one giant blob can't dominate the card;
    /// the middle still scrolls for the rest. The full command is still what gets approved — the
    /// breakdown above spells out its structure regardless.
    private static func truncatedForDisplay(_ text: String, limit: Int = 4000) -> String {
        guard text.count > limit else { return text }
        let omitted = text.count - limit
        return String(text.prefix(limit)) + "\n… [\(omitted) more characters truncated]"
    }

    private func alwaysLabel(_ scope: AlwaysScope) -> String {
        switch scope {
        case .session: "This command, this session"
        case .toolName: "Any \(approval.toolName), this session"
        case .toolNameWithPattern: "\(approval.toolName) matching this pattern"
        }
    }
}

/// The full content of an approval, wrapped and selectable. It no longer scrolls on its own — the
/// enclosing ``BoundedCardScroll`` owns the scrolling for the whole card — so it simply sizes to
/// its (already length-capped) text. The text wraps; it never scrolls horizontally.
private struct ApprovalDetailBox: View {
    let text: String

    var body: some View {
        Text(text)
            .font(.caption.monospaced())
            .textSelection(.enabled)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(8)
            .background(Color(.secondarySystemBackground), in: RoundedRectangle(cornerRadius: 8))
    }
}

/// Wraps the variable-height middle of an input card so it scrolls only once it would exceed
/// `maxHeight`. Below that it sizes to its content (a snug card); above it the content scrolls
/// while the card's header and action buttons stay pinned on-screen — so a giant command or a long
/// list of options can never push Allow/Deny (or Submit) off the bottom of the phone. Shared by the
/// approval and question cards.
struct BoundedCardScroll<Content: View>: View {
    let maxHeight: CGFloat
    @ViewBuilder var content: Content
    @State private var contentHeight: CGFloat = 0

    var body: some View {
        ScrollView(.vertical) {
            content
                .frame(maxWidth: .infinity, alignment: .leading)
                .background(GeometryReader { geo in
                    Color.clear.preference(key: CardContentHeightKey.self, value: geo.size.height)
                })
        }
        .frame(height: contentHeight == 0 ? nil : min(contentHeight, maxHeight))
        .scrollBounceBehavior(.basedOnSize)
        .onPreferenceChange(CardContentHeightKey.self) { contentHeight = $0 }
    }
}

struct CardContentHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}
