"""macOS AI response overlay using NSPanel (PyObjC).

Displays streaming AI responses in a floating panel that stays above all windows.

Visual states:
  - THINKING:  blue background + pulsing "⏳ Thinking..." → hotkey pressed
  - STREAMING: dark background + live text                → AI responding
  - DONE:      green accent bar + final text              → response ready
  - ERROR:     red accent bar + error message              → something failed
"""

from __future__ import annotations

import logging
import threading
from collections.abc import Callable

log = logging.getLogger(__name__)

# Visual state constants
_STATE_IDLE = "idle"
_STATE_THINKING = "thinking"
_STATE_STREAMING = "streaming"
_STATE_DONE = "done"
_STATE_ERROR = "error"


class OverlayWindow:
    """macOS overlay using NSPanel via PyObjC."""

    def __init__(
        self,
        on_insert: Callable[[str], None] | None = None,
        on_cancel: Callable[[], None] | None = None,
    ) -> None:
        self._on_insert = on_insert
        self._on_cancel = on_cancel
        self._full_text = ""
        self._panel = None
        self._text_view = None
        self._status_bar = None
        self._initialized = False
        self._lock = threading.Lock()
        self._state = _STATE_IDLE

    def _ensure_initialized(self) -> bool:
        """Lazily create the NSPanel on first use (must be on main thread)."""
        if self._initialized:
            return True
        try:
            import AppKit  # type: ignore[import]

            # Get screen size to center the panel
            screen = AppKit.NSScreen.mainScreen()
            if screen:
                sf = screen.frame()
                panel_x = (sf.size.width - 420) / 2
                panel_y = (sf.size.height - 320) / 2
            else:
                panel_x, panel_y = 200, 200

            # NSPanel: non-activating, borderless, always on top
            # NSNonactivatingPanelMask (1 << 7 = 128) keeps focus on the previous app
            style_mask = 1 << 7  # NSNonactivatingPanelMask

            self._panel = AppKit.NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
                AppKit.NSMakeRect(panel_x, panel_y, 420, 320),
                style_mask,
                AppKit.NSBackingStoreBuffered,
                False,
            )

            # Level high enough to float above everything (including SEB kiosk ~1000)
            self._panel.setLevel_(1100)
            self._panel.setOpaque_(False)
            self._panel.setBackgroundColor_(
                AppKit.NSColor.colorWithCalibratedRed_green_blue_alpha_(0.12, 0.12, 0.12, 0.92)
            )
            self._panel.setHasShadow_(True)
            self._panel.setMovableByWindowBackground_(True)
            # Keep visible when app is not active (critical for LSUIElement apps)
            self._panel.setHidesOnDeactivate_(False)
            # Visible on all Spaces / full-screen apps
            self._panel.setCollectionBehavior_(
                AppKit.NSWindowCollectionBehaviorCanJoinAllSpaces
                | AppKit.NSWindowCollectionBehaviorStationary
                | AppKit.NSWindowCollectionBehaviorFullScreenAuxiliary
            )

            content = self._panel.contentView()
            content_frame = content.frame()
            w = content_frame.size.width
            h = content_frame.size.height

            # Status bar at top (4px height)
            self._status_bar = AppKit.NSView.alloc().initWithFrame_(
                AppKit.NSMakeRect(0, h - 4, w, 4)
            )
            self._status_bar.setAutoresizingMask_(
                AppKit.NSViewWidthSizable | AppKit.NSViewMinYMargin
            )
            self._status_bar.setWantsLayer_(True)
            self._status_bar.layer().setBackgroundColor_(
                AppKit.NSColor.colorWithCalibratedRed_green_blue_alpha_(
                    0.2, 0.5, 1.0, 1.0
                ).CGColor()
            )
            content.addSubview_(self._status_bar)

            # Scroll view (below status bar)
            scroll_frame = AppKit.NSMakeRect(0, 0, w, h - 4)
            scroll = AppKit.NSScrollView.alloc().initWithFrame_(scroll_frame)
            scroll.setHasVerticalScroller_(True)
            scroll.setAutoresizingMask_(AppKit.NSViewWidthSizable | AppKit.NSViewHeightSizable)

            # Text view
            self._text_view = AppKit.NSTextView.alloc().initWithFrame_(scroll_frame)
            self._text_view.setEditable_(False)
            self._text_view.setSelectable_(True)
            self._text_view.setBackgroundColor_(AppKit.NSColor.clearColor())
            self._text_view.setTextColor_(AppKit.NSColor.colorWithCalibratedWhite_alpha_(0.9, 1.0))
            font = AppKit.NSFont.systemFontOfSize_(13.0)
            self._text_view.setFont_(font)

            scroll.setDocumentView_(self._text_view)
            content.addSubview_(scroll)

            self._initialized = True
            log.debug("macOS overlay panel initialized")
            return True

        except ImportError:
            log.error("PyObjC/AppKit not available — overlay disabled on macOS")
            return False
        except Exception as exc:
            log.error("Failed to initialize macOS overlay: %s", exc)
            return False

    # ------------------------------------------------------------------ #
    # Public API
    # ------------------------------------------------------------------ #

    def show_thinking(self) -> None:
        """Show overlay in THINKING state — blue bar + spinner text."""
        self._state = _STATE_THINKING
        self._full_text = ""
        self._run_on_main(self._do_show_thinking)

    def show_response(self, initial_text: str = "") -> None:
        """Show the overlay panel with text (STREAMING state)."""
        self._full_text = initial_text
        self._state = _STATE_STREAMING
        self._run_on_main(self._do_show, initial_text)

    def append_chunk(self, chunk: str) -> None:
        """Append a text chunk (thread-safe)."""
        self._full_text += chunk
        if self._state == _STATE_THINKING:
            # Transition from thinking to streaming on first chunk
            self._state = _STATE_STREAMING
            self._run_on_main(self._do_transition_to_streaming)
        self._run_on_main(self._do_append, chunk)

    def show_done(self) -> None:
        """Transition to DONE state — green bar."""
        self._state = _STATE_DONE
        self._run_on_main(self._do_set_bar_color, 0.2, 0.8, 0.3)  # green

    def show_error(self, message: str = "") -> None:
        """Transition to ERROR state — red bar."""
        self._state = _STATE_ERROR
        if message:
            self._full_text += message
            self._run_on_main(self._do_append, message)
        self._run_on_main(self._do_set_bar_color, 0.9, 0.2, 0.2)  # red

    def hide_overlay(self) -> None:
        """Hide the panel."""
        self._state = _STATE_IDLE
        self._run_on_main(self._do_hide)

    def apply_theme(self, theme: str) -> None:
        """Update the overlay appearance to match the given theme."""
        self._run_on_main(self._do_apply_theme, theme)

    def retranslate_ui(self) -> None:
        """No-op: macOS overlay has no translatable UI elements."""

    def get_text(self) -> str:
        return self._full_text

    # ------------------------------------------------------------------ #
    # Main-thread operations
    # ------------------------------------------------------------------ #

    def _run_on_main(self, fn, *args) -> None:
        """Schedule *fn* on the main thread via GCD."""
        try:
            import AppKit

            AppKit.NSOperationQueue.mainQueue().addOperationWithBlock_(lambda: fn(*args))
        except Exception:
            log.debug("GCD dispatch failed, calling directly", exc_info=True)
            fn(*args)

    def _do_show_thinking(self) -> None:
        """Show overlay in thinking state: blue bar + placeholder text."""
        if not self._ensure_initialized():
            log.error("Overlay init failed — cannot show thinking state")
            return
        self._do_set_bar_color(0.2, 0.5, 1.0)  # blue
        if self._text_view:
            self._text_view.setString_("\u23f3 Thinking...")
        self._panel.orderFrontRegardless()
        log.info("Overlay shown: THINKING (level=%s)", self._panel.level())

    def _do_transition_to_streaming(self) -> None:
        """Transition from thinking to streaming: change bar to orange."""
        self._do_set_bar_color(1.0, 0.6, 0.0)  # orange = streaming
        if self._text_view:
            self._text_view.setString_("")  # clear "Thinking..." text

    def _do_show(self, text: str) -> None:
        if not self._ensure_initialized():
            log.error("Overlay init failed — cannot show response")
            return
        self._do_set_bar_color(1.0, 0.6, 0.0)  # orange = streaming
        if self._text_view:
            self._text_view.setString_(text)
        self._panel.orderFrontRegardless()
        log.info("Overlay shown: STREAMING (level=%s)", self._panel.level())

    def _do_append(self, chunk: str) -> None:
        if not self._text_view:
            return
        current = self._text_view.string() or ""
        self._text_view.setString_(current + chunk)
        self._text_view.scrollRangeToVisible_((len(current + chunk), 0))

    def _do_set_bar_color(self, r: float, g: float, b: float) -> None:
        """Change the status bar color."""
        if not self._status_bar:
            return
        try:
            import AppKit

            color = AppKit.NSColor.colorWithCalibratedRed_green_blue_alpha_(r, g, b, 1.0)
            self._status_bar.layer().setBackgroundColor_(color.CGColor())
        except Exception:
            log.debug("Failed to set status bar color", exc_info=True)

    def _do_hide(self) -> None:
        if self._panel:
            self._panel.orderOut_(None)
            log.debug("Overlay hidden")

    def _do_apply_theme(self, theme: str) -> None:
        """Apply NSAppearance and colors for the given theme (main thread)."""
        if not self._panel:
            return
        try:
            import AppKit

            if theme == "dark":
                bg_color = AppKit.NSColor.colorWithCalibratedRed_green_blue_alpha_(
                    0.12, 0.12, 0.12, 0.92
                )
                text_color = AppKit.NSColor.colorWithCalibratedWhite_alpha_(0.9, 1.0)
                try:
                    appearance = AppKit.NSAppearance.appearanceNamed_("NSAppearanceNameDarkAqua")
                    self._panel.setAppearance_(appearance)
                except Exception:
                    log.debug("Dark appearance unavailable", exc_info=True)
            else:
                bg_color = AppKit.NSColor.colorWithCalibratedRed_green_blue_alpha_(
                    0.95, 0.95, 0.95, 0.95
                )
                text_color = AppKit.NSColor.colorWithCalibratedWhite_alpha_(0.1, 1.0)
                try:
                    appearance = AppKit.NSAppearance.appearanceNamed_("NSAppearanceNameAqua")
                    self._panel.setAppearance_(appearance)
                except Exception:
                    log.debug("Aqua appearance unavailable", exc_info=True)

            self._panel.setBackgroundColor_(bg_color)
            if self._text_view:
                self._text_view.setTextColor_(text_color)
        except ImportError:
            pass
        except Exception as exc:
            log.warning("Failed to apply theme to macOS overlay: %s", exc)
