import SessionCore
import SwiftUI

/// T-iOS-25 · Quick-reply chip row + 常用语面板 (plan §7; behavior mirror of
/// `public/quick-reply.ts`, data layer in `QuickReplyStore.swift`).
///
/// Visibility decision (documented): the plan step says "waiting 状态才浮出".
/// On the iOS `SessionEvent` stream the raw `ClaudeStatus` is folded away by
/// the engine (`SessionEngine.applyGateFrame` keeps only pending/gate), so the
/// stream's ONLY waiting projection is the HELD GATE — a `status:'waiting',
/// pending:true` frame surfaces as `.gate(GateState)` and the lift as
/// `.gate(nil)`. Chips therefore float while a gate is held AND the terminal
/// is live (not exited/failed). Waiting-without-pending (a Notification
/// permission_prompt with no held relay) never reaches the stream — accepted
/// limitation; the observation point stays the SAME fan-out branches the
/// Gate/Terminal VMs already consume (no new SessionCore surface, no extra
/// fan-out branch needed).
///
/// Send path: a chip tap goes through `TerminalViewModel.sendInput` — the one
/// ordered send pump — so a rapid double-tap yields two frames in tap order,
/// never interleaved, and the read-only guard drops taps on a dead terminal.
struct QuickReplyBar: View {
    enum Copy {
        static let managePhrases = "管理常用语"
    }

    let terminalViewModel: TerminalViewModel
    let gateViewModel: GateViewModel
    let store: QuickReplyStore

    @State private var isPanelPresented = false

    /// Pure visibility rule (see type doc): held gate = the stream's waiting
    /// signal; read-only (exited/failed) always hides.
    static func isVisible(gate: GateState?, isReadOnly: Bool) -> Bool {
        gate != nil && !isReadOnly
    }

    /// The production tap path (also exercised directly by tests): compose the
    /// payload via `QuickReplyPalette` and hand it to the VM's ordered pump.
    static func send(_ chip: QuickReplyChip, through viewModel: TerminalViewModel) {
        viewModel.sendInput(QuickReplyPalette.payload(for: chip))
    }

    var body: some View {
        if Self.isVisible(
            gate: gateViewModel.currentGate,
            isReadOnly: terminalViewModel.isReadOnly
        ) {
            chipRow
                .sheet(isPresented: $isPanelPresented) {
                    QuickReplyPanel(store: store)
                }
        }
    }

    private var chipRow: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: DS.Space.sm8) {
                ForEach(store.allChips) { chip in
                    chipButton(chip)
                }
                managePhrasesButton
            }
            .padding(.horizontal, DS.Space.sm8)
            .padding(.vertical, DS.Space.xs4)
        }
        .background(.regularMaterial, in: Capsule())
        .overlay(
            Capsule().strokeBorder(DS.Palette.hairline, lineWidth: DS.Stroke.hairline)
        )
        .transition(.move(edge: .bottom).combined(with: .opacity))
    }

    private func chipButton(_ chip: QuickReplyChip) -> some View {
        Button {
            Self.send(chip, through: terminalViewModel)
        } label: {
            // Text(verbatim:) — user/label strings render as inert text, never
            // LocalizedStringKey/Markdown (SEC-L3 mirror; T-iOS-23 convention).
            Text(verbatim: chip.label)
                .font(DS.Typography.callout)
                .foregroundStyle(DS.Palette.accent)
                .lineLimit(1)
                .padding(.horizontal, DS.Space.md12)
                .frame(minHeight: DS.Layout.minHitTarget)
                .background(.quaternary, in: Capsule())
        }
        .buttonStyle(.plain)
    }

    private var managePhrasesButton: some View {
        Button {
            isPanelPresented = true
        } label: {
            Image(systemName: "plus")
                .font(DS.Typography.callout.weight(.semibold))
                .foregroundStyle(DS.Palette.accent)
                .frame(width: DS.Layout.minHitTarget, height: DS.Layout.minHitTarget)
                .background(.quaternary, in: Capsule())
        }
        .buttonStyle(.plain)
        .accessibilityLabel(Copy.managePhrases)
    }
}

/// 常用语面板: user-phrase CRUD + drag reorder (增删改序), presented from the
/// chip row's `+`. The add/edit form mirrors the web inline editor: text,
/// optional label (defaults to text) and an append-Enter toggle that starts
/// on. Tapping an existing row loads it into the form for editing (改).
struct QuickReplyPanel: View {
    enum Copy {
        static let title = "常用语"
        static let addSection = "添加新常用语"
        static let customSection = "自定义常用语"
        static let textPlaceholder = "要发送的文本"
        static let labelPlaceholder = "标签（可选，默认同文本）"
        static let appendEnterToggle = "发送后自动回车"
        static let addButton = "添加"
        static let saveButton = "保存修改"
        static let cancelEditButton = "取消编辑"
        static let doneButton = "完成"
        static let emptyState = "还没有自定义常用语"
        /// Payload preview suffix for append-Enter chips (web: `${text}⏎`).
        static let enterSuffixSymbol = "⏎"
    }

    let store: QuickReplyStore

    @Environment(\.dismiss) private var dismiss
    @State private var draftText = ""
    @State private var draftLabel = ""
    /// Mirrors the web editor default: `enterCheck.checked = true`.
    @State private var draftAppendEnter = true
    /// Non-nil while the form edits an existing chip instead of adding.
    @State private var editingChipId: String?

    var body: some View {
        NavigationStack {
            List {
                customSection
                editorSection
            }
            .navigationTitle(Copy.title)
            .toolbar {
                ToolbarItem(placement: .topBarLeading) { EditButton() }
                ToolbarItem(placement: .topBarTrailing) {
                    Button(Copy.doneButton) { dismiss() }
                }
            }
        }
    }

    // MARK: - Custom phrases (delete / reorder / tap-to-edit)

    @ViewBuilder private var customSection: some View {
        Section(Copy.customSection) {
            if store.userChips.isEmpty {
                Text(Copy.emptyState)
                    .font(DS.Typography.callout)
                    .foregroundStyle(DS.Palette.textSecondary)
            } else {
                ForEach(store.userChips) { chip in
                    Button {
                        beginEditing(chip)
                    } label: {
                        chipRow(chip)
                    }
                    .tint(DS.Palette.textPrimary)
                }
                .onDelete { offsets in
                    // Resolve ids FIRST — removing while iterating offsets
                    // would shift indices under us.
                    let ids = offsets.map { store.userChips[$0].id }
                    for id in ids {
                        store.removeChip(id: id)
                    }
                }
                .onMove { fromOffsets, toOffset in
                    store.moveChips(fromOffsets: fromOffsets, toOffset: toOffset)
                }
            }
        }
    }

    private func chipRow(_ chip: QuickReplyChip) -> some View {
        VStack(alignment: .leading, spacing: DS.Space.xs2) {
            // Text(verbatim:) — stored strings are boundary data (SEC-L3 mirror).
            Text(verbatim: chip.label)
                .font(DS.Typography.body)
                .lineLimit(1)
            Text(verbatim: chip.appendEnter
                ? chip.text + Copy.enterSuffixSymbol
                : chip.text)
                .font(DS.Typography.caption)
                .foregroundStyle(DS.Palette.textSecondary)
                .lineLimit(1)
        }
    }

    // MARK: - Add / edit form

    @ViewBuilder private var editorSection: some View {
        Section(Copy.addSection) {
            TextField(Copy.textPlaceholder, text: $draftText)
                .autocorrectionDisabled()
                .textInputAutocapitalization(.never)
            TextField(Copy.labelPlaceholder, text: $draftLabel)
            Toggle(Copy.appendEnterToggle, isOn: $draftAppendEnter)
            Button(editingChipId == nil ? Copy.addButton : Copy.saveButton) {
                commitDraft()
            }
            .disabled(trimmedDraftText.isEmpty)
            if editingChipId != nil {
                Button(Copy.cancelEditButton, role: .cancel) {
                    clearDraft()
                }
            }
        }
    }

    private var trimmedDraftText: String {
        draftText.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private func beginEditing(_ chip: QuickReplyChip) {
        editingChipId = chip.id
        draftText = chip.text
        draftLabel = chip.label
        draftAppendEnter = chip.appendEnter
    }

    /// Web `onSaveClick` semantics: empty text is a no-op (button is disabled
    /// anyway — belt and braces), empty label defaults to the text.
    private func commitDraft() {
        let text = trimmedDraftText
        guard !text.isEmpty else { return }
        let trimmedLabel = draftLabel.trimmingCharacters(in: .whitespacesAndNewlines)
        let label = trimmedLabel.isEmpty ? text : trimmedLabel
        if let id = editingChipId {
            store.updateChip(id: id, text: text, label: label,
                             appendEnter: draftAppendEnter)
        } else {
            store.addChip(text: text, label: label, appendEnter: draftAppendEnter)
        }
        clearDraft()
    }

    private func clearDraft() {
        editingChipId = nil
        draftText = ""
        draftLabel = ""
        draftAppendEnter = true
    }
}
