#!/usr/bin/env python3
"""
Image comparator for React vs HTMX showcase validation.

Compares two PNG/WEBP screenshots and produces:
  1. Similarity percentage (pixels within tolerance / total pixels)
  2. A diff image with mismatches highlighted magenta on a faded background

Tolerance is per-channel: anti-aliasing / sub-pixel hinting is accepted
(default 12 of 255 per channel, tweakable via --tol). Font / layout / color
changes produce large regions of divergence that will exceed the tolerance.

Usage:
  python3 compare.py react.webp htmx.webp [--out diff.webp] [--tol 12]
"""

import argparse
import sys
from pathlib import Path

from PIL import Image, ImageChops, ImageDraw


def load(path):
    img = Image.open(path).convert("RGB")
    return img


def compare(react_path, htmx_path, out_path, tol):
    a = load(react_path)
    b = load(htmx_path)

    if a.size != b.size:
        # Pad the smaller one with transparent/white so we can still diff
        w, h = max(a.width, b.width), max(a.height, b.height)
        pad_a = Image.new("RGB", (w, h), (255, 255, 255))
        pad_b = Image.new("RGB", (w, h), (255, 255, 255))
        pad_a.paste(a, (0, 0))
        pad_b.paste(b, (0, 0))
        a, b = pad_a, pad_b
        size_mismatch = True
    else:
        size_mismatch = False

    diff = ImageChops.difference(a, b)
    # Per-pixel max channel diff
    total = a.width * a.height
    differing = 0
    mask = Image.new("L", a.size, 0)
    mask_pixels = mask.load()
    diff_pixels = diff.load()
    for y in range(a.height):
        for x in range(a.width):
            r, g, bl = diff_pixels[x, y]
            if max(r, g, bl) > tol:
                differing += 1
                mask_pixels[x, y] = 255

    similarity = 100.0 * (total - differing) / total

    # Build diff image: React screenshot faded 50%, with diffs in magenta
    faded = Image.eval(a, lambda v: int(v * 0.4 + 0.6 * 255))
    magenta = Image.new("RGB", a.size, (255, 0, 180))
    out = Image.composite(magenta, faded, mask)

    # Add a header text
    draw = ImageDraw.Draw(out)
    header = (
        f"similarity={similarity:.2f}%  "
        f"differing={differing}/{total}  "
        f"tol={tol}"
        + ("  (SIZE MISMATCH — padded)" if size_mismatch else "")
    )
    draw.rectangle([0, 0, a.width, 24], fill=(0, 0, 0))
    draw.text((8, 4), header, fill=(255, 255, 255))

    out.save(out_path)
    return similarity, differing, total


def main():
    p = argparse.ArgumentParser()
    p.add_argument("react")
    p.add_argument("htmx")
    p.add_argument("--out", default="diff.webp")
    p.add_argument("--tol", type=int, default=12)
    args = p.parse_args()

    sim, diff_px, total = compare(args.react, args.htmx, args.out, args.tol)
    print(f"react  = {args.react}")
    print(f"htmx   = {args.htmx}")
    print(f"diff   -> {args.out}")
    print(f"  similarity = {sim:.2f}%  ({diff_px} / {total} pixels differ > {args.tol})")
    if sim < 99.5:
        sys.exit(1)
    sys.exit(0)


if __name__ == "__main__":
    main()
