"""
Shared display rendering utilities.

Provides common functionality for displays that render text to images
(Pygame, Sixel, Kitty displays).
"""

from typing import Any

ANSI_COLORS = {
    0: (0, 0, 0),
    1: (205, 49, 49),
    2: (13, 188, 121),
    3: (229, 229, 16),
    4: (36, 114, 200),
    5: (188, 63, 188),
    6: (17, 168, 205),
    7: (229, 229, 229),
    8: (102, 102, 102),
    9: (241, 76, 76),
    10: (35, 209, 139),
    11: (245, 245, 67),
    12: (59, 142, 234),
    13: (214, 112, 214),
    14: (41, 184, 219),
    15: (255, 255, 255),
}


def parse_ansi(
    text: str,
) -> list[tuple[str, tuple[int, int, int], tuple[int, int, int], bool]]:
    """Parse ANSI escape sequences into text tokens with colors.

    Args:
        text: Text containing ANSI escape sequences

    Returns:
        List of (text, fg_rgb, bg_rgb, bold) tuples
    """
    tokens = []
    current_text = ""
    fg = (204, 204, 204)
    bg = (0, 0, 0)
    bold = False
    i = 0

    ANSI_COLORS_4BIT = {
        0: (0, 0, 0),
        1: (205, 49, 49),
        2: (13, 188, 121),
        3: (229, 229, 16),
        4: (36, 114, 200),
        5: (188, 63, 188),
        6: (17, 168, 205),
        7: (229, 229, 229),
        8: (102, 102, 102),
        9: (241, 76, 76),
        10: (35, 209, 139),
        11: (245, 245, 67),
        12: (59, 142, 234),
        13: (214, 112, 214),
        14: (41, 184, 219),
        15: (255, 255, 255),
    }

    while i < len(text):
        char = text[i]

        if char == "\x1b" and i + 1 < len(text) and text[i + 1] == "[":
            if current_text:
                tokens.append((current_text, fg, bg, bold))
                current_text = ""

            i += 2
            code = ""
            while i < len(text):
                c = text[i]
                if c.isalpha():
                    break
                code += c
                i += 1

            if code:
                codes = code.split(";")
                for c in codes:
                    if c == "0":
                        fg = (204, 204, 204)
                        bg = (0, 0, 0)
                        bold = False
                    elif c == "1":
                        bold = True
                    elif c == "22":
                        bold = False
                    elif c == "39":
                        fg = (204, 204, 204)
                    elif c == "49":
                        bg = (0, 0, 0)
                    elif c.isdigit():
                        color_idx = int(c)
                        if color_idx in ANSI_COLORS_4BIT:
                            fg = ANSI_COLORS_4BIT[color_idx]
                        elif 30 <= color_idx <= 37:
                            fg = ANSI_COLORS_4BIT.get(color_idx - 30, fg)
                        elif 40 <= color_idx <= 47:
                            bg = ANSI_COLORS_4BIT.get(color_idx - 40, bg)
                        elif 90 <= color_idx <= 97:
                            fg = ANSI_COLORS_4BIT.get(color_idx - 90 + 8, fg)
                        elif 100 <= color_idx <= 107:
                            bg = ANSI_COLORS_4BIT.get(color_idx - 100 + 8, bg)
                    elif c.startswith("38;5;"):
                        idx = int(c.split(";")[-1])
                        if idx < 256:
                            if idx < 16:
                                fg = ANSI_COLORS_4BIT.get(idx, fg)
                            elif idx < 232:
                                c_idx = idx - 16
                                fg = (
                                    (c_idx >> 4) * 51,
                                    ((c_idx >> 2) & 7) * 51,
                                    (c_idx & 3) * 85,
                                )
                            else:
                                gray = (idx - 232) * 10 + 8
                                fg = (gray, gray, gray)
                    elif c.startswith("48;5;"):
                        idx = int(c.split(";")[-1])
                        if idx < 256:
                            if idx < 16:
                                bg = ANSI_COLORS_4BIT.get(idx, bg)
                            elif idx < 232:
                                c_idx = idx - 16
                                bg = (
                                    (c_idx >> 4) * 51,
                                    ((c_idx >> 2) & 7) * 51,
                                    (c_idx & 3) * 85,
                                )
                            else:
                                gray = (idx - 232) * 10 + 8
                                bg = (gray, gray, gray)
            i += 1
        else:
            current_text += char
            i += 1

    if current_text:
        tokens.append((current_text, fg, bg, bold))

    return tokens if tokens else [("", fg, bg, bold)]


def get_default_font_path() -> str | None:
    """Get the path to a default monospace font."""
    import os
    import sys
    from pathlib import Path

    def search_dir(base_path: str) -> str | None:
        if not os.path.exists(base_path):
            return None
        if os.path.isfile(base_path):
            return base_path
        for font_file in Path(base_path).rglob("*"):
            if font_file.suffix.lower() in (".ttf", ".otf", ".ttc"):
                name = font_file.stem.lower()
                if "geist" in name and ("nerd" in name or "mono" in name):
                    return str(font_file)
                if "mono" in name or "courier" in name or "terminal" in name:
                    return str(font_file)
        return None

    search_dirs = []
    if sys.platform == "darwin":
        search_dirs.extend(
            [
                os.path.expanduser("~/Library/Fonts/"),
                "/System/Library/Fonts/",
            ]
        )
    elif sys.platform == "win32":
        search_dirs.extend(
            [
                os.path.expanduser("~\\AppData\\Local\\Microsoft\\Windows\\Fonts\\"),
                "C:\\Windows\\Fonts\\",
            ]
        )
    else:
        search_dirs.extend(
            [
                os.path.expanduser("~/.local/share/fonts/"),
                os.path.expanduser("~/.fonts/"),
                "/usr/share/fonts/",
            ]
        )

    for search_dir_path in search_dirs:
        found = search_dir(search_dir_path)
        if found:
            return found

    if sys.platform != "win32":
        try:
            import subprocess

            for pattern in ["monospace", "DejaVuSansMono", "LiberationMono"]:
                result = subprocess.run(
                    ["fc-match", "-f", "%{file}", pattern],
                    capture_output=True,
                    text=True,
                    timeout=5,
                )
                if result.returncode == 0 and result.stdout.strip():
                    font_file = result.stdout.strip()
                    if os.path.exists(font_file):
                        return font_file
        except Exception:
            pass

    return None


def render_to_pil(
    buffer: list[str],
    width: int,
    height: int,
    cell_width: int = 10,
    cell_height: int = 18,
    font_path: str | None = None,
) -> Any:
    """Render buffer to a PIL Image.

    Args:
        buffer: List of text lines to render
        width: Terminal width in characters
        height: Terminal height in rows
        cell_width: Width of each character cell in pixels
        cell_height: Height of each character cell in pixels
        font_path: Path to TTF/OTF font file (optional)

    Returns:
        PIL Image object
    """
    from PIL import Image, ImageDraw, ImageFont

    img_width = width * cell_width
    img_height = height * cell_height

    img = Image.new("RGBA", (img_width, img_height), (0, 0, 0, 255))
    draw = ImageDraw.Draw(img)

    if font_path:
        try:
            font = ImageFont.truetype(font_path, cell_height - 2)
        except Exception:
            font = ImageFont.load_default()
    else:
        font = ImageFont.load_default()

    for row_idx, line in enumerate(buffer[:height]):
        if row_idx >= height:
            break

        tokens = parse_ansi(line)
        x_pos = 0
        y_pos = row_idx * cell_height

        for text, fg, bg, _bold in tokens:
            if not text:
                continue

            if bg != (0, 0, 0):
                bbox = draw.textbbox((x_pos, y_pos), text, font=font)
                draw.rectangle(bbox, fill=(*bg, 255))

            draw.text((x_pos, y_pos), text, fill=(*fg, 255), font=font)

            if font:
                x_pos += draw.textlength(text, font=font)

    return img
