import SwiftUI
import AppKit
import SwiftTerm

/// A full interactive terminal rooted at the session's worktree. Wraps SwiftTerm's
/// `LocalProcessTerminalView`, which forks the user's login shell behind a real
/// pseudo-terminal — so `vim`, `top`, `git rebase -i`, etc. all work.
///
/// The shell is started once per (view identity); keying the panel on the session +
/// instance id (see `PanelHost`) gives each chat its own persistent shell that
/// survives SwiftUI re-renders but is torn down when the panel or session goes away.
struct TerminalPanel: View {
    let workingDirectory: String?
    @Environment(\.appPalette) private var palette
    @Environment(\.colorScheme) private var colorScheme
    @Environment(TerminalCommandBus.self) private var commands

    var body: some View {
        if let dir = workingDirectory {
            TerminalRepresentable(
                workingDirectory: dir, colorScheme: colorScheme,
                pending: commands.pending, onConsume: { commands.consume($0) })
                .background(Color(nsColor: TerminalTheme.background(colorScheme)))
        } else {
            VStack(spacing: 8) {
                Image(systemName: "terminal").font(.largeTitle).foregroundStyle(.tertiary)
                Text("No worktree yet")
                    .font(.callout).foregroundStyle(.secondary)
                Text("The terminal opens once this chat has provisioned its worktree.")
                    .font(.caption).foregroundStyle(.tertiary).multilineTextAlignment(.center)
            }
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

private struct TerminalRepresentable: NSViewRepresentable {
    let workingDirectory: String
    let colorScheme: ColorScheme
    /// A command to type into the shell (e.g. `claude /login` from the in-chat login
    /// button), or nil. Sent once per distinct request — see `Coordinator`.
    var pending: TerminalCommandBus.Pending? = nil
    var onConsume: (UUID) -> Void = { _ in }

    func makeCoordinator() -> Coordinator { Coordinator() }

    /// Remembers which queued command was already typed, so a redraw doesn't retype it.
    final class Coordinator { var lastHandled: UUID? }

    func makeNSView(context: Context) -> LocalProcessTerminalView {
        let view = LocalProcessTerminalView(frame: .zero)
        view.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
        applyColors(to: view)

        // Launch an interactive login shell in the worktree. `currentDirectory` lands
        // us there; `-l` gives a login shell so the user's PATH/aliases are present.
        let shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
        view.startProcess(
            executable: shell, args: ["-l"], environment: nil, execName: nil,
            currentDirectory: workingDirectory)
        return view
    }

    func updateNSView(_ view: LocalProcessTerminalView, context: Context) {
        // Re-theme on appearance changes; never restart the process.
        applyColors(to: view)

        // Type a freshly-requested command into the live shell, once. The in-chat "Log
        // in" button uses this to drop the user straight into `claude /login` here
        // rather than in a separate terminal they'd open by hand.
        guard let pending, pending.id != context.coordinator.lastHandled else { return }
        context.coordinator.lastHandled = pending.id
        let command = pending.command
        let id = pending.id
        // Give a just-spawned login shell a beat to bring its line editor up before
        // typing — otherwise the keystrokes can land ahead of the prompt and be dropped.
        // Clearing the request happens here too (off the view-update pass) so setting the
        // bus's state doesn't mutate observed state mid-render.
        Task { @MainActor in
            try? await Task.sleep(for: .milliseconds(600))
            view.send(txt: command + "\n")
            onConsume(id)
        }
    }

    static func dismantleNSView(_ view: LocalProcessTerminalView, coordinator: ()) {
        view.terminate()
    }

    private func applyColors(to view: LocalProcessTerminalView) {
        view.nativeBackgroundColor = TerminalTheme.background(colorScheme)
        view.nativeForegroundColor = TerminalTheme.foreground(colorScheme)
        view.caretColor = TerminalTheme.foreground(colorScheme)
    }
}

private enum TerminalTheme {
    static func background(_ scheme: ColorScheme) -> NSColor {
        scheme == .dark
            ? NSColor(srgbRed: 0.06, green: 0.065, blue: 0.08, alpha: 1)
            : NSColor(srgbRed: 0.99, green: 0.99, blue: 0.99, alpha: 1)
    }
    static func foreground(_ scheme: ColorScheme) -> NSColor {
        scheme == .dark
            ? NSColor(srgbRed: 0.86, green: 0.87, blue: 0.90, alpha: 1)
            : NSColor(srgbRed: 0.12, green: 0.13, blue: 0.15, alpha: 1)
    }
}
