// MainTerminalView.swift
// Wires SessionConnection → TerminalViewController + ModifierBar
// MVP: auto-picks the first session (or creates one named "pi")

import Combine
import SwiftUI

@MainActor
struct MainTerminalView: View {
    let credential: SidecarCredential

    @State private var terminalVC = TerminalViewController()
    @State private var connection: SessionConnection? = nil
    @State private var statusText = "Connecting…"
    @State private var currentPiState: PiState? = nil
    @State private var sessionName = ""
    @State private var showSwitcher = false
    @State private var showSettings = false                   // T-2.11
    @State private var activeSessionId: String? = nil        // T-2.6
    @State private var cancellables = Set<AnyCancellable>()
    @StateObject private var registry = SessionRegistry()    // T-2.6
    @EnvironmentObject var appState: AppState

    var body: some View {
        VStack(spacing: 0) {
            // ── Status bar ──────────────────────────────────────────
            StatusBar(
                sessionName: sessionName,
                connectionStatus: statusText,
                piState: $currentPiState,
                onSwitcher: { showSwitcher = true },
                onUnpair: { appState.unpair() },
                onSettings: { showSettings = true }           // T-2.11
            )

            // ── Terminal ────────────────────────────────────────────
            TerminalViewRepresentable(controller: terminalVC)
                .ignoresSafeArea(edges: .bottom)

            Divider()

            // ── Modifier bar ────────────────────────────────────────
            ModifierBar { frame in
                Task {
                    try? await connection?.send(frame)
                }
            }
            .padding(.vertical, 4)
            .background(Color(uiColor: .secondarySystemBackground))
        }
        .task { await initialBootstrap() }
        .task { await registry.refresh(credential: credential) }    // T-2.6
        .onChange(of: activeSessionId) { _, newId in
            guard let newId else { return }
            // Avoid reconnect storm if id already matches the current connection.
            if connection?.id == newId { return }
            Task {
                if let oldConn = connection { await oldConn.suspend() }
                cancellables.removeAll()
                connection = nil
                currentPiState = nil
                sessionName = ""
                terminalVC.feed(data: Data("\u{1B}[H\u{1B}[2J".utf8))
                await bootstrap(sessionId: newId)
            }
        }
        .sheet(isPresented: $showSwitcher) {                        // T-2.6
            SessionSwitcher(registry: registry, credential: credential) { session in
                activeSessionId = session.id
            }
        }
        .sheet(isPresented: $showSettings) {                        // T-2.11
            SettingsView(credential: credential)
                .environmentObject(appState)
        }
    }

    // MARK: - Bootstrap

    private func initialBootstrap() async {
        statusText = "Looking for sessions…"
        do {
            let sessionId = try await resolveSession()
            await bootstrap(sessionId: sessionId)
        } catch {
            statusText = "Error: \(error.localizedDescription)"
        }
    }

    private func bootstrap(sessionId: String) async {
        // Keep activeSessionId in sync. The .onChange handler guards against
        // re-entry via `connection?.id == newId` check (connection is still nil here
        // on first call, but we set it below before any further state change).
        activeSessionId = sessionId
        statusText = "Connecting to \(sessionId)…"
        let conn = SessionConnection(id: sessionId, credential: credential)

            // Wire live ANSI stream → terminal
            conn.stream
                .receive(on: DispatchQueue.main)
                .sink { [terminalVC] data in
                    terminalVC.feed(data: data)
                }
                .store(in: &cancellables)

            // Wire snapshots → terminal (already contains ESC[H + ESC[2J prefix)
            conn.snapshots
                .receive(on: DispatchQueue.main)
                .sink { [terminalVC] data in
                    terminalVC.feed(data: data)
                }
                .store(in: &cancellables)

            // Wire connection state → status text
            conn.$connectionState
                .receive(on: DispatchQueue.main)
                .sink { state in
                    switch state {
                    case .connected:    statusText = "● \(sessionId)"
                    case .connecting:   statusText = "Connecting…"
                    case .disconnected: statusText = "Disconnected"
                    }
                }
                .store(in: &cancellables)

            // Wire resize callback — fires on layout + font changes.
            terminalVC.onResize = { [weak conn] cols, rows in
                guard let conn else { return }
                Task { try? await conn.send(.resize(cols: cols, rows: rows)) }
            }

            // Wire keyboard input from SwiftTerm → sidecar.
            terminalVC.onInput = { [weak conn] data in
                guard let conn,
                      let text = String(data: data, encoding: .utf8) else { return }
                Task { try? await conn.send(.keys(data: text)) }
            }

            // On first connection:
            //   1. Clear SwiftTerm immediately (removes stale content)
            //   2. Send resize so tmux + shell know the real dimensions
            //   3. Wait for SIGWINCH to propagate and shell to redraw
            //   4. Snapshot the now-stable screen state
            conn.$connectionState
                .filter { $0 == .connected }
                .first()
                .receive(on: DispatchQueue.main)
                .sink { [weak conn, terminalVC] _ in
                    // Clear immediately — don't show stale/mismatched content.
                    terminalVC.feed(data: Data("\u{1B}[H\u{1B}[2J".utf8))

                    Task { @MainActor in
                        let (cols, rows) = terminalVC.terminalSize
                        if cols > 0 && rows > 0 {
                            try? await conn?.send(.resize(cols: cols, rows: rows))
                        }
                        // 600 ms: SIGWINCH → fish/bash redraws → tmux pane settles.
                        // SSH sessions need extra round-trip time.
                        try? await Task.sleep(nanoseconds: 600_000_000)
                        try? await conn?.send(.snapshotRequest)
                    }
                }
                .store(in: &cancellables)

            // Wire stateEvents → currentPiState
            conn.stateEvents
                .compactMap { event -> PiState? in
                    if case .state(let s, _, _) = event { return s } else { return nil }
                }
                .receive(on: DispatchQueue.main)
                .sink { state in currentPiState = state }
                .store(in: &cancellables)

            // Wire stateEvents → sessionName
            conn.stateEvents
                .compactMap { event -> String? in
                    if case .sessionMeta(let name, _, _) = event { return name } else { return nil }
                }
                .receive(on: DispatchQueue.main)
                .sink { name in sessionName = name }
                .store(in: &cancellables)

            connection = conn

            let lastSeq: UInt64? = conn.scrollback.sizeBytes > 0
                ? ResumeCursor().lastSeq(for: sessionId)
                : nil
            await conn.resume(from: lastSeq)
    }

    // MARK: - Session resolution

    /// Returns the first existing session id, or creates one named "pi".
    private func resolveSession() async throws -> String {
        let url = URL(string: "http://\(credential.host):\(credential.port)/sessions")!
        var req = URLRequest(url: url)
        req.setValue("Bearer \(credential.bearerToken)", forHTTPHeaderField: "Authorization")

        let (data, _) = try await URLSession.shared.data(for: req)

        struct SessionItem: Decodable { let id: String }
        let sessions = (try? JSONDecoder().decode([SessionItem].self, from: data)) ?? []

        if let first = sessions.first {
            return first.id
        }

        // No sessions — create one
        var createReq = URLRequest(url: url)
        createReq.httpMethod = "POST"
        createReq.setValue("application/json", forHTTPHeaderField: "Content-Type")
        createReq.setValue("Bearer \(credential.bearerToken)", forHTTPHeaderField: "Authorization")
        createReq.httpBody = try? JSONEncoder().encode(["name": "pi"])

        let (createData, _) = try await URLSession.shared.data(for: createReq)
        struct Created: Decodable { let id: String }
        if let created = try? JSONDecoder().decode(Created.self, from: createData) {
            return created.id
        }

        throw URLError(.cannotParseResponse)
    }
}
