import HostRegistry
import SwiftUI

/// T-iOS-15 / T-iPad-2 · App 根入口。历史上这里是单一 `NavigationStack` +
/// 隐私遮罩 + sheets/scenePhase/deepLink 的全部布线；iPad 自适应（T-iPad-2）
/// 把它拆成两层：
/// - `RootView`（本 struct）保持 `@main` 的唯一实例化点不变（WebTermApp 仍
///   `RootView(coordinator:)`），内部委托给 `AdaptiveRootView` 并在这唯一处
///   注入设计系统的 accent tint（`DS.Palette.accent`，DS 建议的「根注入一次」）
///   —— 全 App 的系统控件/工具栏/`.borderedProminent` 由此统一到靛紫强调色。
/// - `AdaptiveRootView` 按 `horizontalSizeClass` 选 `StackRootView`（compact，
///   现有路径，字节级不变）或 `SplitRootView`（regular，iPad 分栏），并把
///   隐私遮罩 / scenePhase / deepLink / sheets 这些**设备无关的横切布线**
///   上提到唯一一处，两分支共享（遮罩因此仍是两分支共同的 ZStack 顶层）。
///
/// `StackRootView` 是原 `RootView` 的 body **原样搬迁** —— compact 分支复用它
/// 不改一行逻辑，故 iPhone 逐屏渲染与适配前一致（零回归硬性要求）。视觉层
/// 只按冻结的 `DS.*` token 重塑（横幅/占位/遮罩），行为不动。
struct RootView: View {
    @Bindable var coordinator: AppCoordinator

    var body: some View {
        AdaptiveRootView(coordinator: coordinator)
            // DS：唯一的根 tint 注入点（Tokens.swift 头注约定）。子树若需别的
            // 语义色（gate 的 amber、终端的 orange）在各自局部 `.tint` 覆盖。
            .tint(DS.Palette.accent)
            // 深色优先 —— 对齐桌面 web 主题（DEFAULT_SETTINGS.theme = 'dark'）。
            // 琥珀金强调色是为暖深色背景设计的（桌面 --bg #100F0D）；浅色底上
            // 金字对比不足。深色下 accent/status 全部高对比，且与桌面观感一致。
            .preferredColorScheme(.dark)
    }
}

/// T-iPad-2 · compact 宽度（iPhone / iPad Slide Over）的根视图 —— 原
/// `RootView` 的 `NavigationStack` 主体原样搬迁，行为字节级不变。横切布线
/// （遮罩/scenePhase/deepLink/sheets）不在这里，已上提到 `AdaptiveRootView`。
struct StackRootView: View {
    @Bindable var coordinator: AppCoordinator
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    var body: some View {
        NavigationStack {
            rootContent
                .navigationDestination(isPresented: terminalBinding) {
                    terminalDestination
                }
        }
    }

    // MARK: - Route switch

    @ViewBuilder private var rootContent: some View {
        switch coordinator.route {
        case .loading:
            ProgressView()
        case .pairing:
            firstRunPairing
        case .sessions:
            sessionList
        }
    }

    @ViewBuilder private var firstRunPairing: some View {
        if let viewModel = coordinator.rootPairingViewModel {
            PairingScreen(viewModel: viewModel) { host in
                coordinator.completeFirstPairing(host)
            }
        } else {
            ProgressView()
        }
    }

    private var sessionList: some View {
        SessionListScreen(
            viewModel: coordinator.sessionList,
            onOpen: { coordinator.open($0) },
            onAddHost: { coordinator.presentAddHost() },
            onDeviceCert: { coordinator.presentDeviceCert() },
            onEnroll: { coordinator.presentEnrollment() }
        )
        .safeAreaInset(edge: .bottom) { continueLastBanner }
        // B3 (HIGH) · A silently-failing device-cert renewal is surfaced here
        // (top inset), so it is observable instead of buried in os.Logger.
        .safeAreaInset(edge: .top) { certRenewalWarningBanner }
        // 横幅出现/消失走 DS 动效（reduceMotion 时塌成瞬切，无位移）。
        .animation(
            DS.Motion.gated(DS.Motion.base, reduceMotion: reduceMotion),
            value: coordinator.continueLastSessionId
        )
        // T-iOS-26 · Projects 入口挂在 RootView 层（SessionListScreen 是
        // T-iOS-23 的 W7 独占文件 —— 列表侧入口整合移交给它）。leading 位，
        // 避开列表自己的 topBarTrailing hostMenu。
        .toolbar { ProjectsToolbarItem(coordinator: coordinator) }
    }

    // MARK: - "继续上次" highlight (cold start step 5)

    @ViewBuilder private var continueLastBanner: some View {
        if coordinator.continueLastSessionId != nil {
            ContinueLastBanner { coordinator.openContinueLast() }
                .transition(.move(edge: .bottom).combined(with: .opacity))
        }
    }

    // MARK: - Device-cert renewal warning (B3 HIGH observability fix)

    @ViewBuilder private var certRenewalWarningBanner: some View {
        if coordinator.isCertificateRenewalFailing {
            CertRenewalWarningBanner()
                .transition(.move(edge: .top).combined(with: .opacity))
        }
    }

    // MARK: - Terminal push

    private var terminalBinding: Binding<Bool> {
        Binding(
            get: { coordinator.terminalController != nil },
            set: { presented in
                guard !presented else { return }
                coordinator.closeTerminal() // back → engine.close() (detach)
            }
        )
    }

    @ViewBuilder private var terminalDestination: some View {
        if let controller = coordinator.terminalController {
            TerminalContainerView(
                controller: controller,
                onNewSessionInCwd: { coordinator.openNewSessionInCurrentCwd() },
                onKillSession: { coordinator.killCurrentSession() } // T-iPad-3
            )
            // T-iOS-29 · identity PER CONTROLLER: an in-place session switch
            // (new-in-cwd, deep link) swaps the controller while the
            // destination stays presented — without a new identity SwiftUI
            // keeps the old SwiftTerm UIView and the new ViewModel's output
            // sink never attaches (makeUIView never re-runs). Also resets the
            // container's per-session @State (plan-gate dismissal, timeline).
            .id(controller.id)
        }
    }
}

// MARK: - Continue-last banner (shared stack + split, DRY)

/// 冷启动第 5 步的「继续上次会话」再入 CTA —— stack 底栏与 split sidebar 底栏
/// 共用同一个组件（DRY）。设计：`.regularMaterial` 底 + 顶部发丝分隔 +
/// 全宽 accent 主按钮（`DSButtonStyle(.primary)`），克制而不喧宾夺主。行为仅
/// 一条 `action`（= `coordinator.openContinueLast()`），出现条件由调用点把关。
struct ContinueLastBanner: View {
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            Label(RootCopy.continueLast, systemImage: "arrow.uturn.forward.circle.fill")
        }
        .buttonStyle(DSButtonStyle(kind: .primary))
        .accessibilityIdentifier("root.continueLastButton")
        .padding(.horizontal, DS.Space.lg16)
        .padding(.top, DS.Space.md12)
        .padding(.bottom, DS.Space.sm8)
        .background(.regularMaterial)
        .overlay(alignment: .top) {
            Rectangle()
                .fill(DS.Palette.hairline)
                .frame(height: DS.Stroke.hairline)
        }
    }
}

// MARK: - Device-cert renewal warning (B3 HIGH observability fix)

/// A quiet, non-blocking warning that the silent device-certificate renewal is
/// failing — surfaced from `AppCoordinator.isCertificateRenewalFailing` so a
/// failing renewal is observable in the UI instead of only in os.Logger. Amber
/// (the `waiting`/needs-me status color) + an SF Symbol so meaning is never
/// carried by color alone. Non-interactive: the existing cert stays valid until
/// expiry, and the next foreground retries automatically.
struct CertRenewalWarningBanner: View {
    var body: some View {
        Label(RootCopy.certRenewalFailing, systemImage: "exclamationmark.shield")
            .font(DS.Typography.caption)
            .foregroundStyle(DS.Palette.statusWaiting)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.horizontal, DS.Space.lg16)
            .padding(.vertical, DS.Space.sm8)
            .background(.regularMaterial)
            .overlay(alignment: .bottom) {
                Rectangle()
                    .fill(DS.Palette.hairline)
                    .frame(height: DS.Stroke.hairline)
            }
            .accessibilityIdentifier("root.certRenewalWarning")
    }
}

// MARK: - Projects toolbar item (shared stack + split, DRY)

/// 「项目」leading 工具栏入口 —— stack 与 split 共用（同 disabled 条件、同
/// `presentProjects` 动作、同 a11y id）。根 tint 令其 label 呈 accent。
struct ProjectsToolbarItem: ToolbarContent {
    @Bindable var coordinator: AppCoordinator

    var body: some ToolbarContent {
        ToolbarItem(placement: .topBarLeading) {
            Button {
                coordinator.presentProjects()
            } label: {
                Label(RootCopy.projects, systemImage: "folder")
            }
            .disabled(coordinator.sessionList.activeHost == nil)
            .accessibilityIdentifier("sessions.projectsButton")
        }
    }
}

/// 根层用户可见文案（internal —— `SplitRootView` 复用同一「项目」/「继续」标签，DRY）。
enum RootCopy {
    static let continueLast = "继续上次会话"
    static let projects = "项目"
    static let done = "完成"
    /// B3 (HIGH) · Shown when the silent device-cert renewal keeps failing.
    static let certRenewalFailing = "设备证书自动续期失败，将在下次前台重试"
}
