import SwiftUI
import AppKit
import NucleicCore

/// A live view of the session's macOS VM screen — a "monitor" hung off the chat so the user can watch
/// the GUI state of the guest the virtual-machine service booted for it (what the agent is doing in it,
/// a build running, a simulator, Setup Assistant, …).
///
/// It polls the VM's current framebuffer a couple of times a second over the same screen-capture path
/// computer-use uses (the native in-guest agent's ScreenCaptureKit when present, else SSH
/// `screencapture`) and renders the latest frame scaled to fit. It is **read-only**: it shows the
/// screen, it doesn't drive it. When no VM is running for the chat — or the guest can't be captured —
/// it shows a status placeholder rather than a stale image.
struct VMMonitorPanel: View {
    let session: Session?
    /// When set, the monitor tracks this exact VM by name instead of the open chat's guest. Used by the
    /// floating guest-display windows the "Always open VM guest display" setting opens, which target a
    /// specific running VM regardless of which chat (if any) is in the foreground.
    var explicitVMName: String? = nil
    @Environment(AppStore.self) private var store

    @State private var frame: NSImage?
    @State private var status: Status = .idle

    /// What the monitor is currently showing / waiting on. Drives the placeholder when there's no frame.
    private enum Status: Equatable {
        case idle          // no chat open, or the host can't run macOS guests at all
        case notRunning    // no VM is booted for this session
        case booting       // VM is up but has no IP / hasn't produced a frame yet
        case noSignal      // VM is running but capture came back empty (unprovisioned guest / VZ bug)
        case live          // showing a frame
    }

    /// The VM name to track: an explicit target when given (floating guest-display window), otherwise
    /// the open chat's per-session guest (`nucleic-mac-<short>…`), or nil with no open chat.
    private var vmName: String? {
        explicitVMName ?? session.map { MacVMManager.vmName(for: $0.id) }
    }

    var body: some View {
        ZStack {
            // A black stage, like a physical monitor: the guest screen sits letterboxed in it, and the
            // placeholder reads as an "off" display when nothing's live.
            Color.black
            if let frame {
                Image(nsImage: frame)
                    .resizable()
                    .interpolation(.medium)
                    .aspectRatio(contentMode: .fit)
            } else {
                placeholder
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        // Restart polling when the open chat changes (a different chat means a different VM).
        .task(id: session?.id) { await poll() }
    }

    private var placeholder: some View {
        VStack(spacing: 10) {
            Image(systemName: placeholderIcon)
                .font(.system(size: 34)).foregroundStyle(.tertiary)
            Text(placeholderMessage)
                .font(.callout).foregroundStyle(.secondary)
                .multilineTextAlignment(.center)
                .fixedSize(horizontal: false, vertical: true)
            if status == .booting || status == .noSignal {
                ProgressView().controlSize(.small)
            }
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    /// Poll the VM's state + screen while the panel is on screen. Keeps the last good frame across a
    /// single failed capture (a transient SSH hiccup shouldn't blank the monitor); only a stopped VM
    /// clears it.
    private func poll() async {
        frame = nil
        guard let vmName, store.macVMSupported else {
            status = .idle
            return
        }
        while !Task.isCancelled {
            let running = await store.runningMacVMs()
            if let entry = running.first(where: { $0.name == vmName }) {
                if let base64 = await store.captureMacVMScreen(name: vmName),
                    let image = Self.decode(base64) {
                    frame = image
                    status = .live
                } else if frame == nil {
                    // No frame yet: distinguish "still booting" (no IP) from "up but not capturable".
                    status = entry.ipAddress == nil ? .booting : .noSignal
                }
                // else: keep showing the last good frame despite this capture miss.
            } else {
                frame = nil
                status = .notRunning
            }
            // Refresh briskly while live; back off when there's nothing to show.
            try? await Task.sleep(for: .milliseconds(status == .live ? 1200 : 2000))
        }
    }

    private static func decode(_ base64: String) -> NSImage? {
        guard let data = Data(base64Encoded: base64) else { return nil }
        return NSImage(data: data)
    }

    private var placeholderIcon: String {
        switch status {
        case .idle: "display.trianglebadge.exclamationmark"
        case .notRunning: "power"
        case .booting: "hourglass"
        case .noSignal: "display.trianglebadge.exclamationmark"
        case .live: "display"
        }
    }

    private var placeholderMessage: String {
        switch status {
        case .idle:
            session == nil
                ? "Open a chat to monitor its macOS VM."
                : "Running a macOS VM needs Apple silicon."
        case .notRunning:
            "No macOS VM is running for this chat. It appears here once the agent uses one."
        case .booting:
            "The macOS VM is starting up…"
        case .noSignal:
            "The VM is running, but its screen isn't capturable. Showing the GUI needs a base image "
                + "provisioned for computer use (Screen Recording permission)."
        case .live:
            ""
        }
    }
}
