import SessionCore
import SwiftUI

/// T-iOS-15 · Terminal destination view: TerminalScreen (T-iOS-11) plus the
/// gate/digest surfaces (T-iOS-14) wired to the controller's GateViewModel —
/// the "接入 TerminalScreen 的 wiring 归 T-iOS-15" hand-off.
///
/// Layout decisions (documented):
/// - `.id(controller.generation)`: a rebuild (background→foreground) swaps the
///   ViewModels; without a new identity SwiftUI would keep the old UIView and
///   `makeUIView` (where the output sink attaches) would never run again.
/// - Digest + tool-gate banner live in a TOP overlay stack: the bottom edge
///   belongs to the keyboard + key bar. TerminalScreen's own ReconnectBanner
///   is also top-aligned, but it hides on `.connected` while gates/digests
///   only arrive connected — practical overlap is nil.
/// - Plan gate presents as a medium-detent sheet; swiping it away is allowed
///   (the user may need to scroll the terminal to read the plan), and a
///   "计划待批" re-entry chip appears until the gate resolves. Deciding
///   removes the gate server-side → `planGate` goes nil → sheet dismisses.
struct TerminalContainerView: View {
    let controller: TerminalSessionController
    /// T-iOS-29 · pass-through to TerminalScreen's toolbar/exit-banner
    /// "在当前目录开新会话" action (RootView supplies the coordinator hop).
    var onNewSessionInCwd: (@MainActor () -> Void)? = nil
    /// T-iPad-3 · pass-through to TerminalScreen's pointer context-menu
    /// "结束会话" action (root layers supply the coordinator hop). nil on
    /// surfaces without a kill affordance (e.g. previews).
    var onKillSession: (@MainActor () -> Void)? = nil
    /// Epoch of a plan gate the user swiped away — suppresses re-present for
    /// THAT gate only; a new epoch re-presents automatically.
    @State private var dismissedPlanGateEpoch: Int?
    /// T-iOS-24 (additive) · Non-nil while the full-timeline sheet is up; a
    /// FRESH VM per presentation (each open re-fetches /events).
    @State private var timelineViewModel: TimelineViewModel?
    /// T-iOS-25 (additive) · Quick-reply palette store — per-container over
    /// `.standard` defaults (non-secret UI prefs, plan §5.3 split).
    @State private var quickReplyStore = QuickReplyStore()
    /// 无障碍：减弱动态效果时，overlay 进出塌成即时切换（DS.Motion.gated）。
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    private enum Metrics {
        /// Clears TerminalScreen's own top-aligned ReconnectBanner zone: a
        /// hit-target-tall pill plus a gap. (They rarely co-exist — the banner
        /// hides on `.connected` while gates/digests only arrive connected.)
        static let overlayTopPadding: CGFloat = DS.Layout.minHitTarget + DS.Space.sm8
    }

    private enum Copy {
        /// The re-entry chip's own amber StatusBadge carries the "needs me"
        /// warning shape, so the copy itself stays plain (no emoji).
        static let planGatePending = "计划待批准"
    }

    var body: some View {
        TerminalScreen(
            viewModel: controller.terminalViewModel,
            onNewSessionInCwd: onNewSessionInCwd,
            onKillSession: onKillSession
        )
        .id(controller.generation)
            .navigationBarTitleDisplayMode(.inline)
            .overlay(alignment: .top) { topOverlays }
            .overlay(alignment: .bottom) { quickReplyOverlay }
            .sheet(isPresented: planGateBinding) { planGateSheet }
            .sheet(item: $timelineViewModel) { TimelineSheet(viewModel: $0) }
    }

    // MARK: - Quick-reply chips (T-iOS-25, additive)

    /// Chips float at the bottom edge (above the keyboard's safe area) ONLY
    /// while a gate is waiting — visibility/read-only logic lives in
    /// `QuickReplyBar.isVisible`, driven by the SAME fan-out branches the two
    /// VMs already consume (no extra branch needed).
    @ViewBuilder private var quickReplyOverlay: some View {
        QuickReplyBar(
            terminalViewModel: controller.terminalViewModel,
            gateViewModel: controller.gateViewModel,
            store: quickReplyStore
        )
        .padding(.horizontal, DS.Space.md12)
        .padding(.bottom, DS.Space.sm8)
        .animation(
            DS.Motion.gated(DS.Motion.base, reduceMotion: reduceMotion),
            value: controller.gateViewModel.currentGate
        )
    }

    // MARK: - Digest + tool gate (top stack)

    @ViewBuilder private var topOverlays: some View {
        let gateViewModel = controller.gateViewModel
        VStack(spacing: DS.Space.sm8) {
            if let digest = gateViewModel.digest {
                AwayDigestView(
                    digest: digest,
                    isExpanded: gateViewModel.isDigestExpanded,
                    onExpand: { expandDigestAndPresentTimeline() },
                    onDismiss: { gateViewModel.dismissDigest() }
                )
            }
            if let gate = gateViewModel.toolGate {
                GateBanner(gate: gate) { affordance, epoch in
                    gateViewModel.decide(affordance, epoch: epoch)
                }
            }
            if hasDismissedPendingPlanGate {
                planGatePendingChip
            }
        }
        .padding(.horizontal, DS.Space.md12)
        .padding(.top, Metrics.overlayTopPadding)
        .animation(DS.Motion.gated(DS.Motion.base, reduceMotion: reduceMotion),
                   value: gateViewModel.toolGate)
        .animation(DS.Motion.gated(DS.Motion.base, reduceMotion: reduceMotion),
                   value: gateViewModel.digest)
    }

    /// The "plan still pending" re-entry chip shown after the user swiped the
    /// plan sheet away — an amber pending-approval pill that re-presents the
    /// sheet on tap.
    private var planGatePendingChip: some View {
        Button {
            dismissedPlanGateEpoch = nil
        } label: {
            HStack(spacing: DS.Space.xs4) {
                StatusBadge(status: .pendingApproval)
                Text(Copy.planGatePending)
                    .font(DS.Typography.callout.weight(.medium))
                    .foregroundStyle(DS.Palette.textPrimary)
            }
            .padding(.horizontal, DS.Space.md12)
            .frame(minHeight: DS.Layout.minHitTarget)
            .background(.regularMaterial, in: Capsule())
            .overlay(
                Capsule().strokeBorder(DS.Palette.statusWaiting, lineWidth: DS.Stroke.hairline)
            )
        }
        .buttonStyle(.plain)
    }

    // MARK: - Timeline drill-down (T-iOS-24, additive)

    /// The digest「展开」affordance does BOTH: inline expansion (which cancels
    /// the auto-fade, T-iOS-14 semantics — the digest must survive under the
    /// sheet) and the full-timeline sheet. No adopted sessionId yet →
    /// `forSession` returns nil → inline expand only (defensive; a digest
    /// only ever arrives after `attached`).
    private func expandDigestAndPresentTimeline() {
        controller.gateViewModel.expandDigest()
        timelineViewModel = TimelineViewModel.forSession(
            controller.terminalViewModel.sessionId,
            source: controller.timelineEventsSource
        )
    }

    // MARK: - Plan gate sheet

    private var hasDismissedPendingPlanGate: Bool {
        guard let gate = controller.gateViewModel.planGate else { return false }
        return gate.epoch == dismissedPlanGateEpoch
    }

    private var planGateBinding: Binding<Bool> {
        Binding(
            get: {
                guard let gate = controller.gateViewModel.planGate else { return false }
                return gate.epoch != dismissedPlanGateEpoch
            },
            set: { presented in
                guard !presented else { return }
                dismissedPlanGateEpoch = controller.gateViewModel.planGate?.epoch
            }
        )
    }

    @ViewBuilder private var planGateSheet: some View {
        if let gate = controller.gateViewModel.planGate {
            PlanGateSheet(gate: gate) { affordance, epoch in
                controller.gateViewModel.decide(affordance, epoch: epoch)
            }
        }
    }
}
