from __future__ import annotations
import re
from typing import Final
HEX_RE: Final[re.Pattern[str]] = re.compile(r"^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
def _clamp_channel(value: int) -> int:
    if not isinstance(value, int):
        raise TypeError("RGB channel values must be integers.")
    if value < 0 or value > 255:
        raise ValueError("RGB channel values must be between 0 and 255.")
    return value
def normalize_hex(hex_color: str) -> str:
    if not isinstance(hex_color, str):
        raise TypeError("hex_color must be a string.")
    cleaned = hex_color.strip()
    if not HEX_RE.fullmatch(cleaned):
        raise ValueError("Hex color must be 3 or 6 hexadecimal characters, with or without '#'.")
    cleaned = cleaned.lstrip("#")
    if len(cleaned) == 3:
        cleaned = "".join(ch * 2 for ch in cleaned)
    return f"#{cleaned.lower()}"
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
    normalized = normalize_hex(hex_color).lstrip("#")
    return tuple(int(normalized[i:i+2], 16) for i in (0, 2, 4))  # type: ignore[return-value]
def rgb_to_hex(r: int, g: int, b: int) -> str:
    r = _clamp_channel(r)
    g = _clamp_channel(g)
    b = _clamp_channel(b)
    return f"#{r:02x}{g:02x}{b:02x}"
def rgb_to_ansi(r: int, g: int, b: int) -> tuple[str, str, str]:
    r = _clamp_channel(r)
    g = _clamp_channel(g)
    b = _clamp_channel(b)
    fg = f"\033[38;2;{r};{g};{b}m"
    bg = f"\033[48;2;{r};{g};{b}m"
    reset = "\033[0m"
    return fg, bg, reset
def hex_to_ansi(hex_color: str) -> tuple[str, str, str]:
    r, g, b = hex_to_rgb(hex_color)
    return rgb_to_ansi(r, g, b)