"""Shared invariants for generated Matplotlib figures."""

from __future__ import annotations

import os
from pathlib import Path

import matplotlib
from matplotlib.colors import to_rgba
from matplotlib.text import Text

MIN_TEXT_SIZE_PT = 10.0
MIN_TEXT_CONTRAST = 4.5
WHITE = (1.0, 1.0, 1.0)


def configure_matplotlib(*, font_family: str = "DejaVu Sans") -> None:
    """Use a Matplotlib-bundled font and an opaque white figure surface."""
    matplotlib.rcParams.update(
        {
            "font.family": font_family,
            "figure.facecolor": "white",
            "axes.facecolor": "white",
            "savefig.facecolor": "white",
        }
    )


def figure_output_path(source_file: str, filename: str) -> Path:
    """Resolve an output beside the generator or in the checker's scratch dir."""
    default_dir = Path(source_file).resolve().parent
    output_dir = Path(os.environ.get("BLOG_FIGURE_OUTPUT_DIR", default_dir))
    output_dir.mkdir(parents=True, exist_ok=True)
    return output_dir / filename


def _linear(channel: float) -> float:
    if channel <= 0.04045:
        return channel / 12.92
    return ((channel + 0.055) / 1.055) ** 2.4


def _luminance(rgb: tuple[float, float, float]) -> float:
    red, green, blue = (_linear(channel) for channel in rgb)
    return 0.2126 * red + 0.7152 * green + 0.0722 * blue


def _contrast(
    first: tuple[float, float, float], second: tuple[float, float, float]
) -> float:
    lighter = max(_luminance(first), _luminance(second))
    darker = min(_luminance(first), _luminance(second))
    return (lighter + 0.05) / (darker + 0.05)


def validate_figure(figure: matplotlib.figure.Figure) -> None:
    """Reject unreadably small or low-contrast rendered text before saving."""
    failures: list[str] = []
    for label in figure.findobj(Text):
        text = label.get_text().strip()
        if not text or not label.get_visible():
            continue

        size = float(label.get_fontsize())
        if size < MIN_TEXT_SIZE_PT:
            failures.append(f"{text!r}: {size:g}pt is below {MIN_TEXT_SIZE_PT:g}pt")

        red, green, blue, alpha = to_rgba(label.get_color())
        if alpha < 1.0:
            failures.append(f"{text!r}: translucent text cannot guarantee contrast")
            continue
        ratio = _contrast((red, green, blue), WHITE)
        if ratio < MIN_TEXT_CONTRAST:
            failures.append(
                f"{text!r}: {ratio:.2f}:1 contrast on white; "
                f"require {MIN_TEXT_CONTRAST:.1f}:1"
            )

    if failures:
        raise ValueError("figure text quality check failed:\n" + "\n".join(failures))
