#!/usr/bin/env python3
"""Visuels septembre 2026 — layouts variés, pas de bouton URL, jours ouvrés seulement."""

from __future__ import annotations

import json
from datetime import date
from pathlib import Path

from PIL import Image, ImageDraw, ImageFilter, ImageFont, ImageOps

ROOT = Path(__file__).resolve().parents[1]
KIT_DIR = ROOT / "www/propositions/planning-social-septembre-2026"
POSTS = KIT_DIR / "daily_posts.json"
OUT = KIT_DIR / "visuels"
SHOT_3AS = ROOT / "www/propositions/kit-social-3as-racing/visuels/3as-racing-desktop.jpg"

W, H = 1920, 1080
ORANGE = (233, 128, 48)
INK = (17, 17, 17)
MUTED = (102, 102, 102)
WHITE = (255, 255, 255)
CREAM = (255, 252, 248)
GREEN = (45, 164, 78)
SHEET = (32, 42, 38)

# Semaine du 7 au 11 sept. 2026 — test de variété.
TEST_DATES = {
    "2026-09-07",
    "2026-09-08",
    "2026-09-09",
    "2026-09-10",
    "2026-09-11",
}


def load_font(size: int, *, bold: bool = False, black: bool = False) -> ImageFont.ImageFont:
    if black:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial Black.ttf",
            "/Library/Fonts/Arial Black.ttf",
            "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
        ]
    elif bold:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
            "/Library/Fonts/Arial Bold.ttf",
        ]
    else:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial.ttf",
            "/Library/Fonts/Arial.ttf",
        ]
    for path in candidates:
        if Path(path).exists():
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


def add_blur_halo(base: Image.Image, center: tuple[int, int], radius: int, color: tuple[int, int, int, int], blur: int) -> Image.Image:
    layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
    ImageDraw.Draw(layer).ellipse(
        (center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius),
        fill=color,
    )
    layer = layer.filter(ImageFilter.GaussianBlur(radius=blur))
    return Image.alpha_composite(base.convert("RGBA"), layer)


def cream_bg() -> Image.Image:
    img = Image.new("RGB", (W, H), CREAM)
    draw = ImageDraw.Draw(img)
    for y in range(H):
        t = y / H
        draw.line([(0, y), (W, y)], fill=(255, int(252 - t * 5), int(248 - t * 8)))
    img = add_blur_halo(img, (1180, 180), 420, (233, 128, 48, 62), blur=120)
    img = add_blur_halo(img, (220, 520), 340, (255, 190, 130, 52), blur=105)
    img = add_blur_halo(img, (1680, 820), 300, (236, 192, 89, 42), blur=90)
    return img.convert("RGB")


def paste_shadow(base: Image.Image, box: tuple[int, int, int, int], radius: int) -> Image.Image:
    x0, y0, x1, y1 = box
    layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
    ImageDraw.Draw(layer).rounded_rectangle(
        (x0 + 12, y0 + 24, x1 + 12, y1 + 24), radius=radius, fill=(20, 24, 36, 70)
    )
    layer = layer.filter(ImageFilter.GaussianBlur(20))
    return Image.alpha_composite(base.convert("RGBA"), layer)


def badge(draw: ImageDraw.ImageDraw, xy: tuple[int, int], text: str) -> None:
    font = load_font(22, bold=True)
    x, y = xy
    tw = draw.textlength(text, font=font)
    draw.rounded_rectangle((x, y, x + tw + 44, y + 56), radius=16, fill=ORANGE)
    draw.text((x + 22, y + 28), text, fill=WHITE, font=font, anchor="lm")


def headline_block(draw: ImageDraw.ImageDraw, x: int, y: int, lines: list[str], sub: str, max_w: int = 780) -> None:
    title_f = load_font(56, black=True)
    sub_f = load_font(28, bold=True)
    for i, line in enumerate(lines):
        draw.text((x, y), line, fill=INK, font=title_f)
        if i == 0:
            lw = draw.textlength(line, font=title_f)
            draw.ellipse((x + int(lw) + 14, y + 16, x + int(lw) + 32, y + 34), fill=ORANGE)
        y += 74
    y += 12
    words, cur = sub.split(), []
    dummy = draw
    for word in words:
        trial = " ".join(cur + [word])
        if dummy.textlength(trial, font=sub_f) > max_w and cur:
            draw.text((x, y), " ".join(cur), fill=ORANGE, font=sub_f)
            y += 40
            cur = [word]
        else:
            cur.append(word)
    if cur:
        draw.text((x, y), " ".join(cur), fill=ORANGE, font=sub_f)


def render_form_recaptcha() -> Image.Image:
    """Lundi : formulaire création de compte + captcha."""
    img = cream_bg()
    draw = ImageDraw.Draw(img)
    badge(draw, (72, 80), "SÉCURITÉ")
    headline_block(draw, 72, 180, ["Les bots", "reprennent"], "reCAPTCHA sur le bon formulaire. Pas un module à 80 €.")

    fx, fy, fw, fh = 920, 90, 920, 900
    img = paste_shadow(img, (fx, fy, fx + fw, fy + fh), 28)
    draw = ImageDraw.Draw(img)
    draw.rounded_rectangle((fx, fy, fx + fw, fy + fh), radius=28, fill=WHITE)
    draw.text((fx + 56, fy + 48), "Créer un compte", fill=INK, font=load_font(34, black=True))
    draw.text((fx + 56, fy + 100), "Boutique PrestaShop", fill=MUTED, font=load_font(20))

    def field(label: str, y: int, placeholder: str) -> None:
        draw.text((fx + 56, y), label, fill=MUTED, font=load_font(18, bold=True))
        draw.rounded_rectangle((fx + 56, y + 32, fx + fw - 56, y + 104), radius=14, fill=(248, 248, 250), outline=(220, 220, 224), width=2)
        draw.text((fx + 80, y + 68), placeholder, fill=(170, 170, 176), font=load_font(22), anchor="lm")

    field("E-mail", fy + 160, "client@mail.com")
    field("Mot de passe", fy + 300, "••••••••")

    cx0, cy0 = fx + 56, fy + 460
    draw.rounded_rectangle((cx0, cy0, fx + fw - 56, cy0 + 140), radius=16, fill=(250, 250, 252), outline=(210, 210, 214), width=2)
    draw.rounded_rectangle((cx0 + 28, cy0 + 42, cx0 + 84, cy0 + 98), radius=8, outline=(90, 90, 96), width=3)
    draw.rectangle((cx0 + 40, cy0 + 58, cx0 + 72, cy0 + 82), fill=GREEN)
    draw.text((cx0 + 108, cy0 + 70), "Je ne suis pas un robot", fill=INK, font=load_font(24, bold=True), anchor="lm")
    draw.text((fx + fw - 88, cy0 + 118), "reCAPTCHA", fill=MUTED, font=load_font(14), anchor="rb")

    draw.rounded_rectangle((fx + 56, fy + 660, fx + fw - 56, fy + 748), radius=16, fill=ORANGE)
    draw.text((fx + fw // 2, fy + 704), "Créer le compte", fill=WHITE, font=load_font(24, bold=True), anchor="mm")
    draw.text((fx + fw // 2, fy + 820), "Sans ça : 200 faux comptes dans la nuit.", fill=MUTED, font=load_font(18), anchor="mm")
    return img.convert("RGB")


def render_center_opc() -> Image.Image:
    """Mardi : typo centrée + stepper checkout."""
    img = cream_bg()
    draw = ImageDraw.Draw(img)
    badge_f = load_font(22, bold=True)
    tw = draw.textlength("PRESTASHOP 9.2", font=badge_f)
    bx = (W - tw - 44) // 2
    draw.rounded_rectangle((bx, 120, bx + tw + 44, 176), radius=16, fill=ORANGE)
    draw.text((W // 2, 148), "PRESTASHOP 9.2", fill=WHITE, font=badge_f, anchor="mm")

    title_f = load_font(72, black=True)
    draw.text((W // 2, 320), "ONE-PAGE", fill=INK, font=title_f, anchor="mm")
    draw.text((W // 2, 410), "CHECKOUT", fill=INK, font=title_f, anchor="mm")
    lw = draw.textlength("CHECKOUT", font=title_f)
    draw.ellipse((W // 2 + int(lw) // 2 + 16, 392, W // 2 + int(lw) // 2 + 36, 412), fill=ORANGE)
    draw.text((W // 2, 500), "Natif, enfin. Pas en prod Q4.", fill=ORANGE, font=load_font(32, bold=True), anchor="mm")

    steps = ["Panier", "Adresse", "Paiement"]
    step_f = load_font(24, bold=True)
    num_f = load_font(22, bold=True)
    gap = 220
    start = (W - gap * 2) // 2
    y = 640
    for i, label in enumerate(steps):
        x = start + i * gap
        draw.ellipse((x - 28, y - 28, x + 28, y + 28), fill=ORANGE)
        draw.text((x, y), str(i + 1), fill=WHITE, font=num_f, anchor="mm")
        draw.text((x, y + 56), label, fill=INK, font=step_f, anchor="mm")
        if i < 2:
            draw.polygon([(x + 48, y - 8), (x + 48, y + 8), (x + 72, y)], fill=ORANGE)
    draw.text((W // 2, 820), "Staging · A/B tunnel · paiements testés", fill=MUTED, font=load_font(24), anchor="mm")
    return img.convert("RGB")


def render_bleed_3as() -> Image.Image:
    """Mercredi : capture réelle du site 3AS, texte à gauche."""
    img = cream_bg()
    shot = ImageOps.fit(Image.open(SHOT_3AS).convert("RGB"), (1180, H), method=Image.Resampling.LANCZOS, centering=(0.45, 0.12))
    fade = Image.new("L", shot.size, 255)
    fd = ImageDraw.Draw(fade)
    for i in range(160):
        fd.rectangle((i, 0, i + 1, H), fill=int(255 * i / 160))
    panel = Image.new("RGB", (W, H), CREAM)
    panel.paste(shot, (W - 1180, 0))
    img.paste(panel, (0, 0))
    # Recolle un fond cream à gauche pour le texte.
    draw = ImageDraw.Draw(img)
    draw.rectangle((0, 0, 820, H), fill=CREAM)
    for y in range(H):
        t = y / H
        draw.line([(0, y), (760, y)], fill=(255, int(252 - t * 5), int(248 - t * 8)))
    img = add_blur_halo(img, (280, 400), 280, (255, 190, 130, 40), blur=90).convert("RGB")
    draw = ImageDraw.Draw(img)
    badge(draw, (64, 90), "CAS CLIENT")
    headline_block(draw, 64, 200, ["300 000 refs", "pièces moto"], "L'ERP ne tenait plus. Recherche véhicule ~4M combinaisons.")
    draw.text((64, 520), "3AS Racing", fill=INK, font=load_font(28, bold=True))
    draw.text((64, 568), "Refonte PrestaShop · facettes · mobile", fill=MUTED, font=load_font(22))
    return img.convert("RGB")


def render_cart() -> Image.Image:
    """Jeudi : capture panier, texte au-dessus."""
    img = cream_bg()
    draw = ImageDraw.Draw(img)
    badge(draw, (72, 48), "CONVERSION")
    headline_block(draw, 72, 120, ["Le panier n'est pas", "une liste d'articles"], "Franco visible. Un CTA. Total lisible.")

    fx, fy, fw = 160, 380, 1600
    fh = 640
    img = paste_shadow(img, (fx, fy, fx + fw, fy + fh), 24)
    draw = ImageDraw.Draw(img)
    draw.rounded_rectangle((fx, fy, fx + fw, fy + fh), radius=24, fill=WHITE)
    draw.text((fx + 48, fy + 36), "Votre panier", fill=INK, font=load_font(28, black=True))
    draw.text((fx + fw - 48, fy + 36), "2 articles", fill=MUTED, font=load_font(20), anchor="ra")

    rows = [("Casque jet", "89,00 €"), ("Gants cuir", "34,00 €")]
    for i, (name, price) in enumerate(rows):
        y = fy + 100 + i * 96
        draw.rounded_rectangle((fx + 40, y, fx + 120, y + 72), radius=12, fill=(240, 240, 244))
        draw.text((fx + 148, y + 36), name, fill=INK, font=load_font(24, bold=True), anchor="lm")
        draw.text((fx + fw - 48, y + 36), price, fill=INK, font=load_font(24, bold=True), anchor="rm")

    # Barre franco
    by = fy + 310
    draw.text((fx + 48, by), "Plus que 7 € pour la livraison offerte", fill=ORANGE, font=load_font(20, bold=True))
    draw.rounded_rectangle((fx + 48, by + 36, fx + fw - 48, by + 52), radius=8, fill=(245, 230, 214))
    draw.rounded_rectangle((fx + 48, by + 36, fx + 48 + int((1600 - 96) * 0.72), by + 52), radius=8, fill=ORANGE)

    draw.line([(fx + 48, fy + 430), (fx + fw - 48, fy + 430)], fill=(230, 230, 234), width=2)
    draw.text((fx + 48, fy + 470), "Total TTC", fill=INK, font=load_font(26, bold=True))
    draw.text((fx + fw - 48, fy + 470), "123,00 €", fill=INK, font=load_font(32, black=True), anchor="rm")
    btn_w = 420
    draw.rounded_rectangle((fx + fw - 48 - btn_w, fy + 530, fx + fw - 48, fy + 600), radius=16, fill=ORANGE)
    draw.text((fx + fw - 48 - btn_w // 2, fy + 565), "Commander", fill=WHITE, font=load_font(24, bold=True), anchor="mm")
    return img.convert("RGB")


def render_sheet() -> Image.Image:
    """Vendredi : grille CSV type export comptable."""
    img = Image.new("RGB", (W, H), SHEET)
    draw = ImageDraw.Draw(img)
    for y in range(H):
        t = y / H
        draw.line([(0, y), (W, y)], fill=(int(32 + t * 8), int(42 + t * 6), int(38 + t * 4)))
    img = add_blur_halo(img, (1400, 200), 380, (233, 128, 48, 50), blur=110).convert("RGB")
    draw = ImageDraw.Draw(img)
    badge(draw, (72, 64), "COMPTA")
    title_f = load_font(56, black=True)
    draw.text((72, 160), "Export CSV", fill=WHITE, font=title_f)
    draw.text((72, 238), "Sage / Cegid", fill=ORANGE, font=title_f)
    draw.text((72, 340), "Quand Pennylane n'est pas le sujet.", fill=(200, 200, 196), font=load_font(26, bold=True))

    headers = ["Date", "Commande", "HT", "TVA", "TTC"]
    rows = [
        ["03/09/2026", "CMD-1842", "120,00", "24,00", "144,00"],
        ["03/09/2026", "CMD-1843", "64,50", "12,90", "77,40"],
        ["04/09/2026", "CMD-1844", "210,00", "42,00", "252,00"],
    ]
    col_w = [200, 220, 160, 160, 160]
    ox, oy = 72, 440
    hf = load_font(18, bold=True)
    rf = load_font(20)
    x = ox
    for i, h in enumerate(headers):
        draw.rectangle((x, oy, x + col_w[i], oy + 52), fill=(48, 62, 56))
        draw.text((x + 16, oy + 26), h, fill=ORANGE, font=hf, anchor="lm")
        x += col_w[i]
    for r, row in enumerate(rows):
        x = ox
        bg = (40, 52, 48) if r % 2 == 0 else (36, 48, 44)
        for i, cell in enumerate(row):
            draw.rectangle((x, oy + 52 + r * 56, x + col_w[i], oy + 108 + r * 56), fill=bg)
            draw.text((x + 16, oy + 80 + r * 56), cell, fill=WHITE, font=rf, anchor="lm")
            x += col_w[i]
    draw.text((72, 780), "API temps réel  ≠  fichier périodique. Ce n'est pas le même module.", fill=(180, 180, 176), font=load_font(22))
    return img.convert("RGB")


RENDERERS = {
    "2026-09-07": render_form_recaptcha,
    "2026-09-08": render_center_opc,
    "2026-09-09": render_bleed_3as,
    "2026-09-10": render_cart,
    "2026-09-11": render_sheet,
}


def drop_weekends(posts: list[dict]) -> list[dict]:
    kept = []
    for post in posts:
        d = date.fromisoformat(post["date"])
        if d.weekday() >= 5:
            jpg = OUT / f"{post['id']}.jpg"
            jpg.unlink(missing_ok=True)
            (OUT / f"{post['id']}.png").unlink(missing_ok=True)
            continue
        kept.append(post)
    return kept


def main() -> int:
    OUT.mkdir(parents=True, exist_ok=True)
    posts = drop_weekends(json.loads(POSTS.read_text(encoding="utf-8")))
    done = 0
    for post in posts:
        if post["date"] not in TEST_DATES:
            continue
        img = RENDERERS[post["date"]]()
        name = f"{post['id']}.jpg"
        img.save(OUT / name, "JPEG", quality=92, optimize=True)
        post["has_visual"] = True
        post["visual"] = {
            "file": f"visuels/{name}",
            "alt": post["title"],
            "format": "1920×1080 JPG 16:9 — LinkedIn, X et Facebook",
        }
        done += 1
        print(f"✓ {name}")
    POSTS.write_text(json.dumps(posts, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(f"{done} visuels test · {len(posts)} posts (week-ends retirés)")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
