import SwiftUI
import AppKit
import NucleicCore

/// Standalone popup windows that host **ephemeral, out-of-project (Scratch) chats** — a full agent
/// with its tools running in a disposable scratch workspace, deliberately kept out of the main
/// window's sidebar/project layout. One window per scratch session, opened from the ⌘N composer's
/// **Ephemeral** button (or the New Scratch Chat command).
///
/// The main window renders exactly one session through `AppStore.openSession*`; a scratch chat lives
/// *off* that slot, so this window drives its session directly by id through the store's
/// session-scoped helpers (`liveSnapshot`/`subscribeLive`/`sendToSession`/…). Closing the window
/// hands off to `AppStore.handleEphemeralWindowClosed`, which discards a truly-ephemeral chat (and
/// its scratch dir) or leaves a Saved one in place.
@MainActor
final class EphemeralChatWindowController: NSObject, NSWindowDelegate {
    static let shared = EphemeralChatWindowController()

    private struct Entry {
        let window: NSWindow
        let store: AppStore
    }
    /// Open windows, keyed by session id, so re-opening the same scratch chat refocuses its window.
    private var entries: [SessionID: Entry] = [:]

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

    /// Open (or refocus) the window for a scratch session.
    func open(sessionID: SessionID, store: AppStore) {
        if let entry = entries[sessionID] {
            entry.window.makeKeyAndOrderFront(nil)
            return
        }
        let content = EphemeralChatWindowView(sessionID: sessionID)
            .environment(store)
            .frame(minWidth: 420, minHeight: 360)

        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 640, height: 560),
            styleMask: [.titled, .closable, .resizable, .miniaturizable],
            backing: .buffered, defer: false)
        window.title = "Scratch chat"
        window.contentViewController = NSHostingController(rootView: content)
        window.isReleasedWhenClosed = false
        window.delegate = self
        window.center()
        // Cascade so several scratch windows don't stack exactly on top of each other.
        window.setFrameOrigin(NSPoint(x: window.frame.origin.x + CGFloat(entries.count) * 28,
                                      y: window.frame.origin.y - CGFloat(entries.count) * 28))
        window.makeKeyAndOrderFront(nil)
        entries[sessionID] = Entry(window: window, store: store)
    }

    /// Programmatically close a scratch window (used by the in-window Discard action, which has
    /// already torn the session down). Removing the entry first means `windowWillClose` finds
    /// nothing to hand off, so the discard isn't attempted twice.
    func close(_ sessionID: SessionID) {
        guard let entry = entries.removeValue(forKey: sessionID) else { return }
        entry.window.close()
    }

    /// A window closing (red button / ⌘W) discards a truly-ephemeral chat; a Saved one is kept and
    /// can be reopened later. `AppStore` makes that decision from its Saved set.
    func windowWillClose(_ notification: Notification) {
        guard let window = notification.object as? NSWindow,
              let (id, entry) = entries.first(where: { $0.value.window === window })
        else { return }
        entries.removeValue(forKey: id)
        Task { await entry.store.handleEphemeralWindowClosed(id) }
    }
}
