import HostRegistry
import SwiftUI
import UIKit
#if !targetEnvironment(simulator)
import VisionKit
#endif

/// T-iOS-12 · Pairing screen: QR scan (device only) + manual URL entry + probe.
/// Pure presentation over `PairingViewModel` — every rule (input validation, the
/// zero-network-before-confirm gate, §5.4 warning tiers, error copy/actions)
/// lives in the VM where it is unit-tested. Presented first-run (T-iOS-15) and
/// as an add/switch-host sheet. Scanner (`DataScannerViewController`) is compiled
/// out on the simulator, where manual entry is the pairing path.
struct PairingScreen: View {
    @Bindable var viewModel: PairingViewModel
    var onPaired: (HostRegistry.Host) -> Void = { _ in } // T-iOS-15 navigate hook

    @State private var manualURLText = ""
    @State private var isShowingScanner = false
    @State private var scannerError: String?

    var body: some View {
        content
            .navigationTitle(ScreenCopy.title)
            .onChange(of: viewModel.pairedHost) { _, paired in
                guard let paired else { return }
                onPaired(paired)
            }
    }
    @ViewBuilder private var content: some View {
        switch viewModel.phase {
        case .idle:
            idleView
        case .confirming(let pending):
            ConfirmHostView(pending: pending, viewModel: viewModel)
        case .probing(let pending):
            probingView(pending)
        case .failed(let pending, let failure):
            FailureView(pending: pending, failure: failure, viewModel: viewModel)
        case .paired(let host):
            pairedView(host)
        }
    }
    private var idleView: some View {
        ScrollView {
            VStack(spacing: DS.Space.xl20) {
                VStack(spacing: DS.Space.md12) { // inviting hero
                    Image(systemName: "desktopcomputer")
                        .font(DS.Typography.largeTitle)
                        .foregroundStyle(DS.Palette.accent)
                        .padding(.top, DS.Space.sm8)
                    Text(ScreenCopy.heroTitle)
                        .font(DS.Typography.title)
                        .foregroundStyle(DS.Palette.textPrimary)
                    Text(ScreenCopy.heroSubtitle)
                        .font(DS.Typography.callout)
                        .foregroundStyle(DS.Palette.textSecondary)
                        .multilineTextAlignment(.center)
                }
                .frame(maxWidth: .infinity)
                Card {
                    VStack(alignment: .leading, spacing: DS.Space.md12) {
                        SectionHeader(title: ScreenCopy.manualSectionTitle)
                        TextField(ScreenCopy.manualPlaceholder, text: $manualURLText)
                            .font(DS.Typography.mono())
                            .keyboardType(.URL)
                            .textInputAutocapitalization(.never)
                            .autocorrectionDisabled()
                            .submitLabel(.go)
                            .onSubmit { viewModel.submitManualURL(manualURLText) }
                            .accessibilityIdentifier("pairing.urlField")
                        Divider()
                        Button(ScreenCopy.manualSubmit) {
                            DS.Haptics.selection()
                            viewModel.submitManualURL(manualURLText)
                        }
                        .buttonStyle(DSButtonStyle(kind: .primary))
                        .accessibilityIdentifier("pairing.submitButton")
                    }
                }
                if let rejection = viewModel.inputRejection {
                    Label(rejection, systemImage: "exclamationmark.circle.fill")
                        .font(DS.Typography.caption)
                        .foregroundStyle(DS.Palette.statusStuck)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                if PairingScanAvailability.isAvailable {
                    Button {
                        scannerError = nil
                        isShowingScanner = true
                    } label: {
                        Label(ScreenCopy.scanButton, systemImage: "qrcode.viewfinder")
                    }
                    .buttonStyle(DSButtonStyle(kind: .secondary)) // hidden on sim
                }
                Text(ScreenCopy.qrHint)
                    .font(DS.Typography.caption)
                    .foregroundStyle(DS.Palette.textSecondary)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            .padding(DS.Space.lg16)
        }
        .sheet(isPresented: $isShowingScanner) { scannerSheet }
    }

    @ViewBuilder private var scannerSheet: some View {
        #if targetEnvironment(simulator)
        // Unreachable: the entry is hidden on the simulator. Kept total.
        Text(ScreenCopy.scanUnavailable)
        #else
        ZStack(alignment: .bottom) {
            QRScannerView(
                onCode: { payload in
                    isShowingScanner = false
                    viewModel.handleScannedCode(payload)
                },
                onError: { message in scannerError = message }
            )
            if let scannerError {
                Text(scannerError)
                    .font(DS.Typography.callout)
                    .foregroundStyle(DS.Palette.statusStuck)
                    .padding(DS.Space.md12)
                    .background(.thinMaterial, in: RoundedRectangle(
                        cornerRadius: DS.Radius.sm8
                    ))
                    .padding(DS.Space.lg16)
            }
        }
        #endif
    }
    private func probingView(_ pending: PairingViewModel.PendingHost) -> some View {
        VStack(spacing: DS.Space.lg16) {
            ProgressView()
            Text(ScreenCopy.probing(pending.displayAddress))
                .font(DS.Typography.callout)
                .foregroundStyle(DS.Palette.textSecondary)
                .multilineTextAlignment(.center)
        }
        .padding(DS.Space.xl20)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
    private func pairedView(_ host: HostRegistry.Host) -> some View {
        VStack(spacing: DS.Space.md12) {
            Image(systemName: "checkmark.circle.fill")
                .font(DS.Typography.largeTitle)
                .foregroundStyle(DS.Palette.statusWorking)
            Text(ScreenCopy.paired(host.name))
                .font(DS.Typography.headline)
                .foregroundStyle(DS.Palette.textPrimary)
        }
        .padding(DS.Space.xl20)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onAppear { DS.Haptics.success() }
    }
}

/// Confirm page — parsed address + §5.4 warning tier + host name.
private struct ConfirmHostView: View {
    let pending: PairingViewModel.PendingHost
    @Bindable var viewModel: PairingViewModel

    var body: some View {
        ScrollView {
            VStack(spacing: DS.Space.xl20) {
                addressCard
                warningTier
                VStack(spacing: DS.Space.md12) {
                    Button(ScreenCopy.connect) {
                        DS.Haptics.selection()
                        Task { await viewModel.confirmConnect() }
                    }
                    .buttonStyle(DSButtonStyle(kind: .primary))
                    .accessibilityIdentifier("pairing.confirmButton")
                    Button(ScreenCopy.cancel) { viewModel.cancel() }
                        .buttonStyle(DSButtonStyle(kind: .secondary))
                }
            }
            .padding(DS.Space.lg16)
        }
    }
    private var addressCard: some View {
        Card {
            VStack(alignment: .leading, spacing: DS.Space.md12) {
                SectionHeader(title: ScreenCopy.confirmSectionTitle)
                // Single-point-derived origin (UITest asserts this exact string).
                Text(pending.displayAddress)
                    .font(DS.Typography.mono())
                    .foregroundStyle(DS.Palette.textPrimary)
                    .textSelection(.enabled)
                Divider()
                TextField(ScreenCopy.namePlaceholder, text: $viewModel.hostName)
                    .font(DS.Typography.body)
            }
        }
    }

    // §5.4 warning tiers — LOGIC frozen (switch cases), only presentation restyled.
    @ViewBuilder private var warningTier: some View {
        switch pending.warning {
        case .none:
            EmptyView()
        case .tailscaleEncrypted: // positive accent badge — encrypted transport
            Card {
                Label(ScreenCopy.tailscaleBadge, systemImage: "lock.shield")
                    .font(DS.Typography.callout)
                    .foregroundStyle(DS.Palette.accent)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
        case .plaintextLAN: // subtle amber note
            Label(ScreenCopy.plaintextNotice, systemImage: "eye")
                .font(DS.Typography.caption)
                .foregroundStyle(DS.Palette.statusWaiting)
                .frame(maxWidth: .infinity, alignment: .leading)
        case .publicHostBlocking:
            publicWarningCard
        }
    }

    /// Prominent red card + acknowledgement gate (Toggle → `hasAcknowledgedPublicRisk`;
    /// required-message on `needsPublicRiskAcknowledgement`). Logic unchanged.
    private var publicWarningCard: some View {
        VStack(alignment: .leading, spacing: DS.Space.md12) {
            Label {
                Text(ScreenCopy.publicWarning)
                    .font(DS.Typography.headline)
                    .foregroundStyle(DS.Palette.textPrimary)
            } icon: {
                Image(systemName: "exclamationmark.octagon.fill")
                    .foregroundStyle(DS.Palette.statusStuck)
            }
            Divider()
            Toggle(ScreenCopy.publicAcknowledge, isOn: $viewModel.hasAcknowledgedPublicRisk)
                .font(DS.Typography.callout)
                .tint(DS.Palette.accent)
            if viewModel.needsPublicRiskAcknowledgement {
                Label(ScreenCopy.publicAckRequired, systemImage: "arrow.up")
                    .font(DS.Typography.caption)
                    .foregroundStyle(DS.Palette.statusStuck)
            }
        }
        .padding(DS.Space.md12)
        .background(DS.Palette.card, in: RoundedRectangle(cornerRadius: DS.Radius.md12))
        .overlay(
            RoundedRectangle(cornerRadius: DS.Radius.md12)
                .strokeBorder(DS.Palette.statusStuck, lineWidth: DS.Stroke.hairline)
        )
    }
}

/// Failure page — inline copy + recovery actions (retry / settings / back).
private struct FailureView: View {
    let pending: PairingViewModel.PendingHost
    let failure: PairingViewModel.FailureDisplay
    let viewModel: PairingViewModel

    var body: some View {
        ScrollView {
            VStack(spacing: DS.Space.xl20) {
                Card {
                    VStack(alignment: .leading, spacing: DS.Space.sm8) {
                        Text(pending.displayAddress)
                            .font(DS.Typography.mono(.caption))
                            .foregroundStyle(DS.Palette.textSecondary)
                            .lineLimit(1)
                            .truncationMode(.middle)
                        Label {
                            Text(failure.message)
                                .font(DS.Typography.callout)
                                .foregroundStyle(DS.Palette.textPrimary)
                        } icon: {
                            Image(systemName: "xmark.octagon.fill")
                                .foregroundStyle(DS.Palette.statusStuck)
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                VStack(spacing: DS.Space.md12) {
                    let needsSettings = failure.action == .openLocalNetworkSettings
                    if needsSettings {
                        Button(ScreenCopy.openSettings) { openAppSettings() }
                            .buttonStyle(DSButtonStyle(kind: .primary))
                    }
                    Button(ScreenCopy.retry) { Task { await viewModel.retry() } }
                        .buttonStyle(DSButtonStyle(kind: needsSettings ? .secondary : .primary))
                    Button(ScreenCopy.back) { viewModel.cancel() }
                        .buttonStyle(DSButtonStyle(kind: .secondary))
                }
            }
            .padding(DS.Space.lg16)
        }
    }

    /// The app's Settings pane hosts its 本地网络 toggle (no deep link exists).
    private func openAppSettings() {
        guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
        UIApplication.shared.open(url)
    }
}

enum PairingScanAvailability {
    /// Simulator: no camera → hidden, manual entry is the pairing path. Device:
    /// requires VisionKit support (`isSupported` is MainActor-isolated).
    @MainActor static var isAvailable: Bool {
        #if targetEnvironment(simulator)
        return false
        #else
        return DataScannerViewController.isSupported
        #endif
    }
}

#if !targetEnvironment(simulator)
/// Thin `DataScannerViewController` wrapper: QR only; first recognized code wins;
/// payload handed to the VM untouched (validation is the VM's job).
private struct QRScannerView: UIViewControllerRepresentable {
    let onCode: (String) -> Void
    let onError: (String) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(onCode: onCode)
    }

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let scanner = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])],
            qualityLevel: .balanced,
            isHighlightingEnabled: true
        )
        scanner.delegate = context.coordinator
        return scanner
    }

    func updateUIViewController(_ scanner: DataScannerViewController, context: Context) {
        guard !scanner.isScanning else { return }
        do {
            try scanner.startScanning()
        } catch {
            // Explicit surfacing (plan §4): shows inline; manual entry remains.
            onError(ScreenCopy.scannerStartFailed(error.localizedDescription))
        }
    }

    @MainActor
    final class Coordinator: NSObject, DataScannerViewControllerDelegate {
        private let onCode: (String) -> Void
        private var hasDelivered = false

        init(onCode: @escaping (String) -> Void) {
            self.onCode = onCode
        }

        func dataScanner(
            _ dataScanner: DataScannerViewController,
            didAdd addedItems: [RecognizedItem],
            allItems: [RecognizedItem]
        ) {
            guard !hasDelivered else { return }
            for item in addedItems {
                if case .barcode(let barcode) = item,
                   let payload = barcode.payloadStringValue {
                    hasDelivered = true
                    onCode(payload)
                    return
                }
            }
        }
    }
}
#endif

private enum ScreenCopy {
    static let title = "配对主机"
    static let heroTitle = "连接你的电脑"
    static let heroSubtitle = "在同一网络里打开电脑上运行的终端会话——手动输入地址，或扫描它的配对二维码。"
    static let manualSectionTitle = "输入地址"
    static let manualPlaceholder = "http://192.168.1.5:3000"
    static let manualSubmit = "连接"
    static let scanButton = "扫描二维码"
    static let scanUnavailable = "模拟器不支持扫码，请手输地址。"
    static let qrHint = "在电脑的 web 终端工具栏点「Connect a device」可显示配对二维码；也可直接输入它的地址。"
    static let confirmSectionTitle = "确认要连接的主机"
    static let namePlaceholder = "主机名称"
    static let connect = "连接"
    static let cancel = "取消"
    static let retry = "重试"
    static let back = "返回"
    static let openSettings = "去设置"
    static let tailscaleBadge = "经 Tailscale 加密（WireGuard 网络层）"
    static let plaintextNotice = "ws:// 明文连接：键击与终端输出可被同一网络内的设备嗅探。仅限可信 LAN，推荐 tailscale serve（wss）。"
    static let publicWarning = "这是公网地址！任何能连上该端口的人都会得到你电脑的 shell。web-terminal 绝不应暴露到公网。"
    static let publicAcknowledge = "我已了解风险，仍要连接"
    static let publicAckRequired = "请先勾选上面的风险确认，再点连接。"

    static func probing(_ address: String) -> String { "正在验证 \(address) …" }
    static func paired(_ name: String) -> String { "已配对：\(name)" }
    static func scannerStartFailed(_ reason: String) -> String {
        "无法启动相机扫描：\(reason)。可改用手输地址。"
    }
}
