"""Fixed, opinionated color registry for COGILES node types."""

from __future__ import annotations

from typing import NamedTuple

from cogiles.errors import CogilesEncodeError


class ColorInfo(NamedTuple):
    """Metadata for a single COGILES node color."""

    symbol: str
    name: str
    rgb: tuple[float, float, float]


COLOR_LIST: list[ColorInfo] = [
    ColorInfo("R", "red", (1.0, 0.0, 0.0)),
    ColorInfo("G", "green", (0.0, 0.5, 0.0)),
    ColorInfo("B", "blue", (0.0, 0.0, 1.0)),
    ColorInfo("Y", "yellow", (1.0, 1.0, 0.0)),
    ColorInfo("C", "cyan", (0.0, 1.0, 1.0)),
    ColorInfo("M", "magenta", (1.0, 0.0, 1.0)),
    ColorInfo("W", "white", (1.0, 1.0, 1.0)),
    ColorInfo("K", "black", (0.0, 0.0, 0.0)),
    ColorInfo("O", "orange", (1.0, 0.5, 0.0)),
    ColorInfo("P", "purple", (0.5, 0.0, 0.5)),
    ColorInfo("T", "teal", (0.0, 0.5, 0.5)),
    ColorInfo("L", "lime", (0.5, 1.0, 0.0)),
    ColorInfo("I", "indigo", (0.3, 0.0, 0.5)),
    ColorInfo("Br", "brown", (0.6, 0.3, 0.0)),
    ColorInfo("Gr", "gray", (0.5, 0.5, 0.5)),
    ColorInfo("Pi", "pink", (1.0, 0.4, 0.7)),
    ColorInfo("Ol", "olive", (0.5, 0.5, 0.0)),
]

# Public dict: symbol -> {"name": str, "rgb": tuple}
COLORS: dict[str, dict[str, object]] = {
    c.symbol: {"name": c.name, "rgb": c.rgb} for c in COLOR_LIST
}
# Bl alias for black (K is canonical)
COLORS["Bl"] = {"name": "black", "rgb": (0.0, 0.0, 0.0)}

# Internal lookup: symbol -> ColorInfo
SYMBOL_MAP: dict[str, ColorInfo] = {c.symbol: c for c in COLOR_LIST}
# Bl alias maps to canonical symbol K
SYMBOL_MAP["Bl"] = ColorInfo("K", "black", (0.0, 0.0, 0.0))

# Internal lookup: rgb -> ColorInfo (canonical entries only)
RGB_MAP: dict[tuple[float, float, float], ColorInfo] = {c.rgb: c for c in COLOR_LIST}


def symbol_to_color_info(symbol: str) -> ColorInfo:
    """Return the ColorInfo for a symbol. Raises KeyError if unknown."""
    return SYMBOL_MAP[symbol]


def rgb_to_symbol(
    rgb: tuple[float, float, float],
    *,
    strict: bool = False,
    node: int | None = None,
) -> str:
    """Map an RGB tuple to the canonical COGILES symbol.

    If *strict* is True, raises CogilesEncodeError when no exact match
    is found. Otherwise, returns the nearest color by Euclidean distance.
    """
    rounded = (round(rgb[0], 4), round(rgb[1], 4), round(rgb[2], 4))
    if rounded in RGB_MAP:
        return RGB_MAP[rounded].symbol

    if strict:
        raise CogilesEncodeError(
            f"No exact color match for RGB {rgb}",
            node=node,
            color=rgb,
        )

    # Nearest match by squared Euclidean distance
    best_dist = float("inf")
    best_symbol = "K"
    for info in COLOR_LIST:
        d = sum((a - b) ** 2 for a, b in zip(rgb, info.rgb))
        if d < best_dist:
            best_dist = d
            best_symbol = info.symbol
    return best_symbol
