"""Load pgt terminal colors from the user's Git configuration."""

from __future__ import annotations

import json
from dataclasses import dataclass
from importlib.resources import files
from typing import TYPE_CHECKING

from pygit2 import Config
from pyratatui import Color, Modifier, Style

if TYPE_CHECKING:
    from pygit2 import Repository

_NAMED_RGB: dict[str, tuple[int, int, int]] = {
    "normal": (204, 204, 204),
    "black": (0, 0, 0),
    "red": (170, 0, 0),
    "green": (0, 170, 0),
    "yellow": (170, 170, 0),
    "blue": (0, 0, 170),
    "magenta": (170, 0, 170),
    "cyan": (0, 170, 170),
    "white": (204, 204, 204),
}

_BRIGHT_NAMED_RGB: dict[str, tuple[int, int, int]] = {
    "black": (85, 85, 85),
    "red": (255, 0, 0),
    "green": (0, 255, 0),
    "yellow": (255, 255, 0),
    "blue": (0, 0, 255),
    "magenta": (255, 0, 255),
    "cyan": (0, 255, 255),
    "white": (255, 255, 255),
}

_DEFAULT_SPECS: dict[str, str] = {
    "color.status.added": "green",
    "color.status.changed": "red",
    "color.status.untracked": "red",
    "color.status.header": "cyan",
    "color.status.branch": "cyan",
    "color.status.nobranch": "red bold",
    "color.branch.current": "cyan",
    "color.branch.local": "cyan",
    "color.diff.new": "green",
    "color.diff.old": "red",
    "color.diff.context": "normal",
    "color.decorate.commit": "yellow",
}

_DEFAULT_SECTION_HEADER_SPEC = "252"
_DEFAULT_LOG_DATE_SPEC = "246"
_FALSE_VALUES = frozenset({"0", "false", "never", "no", "off"})


@dataclass(frozen=True, slots=True)
class ParsedGitColor:
    foreground: tuple[int, int, int] | None
    background: tuple[int, int, int] | None
    bold: bool = False
    dim: bool = False
    reverse: bool = False
    underline: bool = False


@dataclass(frozen=True, slots=True)
class TuiColorTheme:
    enabled: bool
    status_bar: Style
    tab_active: Style
    tab_inactive: Style
    focus: Style
    staged: Style
    unstaged: Style
    untracked: Style
    section_header: Style
    header: Style
    branch: Style
    commit: Style
    log_hash: Style
    log_date: Style
    log_subject: Style
    normal: Style


def _style() -> Style:
    return Style()


_theme = TuiColorTheme(
    enabled=False,
    status_bar=_style().add_modifier(Modifier.reversed()),
    tab_active=_style().bold(),
    tab_inactive=_style(),
    focus=_style().add_modifier(Modifier.reversed()),
    staged=_style(),
    unstaged=_style(),
    untracked=_style(),
    section_header=_style(),
    header=_style().bold(),
    branch=_style(),
    commit=_style(),
    log_hash=_style(),
    log_date=_style(),
    log_subject=_style(),
    normal=_style(),
)


def current_theme() -> TuiColorTheme:
    return _theme


def status_bar_style() -> Style:
    return _theme.status_bar


def active_tab_style() -> Style:
    return _theme.tab_active


def init_tui_color_theme(repo: Repository | None) -> TuiColorTheme:
    """Read Git color settings and build pyratatui styles."""
    global _theme
    config = _load_config(repo)
    if not _colors_enabled(config):
        _theme = _monochrome_theme()
        return _theme

    palette = _load_palette()
    specs = _load_color_specs(config)
    header = _style_from_spec(
        specs.get("color.status.header", _DEFAULT_SPECS["color.status.header"]),
        palette,
        fallback_fg=Color.white(),
        extra_bold=True,
    )
    _theme = TuiColorTheme(
        enabled=True,
        status_bar=_style_from_spec(
            specs.get("color.status.header", _DEFAULT_SPECS["color.status.header"]),
            palette,
            fallback_fg=Color.white(),
            fallback_bg=Color.blue(),
        ),
        tab_active=_style_from_spec(
            specs.get("color.branch.current", _DEFAULT_SPECS["color.branch.current"]),
            palette,
            fallback_fg=Color.indexed(39),
            fallback_bg=Color.indexed(8),
            ignore_spec_background=True,
        ),
        tab_inactive=_style(),
        focus=_style().add_modifier(Modifier.reversed()),
        staged=_style_from_spec(
            specs.get("color.status.added", _DEFAULT_SPECS["color.status.added"]),
            palette,
            fallback_fg=Color.green(),
        ),
        unstaged=_style_from_spec(
            specs.get("color.status.changed", _DEFAULT_SPECS["color.status.changed"]),
            palette,
            fallback_fg=Color.red(),
        ),
        untracked=_style_from_spec(
            specs.get("color.status.untracked", _DEFAULT_SPECS["color.status.untracked"]),
            palette,
            fallback_fg=Color.red(),
        ),
        section_header=_style_from_spec(
            _DEFAULT_SECTION_HEADER_SPEC,
            palette,
            fallback_fg=Color.white(),
        ),
        header=header,
        branch=_style_from_spec(
            specs.get("color.status.branch", _DEFAULT_SPECS["color.status.branch"]),
            palette,
            fallback_fg=Color.cyan(),
        ),
        commit=_style_from_spec(
            specs.get("color.diff.new", _DEFAULT_SPECS["color.diff.new"]),
            palette,
            fallback_fg=Color.green(),
        ),
        log_hash=_style_from_spec(
            specs.get("color.decorate.commit", _DEFAULT_SPECS["color.decorate.commit"]),
            palette,
            fallback_fg=Color.yellow(),
        ),
        log_date=_style_from_spec(
            _DEFAULT_LOG_DATE_SPEC,
            palette,
            fallback_fg=Color.white(),
        ),
        log_subject=_style_from_spec(
            specs.get("color.diff.context", _DEFAULT_SPECS["color.diff.context"]),
            palette,
            fallback_fg=Color.white(),
        ),
        normal=_style(),
    )
    return _theme


def parse_git_color_spec(spec: str | None) -> ParsedGitColor:
    if spec is None or not spec.strip():
        return ParsedGitColor(foreground=None, background=None)
    foreground: tuple[int, int, int] | None = None
    background: tuple[int, int, int] | None = None
    bold = False
    dim = False
    reverse = False
    underline = False
    palette = _load_palette()
    for raw_token in spec.strip().split():
        token = raw_token.casefold()
        if token.startswith("no-"):
            token = token[3:]
        if token in {"bold", "standout"}:
            bold = not raw_token.casefold().startswith("no")
            continue
        if token == "dim":
            dim = not raw_token.casefold().startswith("no")
            continue
        if token == "reverse":
            reverse = not raw_token.casefold().startswith("no")
            continue
        if token in {"ul", "underline"}:
            underline = not raw_token.casefold().startswith("no")
            continue
        if token in {"italic", "blink", "strike"}:
            continue
        rgb = _token_to_rgb(raw_token, palette)
        if rgb is None:
            continue
        if foreground is None:
            foreground = rgb
        elif background is None:
            background = rgb
    return ParsedGitColor(
        foreground=foreground,
        background=background,
        bold=bold,
        dim=dim,
        reverse=reverse,
        underline=underline,
    )


def focus_style(base: Style) -> Style:
    return base.patch(_theme.focus)


def row_style_for_section(
    section: str | None,
    *,
    focused: bool,
    highlight: bool,
    is_header: bool = False,
) -> Style:
    theme = _theme
    if is_header:
        base = theme.section_header
    elif section == "staged":
        base = theme.staged
    elif section == "unstaged":
        base = theme.unstaged
    elif section == "untracked":
        base = theme.untracked
    else:
        base = theme.normal
    if focused and highlight:
        return focus_style(base)
    return base


def log_entry_styles(*, focused: bool, highlight: bool) -> tuple[Style, Style, Style]:
    theme = _theme
    if focused and highlight:
        return (
            focus_style(theme.log_hash),
            focus_style(theme.log_date),
            focus_style(theme.log_subject),
        )
    return (theme.log_hash, theme.log_date, theme.log_subject)


def _load_config(repo: Repository | None) -> Config:
    if repo is not None:
        return repo.config.snapshot()
    return Config.get_global_config()


def _config_value(config: Config, key: str) -> str | None:
    try:
        values = [value for value in config.get_multivar(key) if isinstance(value, str)]
    except KeyError:
        return None
    if not values:
        return None
    return values[-1].strip()


def _colors_enabled(config: Config) -> bool:
    ui = _config_value(config, "color.ui")
    if ui is not None and ui.casefold() in _FALSE_VALUES:
        return False
    status = _config_value(config, "color.status")
    return status is None or status.casefold() not in _FALSE_VALUES


def _load_color_specs(config: Config) -> dict[str, str]:
    specs = dict(_DEFAULT_SPECS)
    for key in _DEFAULT_SPECS:
        value = _config_value(config, key)
        if value:
            specs[key] = value
    return specs


def _load_palette() -> tuple[tuple[int, int, int], ...]:
    raw = json.loads(files("pygittools.tui").joinpath("colors.json").read_text(encoding="utf-8"))
    palette: list[tuple[int, int, int]] = []
    for entry in raw:
        text = str(entry).strip()
        if not text.startswith("#") or len(text) not in {4, 7}:
            palette.append((0, 0, 0))
            continue
        if len(text) == 4:
            text = f"#{text[1]}{text[1]}{text[2]}{text[2]}{text[3]}{text[3]}"
        palette.append((int(text[1:3], 16), int(text[3:5], 16), int(text[5:7], 16)))
    return tuple(palette)


def _token_to_rgb(token: str, palette: tuple[tuple[int, int, int], ...]) -> tuple[int, int, int] | None:
    lowered = token.casefold()
    if lowered == "default":
        return None
    if token.startswith("#"):
        text = token
        if len(text) == 4:
            text = f"#{text[1]}{text[1]}{text[2]}{text[2]}{text[3]}{text[3]}"
        if len(text) == 7:
            return (int(text[1:3], 16), int(text[3:5], 16), int(text[5:7], 16))
        return None
    if token.isdecimal():
        index = int(token)
        if 0 <= index < len(palette):
            return palette[index]
        return None
    bright = False
    name = lowered
    if name.startswith("bright"):
        bright = True
        name = name[6:]
    if name in _NAMED_RGB:
        if bright and name in _BRIGHT_NAMED_RGB:
            return _BRIGHT_NAMED_RGB[name]
        return _NAMED_RGB[name]
    return None


def _color_from_rgb(rgb: tuple[int, int, int] | None, fallback: Color) -> Color:
    if rgb is None:
        return fallback
    return Color.rgb(rgb[0], rgb[1], rgb[2])


def _style_from_spec(
    spec: str,
    palette: tuple[tuple[int, int, int], ...],
    *,
    fallback_fg: Color,
    fallback_bg: Color | None = None,
    extra_bold: bool = False,
    ignore_spec_background: bool = False,
) -> Style:
    del palette
    parsed = parse_git_color_spec(spec)
    style = _style().fg(_color_from_rgb(parsed.foreground, fallback_fg))
    bg = None if ignore_spec_background else parsed.background
    if bg is not None:
        style = style.bg(Color.rgb(bg[0], bg[1], bg[2]))
    elif fallback_bg is not None:
        style = style.bg(fallback_bg)
    if parsed.bold or extra_bold:
        style = style.bold()
    if parsed.dim:
        style = style.dim()
    if parsed.underline:
        style = style.underlined()
    if parsed.reverse:
        style = style.add_modifier(Modifier.reversed())
    return style


def _monochrome_theme() -> TuiColorTheme:
    return TuiColorTheme(
        enabled=False,
        status_bar=_style().add_modifier(Modifier.reversed()),
        tab_active=_style().bold(),
        tab_inactive=_style(),
        focus=_style().add_modifier(Modifier.reversed()),
        staged=_style(),
        unstaged=_style(),
        untracked=_style(),
        section_header=_style(),
        header=_style().bold(),
        branch=_style(),
        commit=_style(),
        log_hash=_style(),
        log_date=_style(),
        log_subject=_style(),
        normal=_style(),
    )