"""Block rendering core - Font loading, text rasterization, word-wrap, and headline assembly.

Provides PIL font-based rendering to terminal half-block characters.
"""

import random
import re
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

from engine import config
from engine.sources import NO_UPPER, SCRIPT_FONTS, SOURCE_LANGS
from engine.translate import detect_location_language, translate_headline


def estimate_block_height(title: str, width: int, fnt=None) -> int:
    """Estimate rendered block height without full PIL rendering.

    Uses font bbox measurement to count wrapped lines, then computes:
    height = num_lines * RENDER_H + (num_lines - 1) + 2

    Args:
        title: Headline text to measure
        width: Terminal width in characters
        fnt: Optional PIL font (uses default if None)

    Returns:
        Estimated height in terminal rows
    """
    if fnt is None:
        fnt = font()
    text = re.sub(r"\s+", " ", title.upper())
    words = text.split()
    lines = 0
    cur = ""
    for word in words:
        test = f"{cur} {word}".strip() if cur else word
        bbox = fnt.getbbox(test)
        if bbox:
            img_h = bbox[3] - bbox[1] + 8
            pix_h = config.RENDER_H * 2
            scale = pix_h / max(img_h, 1)
            term_w = int((bbox[2] - bbox[0] + 8) * scale)
        else:
            term_w = 0
        max_term_w = width - 4 - 4
        if term_w > max_term_w and cur:
            lines += 1
            cur = word
        else:
            cur = test
    if cur:
        lines += 1
    if lines == 0:
        lines = 1
    return lines * config.RENDER_H + max(0, lines - 1) + 2


# ─── FONT LOADING ─────────────────────────────────────────
_FONT_OBJ = None
_FONT_OBJ_KEY = None
_FONT_CACHE = {}


def font():
    """Lazy-load the primary OTF font (path + face index aware)."""
    global _FONT_OBJ, _FONT_OBJ_KEY
    if not config.FONT_PATH:
        raise FileNotFoundError(
            f"No primary font selected. Add .otf/.ttf/.ttc files to {config.FONT_DIR}."
        )
    key = (config.FONT_PATH, config.FONT_INDEX, config.FONT_SZ)
    if _FONT_OBJ is None or key != _FONT_OBJ_KEY:
        _FONT_OBJ = ImageFont.truetype(
            config.FONT_PATH, config.FONT_SZ, index=config.FONT_INDEX
        )
        _FONT_OBJ_KEY = key
    return _FONT_OBJ


def clear_font_cache():
    """Reset cached font objects after changing primary font selection."""
    global _FONT_OBJ, _FONT_OBJ_KEY
    _FONT_OBJ = None
    _FONT_OBJ_KEY = None


def load_font_face(font_path, font_index=0, size=None):
    """Load a specific face from a font file or collection."""
    font_size = size or config.FONT_SZ
    return ImageFont.truetype(font_path, font_size, index=font_index)


def list_font_faces(font_path, max_faces=64):
    """Return discoverable face indexes + display names from a font file."""
    faces = []
    for idx in range(max_faces):
        try:
            fnt = load_font_face(font_path, idx)
        except Exception:
            if idx == 0:
                raise
            break
        family, style = fnt.getname()
        display = f"{family} {style}".strip()
        if not display:
            display = f"{Path(font_path).stem} [{idx}]"
        faces.append({"index": idx, "name": display})
    return faces


def font_for_lang(lang=None):
    """Get appropriate font for a language."""
    if lang is None or lang not in SCRIPT_FONTS:
        return font()
    if lang not in _FONT_CACHE:
        try:
            _FONT_CACHE[lang] = ImageFont.truetype(SCRIPT_FONTS[lang], config.FONT_SZ)
        except Exception:
            _FONT_CACHE[lang] = font()
    return _FONT_CACHE[lang]


# ─── RASTERIZATION ────────────────────────────────────────
def render_line(text, fnt=None):
    """Render a line of text as terminal rows using OTF font + half-blocks."""
    if fnt is None:
        fnt = font()
    bbox = fnt.getbbox(text)
    if not bbox or bbox[2] <= bbox[0]:
        return [""]
    pad = 4
    img_w = bbox[2] - bbox[0] + pad * 2
    img_h = bbox[3] - bbox[1] + pad * 2
    img = Image.new("L", (img_w, img_h), 0)
    draw = ImageDraw.Draw(img)
    draw.text((-bbox[0] + pad, -bbox[1] + pad), text, fill=255, font=fnt)
    pix_h = config.RENDER_H * 2
    hi_h = pix_h * config.SSAA
    scale = hi_h / max(img_h, 1)
    new_w_hi = max(1, int(img_w * scale))
    img = img.resize((new_w_hi, hi_h), Image.Resampling.LANCZOS)
    new_w = max(1, int(new_w_hi / config.SSAA))
    img = img.resize((new_w, pix_h), Image.Resampling.LANCZOS)
    data = img.tobytes()
    thr = 80
    rows = []
    for y in range(0, pix_h, 2):
        row = []
        for x in range(new_w):
            top = data[y * new_w + x] > thr
            bot = data[(y + 1) * new_w + x] > thr if y + 1 < pix_h else False
            if top and bot:
                row.append("█")
            elif top:
                row.append("▀")
            elif bot:
                row.append("▄")
            else:
                row.append(" ")
        rows.append("".join(row))
    while rows and not rows[-1].strip():
        rows.pop()
    while rows and not rows[0].strip():
        rows.pop(0)
    return rows if rows else [""]


def big_wrap(text, max_w, fnt=None):
    """Word-wrap text and render with OTF font."""
    if fnt is None:
        fnt = font()
    words = text.split()
    lines, cur = [], ""
    for word in words:
        test = f"{cur} {word}".strip() if cur else word
        bbox = fnt.getbbox(test)
        if bbox:
            img_h = bbox[3] - bbox[1] + 8
            pix_h = config.RENDER_H * 2
            scale = pix_h / max(img_h, 1)
            term_w = int((bbox[2] - bbox[0] + 8) * scale)
        else:
            term_w = 0
        if term_w > max_w - 4 and cur:
            lines.append(cur)
            cur = word
        else:
            cur = test
    if cur:
        lines.append(cur)
    out = []
    for i, ln in enumerate(lines):
        out.extend(render_line(ln, fnt))
        if i < len(lines) - 1:
            out.append("")
    return out


# ─── HEADLINE BLOCK ASSEMBLY ─────────────────────────────
def make_block(title, src, ts, w):
    """Render a headline into a content block with color.

    Args:
        title: Headline text to render
        src: Source identifier (for metadata)
        ts: Timestamp string (for metadata)
        w: Width constraint in terminal characters

    Returns:
        tuple: (content_lines, color_code, meta_row_index)
            - content_lines: List of rendered text lines
            - color_code: ANSI color code for display
            - meta_row_index: Row index of metadata line
    """
    target_lang = (
        (SOURCE_LANGS.get(src) or detect_location_language(title))
        if config.MODE == "news"
        else None
    )
    lang_font = font_for_lang(target_lang)
    if target_lang:
        title = translate_headline(title, target_lang)
    # Don't uppercase scripts that have no case (CJK, Arabic, etc.)
    if target_lang and target_lang in NO_UPPER:
        title_up = re.sub(r"\s+", " ", title)
    else:
        title_up = re.sub(r"\s+", " ", title.upper())
    for old, new in [
        ("\u2019", "'"),
        ("\u2018", "'"),
        ("\u201c", '"'),
        ("\u201d", '"'),
        ("\u2013", "-"),
        ("\u2014", "-"),
    ]:
        title_up = title_up.replace(old, new)
    big_rows = big_wrap(title_up, w - 4, lang_font)
    hc = random.choice(
        [
            "\033[38;5;46m",  # matrix green
            "\033[38;5;34m",  # dark green
            "\033[38;5;82m",  # lime
            "\033[38;5;48m",  # sea green
            "\033[38;5;37m",  # teal
            "\033[38;5;44m",  # cyan
            "\033[38;5;87m",  # sky
            "\033[38;5;117m",  # ice blue
            "\033[38;5;250m",  # cool white
            "\033[38;5;156m",  # pale green
            "\033[38;5;120m",  # mint
            "\033[38;5;80m",  # dark cyan
            "\033[38;5;108m",  # grey-green
            "\033[38;5;115m",  # sage
            "\033[1;38;5;46m",  # bold green
            "\033[1;38;5;250m",  # bold white
        ]
    )
    content = ["  " + r for r in big_rows]
    content.append("")
    meta = f"\u2591 {src} \u00b7 {ts}"
    content.append(" " * max(2, w - len(meta) - 2) + meta)
    return content, hc, len(content) - 1  # (rows, color, meta_row_index)
