"""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(),
	)