#!/usr/bin/env python3
"""Mockups premium Sellsy × PrestaShop / WordPress — masquage blur / pixelate."""

from __future__ import annotations

import math
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "assets" / "sellsy-integration-mockups"

PRESTASHOP_SRC = Path(
    "/Users/arnaudmerigeau/.cursor/projects/Users-arnaudmerigeau-Locals-arnaudmerigeau/assets/"
    "CleanShot_2026-05-28_at_11.46.30-3b6132ba-e26c-4304-a2af-7303b484c2aa.png"
)
WORDPRESS_SRC = Path(
    "/Users/arnaudmerigeau/.cursor/projects/Users-arnaudmerigeau-Locals-arnaudmerigeau/assets/"
    "CleanShot_2026-05-28_at_11.53.40-90e42076-5cfb-478a-a149-b4fcdb40dd28.png"
)

FONT = "/System/Library/Fonts/Supplemental/Arial.ttf"
FONT_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"


def font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
    try:
        return ImageFont.truetype(FONT_BOLD if bold else FONT, size)
    except OSError:
        return ImageFont.load_default()


def pixelate(img: Image.Image, box: tuple[int, int, int, int], blocks: int = 14) -> None:
    x1, y1, x2, y2 = box
    w, h = x2 - x1, y2 - y1
    if w < 4 or h < 4:
        return
    region = img.crop(box)
    tiny_w = max(1, w // blocks)
    tiny_h = max(1, h // blocks)
    tiny = region.resize((tiny_w, tiny_h), Image.Resampling.NEAREST)
    img.paste(tiny.resize((w, h), Image.Resampling.NEAREST), box)


def blur(img: Image.Image, box: tuple[int, int, int, int], radius: int = 16) -> None:
    x1, y1, x2, y2 = box
    pad = radius + 2
    px1, py1 = max(0, x1 - pad), max(0, y1 - pad)
    px2, py2 = min(img.width, x2 + pad), min(img.height, y2 + pad)
    region = img.crop((px1, py1, px2, py2))
    blurred = region.filter(ImageFilter.GaussianBlur(radius=radius))
    crop = blurred.crop((x1 - px1, y1 - py1, x2 - px1, y2 - py1))
    img.paste(crop, box)


def mask_box(img: Image.Image, box: tuple[int, int, int, int], *, mode: str = "pixelate") -> None:
    x1, y1, x2, y2 = box
    pad = 4
    box = (max(0, x1 - pad), max(0, y1 - pad), min(img.width, x2 + pad), min(img.height, y2 + pad))
    if mode == "blur":
        blur(img, box, radius=24)
    elif mode == "both":
        pixelate(img, box, blocks=6)
        blur(img, box, radius=12)
    else:
        pixelate(img, box, blocks=7)


def mask_prestashop(img: Image.Image) -> Image.Image:
    out = img.copy().convert("RGB")
    zones = [
        ((350, 168, 975, 210), "pixelate"),
        ((350, 238, 975, 280), "pixelate"),
        ((350, 410, 975, 450), "both"),
        ((350, 460, 975, 500), "both"),
        ((350, 510, 975, 550), "both"),
        ((350, 560, 975, 600), "both"),
        ((350, 630, 975, 755), "blur"),
        ((600, 4, 1015, 38), "pixelate"),
    ]
    for box, mode in zones:
        mask_box(out, box, mode=mode)
    return out


def mask_wordpress(img: Image.Image) -> Image.Image:
    out = img.copy().convert("RGB")
    zones = [
        ((358, 100, 965, 116), "pixelate"),
        ((358, 133, 965, 149), "pixelate"),
        ((358, 165, 965, 181), "pixelate"),
        ((358, 195, 965, 213), "pixelate"),
        ((358, 243, 965, 265), "pixelate"),
        ((358, 425, 965, 441), "both"),
        ((358, 455, 965, 471), "both"),
        ((358, 485, 965, 501), "both"),
        ((38, 4, 175, 30), "pixelate"),
        ((860, 4, 1015, 30), "pixelate"),
    ]
    for box, mode in zones:
        mask_box(out, box, mode=mode)
    return out


def mesh_gradient(w: int, h: int, palette: list[tuple[int, int, int]]) -> Image.Image:
    """Fond dégradé mesh multi-couleurs."""
    arr = np.zeros((h, w, 3), dtype=np.float32)
    orbs = [
        (0.18, 0.22, palette[0], 0.55),
        (0.82, 0.15, palette[1], 0.50),
        (0.50, 0.88, palette[2], 0.45),
        (0.72, 0.62, palette[3], 0.35),
    ]
    yy, xx = np.mgrid[0:h, 0:w].astype(np.float32)
    nx, ny = xx / w, yy / h
    for ox, oy, color, strength in orbs:
        dist = ((nx - ox) ** 2 + (ny - oy) ** 2) * (1.0 / strength)
        weight = np.exp(-dist * 3.2)[..., None]
        arr += weight * np.array(color, dtype=np.float32)
    arr = np.clip(arr, 0, 255).astype(np.uint8)
    # assombrir légèrement le bas
    fade = np.linspace(1.0, 0.82, h, dtype=np.float32)[:, None, None]
    arr = (arr.astype(np.float32) * fade).astype(np.uint8)
    return Image.fromarray(arr, "RGB")


def rounded_mask(size: tuple[int, int], radius: int) -> Image.Image:
    w, h = size
    mask = Image.new("L", size, 0)
    ImageDraw.Draw(mask).rounded_rectangle((0, 0, w - 1, h - 1), radius=radius, fill=255)
    return mask


def drop_shadow(size: tuple[int, int], radius: int, blur_px: int, opacity: int, offset: tuple[int, int]) -> Image.Image:
    w, h = size
    sw, sh = w + blur_px * 4, h + blur_px * 4
    layer = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
    draw = ImageDraw.Draw(layer)
    ox, oy = blur_px * 2 + offset[0], blur_px * 2 + offset[1]
    draw.rounded_rectangle((ox, oy, ox + w - 1, oy + h - 1), radius=radius, fill=(0, 0, 0, opacity))
    return layer.filter(ImageFilter.GaussianBlur(blur_px))


def browser_shell(content: Image.Image, url: str, accent: tuple[int, int, int]) -> Image.Image:
    rgb = accent
    w = content.width
    bar_h = max(52, int(w * 0.038))
    radius = max(14, int(w * 0.014))
    total_h = bar_h + content.height

    shell = Image.new("RGBA", (w, total_h), (0, 0, 0, 0))
    draw = ImageDraw.Draw(shell)

    # corps fenêtre
    draw.rounded_rectangle((0, 0, w - 1, total_h - 1), radius=radius, fill=(18, 20, 26, 255))
    draw.rounded_rectangle((1, 1, w - 2, total_h - 2), radius=radius - 1, fill=(248, 249, 252, 255))

    # barre titre
    draw.rounded_rectangle((1, 1, w - 2, bar_h), radius=radius, fill=(240, 242, 247, 255))
    draw.line((8, bar_h, w - 8, bar_h), fill=(220, 224, 232), width=1)

    for i, c in enumerate([(255, 96, 88), (255, 189, 46), (40, 202, 65)]):
        cx = 22 + i * 20
        draw.ellipse((cx - 6, 16, cx + 6, 28), fill=c + (255,))

    # url pill
    pill_x1, pill_x2 = 88, w - 20
    draw.rounded_rectangle((pill_x1, 12, pill_x2, bar_h - 12), radius=8, fill=(255, 255, 255, 255))
    draw.rounded_rectangle((pill_x1, 12, pill_x2, bar_h - 12), radius=8, outline=(216, 220, 228), width=1)
    draw.rounded_rectangle((pill_x1 + 10, 18, pill_x1 + 18, bar_h - 18), radius=2, fill=rgb + (255,))
    f = font(max(13, bar_h // 4))
    draw.text((pill_x1 + 26, bar_h // 2 - 7), url, fill=(70, 76, 88), font=f)

    shell.paste(content, (0, bar_h))
    return shell


def reflection(floater: Image.Image, alpha: int = 55, fade_start: float = 0.35) -> Image.Image:
    w, h = floater.size
    refl_h = int(h * 0.28)
    crop = floater.crop((0, h - refl_h, w, h)).transpose(Image.Transpose.FLIP_TOP_BOTTOM)
    mask = Image.new("L", (w, crop.height), 0)
    for y in range(crop.height):
        t = y / crop.height
        val = int(alpha * max(0, 1 - t / fade_start) ** 1.6)
        ImageDraw.Draw(mask).line((0, y, w, y), fill=val)
    crop.putalpha(mask)
    return crop


def compose_showcase(
    screenshot: Image.Image,
    *,
    title: str,
    subtitle: str,
    url: str,
    accent: tuple[int, int, int],
    bg_palette: list[tuple[int, int, int]],
    canvas_size: tuple[int, int] = (2400, 1600),
    content_width: int = 1720,
    tilt: float = 0.0,
) -> Image.Image:
    cw, ch = canvas_size
    bg = mesh_gradient(cw, ch, bg_palette)

    scale = content_width / screenshot.width
    content = screenshot.resize(
        (content_width, int(screenshot.height * scale)),
        Image.Resampling.LANCZOS,
    )
    shell = browser_shell(content, url, accent)
    if abs(tilt) > 0.01:
        shell = shell.rotate(tilt, resample=Image.Resampling.BICUBIC, expand=True)

    sw, sh = shell.size
    pad = 80
    fx = (cw - sw) // 2
    fy = (ch - sh) // 2 + 40

    # halo accent
    halo = Image.new("RGBA", (sw + 200, sh + 200), (0, 0, 0, 0))
    hd = ImageDraw.Draw(halo)
    hd.ellipse((0, 0, sw + 199, sh + 199), fill=accent + (38,))
    halo = halo.filter(ImageFilter.GaussianBlur(60))
    bg_rgba = bg.convert("RGBA")
    bg_rgba.alpha_composite(halo, (fx - 100, fy - 80))

    shadow = drop_shadow((sw, sh), radius=18, blur_px=52, opacity=110, offset=(0, 32))
    shadow2 = drop_shadow((sw, sh), radius=18, blur_px=18, opacity=50, offset=(0, 8))
    bg_rgba.alpha_composite(shadow, (fx - 104, fy - 78))
    bg_rgba.alpha_composite(shadow2, (fx - 36, fy - 10))
    bg_rgba.alpha_composite(shell, (fx, fy))

    refl = reflection(shell, alpha=48)
    bg_rgba.alpha_composite(refl, (fx, fy + sh - 8))

    draw = ImageDraw.Draw(bg_rgba)
    tf = font(56, bold=True)
    sf = font(26)
    draw.text((pad, pad), title, fill=(255, 255, 255, 245), font=tf)
    draw.text((pad, pad + 68), subtitle, fill=(255, 255, 255, 170), font=sf)

    # badge plateforme
    bf = font(18, bold=True)
    badge_w = int(draw.textlength(title.split("×")[0].strip() if "×" in title else "Sync", font=bf)) + 40
    bx, by = pad, pad + 118
    draw.rounded_rectangle((bx, by, bx + 180, by + 40), radius=20, fill=accent + (220,))
    label = "PrestaShop" if "Presta" in title else "WordPress"
    tw = draw.textlength(label, font=bf)
    draw.text((bx + (180 - tw) / 2, by + 10), label, fill=(255, 255, 255), font=bf)

    # vignette
    vig = Image.new("L", (cw, ch), 0)
    vd = ImageDraw.Draw(vig)
    vd.ellipse((-cw // 4, -ch // 6, cw + cw // 4, ch + ch // 3), fill=255)
    vig = vig.filter(ImageFilter.GaussianBlur(cw // 8))
    dark = Image.new("RGBA", (cw, ch), (0, 0, 0, 0))
    dark.putalpha(Image.eval(vig, lambda p: 255 - int(p * 0.42)))
    bg_rgba.alpha_composite(dark, (0, 0))

    return bg_rgba.convert("RGB")


def compose_duo_hero(presta: Image.Image, wp: Image.Image) -> Image.Image:
    cw, ch = 2400, 1350
    palette = [(12, 18, 42), (28, 36, 88), (64, 28, 96), (8, 52, 72)]
    bg = mesh_gradient(cw, ch, palette).convert("RGBA")

    draw = ImageDraw.Draw(bg)
    tf, sf = font(62, bold=True), font(28)
    draw.text((100, 72), "Intégration Sellsy", fill=(255, 255, 255))
    draw.text((100, 148), "Synchronisation bidirectionnelle · PrestaShop & WordPress", fill=(200, 206, 220), font=sf)

    cards = [
        (presta, "PrestaShop", "Module (AM) Sellsy Sync", "admin.boutique-client.fr/module/amsellsy", (223, 0, 122), 0, 80, 300, 1040),
        (wp, "WordPress", "Connexion API Sellsy", "admin.site-client.fr/wp-admin/", (33, 117, 155), 0, 1280, 300, 1040),
    ]

    for shot, platform, caption, url, accent, tilt, ox, oy, width in cards:
        scale = width / shot.width
        content = shot.resize((width, int(shot.height * scale)), Image.Resampling.LANCZOS)
        shell = browser_shell(content, url, accent)
        if abs(tilt) > 0.01:
            shell = shell.rotate(tilt, resample=Image.Resampling.BICUBIC, expand=True)
        sw, sh = shell.size

        halo = Image.new("RGBA", (sw + 160, sh + 160), (0, 0, 0, 0))
        ImageDraw.Draw(halo).ellipse((0, 0, sw + 159, sh + 159), fill=accent + (32,))
        halo = halo.filter(ImageFilter.GaussianBlur(50))
        bg.alpha_composite(halo, (ox - 80, oy - 60))

        shadow = drop_shadow((sw, sh), 16, 42, 90, (0, 24))
        bg.alpha_composite(shadow, (ox - 84, oy - 54))
        bg.alpha_composite(shell, (ox, oy))

        cf, lf = font(22, bold=True), font(16)
        draw.text((ox, oy - 42), platform, fill=accent + (255,), font=cf)
        draw.text((ox, oy - 16), caption, fill=(180, 186, 200), font=lf)

    # connecteur central
    cx, cy = cw // 2, ch // 2 + 80
    glow = Image.new("RGBA", (200, 200), (0, 0, 0, 0))
    gd = ImageDraw.Draw(glow)
    gd.ellipse((0, 0, 199, 199), fill=(120, 140, 255, 60))
    glow = glow.filter(ImageFilter.GaussianBlur(20))
    bg.alpha_composite(glow, (cx - 100, cy - 100))
    draw.rounded_rectangle((cx - 72, cy - 36, cx + 72, cy + 36), radius=18, fill=(255, 255, 255, 28), outline=(255, 255, 255, 80))
    sf2 = font(20, bold=True)
    tw = draw.textlength("Sellsy", font=sf2)
    draw.text((cx - tw / 2, cy - 12), "Sellsy", fill=(255, 255, 255), font=sf2)

    return bg.convert("RGB")


def main() -> None:
    OUT.mkdir(parents=True, exist_ok=True)

    presta = mask_prestashop(Image.open(PRESTASHOP_SRC))
    wp = mask_wordpress(Image.open(WORDPRESS_SRC))

    presta.save(OUT / "sellsy-prestashop-redacted.png", optimize=True)
    wp.save(OUT / "sellsy-wordpress-redacted.png", optimize=True)

    presta_showcase = compose_showcase(
        presta,
        title="Sellsy × PrestaShop",
        subtitle="Configuration du connecteur · sync produits, commandes & statuts",
        url="admin.boutique-client.fr/module/amsellsy",
        accent=(223, 1, 127),
        bg_palette=[(18, 10, 38), (52, 8, 64), (10, 24, 58), (80, 12, 48)],
    )
    wp_showcase = compose_showcase(
        wp,
        title="Sellsy × WordPress",
        subtitle="Back-office WooCommerce · import catalogue & espaces clients",
        url="admin.site-client.fr/wp-admin/",
        accent=(33, 117, 155),
        bg_palette=[(8, 22, 48), (12, 48, 72), (18, 32, 58), (6, 18, 36)],
    )
    hero = compose_duo_hero(presta, wp)

    presta_showcase.save(OUT / "sellsy-prestashop-configuration-mockup.png", optimize=True)
    wp_showcase.save(OUT / "sellsy-wordpress-configuration-mockup.png", optimize=True)
    hero.save(OUT / "sellsy-integration-hero.jpg", quality=92, optimize=True)

    for img, name in [
        (presta_showcase, "sellsy-prestashop-thumb.jpg"),
        (wp_showcase, "sellsy-wordpress-thumb.jpg"),
    ]:
        t = img.copy()
        t.thumbnail((1200, 800), Image.Resampling.LANCZOS)
        t.save(OUT / name, quality=90, optimize=True)

    print(f"Visuels générés → {OUT}")


if __name__ == "__main__":
    main()
