"""Simple visual diff utilities lifted from svg2ooxml test harness (refactored)."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Tuple

try:
    import numpy as np  # type: ignore[import-not-found]
except ImportError:  # pragma: no cover
    np = None  # type: ignore[assignment]

from PIL import Image, ImageChops


@dataclass
class DiffResult:
    """Result of comparing two images."""

    similarity: float
    pixel_diff_percentage: float
    passed: bool
    threshold: float
    diff_image: Optional[Image.Image] = None
    baseline_shape: Optional[Tuple[int, int]] = None
    candidate_shape: Optional[Tuple[int, int]] = None

    def save_diff(self, path: Path | str) -> None:
        if self.diff_image is None:
            raise ValueError("Diff image not generated - set generate_diff=True")
        output_path = Path(path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        self.diff_image.save(str(output_path))


class VisualDiffer:
    """Lightweight visual comparison based on mean absolute error."""

    def __init__(self, *, threshold: float = 0.97, pixel_diff_threshold: int = 8):
        if not 0.0 <= threshold <= 1.0:
            raise ValueError("threshold must be between 0.0 and 1.0")
        if not 0 <= pixel_diff_threshold <= 255:
            raise ValueError("pixel_diff_threshold must be in [0, 255]")
        self.threshold = threshold
        self.pixel_diff_threshold = pixel_diff_threshold

    def compare(
        self,
        baseline: Image.Image,
        candidate: Image.Image,
        *,
        generate_diff: bool = True,
    ) -> DiffResult:
        base_rgb = baseline.convert("RGB")
        cand_rgb = candidate.convert("RGB")

        if base_rgb.size != cand_rgb.size:
            raise ValueError(
                f"Image shapes do not match: {base_rgb.size} vs {cand_rgb.size}"
            )

        base_raw = base_rgb.tobytes()
        cand_raw = cand_rgb.tobytes()
        total = len(base_raw) // 3

        abs_sum = 0
        diff_count = 0
        for i in range(total):
            off = i * 3
            dr = abs(base_raw[off] - cand_raw[off])
            dg = abs(base_raw[off + 1] - cand_raw[off + 1])
            db = abs(base_raw[off + 2] - cand_raw[off + 2])
            abs_sum += dr + dg + db
            if max(dr, dg, db) > self.pixel_diff_threshold:
                diff_count += 1

        mean_abs_diff = abs_sum / (total * 3) if total else 0.0
        similarity = float(max(0.0, min(1.0, 1.0 - mean_abs_diff / 255.0)))
        pixel_diff_percentage = (diff_count / total * 100.0) if total else 0.0

        diff_image = None
        if generate_diff:
            diff = ImageChops.difference(base_rgb, cand_rgb).convert("RGB")
            diff_image = diff

        return DiffResult(
            similarity=similarity,
            pixel_diff_percentage=pixel_diff_percentage,
            passed=similarity >= self.threshold,
            threshold=self.threshold,
            diff_image=diff_image,
            baseline_shape=(base_rgb.size[1], base_rgb.size[0]),
            candidate_shape=(cand_rgb.size[1], cand_rgb.size[0]),
        )
