// TerminalViewController.swift
// UIKit view-controller hosting a SwiftTerm TerminalView.
//
// SwiftTerm API discoveries (T-2.3):
//   • TerminalView is UIScrollView (not UIView) — autolayout fill works the same way.
//   • feed(byteArray: ArraySlice<UInt8>) ingests ANSI bytes; can be called from any thread.
//   • feed(text: String) is the string variant.
//   • installColors([SwiftTerm.Color]) sets the 16-color ANSI palette.
//   • nativeForegroundColor / nativeBackgroundColor are UIColor properties on TerminalView.
//   • font (UIFont) property drives the rendered glyph size and triggers a resize.
//   • getTerminal() → Terminal gives access to .cols / .rows.
//   • Keyboard input flows OUT through TerminalViewDelegate.send(source:data:).
//   • TerminalViewDelegate is not @MainActor; delegate methods are called on the main
//     thread by SwiftTerm's UIKit layer.  We use MainActor.assumeIsolated to bridge
//     the isolation gap in Swift 6 strict-concurrency mode.

import UIKit
import SwiftTerm

@MainActor
public final class TerminalViewController: UIViewController {

    // MARK: Terminal view

    /// The underlying SwiftTerm view.  Available after `viewDidLoad`.
    public private(set) var terminalView: TerminalView!

    // MARK: Callbacks

    /// Called whenever the user types into the terminal — byte data to be
    /// forwarded to the remote PTY (wired by T-2.5).
    public var onInput: ((Data) -> Void)?

    /// Called when SwiftTerm recalculates its column/row count (layout,
    /// font change, rotation). Forward this to the sidecar so tmux resizes.
    public var onResize: ((Int, Int) -> Void)?

    // MARK: View lifecycle

    public override func viewDidLoad() {
        super.viewDidLoad()

        let activeTheme = ThemeStore.shared.current
        let activeFont  = FontStore.shared.scaledFont

        // Create the terminal view with the current font.
        // TerminalView(frame:font:) is the designated initialiser on iOS.
        let tv = TerminalView(frame: .zero, font: activeFont)
        tv.terminalDelegate = self
        tv.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(tv)
        NSLayoutConstraint.activate([
            tv.topAnchor.constraint(equalTo: view.topAnchor),
            tv.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tv.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tv.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])

        terminalView = tv

        // Apply the active theme so the view renders with correct colours from
        // the very first frame, before any data arrives.
        apply(theme: activeTheme)
    }

    // MARK: Data ingestion

    /// Feed raw ANSI bytes into the terminal emulator.
    /// SwiftTerm's `feed(byteArray:)` is thread-safe; this wrapper is
    /// intentionally @MainActor so callers remain on the main actor.
    public func feed(data: Data) {
        let bytes = [UInt8](data)
        terminalView.feed(byteArray: bytes[...])
    }

    /// Feed a base64-encoded ANSI snapshot (e.g. from the sidecar's
    /// snapshot frame) into the terminal emulator.
    public func feedSnapshot(base64: String) {
        guard let data = Data(base64Encoded: base64,
                              options: .ignoreUnknownCharacters) else {
            return // malformed input — ignore silently
        }
        feed(data: data)
    }

    // MARK: Theme

    /// Apply a `TerminalTheme`, updating foreground/background colours and
    /// the full 16-colour ANSI palette.
    public func apply(theme: TerminalTheme) {
        guard let tv = terminalView else { return }

        // Foreground / background via UIColor properties.
        tv.nativeForegroundColor = UIColor(themeColor: theme.foreground)
        tv.nativeBackgroundColor = UIColor(themeColor: theme.background)

        // 16-colour ANSI palette via SwiftTerm's installColors API.
        tv.installColors(theme.swiftTermAnsiColors)
    }

    // MARK: Font

    /// Apply a `TerminalFont` at the current point size from `FontStore`.
    public func apply(font: TerminalFont) {
        guard let tv = terminalView else { return }
        tv.font = font.uiFont.withSize(FontStore.shared.size)
    }

    /// Apply a `TerminalFont` at an explicit point size.
    public func apply(font: TerminalFont, size: CGFloat) {
        guard let tv = terminalView else { return }
        tv.font = font.uiFont.withSize(size)
    }

    // MARK: Terminal dimensions

    /// Current terminal size in columns and rows.
    public var terminalSize: (cols: Int, rows: Int) {
        guard let tv = terminalView else { return (80, 24) }
        let t = tv.getTerminal()
        return (t.cols, t.rows)
    }
}

// MARK: - TerminalViewDelegate

// TerminalViewDelegate is not @MainActor, but SwiftTerm always calls its
// methods on the main thread (they're UIKit callbacks).  We use
// `MainActor.assumeIsolated` — a zero-cost annotation that asserts we're
// already on the main actor — to satisfy the Swift 6 isolation checker.

extension TerminalViewController: TerminalViewDelegate {

    // MARK: Required — keyboard input (terminal → host)

    public nonisolated func send(source: TerminalView, data: ArraySlice<UInt8>) {
        MainActor.assumeIsolated {
            onInput?(Data(data))
        }
    }

    // MARK: Required — size change notification

    public nonisolated func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
        // Propagate to whoever manages the sidecar connection so they can
        // call `tmux resize-window` and keep line-wrapping in sync.
        MainActor.assumeIsolated {
            onResize?(newCols, newRows)
        }
    }

    // MARK: Required — title change

    public nonisolated func setTerminalTitle(source: TerminalView, title: String) {
        // No-op: future tasks may expose this via a published property.
    }

    // MARK: Required — working directory update

    public nonisolated func hostCurrentDirectoryUpdate(source: TerminalView,
                                                       directory: String?) {
        // No-op for now.
    }

    // MARK: Required — scroll position

    public nonisolated func scrolled(source: TerminalView, position: Double) {
        // No-op for now.
    }

    // MARK: Required — hyperlink activation

    public nonisolated func requestOpenLink(source: TerminalView,
                                            link: String,
                                            params: [String: String]) {
        // Open URL on the main actor.
        MainActor.assumeIsolated {
            guard let url = URL(string: link) else { return }
            UIApplication.shared.open(url)
        }
    }

    // MARK: Required — bell

    public nonisolated func bell(source: TerminalView) {
        // Haptic feedback on bell — UIKit APIs require main thread.
        MainActor.assumeIsolated {
            let generator = UIImpactFeedbackGenerator(style: .light)
            generator.impactOccurred()
        }
    }

    // MARK: Optional — clipboard write (OSC 52)

    public nonisolated func clipboardCopy(source: TerminalView, content: Data) {
        MainActor.assumeIsolated {
            UIPasteboard.general.setData(content,
                                         forPasteboardType: "public.utf8-plain-text")
        }
    }

    // MARK: Optional — clipboard read (OSC 52 query)

    public nonisolated func clipboardRead(source: TerminalView) -> Data? {
        // Return nil to deny clipboard read requests for security.
        nil
    }

    // MARK: Optional — iTerm2 OSC 1337 unhandled content

    public nonisolated func iTermContent(source: TerminalView,
                                         content: ArraySlice<UInt8>) {
        // No-op: iTerm2 extensions are not supported in this client.
    }

    // MARK: Optional — range update notification

    public nonisolated func rangeChanged(source: TerminalView,
                                         startY: Int, endY: Int) {
        // No-op: notifyUpdateChanges is false (default); this won't be called.
    }
}

// MARK: - UIColor convenience

private extension UIColor {
    /// Constructs a UIColor from an 8-bit `ThemeColor`.
    convenience init(themeColor c: ThemeColor) {
        self.init(
            red:   CGFloat(c.r) / 255,
            green: CGFloat(c.g) / 255,
            blue:  CGFloat(c.b) / 255,
            alpha: 1
        )
    }
}
