import SwiftUI
import AppKit
import NucleicCore

/// Drives the **operator-assist hand-off** (`mac_vm_request_operator`): when the user accepts an
/// agent's request to help operate its macOS VM, this brings the session's live, interactive VM window
/// on-screen and floats a small **walkthrough popup** over it that shows what the agent needs done and
/// collects the outcome. The popup — not the in-chat "Open viewer & help" button — is what resolves the
/// still-pending approval, so the agent's `mac_vm_request_operator` call stays blocked until the user
/// finishes ("I did it"), gives up ("Couldn't do it"), or closes the popup (treated as declining).
///
/// App-level and single-shot (one hand-off at a time — the agent blocks on each call), modeled on
/// `MacVMGuestDisplayController`. The interactive guest window itself is the real `VZVirtualMachineView`
/// surface (`MacVMComputerSurface`), brought on-screen via `AppStore.presentMacVMOperatorViewer`.
@MainActor
final class MacVMOperatorAssistController: NSObject, NSWindowDelegate {
    static let shared = MacVMOperatorAssistController()

    private var popup: NSWindow?
    /// The request currently being assisted, so the popup's buttons resolve the right approval and we
    /// ignore a duplicate `begin` for the same one.
    private var request: ApprovalRequest?
    private weak var store: AppStore?
    /// The VM whose viewer we brought on-screen, so we can send it back off-screen when we finish.
    private var vmName: String?
    /// Set once the approval has been resolved (by a button or a close), so the window-close delegate
    /// doesn't double-resolve and a programmatic teardown isn't misread as a user decline.
    private var resolved = false

    private override init() { super.init() }

    /// Begin the hand-off for `request`: surface the interactive VM viewer and float the walkthrough
    /// popup over it. Idempotent for the same request; a different request replaces the current one.
    func begin(request: ApprovalRequest, store: AppStore) {
        if let current = self.request, current.id == request.id, popup != nil { return }
        // A stale hand-off somehow still open (shouldn't happen — the agent blocks per call): tear it
        // down without resolving so we start clean for the new one.
        if popup != nil { teardownWindow() }

        self.request = request
        self.store = store
        self.resolved = false
        let name = request.input["vm_name"]?.stringValue
        self.vmName = name
        let instructions = request.input["instructions"]?.stringValue ?? ""
        let reason = request.input["reason"]?.stringValue

        // Bring the live, interactive guest window on-screen for the user to drive by hand.
        if let name {
            Task { await store.presentMacVMOperatorViewer(name: name, visible: true) }
        }

        let content = OperatorAssistWalkthrough(
            instructions: instructions,
            reason: reason,
            onDone: { [weak self] note in self?.resolve(completed: true, note: note) },
            onCantDo: { [weak self] note in self?.resolve(completed: false, note: note) })

        // A floating utility panel over the viewer: titled + closable (closing = decline), always above
        // the guest window so the instructions stay visible while the user works in the VM. Positioned
        // top-right so it doesn't cover the middle of the guest.
        let panel = NSPanel(
            contentRect: NSRect(x: 0, y: 0, width: 380, height: 260),
            styleMask: [.titled, .closable, .utilityWindow, .hudWindow],
            backing: .buffered, defer: false)
        panel.title = "The agent needs your help"
        panel.contentViewController = NSHostingController(rootView: content)
        panel.isReleasedWhenClosed = false
        panel.level = .floating
        panel.hidesOnDeactivate = false
        panel.isFloatingPanel = true
        panel.delegate = self
        if let screen = NSScreen.main {
            let visible = screen.visibleFrame
            let size = panel.frame.size
            panel.setFrameOrigin(NSPoint(
                x: visible.maxX - size.width - 24,
                y: visible.maxY - size.height - 24))
        } else {
            panel.center()
        }
        panel.orderFrontRegardless()
        NSApp.activate(ignoringOtherApps: true)
        self.popup = panel
    }

    /// A button chose an outcome: resolve the pending approval with `{completed, note}` (the backend
    /// returns this to the agent), then send the viewer off-screen and close the popup.
    private func resolve(completed: Bool, note: String) {
        guard !resolved, let request, let store else { return }
        resolved = true
        let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
        Task {
            try? await store.respondToApproval(
                request.id,
                .allow(updatedInput: .object([
                    "completed": .bool(completed),
                    "note": .string(trimmed),
                ])),
                by: store.localResponderID)
        }
        finish()
    }

    /// Closing the popup without choosing an outcome declines the hand-off, so the agent's call never
    /// hangs. Programmatic teardown clears the delegate first, so it never reaches here.
    func windowWillClose(_ notification: Notification) {
        guard !resolved, let request, let store else { return }
        resolved = true
        Task {
            try? await store.respondToApproval(
                request.id,
                .deny(reason: "The user closed the assist without completing it."),
                by: store.localResponderID)
        }
        // The window is already closing; just return the viewer off-screen and clear state.
        if let vmName { Task { await store.presentMacVMOperatorViewer(name: vmName, visible: false) } }
        self.popup = nil
        self.request = nil
        self.vmName = nil
        self.store = nil
    }

    /// Return the viewer off-screen and tear down the popup after an outcome was chosen.
    private func finish() {
        if let vmName, let store {
            Task { await store.presentMacVMOperatorViewer(name: vmName, visible: false) }
        }
        teardownWindow()
        request = nil
        vmName = nil
        store = nil
    }

    /// Close the popup without firing the decline path (clear the delegate first).
    private func teardownWindow() {
        popup?.delegate = nil
        popup?.orderOut(nil)
        popup = nil
    }
}

/// The walkthrough shown in the floating popup: what the agent needs done, why, and the controls to
/// report the outcome. The user operates the VM in the separate viewer window; this collects the note
/// they leave and which button they press.
private struct OperatorAssistWalkthrough: View {
    let instructions: String
    let reason: String?
    let onDone: (String) -> Void
    let onCantDo: (String) -> Void

    @State private var note: String = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(alignment: .firstTextBaseline, spacing: 8) {
                Image(systemName: "hand.point.up.left.fill")
                    .foregroundStyle(.tint)
                Text("Operate the VM in the window behind this, then report back.")
                    .font(.callout)
                    .fixedSize(horizontal: false, vertical: true)
            }

            VStack(alignment: .leading, spacing: 4) {
                Text("What to do")
                    .font(.caption2.weight(.semibold)).foregroundStyle(.secondary)
                ScrollView(.vertical) {
                    Text(instructions.isEmpty ? "(no instructions provided)" : instructions)
                        .font(.callout)
                        .textSelection(.enabled)
                        .fixedSize(horizontal: false, vertical: true)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(maxHeight: 90)
            }

            if let reason, !reason.isEmpty {
                Text("Agent's reason: \(reason)")
                    .font(.caption).foregroundStyle(.secondary)
                    .fixedSize(horizontal: false, vertical: true)
            }

            TextField("Optional note back to the agent…", text: $note, axis: .vertical)
                .textFieldStyle(.roundedBorder)
                .lineLimit(1...3)

            HStack(spacing: 8) {
                Spacer()
                Button("Couldn't do it") { onCantDo(note) }
                Button("I did it") { onDone(note) }
                    .keyboardShortcut(.defaultAction)
                    .buttonStyle(.borderedProminent)
            }
        }
        .padding(16)
        .frame(width: 380)
    }
}
