from __future__ import annotations

import argparse
import shutil
from pathlib import Path

import numpy as np
from PIL import Image, ImageFilter


def inpaint_pixels(data: np.ndarray, mask: np.ndarray) -> np.ndarray:
    result = data.astype(np.float32).copy()
    remaining = mask.copy()

    for _ in range(data.shape[0] + data.shape[1]):
        known = ~remaining
        count = np.zeros(remaining.shape, dtype=np.float32)
        total = np.zeros(result.shape, dtype=np.float32)

        neighbor = known[:-1, :]
        count[1:, :] += neighbor
        total[1:, :] += result[:-1, :] * neighbor[..., None]

        neighbor = known[1:, :]
        count[:-1, :] += neighbor
        total[:-1, :] += result[1:, :] * neighbor[..., None]

        neighbor = known[:, :-1]
        count[:, 1:] += neighbor
        total[:, 1:] += result[:, :-1] * neighbor[..., None]

        neighbor = known[:, 1:]
        count[:, :-1] += neighbor
        total[:, :-1] += result[:, 1:] * neighbor[..., None]

        boundary = remaining & (count > 0)
        if not boundary.any():
            break

        result[boundary] = total[boundary] / count[boundary, None]
        remaining[boundary] = False

        if not remaining.any():
            break

    if remaining.any():
        known_pixels = result[~remaining]
        fill = np.median(known_pixels, axis=0) if len(known_pixels) else np.array([255, 255, 255])
        result[remaining] = fill

    return np.clip(result, 0, 255).astype(np.uint8)


def build_watermark_mask(image: Image.Image) -> Image.Image:
    rgb = image.convert("RGB")
    data = np.asarray(rgb).astype(np.int16)
    height, width = data.shape[:2]

    yy, xx = np.mgrid[:height, :width]
    light_right_width = min(width, max(140, min(190, int(width * 0.27))))
    dark_right_width = min(width, max(180, min(360, int(width * 0.42))))
    light_bottom_height = min(height, max(28, min(72, int(height * 0.1))))
    dark_bottom_height = min(height, max(28, min(54, int(height * 0.075))))
    light_right_band = xx >= width - light_right_width
    dark_right_band = xx >= width - dark_right_width
    light_target_corner = light_right_band & (yy >= height - light_bottom_height)
    dark_target_corner = dark_right_band & (yy >= height - dark_bottom_height)

    channel_max = data.max(axis=2)
    channel_min = data.min(axis=2)
    saturation = channel_max - channel_min
    brightness = data.mean(axis=2)
    blurred = np.asarray(rgb.filter(ImageFilter.GaussianBlur(3))).astype(np.int16)
    contrast = np.abs(data - blurred).mean(axis=2)

    corner_brightness = brightness[light_target_corner]
    local_background = float(np.median(corner_brightness)) if corner_brightness.size else 255.0

    light_background_mark = (
        (saturation <= 36)
        & (brightness >= 165)
        & (brightness <= 254)
        & (channel_min >= 160)
    )

    dark_background_mark = (
        (saturation <= 48)
        & (brightness >= max(58, local_background + 14))
        & (brightness <= 240)
        & (channel_min >= 42)
    )

    if local_background < 155:
        contrast_mark = (
            (contrast >= 5)
            & (saturation <= 105)
            & (brightness >= max(70, local_background + 16))
            & (brightness <= 252)
        )
        mask = dark_target_corner & (dark_background_mark | contrast_mark)
    else:
        contrast_mark = (
            (contrast >= 4)
            & (saturation <= 72)
            & (brightness <= local_background - 3)
            & (brightness >= 145)
        )
        mask = light_target_corner & (light_background_mark | contrast_mark)

    mask_img = Image.fromarray((mask.astype(np.uint8)) * 255, mode="L")
    mask_img = mask_img.filter(ImageFilter.MaxFilter(3))
    mask_img = mask_img.filter(ImageFilter.GaussianBlur(0.6))
    return mask_img


def remove_watermark(source: Path, destination: Path, quality: int) -> bool:
    with Image.open(source) as opened:
        image = opened.convert("RGBA")

    mask = build_watermark_mask(image)
    if not mask.getbbox():
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, destination)
        return False

    base = image.convert("RGB")
    data = np.asarray(base).copy()
    soft_mask = np.asarray(mask)
    mask_array = soft_mask > 12

    if mask_array.any():
        ys, xs = np.where(mask_array)
        pad = 20
        y0 = max(0, int(ys.min()) - pad)
        y1 = min(data.shape[0], int(ys.max()) + pad + 1)
        x0 = max(0, int(xs.min()) - pad)
        x1 = min(data.shape[1], int(xs.max()) + pad + 1)
        roi = data[y0:y1, x0:x1]
        roi_mask = mask_array[y0:y1, x0:x1]
        inpainted = inpaint_pixels(roi, roi_mask)
        data[y0:y1, x0:x1][roi_mask] = inpainted[roi_mask]

    filled = Image.fromarray(data, mode="RGB")
    cleaned = Image.composite(filled.convert("RGBA"), image, mask)

    destination.parent.mkdir(parents=True, exist_ok=True)
    cleaned.save(destination, format="WEBP", quality=quality, method=6)
    return True


def main() -> None:
    parser = argparse.ArgumentParser(description="Remove article platform watermarks from lower-right corners.")
    parser.add_argument("--source", default="public/articles")
    parser.add_argument("--out", default=".tmp/watermark-cleaned")
    parser.add_argument("--quality", type=int, default=96)
    args = parser.parse_args()

    source_root = Path(args.source)
    out_root = Path(args.out)
    files = sorted(source_root.rglob("*.awebp"))
    changed = 0

    for source in files:
        destination = out_root / source.relative_to(source_root)
        if remove_watermark(source, destination, args.quality):
            changed += 1

    print(f"processed={len(files)} changed={changed} out={out_root}")


if __name__ == "__main__":
    main()
