// DevicePreviewScreen.swift — Capstone window for the Device Preview track.
//
// Layout:
//   - Sidebar: grouped list of standalone device types across 6 categories
//     (17 in the App Store build, 20 when an external desktop bridge is active).
//   - Main canvas: the selected device's view, centered with a header + byline.
//   - Toolbar: Agent / State / Sessions pickers. A running animation clock
//     ticks `selection.animationFrame` forward twice per second so dynamic
//     devices (Stream Deck+ slot, TUI terrarium) visibly animate.
//
// This window opens via:
//   - macOS menu bar: "Preview Devices" button in ControlTowerPanel.
//   - First launch: the device-empty banner nudges the user here.
//   - `openWindow(id: "device-preview")` from anywhere else.
//
// The window is macOS-only in the App scene, but the screen View is
// platform-agnostic so iOS compiles it too — useful for future iPad previews.

import SwiftUI

// MARK: - Screen

struct DevicePreviewScreen: View {
    @State private var selection = DevicePreviewSelection(
        agent: .claudeCode,
        state: .processing,
        sessionCount: 1,
        device: .streamDeckPlus
    )

    /// Live-follow mode: derive agent/state/session-count from the daemon's
    /// aggregate state instead of the toolbar pickers, so each preview shows
    /// (approximately) what that device is rendering RIGHT NOW. First step
    /// toward per-device emulators — the mapping is coarse (the preview input
    /// model is agent+state+count), but it turns the catalog into a live
    /// mirror for QA without hardware.
    @State private var followLive = false
    @EnvironmentObject private var stateHolder: AgentStateHolder

    /// Mark first-view-seen. The window is the only caller that should write
    /// this flag — don't leak the side-effect into subviews.
    @EnvironmentObject private var preferences: AppPreferences

    private let sessionCountOptions: [Int] = [0, 1, 2, 4]

#if os(macOS)
    /// External CLI daemon presence gates the desktop-bridge-only device
    /// previews. When absent, the picker hides Android / e-ink / TC001
    /// rows so the catalog reflects only what this app can drive itself.
    @EnvironmentObject private var daemonService: DaemonService

    private var visibleDevices: [PreviewDevice] {
        if daemonService.isUsingExternalDaemon {
            return PreviewDevice.allCases
        }
        return PreviewDevice.allCases.filter { !$0.requiresDesktopBridge }
    }
#else
    private var visibleDevices: [PreviewDevice] {
        PreviewDevice.allCases.filter { !$0.requiresDesktopBridge }
    }
#endif

    var body: some View {
        #if os(macOS)
        NavigationSplitView {
            sidebar
                .navigationSplitViewColumnWidth(min: 220, ideal: 260, max: 320)
                .scrollContentBackground(.hidden)
        } detail: {
            detail
        }
        .frame(minWidth: 900, minHeight: 600)
        .aquariumSurface()
        .onAppear {
            if !preferences.hasSeenDevicePreview {
                preferences.hasSeenDevicePreview = true
            }
            let pool = visibleDevices
            if !pool.contains(selection.device), let first = pool.first {
                selection.device = first
            }
        }
        #else
        // iOS has no NavigationSplitView detail column with this API shape
        // before iOS 17.0, and the preview screen is macOS-facing; on iOS we
        // just show the detail.
        VStack(spacing: 0) {
            toolbar
            Divider()
            detail
        }
        .aquariumSurface()
        #endif
    }

    // MARK: - Sidebar

    private var sidebar: some View {
        let pool = visibleDevices
        return List(selection: Binding<PreviewDevice?>(
            get: { selection.device },
            set: { new in if let new { selection.device = new } }
        )) {
            ForEach(PreviewDevice.Category.allCases) { cat in
                let entries = pool.filter { $0.category == cat }
                if !entries.isEmpty {
                    Section(cat.displayName) {
                        ForEach(entries) { dev in
                            Text(dev.displayName)
                                .tag(dev)
                                .listRowBackground(Color.clear)
                        }
                    }
                }
            }
        }
        .listStyle(.sidebar)
#if os(macOS)
        .onChange(of: daemonService.isUsingExternalDaemon) { _, _ in
            if !pool.contains(selection.device), let first = pool.first {
                selection.device = first
            }
        }
#endif
    }

    // MARK: - Detail

    private var detail: some View {
        VStack(alignment: .leading, spacing: 0) {
            toolbar
            Divider()
            ScrollView {
                VStack(spacing: 24) {
                    // Tagline
                    Text("Hardware optional. Here's what your agents look like on each device.")
                        .font(.system(size: 14))
                        .foregroundStyle(.secondary)
                        .frame(maxWidth: .infinity, alignment: .leading)

                    // Device header
                    VStack(alignment: .leading, spacing: 4) {
                        Text(selection.device.displayName)
                            .font(.system(size: 22, weight: .bold))
                        Text(selection.device.byline)
                            .font(.system(size: 12))
                            .foregroundStyle(.secondary)
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)

                    // The device itself
                    TimelineView(.animation(minimumInterval: 0.1, paused: false)) { context in
                        deviceBody(animationFrame: frameFromTimeline(context.date))
                    }
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 12)
                }
                .padding(24)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            // Detail stays transparent — aquariumSurface paints the window.
            // A translucent dark veil keeps the preview area subtly distinct
            // from the sidebar without interrupting the gradient.
            .background(Color.black.opacity(0.18))
        }
    }

    // MARK: - Toolbar

    private var toolbar: some View {
        HStack(spacing: 16) {
            Picker("Agent", selection: $selection.agent) {
                ForEach(PixooPreviewAgent.allCases) { agent in
                    Text(agent.displayName).tag(agent)
                }
            }
            .pickerStyle(.menu)
            .frame(maxWidth: 180)
            .disabled(followLive)

            Picker("State", selection: $selection.state) {
                ForEach(PixooPreviewState.allCases) { state in
                    Text(state.displayName).tag(state)
                }
            }
            .pickerStyle(.menu)
            .frame(maxWidth: 180)
            .disabled(followLive)

            Picker("Sessions", selection: $selection.sessionCount) {
                ForEach(sessionCountOptions, id: \.self) { n in
                    Text("\(n)").tag(n)
                }
            }
            .pickerStyle(.segmented)
            .frame(maxWidth: 200)
            .disabled(followLive)

            Spacer()

            // Live-follow: mirror the daemon's current sessions instead of
            // the synthetic picker state.
            Toggle(isOn: $followLive) {
                Label("Live", systemImage: "dot.radiowaves.left.and.right")
            }
            .toggleStyle(.button)
            .help("Follow the live daemon state — previews mirror what devices show right now")
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 10)
    }

    // MARK: - Device dispatch

    @ViewBuilder
    private func deviceBody(animationFrame: Int) -> some View {
        // Merge the toolbar selection (or, in live-follow mode, the daemon's
        // aggregate state) with the animation frame so each per-device view
        // receives a single, coherent DevicePreviewSelection.
        let inputs = followLive ? Self.liveSelectionInputs(from: stateHolder.state) : nil
        let live = DevicePreviewSelection(
            agent: inputs?.agent ?? selection.agent,
            state: inputs?.state ?? selection.state,
            sessionCount: inputs?.sessionCount ?? selection.sessionCount,
            device: selection.device,
            animationFrame: animationFrame,
            // Rich per-session/usage snapshot for the emulator-grade previews
            // (D200H etc.). Only attached in live-follow mode; the coarse
            // agent/state/count above stays as the fallback for previews that
            // don't consume it yet.
            live: followLive ? LivePreviewData.from(stateHolder.state) : nil
        )

        switch selection.device {
        case .streamDeckKey:     StreamDeckKeyPreview(selection: live)
        case .streamDeckPlus:    StreamDeckPlusPreview(selection: live)
        case .d200hKey:          D200HKeyPreview(selection: live)
        case .d200hDeck:         D200HDeckPreview(selection: live)
        case .iPadLandscape:     IPadLandscapePreview(selection: live)
        case .androidTablet:     AndroidTabletPreview(selection: live)
        case .einkMono:          EinkMonoPreview(selection: live)
        case .einkColor:         EinkColorPreview(selection: live)
        case .inkDeck:           InkDeckPreview(selection: live)
        case .esp32_86box:       Esp3286BoxPreview(selection: live)
        case .esp32_35Landscape: Esp3235LandscapePreview(selection: live)
        case .esp32_35Portrait:  Esp3235PortraitPreview(selection: live)
        case .esp32Round:        Esp32RoundPreview(selection: live)
        case .esp32Ttgo:         Esp32TtgoPreview(selection: live)
        case .esp32Ips10:        Esp32Ips10Preview(selection: live)
        case .pixoo64:           Pixoo64Preview(selection: live)
        case .ulanziMatrix:      UlanziMatrixPreview(selection: live)
        case .timeboxMini:       TimeboxMiniPreview(selection: live)
        case .iDotMatrix:        IDotMatrixPreview(selection: live)
        case .terminalTerrarium: TerminalTerrariumPreview(selection: live)
        }
    }

    /// Convert a Date tick into a monotonic integer animation frame. We use
    /// seconds * 10 so Canvas / SessionSlotView animations feel smooth but
    /// don't burn CPU — the animations internally are cheap (angle/sin).
    private func frameFromTimeline(_ date: Date) -> Int {
        Int(date.timeIntervalSinceReferenceDate * 10)
    }

    // MARK: - Live-follow mapping

    /// Coarse daemon-state → preview-selection mapping for live-follow mode.
    /// Awaiting wins over processing (attention beats motion — same priority
    /// every physical surface uses); session count clamps to the preview's
    /// supported {0, 1, 2, 4} buckets.
    static func liveSelectionInputs(
        from state: DashboardState
    ) -> (agent: PixooPreviewAgent, state: PixooPreviewState, sessionCount: Int) {
        let alive = state.siblingSessions.filter(\.alive)
        guard state.bridgeConnected else { return (.claudeCode, .disconnected, 0) }

        let agentType = state.agentType ?? alive.first?.agentType
        let agent: PixooPreviewAgent
        switch agentType {
        case "codex-cli", "codex-app": agent = .codex
        case "opencode":               agent = .opencode
        case "openclaw":               agent = .openclaw
        case "antigravity":            agent = .antigravity
        default:                       agent = .claudeCode
        }

        let anyAwaiting = state.state.isAwaiting
            || alive.contains { AgentConnectionState(rawValue: $0.state ?? "")?.isAwaiting == true }
        let anyProcessing = state.state == .processing
            || alive.contains { $0.state == "processing" }
        let previewState: PixooPreviewState = anyAwaiting
            ? .awaitingPrompt
            : (anyProcessing ? .processing : .idle)

        let count: Int
        switch alive.count {
        case 0, 1, 2: count = alive.count
        case 3:       count = 2
        default:      count = 4
        }
        return (agent, previewState, count)
    }
}

#if DEBUG
#Preview("Device preview") {
    DevicePreviewScreen()
        .environmentObject(AppPreferences.shared)
        .environmentObject(AgentStateHolder())
        .frame(width: 1100, height: 760)
}
#endif
