from __future__ import annotations

import re
from pathlib import Path
from typing import Sequence

from PIL import Image, ImageDraw, ImageFont

__all__ = ["make_comparison", "save_compare"]

# Tokens for pixel-aware wrap: ASCII word, whitespace run, or a single CJK char.
_WRAP_TOKEN_RE = re.compile(r"\s+|[\u4e00-\u9fff]|[^\s\u4e00-\u9fff]+")
_CJK_RE = re.compile(r"[\u4e00-\u9fff]")

# Font search order: CJK-capable first so Chinese prompts render properly.
_CJK_FONTS = (
    "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
    "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
    "/usr/share/fonts/google-noto-cjk-vf-fonts/NotoSansCJK-VF.otf.ttc",
    "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
    "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
    "./fonts/Noto_Sans_SC/static/NotoSansSC-Regular.ttf",
)
_LATIN_FONTS = (
    "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    "/usr/share/fonts/dejavu/DejaVuSans.ttf",
    "DejaVuSans.ttf",
)

_warned_missing_cjk = False


def _load_font(size: int) -> tuple[ImageFont.ImageFont | ImageFont.FreeTypeFont, bool]:
    """Return (font, has_cjk_coverage). Falls back to PIL default if nothing usable."""
    for path in _CJK_FONTS:
        try:
            return ImageFont.truetype(path, size=size), True
        except OSError:
            continue
    for path in _LATIN_FONTS:
        try:
            return ImageFont.truetype(path, size=size), False
        except OSError:
            continue
    try:
        return ImageFont.load_default(size=size), False
    except TypeError:
        return ImageFont.load_default(), False


def _wrap_text(text: str, font, max_width: int) -> list[str]:
    """Greedy pixel-aware wrap. Keeps ASCII words intact, splits CJK per-char."""
    lines: list[str] = []
    for paragraph in text.split("\n"):
        cur = ""
        for tok in _WRAP_TOKEN_RE.findall(paragraph):
            candidate = cur + tok
            if font.getlength(candidate.rstrip()) <= max_width:
                cur = candidate
                continue
            if cur.strip():
                lines.append(cur.rstrip())
            cur = "" if tok.isspace() else tok
        if cur.rstrip():
            lines.append(cur.rstrip())
    return lines or [""]


def make_comparison(
    inputs: Sequence[Image.Image],
    output: Image.Image,
    prompt: str,
    *,
    pad: int = 16,
    bg: tuple[int, int, int] = (255, 255, 255),
) -> Image.Image:
    """Return ``[inputs... | output]`` stacked horizontally with ``prompt`` below.

    Inputs are letterboxed to match the output's height so the row lines up
    cleanly regardless of aspect ratio.
    """
    row_h = output.size[1]
    row_imgs: list[Image.Image] = []
    for im in inputs:
        if im.size[1] != row_h:
            new_w = max(1, round(im.size[0] * row_h / im.size[1]))
            im = im.resize((new_w, row_h), Image.LANCZOS)
        row_imgs.append(im)
    row_imgs.append(output)
    row_w = sum(im.size[0] for im in row_imgs) + pad * (len(row_imgs) + 1)

    font, has_cjk = _load_font(max(18, row_h // 30))
    global _warned_missing_cjk
    if not has_cjk and _CJK_RE.search(prompt) and not _warned_missing_cjk:
        print(
            "[compare] prompt contains CJK but no CJK-capable font was found; "
            "Chinese characters will render as tofu. Install e.g. `fonts-noto-cjk` "
            "(Debian/Ubuntu) or `google-noto-cjk-fonts` (RHEL-family) for proper rendering."
        )
        _warned_missing_cjk = True

    lines = _wrap_text(prompt, font, row_w - pad * 2)
    bbox = font.getbbox("Ag中")
    line_h = max(1, int((bbox[3] - bbox[1]) * 1.3))
    text_h = line_h * len(lines) + pad * 2

    canvas = Image.new("RGB", (row_w, row_h + pad * 2 + text_h), bg)
    x = pad
    for im in row_imgs:
        canvas.paste(im, (x, pad))
        x += im.size[0] + pad
    draw = ImageDraw.Draw(canvas)
    y = row_h + pad * 2
    for line in lines:
        draw.text((pad, y), line, fill=(0, 0, 0), font=font)
        y += line_h
    return canvas


def save_compare(
    out_path: Path,
    inputs: Sequence[Image.Image],
    output: Image.Image,
    prompt: str,
) -> None:
    """Save a comparison next to ``out_path`` as ``<stem>_compare<suffix>``."""
    cmp_path = out_path.with_name(f"{out_path.stem}_compare{out_path.suffix}")
    make_comparison(inputs, output, prompt).save(cmp_path)
    print(f"[saved] {cmp_path}")
