import AppKit
import WebKit

/// A small transparent window whose only job is to catch the click on the X.
///
/// The scene window itself is click-through (`ignoresMouseEvents`), which is what makes
/// the alien harmless while you work — but a click-through window can't have a button
/// in it. So the X is *drawn* in the web view and *clicked* here, in a window barely
/// bigger than the button.
private final class ClickCatcherView: NSView {
    var onClick: (() -> Void)?
    var onHover: ((Bool) -> Void)?

    override func mouseDown(with event: NSEvent) { onClick?() }

    override func resetCursorRects() {
        addCursorRect(bounds, cursor: .pointingHand)
    }

    override func updateTrackingAreas() {
        super.updateTrackingAreas()
        trackingAreas.forEach(removeTrackingArea)
        addTrackingArea(NSTrackingArea(
            rect: bounds,
            options: [.mouseEnteredAndExited, .activeAlways],
            owner: self))
    }

    override func mouseEntered(with event: NSEvent) { onHover?(true) }
    override func mouseExited(with event: NSEvent) { onHover?(false) }
}

/// Owns the transparent, click-through, always-on-top window the alien flies across.
/// Reminders are queued so two things due at once don't draw on top of each other.
@MainActor
final class OverlayController: NSObject, WKScriptMessageHandler {
    private var window: NSWindow?
    private var webView: WKWebView?
    private var closeWindow: NSWindow?
    private var hostScreen: NSScreen?
    private var teardown: DispatchWorkItem?
    private var queue: [Reminder] = []
    private var isShowing = false

    /// Kept in sync with Config.sceneScale by AppDelegate.
    var scale: Double = 1.4
    /// Empty means rotate through the brands; otherwise a fixed palette name.
    var fixedPaletteName: String = ""

    /// True while a scene is on screen — lets the menu enable/disable Dismiss.
    var isOnScreen: Bool { isShowing }

    /// Queue a reminder. Shows immediately if the screen is free.
    func show(_ reminder: Reminder) {
        queue.append(reminder)
        drainQueue()
    }

    /// Close whatever's on screen now and drop anything queued behind it.
    /// This is the X button and the ⌃⌥⌘X hotkey.
    func dismissAll() {
        queue.removeAll()
        guard isShowing else { return }
        dismiss()
    }

    private func drainQueue() {
        guard !isShowing, !queue.isEmpty else { return }
        let reminder = queue.removeFirst()
        isShowing = true
        present(eyebrow: reminder.kind.eyebrow, line: reminder.spokenLine)
    }

    private func present(eyebrow: String, line: String) {
        // Use whichever screen the pointer is on, so it shows up where she's looking.
        let mouse = NSEvent.mouseLocation
        let screen = NSScreen.screens.first { NSMouseInRect(mouse, $0.frame, false) }
            ?? NSScreen.main
            ?? NSScreen.screens.first
        guard let screen else { return }
        hostScreen = screen

        let config = WKWebViewConfiguration()
        config.userContentController.add(self, name: "hud")
        // No page chrome, no scrollbars, no background.
        let web = WKWebView(frame: NSRect(origin: .zero, size: screen.frame.size),
                            configuration: config)
        web.setValue(false, forKey: "drawsBackground")
        web.autoresizingMask = [.width, .height]

        let win = NSWindow(contentRect: screen.frame,
                           styleMask: [.borderless],
                           backing: .buffered,
                           defer: false)
        win.isOpaque = false
        win.backgroundColor = .clear
        win.hasShadow = false
        // Above full-screen apps and normal windows, below the screen saver.
        win.level = NSWindow.Level(Int(CGWindowLevelForKey(.overlayWindow)) + 1)
        win.ignoresMouseEvents = true          // clicks pass straight through
        win.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary,
                                  .ignoresCycle]
        win.isReleasedWhenClosed = false
        win.contentView = web
        win.setFrame(screen.frame, display: false)
        // orderFrontRegardless keeps focus with whatever she's actually working in.
        win.orderFrontRegardless()

        window = win
        webView = web
        // A different brand each time it appears, unless one is pinned in Settings.
        let palette = Palette.named(fixedPaletteName) ?? Palette.advanceRotation()
        web.loadHTMLString(
            Alien.html(eyebrow: eyebrow, line: line, scale: scale, palette: palette),
            baseURL: nil)

        // Backstop: if the page's JS never reports in, close it anyway.
        let work = DispatchWorkItem { [weak self] in self?.dismiss() }
        teardown = work
        DispatchQueue.main.asyncAfter(deadline: .now() + Alien.sceneDuration + 1.2,
                                      execute: work)
    }

    // MARK: - Messages from the scene

    func userContentController(_ controller: WKUserContentController,
                              didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: Any],
              let type = body["t"] as? String else { return }

        switch type {
        case "closeRect":
            guard let x = body["x"] as? Double, let y = body["y"] as? Double,
                  let w = body["w"] as? Double, let h = body["h"] as? Double
            else { return }
            placeCatcher(cssRect: NSRect(x: x, y: y, width: w, height: h))
        case "closeGone":
            tearDownCatcher()
        case "done":
            dismiss()
        default:
            break
        }
    }

    /// Places the catcher over the X. `cssRect` is in web-view points, top-left origin.
    private func placeCatcher(cssRect: NSRect) {
        guard let screen = hostScreen else { return }
        tearDownCatcher()

        // The craft bobs up and down continuously, so the X drifts a few points
        // vertically. Pad the catcher to cover the whole travel.
        let padY = 11.0 * scale
        let padX = 3.0

        // Web-view coords are top-left origin; AppKit screen coords are bottom-left.
        let frame = NSRect(
            x: screen.frame.minX + cssRect.minX - padX,
            y: screen.frame.maxY - cssRect.maxY - padY,
            width: cssRect.width + padX * 2,
            height: cssRect.height + padY * 2)

        NSLog("[AlienHUD] catcher css=\(cssRect) screen=\(screen.frame) frame=\(frame)")

        let view = ClickCatcherView(frame: NSRect(origin: .zero, size: frame.size))
        view.onClick = { [weak self] in self?.dismissAll() }
        view.onHover = { [weak self] hot in
            self?.webView?.evaluateJavaScript("window.hudSetHot(\(hot))")
        }

        // A non-activating panel: it takes the click without pulling focus away from
        // whatever you're actually working in.
        let win = NSPanel(contentRect: frame,
                          styleMask: [.borderless, .nonactivatingPanel],
                          backing: .buffered,
                          defer: false)
        win.isFloatingPanel = true
        win.becomesKeyOnlyIfNeeded = true
        win.isOpaque = false
        win.backgroundColor = .clear
        win.hasShadow = false
        // One notch above the scene window so it gets the click.
        win.level = NSWindow.Level(Int(CGWindowLevelForKey(.overlayWindow)) + 2)
        win.ignoresMouseEvents = false
        win.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary,
                                  .ignoresCycle]
        win.isReleasedWhenClosed = false
        win.contentView = view
        win.orderFrontRegardless()

        closeWindow = win
    }

    private func tearDownCatcher() {
        closeWindow?.orderOut(nil)
        closeWindow?.contentView = nil
        closeWindow?.close()
        closeWindow = nil
    }

    private func dismiss() {
        teardown?.cancel()
        teardown = nil
        tearDownCatcher()

        webView?.configuration.userContentController
            .removeScriptMessageHandler(forName: "hud")
        webView?.stopLoading()
        webView?.removeFromSuperview()
        webView = nil

        window?.orderOut(nil)
        window?.contentView = nil
        window?.close()
        window = nil
        hostScreen = nil

        isShowing = false
        // Small gap so back-to-back reminders don't feel like one blur.
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            self?.drainQueue()
        }
    }
}
