from __future__ import annotations

import sys
import tomllib

from dataclasses import dataclass
from pathlib     import Path
from typing      import Any


VALID_SYNTAX_PALETTES: tuple[str, ...] = ("dark", "light", "mariana", "retro_blue")


@dataclass
class ThemePalette:
 window_bg:                    tuple[int, int, int] = (0, 0, 0)
 child_bg:                     tuple[int, int, int] = (0, 0, 0)
 popup_bg:                     tuple[int, int, int] = (0, 0, 0)
 border:                       tuple[int, int, int] = (60, 60, 60)
 border_shadow:                tuple[int, int, int] = (0, 0, 0)
 frame_bg:                     tuple[int, int, int] = (45, 45, 45)
 frame_bg_hovered:             tuple[int, int, int] = (60, 60, 60)
 frame_bg_active:              tuple[int, int, int] = (75, 75, 75)
 title_bg:                     tuple[int, int, int] = (40, 40, 40)
 title_bg_active:              tuple[int, int, int] = (60, 45, 15)
 title_bg_collapsed:           tuple[int, int, int] = (30, 30, 30)
 menu_bar_bg:                  tuple[int, int, int] = (35, 35, 35)
 scrollbar_bg:                 tuple[int, int, int] = (30, 30, 30)
 scrollbar_grab:               tuple[int, int, int] = (80, 80, 80)
 scrollbar_grab_hovered:       tuple[int, int, int] = (100, 100, 100)
 scrollbar_grab_active:        tuple[int, int, int] = (120, 120, 120)
 check_mark:                   tuple[int, int, int] = (200, 200, 200)
 slider_grab:                  tuple[int, int, int] = (60, 60, 60)
 slider_grab_active:           tuple[int, int, int] = (100, 100, 100)
 button:                       tuple[int, int, int] = (60, 60, 60)
 button_hovered:               tuple[int, int, int] = (100, 100, 100)
 button_active:                tuple[int, int, int] = (120, 120, 120)
 header:                       tuple[int, int, int] = (60, 60, 60)
 header_hovered:               tuple[int, int, int] = (100, 100, 100)
 header_active:                tuple[int, int, int] = (120, 120, 120)
 separator:                    tuple[int, int, int] = (60, 60, 60)
 separator_hovered:            tuple[int, int, int] = (100, 100, 100)
 separator_active:             tuple[int, int, int] = (200, 200, 200)
 resize_grip:                  tuple[int, int, int] = (60, 60, 60)
 resize_grip_hovered:          tuple[int, int, int] = (100, 100, 100)
 resize_grip_active:           tuple[int, int, int] = (200, 200, 200)
 tab:                          tuple[int, int, int] = (60, 60, 60)
 tab_hovered:                  tuple[int, int, int] = (100, 100, 100)
 tab_selected:                 tuple[int, int, int] = (100, 100, 100)
 tab_dimmed:                   tuple[int, int, int] = (60, 60, 60)
 tab_dimmed_selected:          tuple[int, int, int] = (100, 100, 100)
 docking_preview:              tuple[int, int, int] = (100, 100, 100)
 docking_empty_bg:             tuple[int, int, int] = (20, 20, 20)
 text:                         tuple[int, int, int] = (200, 200, 200)
 text_disabled:                tuple[int, int, int] = (130, 130, 130)
 text_selected_bg:             tuple[int, int, int] = (60, 100, 150)
 table_header_bg:              tuple[int, int, int] = (55, 55, 55)
 table_border_strong:          tuple[int, int, int] = (60, 60, 60)
 table_border_light:           tuple[int, int, int] = (40, 40, 40)
 table_row_bg:                 tuple[int, int, int] = (0, 0, 0)
 table_row_bg_alt:             tuple[int, int, int] = (10, 10, 10)
 nav_cursor:                   tuple[int, int, int] = (100, 100, 100)
 nav_windowing_dim_bg:         tuple[int, int, int] = (20, 20, 20)
 nav_windowing_highlight:      tuple[int, int, int] = (200, 200, 200)
 modal_window_dim_bg:          tuple[int, int, int] = (10, 10, 10)
 plot_lines:                   tuple[int, int, int] = (100, 100, 100)
 plot_lines_hovered:           tuple[int, int, int] = (200, 100, 100)
 plot_histogram:               tuple[int, int, int] = (100, 100, 100)
 plot_histogram_hovered:       tuple[int, int, int] = (200, 100, 100)
 drag_drop_target:             tuple[int, int, int] = (200, 200, 0)
 drag_drop_target_bg:          tuple[int, int, int] = (0, 0, 0)
 input_text_cursor:            tuple[int, int, int] = (200, 200, 200)
 tab_dimmed_selected_overline: tuple[int, int, int] = (100, 100, 100)
 tab_selected_overline:        tuple[int, int, int] = (100, 100, 100)
 text_link:                    tuple[int, int, int] = (60, 100, 150)
 tree_lines:                   tuple[int, int, int] = (60, 60, 60)
 unsaved_marker:               tuple[int, int, int] = (200, 200, 200)

 # Semantic colors
 status_success:  tuple[int, int, int] = (80, 255, 80)
 status_warning:  tuple[int, int, int] = (255, 152, 48)
 status_error:    tuple[int, int, int] = (255, 72, 64)
 status_info:     tuple[int, int, int] = (0, 255, 255)
 bubble_user:     tuple[int, int, int] = (30, 45, 75)
 bubble_ai:       tuple[int, int, int] = (35, 65, 45)
 bubble_vendor:   tuple[int, int, int] = (65, 55, 30)
 bubble_system:   tuple[int, int, int] = (25, 25, 25)
 slice_manual:    tuple[int, int, int] = (255, 165, 0)
 slice_auto:      tuple[int, int, int] = (0, 255, 0)
 slice_selection: tuple[int, int, int] = (100, 100, 255)
 
 # Diff colors
 diff_added:   tuple[int, int, int] = (51, 230, 51)
 diff_removed: tuple[int, int, int] = (230, 51, 51)
 diff_header:  tuple[int, int, int] = (77, 178, 255)

 @classmethod
 def from_dict(cls, data: dict[str, Any]) -> ThemePalette:
  kwargs: dict[str, Any] = {}
  for k, v in data.items():
   if hasattr(cls, k) and isinstance(v, (list, tuple)) and len(v) == 3:
    kwargs[k] = (int(v[0]), int(v[1]), int(v[2]))
  return cls(**kwargs)

 def to_dict(self) -> dict[str, Any]:
  return {k: list(v) for k, v in self.__dict__.items() if isinstance(v, tuple)}


@dataclass
class ThemeFile:
 name:           str
 palette:        ThemePalette
 syntax_palette: str
 source_path:    Path
 scope:          str
 description:    str = ""

 def __post_init__(self) -> None:
  if self.syntax_palette not in VALID_SYNTAX_PALETTES:
   raise ValueError(
    f"invalid syntax_palette '{self.syntax_palette}' in {self.source_path}; "
    f"must be one of {VALID_SYNTAX_PALETTES}"
   )

 def with_scope(self, scope: str) -> ThemeFile:
  return ThemeFile(
   name           = self.name,
   palette        = self.palette,
   syntax_palette = self.syntax_palette,
   source_path    = self.source_path,
   scope          = scope,
   description    = self.description,
  )

 def to_dict(self) -> dict[str, Any]:
  return {
   "syntax_palette": self.syntax_palette,
   "description": self.description,
   "colors": self.palette.to_dict(),
  }

 @classmethod
 def from_dict(cls, name: str, data: dict[str, Any], source_path: Path, scope: str) -> ThemeFile:
  if "colors" not in data:
   raise ValueError(
    f"theme file {source_path} is missing required [colors] section"
   )
  syntax_palette = data.get("syntax_palette", "dark")
  if syntax_palette not in VALID_SYNTAX_PALETTES:
   raise ValueError(
    f"invalid syntax_palette '{syntax_palette}' in {source_path}; "
    f"must be one of {VALID_SYNTAX_PALETTES}"
   )
  return cls(
   name           = name,
   palette        = ThemePalette.from_dict(data["colors"]),
   syntax_palette = syntax_palette,
   source_path    = source_path,
   scope          = scope,
   description    = str(data.get("description", "")),
  )


def load_theme_file(path: Path, scope: str) -> ThemeFile:
 if not path.exists():
  raise FileNotFoundError(f"theme file not found: {path}")
 try:
  with open(path, "rb") as f:
   data = tomllib.load(f)
 except Exception as e:
  raise ValueError(f"failed to parse theme TOML {path}: {e}") from e
 if not isinstance(data, dict):
  raise ValueError(f"theme TOML {path} must be a top-level table")
 name  = data.get("name", path.stem)
 theme = ThemeFile.from_dict(name, data, source_path=path, scope=scope)
 return theme


def load_themes_from_dir(path: Path, scope: str) -> dict[str, ThemeFile]:
 out: dict[str, ThemeFile] = {}
 if not path.exists():
  return out
 for child in sorted(path.iterdir()):
  if not child.is_file():
   continue
  if child.suffix.lower() != ".toml":
   continue
  try:
   theme = load_theme_file(child, scope=scope)
  except (FileNotFoundError, ValueError) as e:
   print(f"warning: {e}", file=sys.stderr)
   continue
  out[theme.name] = theme
 return out


def load_themes_from_toml(path: Path, scope: str) -> dict[str, ThemeFile]:
 out: dict[str, ThemeFile] = {}
 if not path.exists():
  return out
 try:
  with open(path, "rb") as f:
   data = tomllib.load(f)
 except Exception as e:
  print(f"warning: failed to parse {path}: {e}", file=sys.stderr)
  return out
 
 if not isinstance(data, dict): return out
 
 themes_sec = data.get("themes", {})
 if not isinstance(themes_sec, dict): return out
 
 for name, theme_data in themes_sec.items():
  if not isinstance(theme_data, dict): continue
  try:
   theme = ThemeFile.from_dict(name, theme_data, source_path=path, scope=scope)
  except ValueError as e:
   print(f"warning: {name}: {e}", file=sys.stderr)
   continue
  out[name] = theme
 return out
