#!/usr/bin/env python3
"""
card_analyzer.py  —  Analyze a TFM card image region by region.

Usage:
    python card_analyzer.py <image.png> [--rotate 90]
    python card_analyzer.py <image.png> --region x1 y1 x2 y2

Outputs detected background color, text color, and estimated font size for
each queried region.
"""
import argparse
import json
import sys
import numpy as np
from PIL import Image


# ── pixel-level helpers ──────────────────────────────────────────────────────

def region_arr(arr, x1, y1, x2, y2):
    return arr[y1:y2, x1:x2].astype(float)


def background_color(arr_region, bright_thresh=200):
    """Median color of pixels brighter than bright_thresh in all channels."""
    r = arr_region
    mask = (r[:,:,0] > bright_thresh) & (r[:,:,1] > bright_thresh) & (r[:,:,2] > bright_thresh)
    if mask.sum() < 10:
        # fall back: top 40% brightest pixels
        brightness = r.mean(axis=2)
        flat = brightness.flatten()
        thr = np.percentile(flat, 60)
        mask = brightness > thr
    samples = r[mask]
    return tuple(int(np.median(samples[:, c])) for c in range(3))


def text_color(arr_region, dark_thresh=80):
    """Median color of clearly dark pixels (text on light bg)."""
    r = arr_region
    brightness = r.mean(axis=2)
    mask = brightness < dark_thresh
    if mask.sum() < 5:
        # try harder: bottom 20% brightest
        thr = np.percentile(brightness, 15)
        mask = brightness < max(thr, 20)
    if mask.sum() == 0:
        return (30, 30, 30)
    samples = r[mask]
    return tuple(int(np.median(samples[:, c])) for c in range(3))


def saturated_text_colors(arr_region, sat_thresh=0.45):
    """
    For colored text (corp names).
    Returns dominant fill & stroke color guesses as (fill_rgb, stroke_rgb).
    Splits pixels by hue: warm-yellow vs warm-red.
    """
    r = arr_region
    R, G, B = r[:,:,0], r[:,:,1], r[:,:,2]
    maxc = np.maximum(np.maximum(R, G), B)
    minc = np.minimum(np.minimum(R, G), B)
    sat = (maxc - minc) / (maxc + 1)
    sat_mask = sat > sat_thresh

    sat_px = r[sat_mask]
    if len(sat_px) == 0:
        return None, None

    # yellow = R high, G high, B low
    yel = sat_px[(sat_px[:,0] > 180) & (sat_px[:,1] > 130) & (sat_px[:,2] < 100)]
    # red = R high, G+B low
    red = sat_px[(sat_px[:,0] > 150) & (sat_px[:,0] - sat_px[:,1] > 60) & (sat_px[:,0] - sat_px[:,2] > 60)]

    fill_color  = tuple(int(np.median(yel[:, c])) for c in range(3)) if len(yel) > 5 else None
    stroke_color = tuple(int(np.median(red[:, c])) for c in range(3)) if len(red) > 5 else None

    # Decide which is dominant (more pixels)
    if len(yel) >= len(red):
        return fill_color, stroke_color  # yellow-dominant: fill=yellow, stroke=red
    else:
        return stroke_color, fill_color  # red-dominant: fill=red, stroke=yellow


# ── font-size estimation ─────────────────────────────────────────────────────

def estimate_font_size(arr_region, dark_thresh=80, min_line_px=6):
    """
    Scan rows for dark pixels; group consecutive rows into text lines.
    Returns (median_cap_height_px, list_of_line_heights).
    """
    brightness = arr_region.mean(axis=2)
    # also include saturated colored text
    R,G,B = arr_region[:,:,0], arr_region[:,:,1], arr_region[:,:,2]
    maxc = np.maximum(np.maximum(R,G), B)
    minc = np.minimum(np.minimum(R,G), B)
    sat = (maxc - minc) / (maxc + 1)

    text_rows = ((brightness < dark_thresh) | (sat > 0.4)).any(axis=1)

    in_line, lines = False, []
    for i, v in enumerate(text_rows):
        if v and not in_line:
            in_line = True; ls = i
        elif not v and in_line:
            in_line = False
            h = i - ls
            if h >= min_line_px:
                lines.append(h)

    if not lines:
        return 0, []
    return int(np.median(lines)), lines


# ── full region analysis ─────────────────────────────────────────────────────

def analyze_region(img_arr, x1, y1, x2, y2, label="region"):
    reg = region_arr(img_arr, x1, y1, x2, y2)
    cap_h, line_heights = estimate_font_size(reg)

    # Try colored text first
    fill, stroke = saturated_text_colors(reg)
    if fill or stroke:
        bg = background_color(reg)
        result = dict(
            label=label,
            region=[x1,y1,x2,y2],
            bg_color=bg,
            text_style="colored",
            fill_color=fill,
            stroke_color=stroke,
            est_font_px=cap_h,
            line_heights=line_heights,
        )
    else:
        bg  = background_color(reg)
        txt = text_color(reg)
        result = dict(
            label=label,
            region=[x1,y1,x2,y2],
            bg_color=bg,
            text_style="solid",
            text_color=txt,
            est_font_px=cap_h,
            line_heights=line_heights,
        )

    return result


# ── CLI ──────────────────────────────────────────────────────────────────────

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("image")
    ap.add_argument("--rotate", type=int, default=0, help="Rotate CCW degrees (e.g. 90)")
    ap.add_argument("--region", nargs=4, type=int, metavar=("X1","Y1","X2","Y2"))
    args = ap.parse_args()

    img = Image.open(args.image).convert("RGB")
    if args.rotate:
        img = img.rotate(args.rotate, expand=True)
    arr = np.array(img)
    print(f"Image size (after rotation): {img.width}x{img.height}")

    if args.region:
        x1,y1,x2,y2 = args.region
        result = analyze_region(arr, x1, y1, x2, y2, label="cli_region")
        print(json.dumps(result, indent=2))
    else:
        print("Provide --region x1 y1 x2 y2  to analyze a specific area.")
        print("Example: python card_analyzer.py card.png --rotate 90 --region 300 448 938 615")


if __name__ == "__main__":
    main()
