"""栅格图在版心内的统一缩放：优先占满可用宽度；过高则整体缩小至不超过版心高度。"""

from __future__ import annotations

import html
import re
from pathlib import Path
from typing import Any

# CSS / 屏幕惯例：96 CSS 像素 ≈ 1 英寸
_CSS_PX_PER_PT = 96.0 / 72.0
_CM_PER_IN = 2.54
_PT_PER_CM = 72.0 / _CM_PER_IN


def read_raster_pixel_size(path: Path) -> tuple[int, int] | None:
    """返回 ``(width_px, height_px)``；无法读取时 ``None``。"""
    try:
        from PIL import Image

        with Image.open(path) as im:
            return im.size
    except Exception:
        return None


def px_to_pt_intrinsic(iw: int, ih: int) -> tuple[float, float]:
    """将像素尺寸按 CSS px→pt 换算为排版点值（无 DPI 元数据时的默认）。"""
    return iw / _CSS_PX_PER_PT, ih / _CSS_PX_PER_PT


def fit_display_pt(
    iw: int,
    ih: int,
    *,
    max_width_pt: float,
    max_height_pt: float,
) -> tuple[float, float]:
    """
    先按 ``max_width_pt`` 缩放到满宽，若高度超出 ``max_height_pt`` 则同比缩小。
    返回 ``(width_pt, height_pt)``（保持宽高比）。
    """
    if iw <= 0 or ih <= 0 or max_width_pt <= 0 or max_height_pt <= 0:
        return max_width_pt * 0.5, max(max_height_pt * 0.3, 36.0)
    iw_pt, ih_pt = px_to_pt_intrinsic(iw, ih)
    s = max_width_pt / iw_pt
    w_pt = max_width_pt
    h_pt = ih_pt * s
    if h_pt > max_height_pt:
        s2 = max_height_pt / h_pt
        w_pt *= s2
        h_pt = max_height_pt
    return w_pt, h_pt


def fit_display_cm(
    iw: int,
    ih: int,
    *,
    max_width_cm: float,
    max_height_cm: float,
) -> tuple[float, float]:
    """同上，输出厘米（Word ReportLab 等）。"""
    w_pt, h_pt = fit_display_pt(
        iw,
        ih,
        max_width_pt=max_width_cm * _PT_PER_CM,
        max_height_pt=max_height_cm * _PT_PER_CM,
    )
    return w_pt / _PT_PER_CM, h_pt / _PT_PER_CM


def suggest_page_break_before_raster(
    iw: int,
    ih: int,
    *,
    body_width_pt: float,
    body_height_pt: float,
    height_fraction: float = 0.42,
) -> bool:
    """
    判断插图前是否宜用 ``page-break-before: always``：
    若按满版宽放置时高度超过版心一定比例，或图为竖长条，则优先从新页排，避免挤在页底被压得过窄。
    """
    if iw <= 0 or ih <= 0:
        return False
    # 极小占位图（如 1×1）避免误判为高块从而 ``always`` 导致 Story 分页异常
    if iw < 8 or ih < 8:
        return False
    iw_pt, ih_pt = px_to_pt_intrinsic(iw, ih)
    s = body_width_pt / iw_pt
    h_if_full_w = ih_pt * s
    tall = ih >= iw * 1.02
    return h_if_full_w > body_height_pt * height_fraction or tall


def strip_br_inside_html_tables(html: str) -> str:
    """去掉 ``<table>…</table>`` 内的 ``<br>``，减轻 Markdown ``nl2br`` 对表格格的破坏。"""

    def _clean_block(m: re.Match[str]) -> str:
        block = m.group(0)
        return re.sub(r"<br\s*/?>", " ", block, flags=re.I)

    return re.sub(r"<table\b[^>]*>.*?</table>", _clean_block, html, flags=re.I | re.S)


_IMG_TAG_RE = re.compile(
    r"<img\b([^>]*?)(/?)>", flags=re.I | re.S
)
_SRC_RE = re.compile(r"""src\s*=\s*["']([^"']+)["']""", re.I)


def augment_img_tags_for_story(
    html_fragment: str,
    archive_dir: Path,
    *,
    body_width_pt: float,
    body_height_pt: float,
) -> tuple[str, dict[str, Any]]:
    """
    为 ``<img>`` 外包 ``aicd-figure``，写入按版心缩放后的宽高（pt），并按需加换页。
    返回 ``(new_html, meta)``；``meta`` 含 ``missing_srcs``、``wrapped_images``。
    """
    missing: list[str] = []
    wrapped = 0
    ad = archive_dir.resolve()

    def _sub(m: re.Match[str]) -> str:
        nonlocal wrapped
        attrs = m.group(1) or ""
        closed = m.group(2) or ""
        sm = _SRC_RE.search(attrs)
        if not sm:
            return m.group(0)
        raw_src = sm.group(1).strip()
        if raw_src.startswith(("data:", "http://", "https://")):
            return m.group(0)
        try:
            cand = (ad / raw_src).resolve()
            cand.relative_to(ad)
        except (OSError, ValueError):
            missing.append(raw_src)
            return m.group(0)
        if not cand.is_file():
            missing.append(raw_src)
            return m.group(0)
        sz = read_raster_pixel_size(cand)
        if not sz:
            missing.append(f"{raw_src} (unreadable image)")
            return m.group(0)
        iw, ih = sz
        w_pt, h_pt = fit_display_pt(
            iw, ih, max_width_pt=body_width_pt, max_height_pt=body_height_pt
        )
        brk = suggest_page_break_before_raster(
            iw,
            ih,
            body_width_pt=body_width_pt,
            body_height_pt=body_height_pt,
        )
        pb = "always" if brk else "auto"
        wrapped += 1
        esc = html.escape(raw_src, quote=True)
        return (
            f'<div class="aicd-figure" style="page-break-before: {pb}; '
            f'page-break-inside: avoid; margin: 0.35em 0; text-align: center;">'
            f'<img src="{esc}" style="display: block; margin: 0 auto; '
            f"width: {w_pt:.1f}pt; height: {h_pt:.1f}pt;\" />"
            "</div>"
        )

    new_html = _IMG_TAG_RE.sub(_sub, html_fragment)
    # Markdown 常见 ``<p><img/></p>``；外包 ``div`` 后变成 ``<p><div…>``，对 HTML 无效且 PyMuPDF Story 会丢图。
    new_html = _unwrap_aicd_figure_paragraphs(new_html)
    return new_html, {
        "missing_srcs": missing,
        "wrapped_images": wrapped,
    }


def _unwrap_aicd_figure_paragraphs(html: str) -> str:
    return re.sub(
        r"<p>\s*(<div\s+class=\"aicd-figure\"[^>]*>.*?</div>)\s*</p>",
        r"\1",
        html,
        flags=re.I | re.S,
    )


def collect_markdown_image_refs(markdown: str) -> list[str]:
    """``![](path)`` 中的路径列表（不去重）。"""
    return re.findall(r"!\[[^\]]*\]\(([^)]+)\)", markdown)
