# theme_2.py
"""
Theming support for manual_slop GUI — imgui-bundle port.

Replaces theme.py (DearPyGui-specific) with imgui-bundle equivalents.
Palettes are applied via imgui.get_style().set_color_() calls or hello_imgui.apply_theme().
Font loading uses hello_imgui.load_font().
Scale uses imgui.get_style().font_scale_main.
"""

import typing

from contextlib   import nullcontext
from imgui_bundle import imgui, hello_imgui
from typing       import Any, Optional

from src               import imgui_scopes as imscope
from src.module_loader import _require_warmed
from src.paths         import get_global_themes_path
from src.theme_models  import ThemeFile, load_themes_from_dir, load_themes_from_toml

# Removed top-level NERV imports (startup_speedup_20260606 Phase 5B).
# src.theme_nerv and src.theme_nerv_fx are warmed on AppController's _io_pool
# and accessed via _require_warmed() at the use sites below (NERV palette
# branch of apply(), ai_text_color() when is_nerv_active(), and render_post_fx()).


# ------------------------------------------------------------------ palettes

# Each palette maps imgui color enum values to (R, G, B, A) floats [0..1].
# Only keys that differ from the ImGui dark defaults need to be listed.
# ------------------------------------------------------------------ internal helpers

def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]:
 """Convert 0-255 RGBA to 0.0-1.0 floats."""
 return (r / 255.0, g / 255.0, b / 255.0, a / 255.0)

def _hex(rgb: tuple[int, int, int]) -> tuple[float, float, float, float]:
 return (rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0, 1.0)

def _build_imgui_colour_dict(theme: ThemeFile) -> dict[int, tuple[float, float, float, float]]:
 from imgui_bundle import imgui
 out: dict[int, tuple[float, float, float, float]] = {}
 palette_dict = theme.palette.to_dict()
 for col_name, rgb in palette_dict.items():
  if hasattr(imgui.Col_, col_name):
   enum_val = getattr(imgui.Col_, col_name)
   if isinstance(enum_val, int):
    out[enum_val] = _hex(rgb)
 return out

def _build_semantic_colour_dict(theme: ThemeFile) -> dict[str, tuple[float, float, float, float]]:
 out: dict[str, tuple[float, float, float, float]] = {}
 palette_dict = theme.palette.to_dict()
 from imgui_bundle import imgui
 for col_name, rgb in palette_dict.items():
  if not hasattr(imgui.Col_, col_name):
   out[col_name] = _hex(rgb)
 return out

# ------------------------------------------------------------------ state

_BUILTIN_PALETTES: dict[str, dict[int, tuple]] = {
 "ImGui Dark": {},
 "NERV":       {},
}

_TOML_PALETTES:       dict[str, ThemeFile] = {}
_TOML_COLOUR_CACHE:   dict[str, dict[int, tuple[float, float, float, float]]] = {}
_TOML_SEMANTIC_CACHE: dict[str, dict[str, tuple[float, float, float, float]]] = {}

_current_palette:    str = "10x Dark"
_current_font_path:  str = "fonts/Inter-Regular.ttf"
_current_font_size:  float = 16.0
_current_scale:      float = 1.0
_transparency:       float = 1.0
_child_transparency: float = 1.0

# Per-palette tone mapping: { "Palette Name": value }
_brightness: dict[str, float] = {}
_contrast:   dict[str, float] = {}
_gamma:      dict[str, float] = {}

def _get_tm(d: dict[str, float], palette: str, default: float) -> float:
 return d.get(palette, default)

def get_brightness(palette: str) -> float: return _get_tm(_brightness, palette, 1.0)
def get_contrast(palette: str)   -> float: return _get_tm(_contrast,   palette, 1.0)
def get_gamma(palette: str)      -> float: return _get_tm(_gamma,      palette, 1.0)

def set_brightness(palette: str, val: float) -> None: _brightness[palette] = val; apply(palette)
def set_contrast(palette: str, val: float)   -> None: _contrast[palette]   = val; apply(palette)
def set_gamma(palette: str, val: float)      -> None: _gamma[palette]      = val; apply(palette)

def reset_tone_mapping(palette: str) -> None:
 for d in [_brightness, _contrast, _gamma]:
  if palette in d: del d[palette]
 apply(palette)

def _tone_map(rgb: tuple[float, float, float, float], palette: str) -> tuple[float, float, float, float]:
 b, c, g = get_brightness(palette), get_contrast(palette), get_gamma(palette)
 r, g_val, bl, a = rgb
 # 1. Brightness
 r *= b; g_val *= b; bl *= b
 # 2. Contrast
 r = (r - 0.5) * c + 0.5; g_val = (g_val - 0.5) * c + 0.5; bl = (bl - 0.5) * c + 0.5
 # 3. Gamma
 r = max(0, r)**(1.0/g); g_val = max(0, g_val)**(1.0/g); bl = max(0, bl)**(1.0/g)
 return (max(0.0, min(1.0, r)), max(0.0, min(1.0, g_val)), max(0.0, min(1.0, bl)), a)

# NERV FX objects (CRTFilter, AlertPulsing, StatusFlicker) are now created
# lazily in render_post_fx() so the src.theme_nerv_fx import is deferred
# to first use. The static-level instantiations were removed in Phase 5B.

# ------------------------------------------------------------------ public API

def get_current_palette() -> str:
 return _current_palette

def is_nerv_active() -> bool:
 return _current_palette == "NERV"

def get_current_font_path() -> str:
 return _current_font_path

def get_current_font_size() -> float:
 return _current_font_size

def get_current_scale() -> float:
 return _current_scale

def get_transparency() -> float:
 return _transparency

def set_transparency(val: float) -> None:
 global _transparency
 _transparency = val
 apply(_current_palette)

def get_child_transparency() -> float:
 return _child_transparency

def set_child_transparency(val: float) -> None:
 global _child_transparency
 _child_transparency = val
 apply(_current_palette)

def get_table_color(is_alt: bool = False, alpha: float = 1.0) -> imgui.ImVec4:
 """Returns a tone-mapped table row background color."""
 key = "table_row_bg_alt" if is_alt else "table_row_bg"
 return get_color(key, alpha=alpha)

def get_color(name: str, alpha: float = 1.0) -> imgui.ImVec4:
 """Return a tone-mapped semantic color from the current palette."""
 palette_name = _current_palette
 
 # 1. Check TOML-loaded themes first
 if palette_name in _TOML_PALETTES:
  theme_file = _TOML_PALETTES[palette_name]
  
  # A. Check semantic cache (for non-ImGui keys)
  if palette_name in _TOML_SEMANTIC_CACHE:
   d = _TOML_SEMANTIC_CACHE[palette_name]
   if name in d:
    rgba    = list(d[name])
    rgba[3] = alpha
    return imgui.ImVec4(*_tone_map(tuple(rgba), palette_name))
    
  # B. Check ThemePalette dataclass fields (for basics like 'text')
  if hasattr(theme_file.palette, name):
   rgb = getattr(theme_file.palette, name)
   if isinstance(rgb, tuple) and len(rgb) == 3:
    return imgui.ImVec4(*_tone_map((rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0, alpha), palette_name))

 # 2. Hardcoded fallbacks if not in TOML (matches ThemePalette defaults)
 fallbacks = {
  "text":              (200, 200, 200),
  "text_disabled":     (130, 130, 130),
  "status_success":    (80, 255, 80),
  "status_warning":    (255, 152, 48),
  "status_error":      (255, 72, 64),
  "status_info":       (0, 255, 255),
  "bubble_user":       (30, 45, 75),
  "bubble_ai":         (35, 65, 45),
  "bubble_vendor":     (65, 55, 30),
  "bubble_system":     (25, 25, 25),
  "slice_manual":      (255, 165, 0),
  "slice_auto":        (0, 255, 0),
  "slice_selection":   (100, 100, 255),
  "diff_added":        (51, 230, 51),
  "diff_removed":      (230, 51, 51),
  "diff_header":       (77, 178, 255),
  "table_header_text": (255, 255, 255),
  "table_row_bg":      (0, 0, 0),
  "table_row_bg_alt":  (10, 10, 10),
 }
 if name in fallbacks:
  rgb = fallbacks[name]
  return imgui.ImVec4(*_tone_map((rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0, alpha), palette_name))
 
 # 3. Last resort
 return imgui.ImVec4(1, 1, 1, alpha)

def get_role_tint(role: str) -> imgui.ImVec4:
 """Returns a subtle background tint color based on the message role."""
 mapping = {
  "User":       "bubble_user",
  "AI":         "bubble_ai",
  "Vendor API": "bubble_vendor",
 }
 return get_color(mapping.get(role, "bubble_system"), alpha=0.6)

def apply(palette_name: str) -> None:
 """Apply a named palette by setting all ImGui style colors and professional styling."""
 global _current_palette
 _current_palette = palette_name
 if palette_name == 'NERV':
  theme_nerv = _require_warmed("src.theme_nerv")
  theme_nerv.apply_nerv()
  apply_syntax_palette(get_syntax_palette_for_theme(palette_name))
  return
 
 # 1. Apply base colors
 if palette_name in _BUILTIN_PALETTES:
  colours = _BUILTIN_PALETTES[palette_name]
  imgui.style_colors_dark()
  style = imgui.get_style()
  for col_enum, rgba in colours.items():
   style.set_color_(col_enum, imgui.ImVec4(*_tone_map(rgba, palette_name)))
 elif palette_name in _TOML_PALETTES:
  colours = _TOML_COLOUR_CACHE.get(palette_name, {})
  if not colours:
   theme   = _TOML_PALETTES[palette_name]
   colours = _build_imgui_colour_dict(theme)
   _TOML_COLOUR_CACHE[palette_name] = colours
  imgui.style_colors_dark()
  style = imgui.get_style()
  for colenum, rgba in colours.items():
   style.set_color_(colenum, imgui.ImVec4(*_tone_map(rgba, palette_name)))
 elif hasattr(hello_imgui.ImGuiTheme_, palette_name):
  theme_enum = getattr(hello_imgui.ImGuiTheme_, palette_name)
  hello_imgui.apply_theme(theme_enum)
 else:
  imgui.style_colors_dark()

 # 2. Professional tweaks
 style = imgui.get_style()
 style.window_rounding    = 6.0
 style.child_rounding     = 4.0
 style.frame_rounding     = 4.0
 style.popup_rounding     = 4.0
 style.scrollbar_rounding = 12.0
 style.grab_rounding      = 4.0
 style.tab_rounding       = 4.0
 style.window_border_size = 1.0
 style.frame_border_size  = 1.0
 style.popup_border_size  = 1.0
 
 win_bg   = style.color_(imgui.Col_.window_bg)
 win_bg.w = _transparency
 style.set_color_(imgui.Col_.window_bg, win_bg)
 
 for col_idx in [imgui.Col_.child_bg, imgui.Col_.frame_bg, imgui.Col_.popup_bg]:
  c   = style.color_(col_idx)
  c.w = _child_transparency
  style.set_color_(col_idx, c)
 
 style.window_padding             = imgui.ImVec2(8.0, 8.0)
 style.frame_padding              = imgui.ImVec2(8.0, 4.0)
 style.item_spacing               = imgui.ImVec2(8.0, 4.0)
 style.item_inner_spacing         = imgui.ImVec2(4.0, 4.0)
 style.scrollbar_size             = 14.0
 style.anti_aliased_lines         = True
 style.anti_aliased_fill          = True
 style.anti_aliased_lines_use_tex = True

 # 3. Sync syntax palette and clear markdown render cache
 apply_syntax_palette(get_syntax_palette_for_theme(palette_name))
 try:
  import src.markdown_helper
  src.markdown_helper.get_renderer().clear_cache()
 except (ImportError, AttributeError):
  pass

def apply_current() -> None:
 """Apply the loaded palette and scale."""
 apply(_current_palette)
 set_scale(_current_scale)

def get_palette_names() -> list[str]:
 """Returns a list of all available palettes."""
 names = list(_BUILTIN_PALETTES.keys())
 names.extend(sorted(_TOML_PALETTES.keys()))
 hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count']
 hi_themes = [n for n in hi_themes if not hasattr(int, n)]
 names.extend(sorted(hi_themes))
 return names

# ------------------------------------------------------------------ persistence

def save_to_config(config: dict) -> None:
 """Persist theme settings into the config dict."""
 config.setdefault("theme", {})
 config["theme"]["palette"]            = _current_palette
 config["theme"]["font_path"]          = _current_font_path
 config["theme"]["font_size"]          = _current_font_size
 config["theme"]["scale"]              = _current_scale
 config["theme"]["transparency"]       = _transparency
 config["theme"]["child_transparency"] = _child_transparency
 tm = {}
 for p in set(list(_brightness.keys()) + list(_contrast.keys()) + list(_gamma.keys())):
  tm[p] = {
   "brightness": _brightness.get(p, 1.0),
   "contrast":   _contrast.get(p, 1.0),
   "gamma":      _gamma.get(p, 1.0)
  }
 config["theme"]["tone_mapping"] = tm

def load_from_config(config: dict) -> None:
 """Read [theme] from config."""
 import sys
 global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _brightness, _contrast, _gamma
 t = config.get("theme", {})
 _current_palette = t.get("palette", "10x Dark")
 if _current_palette in ("", "DPG Default"):
  _current_palette = "10x Dark"
 _current_font_path  = t.get("font_path", "fonts/Inter-Regular.ttf")
 _current_font_size  = float(t.get("font_size",          16.0))
 _current_scale      = float(t.get("scale",              1.0))
 _transparency       = float(t.get("transparency",       1.0))
 _child_transparency = float(t.get("child_transparency", 1.0))
 tm          = t.get("tone_mapping", {})
 _brightness = {p: float(v.get("brightness", 1.0)) for p, v in tm.items()}
 _contrast   = {p: float(v.get("contrast",   1.0)) for p, v in tm.items()}
 _gamma      = {p: float(v.get("gamma",      1.0)) for p, v in tm.items()}

# ------------------------------------------------------------------ external themes

def load_themes_from_disk() -> None:
 """Load all themes from the global themes directory."""
 global _TOML_PALETTES, _TOML_COLOUR_CACHE, _TOML_SEMANTIC_CACHE
 themes_dir = get_global_themes_path()
 loaded: dict[str, ThemeFile] = {}
 if themes_dir.exists() and themes_dir.is_dir():
  loaded.update(load_themes_from_dir(themes_dir, scope="global"))
 _TOML_PALETTES       = loaded
 _TOML_COLOUR_CACHE   = {name: _build_imgui_colour_dict(t)    for name, t in loaded.items()}
 _TOML_SEMANTIC_CACHE = {name: _build_semantic_colour_dict(t) for name, t in loaded.items()}

def get_syntax_palette_for_theme(theme_name: str) -> str:
 """Return the syntax palette name (one of dark/light/mariana/retro_blue)
 associated with the given UI theme. Falls back to 'dark' for unknown
 themes and for non-TOML built-ins."""
 if theme_name in _TOML_PALETTES:
  return _TOML_PALETTES[theme_name].syntax_palette
 return "dark"

def apply_syntax_palette(palette_name: str) -> None:
 """Set the default imgui_color_text_edit palette. palette_name must
 be one of: dark, light, mariana, retro_blue. No-op for unknown names."""
 from imgui_bundle import imgui_color_text_edit as ed
 if not hasattr(ed.TextEditor, "PaletteId"):
  return
 palette_id = getattr(ed.TextEditor.PaletteId, palette_name, None)
 if palette_id is None:
  return
 ed.TextEditor.set_default_palette(palette_id)

# ------------------------------------------------------------------ font & scaling

def set_scale(factor: float) -> None:
 global _current_scale
 _current_scale = factor
 imgui.get_style().font_scale_main = factor

def get_font_loading_params() -> tuple[str, float]:
 return _current_font_path, _current_font_size

def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme:
 tt = hello_imgui.ImGuiTweakedTheme()
 if hasattr(hello_imgui.ImGuiTheme_, _current_palette):
  tt.theme = getattr(hello_imgui.ImGuiTheme_, _current_palette)
 else:
  tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark
 tt.tweaks.rounding = 6.0
 return tt

# ------------------------------------------------------------------ specialized colors

def ai_text_color() -> imgui.ImVec4:
 if is_nerv_active():
  theme_nerv = _require_warmed("src.theme_nerv")
  return imgui.ImVec4(*theme_nerv.DATA_GREEN)
 return imgui.get_style().color_(imgui.Col_.text)

def ai_text_style():
 return imscope.style_color(imgui.Col_.text, ai_text_color())

 """Returns a subtle background tint color based on the message role."""
def render_post_fx(width: float, height: float, ai_status: str, crt_enabled: bool) -> None:
 """Updates and renders the alert and CRT filters."""
 theme_nerv_fx = _require_warmed("src.theme_nerv_fx")
 alert_pulsing = theme_nerv_fx.AlertPulsing()
 crt_filter = theme_nerv_fx.CRTFilter()
 alert_pulsing.update(ai_status)
 alert_pulsing.render(width, height)
 crt_filter.enabled = crt_enabled
 crt_filter.render(width, height)

# ------------------------------------------------------------------ init
load_themes_from_disk()
