"""
Font loading + a text-style cache.

Resolves the TextStyle tokens from design.tokens into cached
pygame.font.Font objects, and renders text with the right tracking
(letter-spacing), which pygame doesn't do natively.
"""

from __future__ import annotations

import os
from functools import lru_cache

import pygame

from ..design import tokens as t

_FONT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "fonts")


@lru_cache(maxsize=64)
def _font(filename: str, size: int) -> pygame.font.Font:
    path = os.path.join(_FONT_DIR, filename)
    if not os.path.exists(path):
        # Fallback to the default pygame font so we never hard-crash on a
        # missing TTF (e.g. mid-development). Visual will be off but usable.
        return pygame.font.Font(None, size)
    return pygame.font.Font(path, size)


def font_for(style: t.TextStyle) -> pygame.font.Font:
    return _font(style.font, style.size)


# Map typographic punctuation the bundled font subset lacks → ASCII covered
# glyphs, so Clerk copy never renders "tofu" boxes. (★ · … ▏ are covered, so
# they're deliberately left alone.)
_SANITIZE = {
    0x2014: "-", 0x2013: "-", 0x2012: "-", 0x2011: "-", 0x2212: "-",  # dashes
    0x2018: "'", 0x2019: "'", 0x201C: '"', 0x201D: '"',               # quotes
    0x00A0: " ",                                                       # nbsp
}


def render(
    style: t.TextStyle,
    text: str,
    color: tuple[int, int, int],
    *,
    upper: bool = False,
) -> pygame.Surface:
    """
    Render `text` in `style`, honoring its tracking (letter-spacing).
    Returns a per-pixel-alpha Surface.
    """
    text = text.translate(_SANITIZE)
    if upper:
        text = text.upper()

    f = font_for(style)

    # Letter-spacing must be a WHOLE number of pixels. Sub-pixel tracking with
    # a per-glyph float cursor accumulates rounding error, so glyphs land on
    # uneven gaps — that reads as shimmer / "not pixel-perfect", worst on small
    # text. Snap tracking to an integer: styles whose tracking rounds to 0
    # (hero/title/caption/labels) render in ONE crisp pass; only genuinely
    # spaced styles (eyebrow) advance, and then by a uniform integer step.
    track = int(round(style.tracking))
    if track == 0 or len(text) <= 1:
        return f.render(text, True, color)

    glyphs = [f.render(ch, True, color) for ch in text]
    widths = [g.get_width() for g in glyphs]
    height = max((g.get_height() for g in glyphs), default=f.get_height())
    total = sum(widths) + track * (len(text) - 1)
    surf = pygame.Surface((max(total, 1), height), pygame.SRCALPHA)
    x = 0
    for g, w in zip(glyphs, widths):
        surf.blit(g, (x, 0))           # integer x → uniform, grid-aligned gaps
        x += w + track
    return surf


def measure(style: t.TextStyle, text: str, *, upper: bool = False) -> tuple[int, int]:
    surf = render(style, text, (255, 255, 255), upper=upper)
    return surf.get_size()
