"""
Compact horizontal block-type timeline shown after sequence loading.

Each sync block is rendered as a proportional coloured segment.
Background, text and chrome colours are derived from the OS palette at paint
time so the widget looks correct in both light and dark OS themes.
Click or hover to inspect; emits blockClicked(sync_index).
"""
from __future__ import annotations

from typing import List, Optional

from PySide6.QtCore import Signal, Qt, QRect
from PySide6.QtGui import (
    QPainter, QColor, QPen, QFont, QFontMetrics,
)
from PySide6.QtWidgets import QWidget, QToolTip, QSizePolicy

from src.gui.adapters import BlockRow


def system_is_dark() -> bool:
    """Return True when the app is running in dark mode."""
    from src import theme as _theme
    return _theme.is_dark()

# -- block colour map (semantic, theme-neutral vivid hues) --------------------

def _row_color(row: BlockRow) -> QColor:
    if row.is_delay:
        return QColor({
            "START": "#f48fb1",   # pink
            "RF":    "#ffcc80",   # amber
            "TR":    "#90caf9",   # light-blue
        }.get(row.delay_type, "#bdbdbd"))
    types = set(row.block_type.split(", "))
    if "RF" in types and "ADC" in types:
        return QColor("#ffb74d")   # orange  - mixed
    if "RF" in types:
        return QColor("#e53935")   # red
    if "ADC" in types:
        return QColor("#43a047")   # green
    if "GRAD" in types:
        return QColor("#8e24aa")   # purple
    return QColor("#bdbdbd")       # gray    - plain / empty


_LEGEND = [
    ("#f48fb1", "Start dly"),
    ("#ffcc80", "RF dly"),
    ("#90caf9", "TR dly"),
    ("#e53935", "RF"),
    ("#43a047", "ADC"),
    ("#8e24aa", "Grad"),
    ("#ffb74d", "RF+ADC"),
    ("#bdbdbd", "Delay"),
]

# -- geometry constants --------------------------------------------------------

_LEGEND_H = 16   # legend swatch row height (px)
_BAR_H    = 28   # coloured block bar height (px)
_TICK_H   = 18   # time-tick label row height (px)
_TOTAL_H  = _LEGEND_H + _BAR_H + _TICK_H   # 62 px total

_TICK_FONT   = QFont("Arial", 7)
_LEGEND_FONT = QFont("Arial", 7)


class SchemePanel(QWidget):
    """
    Proportional block-type timeline.  Adapts to the OS light/dark palette -
    background, text and chrome colours are derived from QPalette at paint time.
    Emits blockClicked(sync_index) on left-click.
    """
    blockClicked = Signal(int)

    def __init__(self, parent: QWidget | None = None):
        super().__init__(parent)
        # WA_OpaquePaintEvent: Qt won't pre-clear background before paintEvent.
        # WA_NoSystemBackground: OS won't paint its own background colour.
        # Both are still needed - we fill explicitly from the palette instead of
        # a hardcoded white, and these attributes prevent any flicker or bleed.
        self.setAttribute(Qt.WA_OpaquePaintEvent, True)
        self.setAttribute(Qt.WA_NoSystemBackground, True)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.setFixedHeight(_TOTAL_H)
        self.setMouseTracking(True)
        self._rows: List[BlockRow] = []
        self._total_dur: float = 0.0
        self._selected: int = -1
        self._hovered: int = -1

    # -- public ---------------------------------------------------------------

    def load_rows(self, rows: List[BlockRow]) -> None:
        self._rows = rows
        self._total_dur = rows[-1].t_end if rows else 0.0
        self._hovered = -1
        self.update()

    def select_block(self, sync_index: int) -> None:
        if self._selected != sync_index:
            self._selected = sync_index
            self.update()

    def clear(self) -> None:
        self._rows = []
        self._total_dur = 0.0
        self._selected = -1
        self._hovered = -1
        self.update()

    # -- palette helper -------------------------------------------------------

    def _theme_colors(self) -> dict:
        """Return explicit paint-time colours for the current OS theme.

        Explicit hex values are used instead of QPalette roles because on
        Windows 11 the widget-area palette may not reflect dark mode even
        when the title bar is dark (see system_is_dark()).
        """
        if system_is_dark():
            return {
                "bg":          QColor("#1a1a2e"),
                "text":        QColor("#ddddff"),
                "tick":        QColor("#666688"),
                "border":      QColor("#333355"),
                "placeholder": QColor("#555577"),
                "ph_bg":       QColor("#16162a"),
                "sel_border":  QColor("#f0c040"),
            }
        return {
            "bg":          QColor("#f0f0f8"),
            "text":        QColor("#1a1a2e"),
            "tick":        QColor("#555577"),
            "border":      QColor("#aaaacc"),
            "placeholder": QColor("#7777aa"),
            "ph_bg":       QColor("#e8e8f8"),
            "sel_border":  QColor("#e65100"),
        }

    # -- painting -------------------------------------------------------------

    def paintEvent(self, _event) -> None:
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing, False)

        tc = self._theme_colors()
        # First draw call: fill our own background from the OS palette so
        # no OS-theme bleed can appear in gaps between painted rectangles.
        p.fillRect(self.rect(), tc["bg"])

        if not self._rows or self._total_dur == 0:
            self._paint_placeholder(p, tc)
        else:
            self._paint_legend(p, tc)
            self._paint_blocks(p, tc)
            self._paint_ticks(p, tc)

        p.end()

    def _paint_placeholder(self, p: QPainter, tc: dict) -> None:
        p.fillRect(self.rect(), tc["ph_bg"])
        p.setPen(tc["placeholder"])
        p.setFont(QFont("Arial", 9))
        p.drawText(self.rect(), Qt.AlignCenter,
                   "Load and run a .seq file to see the sequence scheme")

    def _paint_legend(self, p: QPainter, tc: dict) -> None:
        p.setFont(_LEGEND_FONT)
        fm = QFontMetrics(_LEGEND_FONT)
        x = 4
        swatch_w = 12
        swatch_h = _LEGEND_H - 4
        swatch_y = 2
        for color_hex, label in _LEGEND:
            # coloured swatch with thin border
            p.fillRect(x, swatch_y, swatch_w, swatch_h, QColor(color_hex))
            p.setPen(QPen(tc["border"], 1))
            p.drawRect(x, swatch_y, swatch_w - 1, swatch_h - 1)
            x += swatch_w + 2
            # label text
            text_w = fm.horizontalAdvance(label) + 4
            p.setPen(tc["text"])
            p.drawText(x, 0, text_w, _LEGEND_H, Qt.AlignVCenter | Qt.AlignLeft, label)
            x += text_w + 6

    def _paint_blocks(self, p: QPainter, tc: dict) -> None:
        w     = self.width()
        y0    = _LEGEND_H
        bar_h = _BAR_H
        scale = w / self._total_dur

        # Subsample dense sequences so we never draw more than 3000 rects
        rows = self._rows
        if len(rows) > 3000:
            step = len(rows) // 3000 + 1
            rows = rows[::step]

        for row in rows:
            x  = int(row.t_start * scale)
            bw = max(1, int((row.t_end - row.t_start) * scale))
            color = _row_color(row)

            if row.sync_index == self._selected:
                p.fillRect(x, y0, bw, bar_h, color.lighter(150))
                p.setPen(QPen(tc["sel_border"], 1))
                p.drawRect(x, y0, max(0, bw - 1), bar_h - 1)
            elif row.sync_index == self._hovered:
                p.fillRect(x, y0, bw, bar_h, color.lighter(125))
            else:
                p.fillRect(x, y0, bw, bar_h, color)

        # bottom border line
        p.setPen(QPen(tc["border"], 1))
        p.drawLine(0, y0 + bar_h, w, y0 + bar_h)

    def _paint_ticks(self, p: QPainter, tc: dict) -> None:
        w       = self.width()
        tick_y  = _LEGEND_H + _BAR_H      # top of tick row
        label_y = tick_y + 4
        scale   = w / self._total_dur
        p.setFont(_TICK_FONT)
        p.setPen(tc["tick"])
        n_ticks = max(2, w // 90)
        for i in range(n_ticks + 1):
            t      = self._total_dur * i / n_ticks
            x_tick = int(t * scale)
            p.drawLine(x_tick, tick_y, x_tick, tick_y + 3)
            label = (f"{t * 1e3:.2f}ms" if self._total_dur >= 1e-3
                     else f"{t * 1e6:.0f}uss")
            p.drawText(x_tick + 2, label_y, 70, _TICK_H - 4,
                       Qt.AlignLeft | Qt.AlignTop, label)

    # -- mouse ----------------------------------------------------------------

    def mousePressEvent(self, event) -> None:
        row = self._row_at(event.position().x())
        if row is not None:
            self._selected = row.sync_index
            self.update()
            self.blockClicked.emit(row.sync_index)

    def mouseMoveEvent(self, event) -> None:
        row = self._row_at(event.position().x())
        new_h = row.sync_index if row else -1
        if new_h != self._hovered:
            self._hovered = new_h
            self.update()
        if row is not None:
            tip = (
                f"Block #{row.sync_index}  [{row.block_type}]\n"
                f"t = {row.t_start * 1e6:.2f} - {row.t_end * 1e6:.2f} uss  "
                f"({(row.t_end - row.t_start) * 1e6:.2f} uss)"
            )
            if row.is_delay:
                tip += f"\n-> Inserted {row.delay_type} delay"
            QToolTip.showText(event.globalPosition().toPoint(), tip, self)
        else:
            QToolTip.hideText()

    def leaveEvent(self, _event) -> None:
        if self._hovered != -1:
            self._hovered = -1
            self.update()

    # -- helper ---------------------------------------------------------------

    def _row_at(self, x_px: float) -> Optional[BlockRow]:
        if not self._rows or self._total_dur == 0 or self.width() == 0:
            return None
        t = x_px / self.width() * self._total_dur
        for row in self._rows:
            if row.t_start <= t < row.t_end:
                return row
        return None
