// 14C/14D 键盘扩展入口。
//
// 架构选择：UIInputViewController（系统强制入口）+ UIHostingController 包 SwiftUI 子视图。
// 选这套而不是纯 UIKit 的原因：
//   1. 四态 UI / light-dark 主题用 SwiftUI 表达成本最低，且 DuduTheme 即 SwiftUI Color；
//   2. 键盘高度、textDocumentProxy、地球键等"宿主约束"仍由 UIKit 层（本类）负责——
//      这是键盘扩展里验证最充分的稳态组合（doc/frontend-design.html 6.4 也指定
//      "UIInputViewController + SwiftUI 子视图"）；
//   3. 必须用 UIKit API 的点（handleInputModeList 地球键）用 UIViewRepresentable 桥接。
//
// 14D（语音主路径 = deep link 降级）：
//   1A spike 真机实证键盘扩展进程内无法录音 —— 即使开启"允许完全访问"，
//   AVAudioSession setActive/start 也直接报 CoreAudio 2003329396（iOS 平台硬限制）。
//   因此 MicBar 不再跑 MockAsrDriver 假录音，改为：
//     按一下 → extensionContext.open(dudu://dictate) 打开主 App 听写页（录音 + WS 推流在主 App 进程）
//     → 听写定稿写 App Group pending_text（+ 剪贴板双保险）
//     → 用户手动切回原 App 调起本键盘（iOS 无自动跳回 API）
//     → viewWillAppear 检出 120s 内的 pending_text，textDocumentProxy.insertText 自动上屏。
//   14C 的录音态 UI / 流式上屏框架（CommitController）已随 21D 删除（零调用方），
//   双缓冲 (final, partial) 语义已在主 App DictationController 复刻。
import SwiftUI
import UIKit

final class KeyboardViewController: UIInputViewController {
    private let model = KeyboardViewModel()
    private var toastTimer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        wireModel()

        let host = UIHostingController(
            rootView: KeyboardRootView(model: model, inputController: self))
        host.view.backgroundColor = .clear
        addChild(host)
        view.addSubview(host.view)
        host.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            host.view.topAnchor.constraint(equalTo: view.topAnchor),
            host.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])
        host.didMove(toParent: self)

        // 键盘面板高度：工具条 + 提示条 + MicBar 56 + 功能键行 40 + 间距；
        // iPad（14E）：系统键盘更高、行距更松，加高并由 KeyboardRootView 放大间距
        let panelHeight: CGFloat = UIDevice.current.userInterfaceIdiom == .pad ? 280 : 252
        let height = view.heightAnchor.constraint(equalToConstant: panelHeight)
        height.priority = UILayoutPriority(999) // 让位给系统的强制约束，避免约束冲突日志
        height.isActive = true
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 账户态可能被主 App 改写（登录 / 购买 / 听写 usage 回写 / 调试切换），每次出现时重读
        model.reloadAccount()
        model.needsInputModeSwitch = needsInputModeSwitchKey
        model.hasFullAccess = hasFullAccess
        // 回写 App Group：主 App 引导第 2 步（完全访问）/ 第 4 步（试一试）据此自动判定
        model.recordActive(fullAccess: hasFullAccess)
        trackKeyboardEnabledOnce()
        // 14D deep link 回流：主 App 听写完成 → 用户切回原 App 调起本键盘 → 自动上屏
        insertPendingTextIfAny()
    }

    deinit {
        toastTimer?.invalidate()
    }

    /// kb.enabled 漏斗事件只发一次（首次真实打开 dudu 键盘）
    private func trackKeyboardEnabledOnce() {
        let d = UserDefaults(suiteName: AccountStore.appGroupID) ?? .standard
        guard !d.bool(forKey: "metrics.kb_enabled_sent") else { return }
        d.set(true, forKey: "metrics.kb_enabled_sent")
        MetricsQueue.shared.track(.kbEnabled)
    }

    // MARK: - 14D pending_text 自动上屏

    /// 主 App 听写定稿写入 App Group（pending_text + 时间戳），这里取走并 insertText。
    /// 120s 过期（PendingTextStore 内判定）防旧文本误上屏；过期后剪贴板仍可手动粘贴。
    private func insertPendingTextIfAny() {
        guard let pending = PendingTextStore.consume() else { return }
        textDocumentProxy.insertText(pending.text)
        showToast("已上屏")
        // 打点：主 App 写入 → 键盘上屏的跨进程延迟。MetricWhitelist 没有 deep link
        // 听写专属事件名（白名单外服务端直接丢弃，不发明新名字），复用语义最接近的
        // asr.release_to_commit_ms（"松开 → 上屏"延迟），props.source 区分链路。
        MetricsQueue.shared.track(.asrReleaseToCommitMs, props: [
            "ms": .number((pending.delaySeconds * 1000).rounded()),
            "source": .string("dictate"),
        ])
    }

    /// 工具条短暂提示（2s 后自动消失）
    private func showToast(_ text: String) {
        model.toast = text
        toastTimer?.invalidate()
        toastTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in
            self?.model.toast = nil
        }
    }

    // MARK: - 模型接线

    private func wireModel() {
        model.onInsert = { [weak self] text in
            self?.textDocumentProxy.insertText(text)
        }
        model.onDelete = { [weak self] in
            self?.textDocumentProxy.deleteBackward()
        }
        model.onReturn = { [weak self] in
            // 发送键 = 宿主 return；宿主把它解释为换行还是发送由其 returnKeyType 决定
            self?.textDocumentProxy.insertText("\n")
        }
        model.onOpenApp = { [weak self] path in self?.openMainApp(path) }
    }

    // MARK: - deep link 跳主 App（dictate 听写 / guest 登录 / quota 充值 / 设置齿轮）

    private func openMainApp(_ path: String) {
        guard let url = URL(string: "dudu://\(path)") else { return }
        if path == "dictate" {
            // 听写发起漏斗打点：MetricWhitelist 没有 dictate_open（白名单外服务端直接
            // 丢弃，不发明新名字），复用最接近的 asr.session 并以 props.alias 标记；
            // 后端白名单扩充 dictate.open 后再切正式事件名。
            MetricsQueue.shared.track(.asrSession, props: [
                "alias": .string("dictate_open"),
            ])
        }
        // 键盘扩展拿不到 UIApplication.shared（extension API only），
        // 先走 extensionContext.open，失败再沿响应链找宿主的 openURL:（键盘扩展惯用兜底）
        extensionContext?.open(url) { [weak self] ok in
            if !ok { self?.openViaResponderChain(url) }
        }
    }

    private func openViaResponderChain(_ url: URL) {
        let selector = NSSelectorFromString("openURL:")
        var responder: UIResponder? = self
        while let current = responder {
            if current.responds(to: selector) {
                current.perform(selector, with: url)
                return
            }
            responder = current.next
        }
    }
}

// MARK: - 视图模型（SwiftUI 层的唯一状态源，由本控制器驱动）

final class KeyboardViewModel: ObservableObject {
    @Published var account: AccountState = .guest
    @Published var balanceSeconds = 0
    @Published var trialRemainingSeconds = 0
    @Published var needsInputModeSwitch = true
    @Published var hasFullAccess = false
    /// 工具条短暂提示（"已上屏"），非 nil 时替换常规状态文案
    @Published var toast: String?

    // 动作出口，由 KeyboardViewController 接线
    var onInsert: (String) -> Void = { _ in }
    var onDelete: () -> Void = {}
    var onReturn: () -> Void = {}
    var onOpenApp: (String) -> Void = { _ in }

    private let store = AccountStore()

    func reloadAccount() {
        store.reload()
        account = store.state
        balanceSeconds = store.balanceSeconds
        trialRemainingSeconds = store.trialRemainingSeconds
    }

    /// 键盘出现时回写 App Group 标记（完全访问态 + 打开过键盘），供主 App 引导检测
    func recordActive(fullAccess: Bool) {
        store.recordKeyboardActive(fullAccess: fullAccess)
    }
}
