#!/usr/bin/env python3
"""Génère la page HTML du kit social à partir de exports/planning-social-ete-2026/posts.json."""

from __future__ import annotations

import html
import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DEFAULT_KIT_DIR = ROOT / "www/propositions/planning-social-ete-2026"


FRENCH_DAYS = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")


def fix_day_from_date(post: dict) -> dict:
    """Recalcule le jour de la semaine à partir de la date ISO."""
    from datetime import date

    if not post.get("date"):
        return post
    d = date.fromisoformat(post["date"])
    return {**post, "day": FRENCH_DAYS[d.weekday()]}


def load_kit_data(base: Path) -> dict:
    json_path = base / "posts.json"
    if not json_path.exists():
        fallback = ROOT / "exports/planning-social-ete-2026/posts.json"
        if fallback.exists():
            json_path = fallback
    data = json.loads(json_path.read_text(encoding="utf-8"))
    daily_path = json_path.parent / "daily_posts.json"
    if not daily_path.exists() and base.resolve() == DEFAULT_KIT_DIR.resolve():
        daily_fallback = ROOT / "exports/planning-social-ete-2026/daily_posts.json"
        if daily_fallback.exists():
            daily_path = daily_fallback
    if daily_path.exists():
        daily = json.loads(daily_path.read_text(encoding="utf-8"))
        raw = daily if isinstance(daily, list) else daily.get("daily_posts", [])
        data["daily_posts"] = [fix_day_from_date(p) for p in raw]
    else:
        data["daily_posts"] = []
    data["posts"] = [fix_day_from_date(p) for p in data.get("posts", [])]
    return data


def resolve_kit_paths(kit_dir: Path | None = None) -> tuple[Path, Path, Path, str]:
    base = kit_dir or DEFAULT_KIT_DIR
    data = load_kit_data(base)
    slug = data["meta"]["slug"]
    kit_base_url = f"https://www.arnaud-merigeau.fr/propositions/{slug}"
    out_html = base / "content.html"
    return base / "posts.json", base, out_html, kit_base_url


def utm(url: str, source: str, campaign: str, content: str) -> str:
    sep = "&" if "?" in url else "?"
    return f"{url}{sep}utm_source={source}&utm_medium=social&utm_campaign={campaign}&utm_content={content}"


def utm_source_for_network(net: str) -> str:
    return "twitter" if net == "x" else net


CATEGORY_EMOJIS: dict[str, tuple[str, str]] = {
    "Sécurité": ("🔒", "🛡️"),
    "Développement": ("⚡", "🔧"),
    "Analytics": ("📊", "📈"),
    "Infrastructure": ("🌐", "🔐"),
    "PrestaShop": ("🛒", "⚙️"),
    "Import / Connecteur": ("📦", "🔗"),
    "Import": ("📦", "🔗"),
    "Performance": ("⚡", "🚀"),
    "E-commerce": ("🛍️", "💡"),
    "DevOps": ("⏰", "🛠️"),
    "Migration": ("📝", "🔄"),
    "SEO": ("🔍", "📈"),
    "Freelance": ("💼", "📐"),
    "Conversion": ("🚚", "💡"),
    "Facturation": ("📋", "🧾"),
    "Développement / IA": ("🤖", "✨"),
    "A/B test": ("📈", "🧪"),
    "SAV / IA": ("💬", "🤖"),
    "Comptabilité": ("📋", "💶"),
    "Business": ("📊", "💰"),
    "Produit": ("🛒", "📦"),
}


def count_emojis(text: str) -> int:
    import re

    return len(re.findall(r"[\U0001F300-\U0001F9FF\u2600-\u27BF]", text))


def ensure_emojis(text: str, category: str) -> str:
    """Garantit 1 à 2 emojis dans le corps du post (hors signature)."""
    if count_emojis(text) >= 2:
        return text
    pair = CATEGORY_EMOJIS.get(category, ("💡", "👇"))
    if count_emojis(text) == 0:
        text = f"{pair[0]} {text}"
    if count_emojis(text) < 2:
        lines = text.split("\n")
        if len(lines) > 2:
            lines[-1] = f"{lines[-1]} {pair[1]}"
            text = "\n".join(lines)
        else:
            text = f"{text} {pair[1]}"
    return text


def build_daily_social_body(
    body: str,
    net: str,
    post: dict,
    meta: dict,
) -> tuple[str, str]:
    """Ajoute signature + un seul lien (sans UTM). Retourne (texte, url affichée)."""
    contact_url = meta.get("contact_url", "https://www.arnaud-merigeau.fr/contact/")
    link_url = post.get("url") or contact_url
    is_contact = link_url.rstrip("/").endswith("/contact")
    label = post.get("resource_label") or ("Me contacter" if is_contact else "En savoir plus")
    body = ensure_emojis(body, post.get("category", ""))

    custom = (meta.get("daily_signoff") or {}).get(net)
    if custom:
        suffix = custom.format(label=label, url=link_url)
        if not suffix.startswith("\n"):
            suffix = "\n\n" + suffix.lstrip()
    elif net == "linkedin":
        suffix = (
            f"\n\n👋 Je suis Arnaud Mérigeau, freelance PrestaShop & WordPress certifié Experts "
            f"(+15 ans). Un sujet similaire chez vous ?\n"
            f"→ {label} : {link_url}"
        )
    elif net == "x":
        suffix = (
            f"\n\n👋 Arnaud Mérigeau · freelance PS & WP certifié Experts (+15 ans)\n"
            f"{label} → {link_url}"
        )
    else:
        suffix = (
            f"\n\n👋 Arnaud Mérigeau · Freelance PrestaShop & WordPress certifié Experts (+15 ans)\n"
            f"→ {label} : {link_url}"
        )
    return body.rstrip() + suffix, link_url


def parse_time_minutes(value: str) -> int:
    """Convertit « 8h30 » ou « 10h00 » en minutes depuis minuit."""
    normalized = value.strip().lower().replace("h", ":")
    parts = normalized.split(":")
    hours = int(parts[0])
    minutes = int(parts[1]) if len(parts) > 1 and parts[1] else 0
    return hours * 60 + minutes


def merge_chronological_feed(main_posts: list[dict], daily_posts: list[dict]) -> list[dict]:
    """Fusionne principaux + quotidiens, triés du plus proche au plus lointain."""
    from datetime import date

    ref = date.today()
    feed: list[dict] = []
    for post in main_posts:
        feed.append({**post, "feed_kind": "main"})
    for post in daily_posts:
        feed.append({**post, "feed_kind": "daily"})

    def sort_key(post: dict) -> tuple:
        post_date = date.fromisoformat(post["date"])
        minutes = parse_time_minutes(post.get("time", "12h00"))
        if post_date >= ref:
            return (0, post_date.toordinal(), minutes)
        return (1, -post_date.toordinal(), minutes)

    feed.sort(key=sort_key)
    return feed


def render_feed_item(
    post: dict, footers: dict, campaign: str, kit_base_url: str, meta: dict | None = None
) -> str:
    if post.get("feed_kind") == "daily":
        return render_daily_post(post, meta or {}, campaign, kit_base_url)
    return render_post(post, footers, campaign, kit_base_url)


def render_post(post: dict, footers: dict, campaign: str, kit_base_url: str) -> str:
    slug = post["url_slug"]
    urls = {
        "linkedin": utm(post["url"], "linkedin", campaign, slug),
        "x": utm(post["url"], "twitter", campaign, slug),
        "facebook": utm(post["url"], "facebook", campaign, slug),
    }

    networks = []
    for net, key, label in [
        ("linkedin", "linkedin", "LinkedIn"),
        ("x", "x", "X (Twitter)"),
        ("facebook", "facebook", "Facebook"),
    ]:
        body = post[key].format(
            url_linkedin=urls["linkedin"],
            url_x=urls["x"],
            url_facebook=urls["facebook"],
            footer=footers[key],
        )
        networks.append(
            f"""
      <div class="network" data-network="{net}">
        <div class="network-head">
          <h4>{label}</h4>
          <button type="button" class="btn-copy" data-copy-target="copy-{post['id']}-{net}">Copier</button>
        </div>
        <pre id="copy-{post['id']}-{net}" class="copy-block">{html.escape(body)}</pre>
        <p class="utm-hint">Lien tracké : <a href="{html.escape(urls[net])}" target="_blank" rel="noopener">{html.escape(urls[net])}</a></p>
      </div>"""
        )

    hashtags = " ".join(post.get("hashtags", []))
    note = post.get("note", "")
    note_html = f'<p class="post-note">💡 {html.escape(note)}</p>' if note else ""

    carousel = post.get("carousel")
    carousel_html = ""
    if carousel:
        items = "".join(f"<li>{html.escape(s)}</li>" for s in carousel)
        carousel_html = f'<div class="carousel-block"><h4>Slides carrousel (texte)</h4><ol>{items}</ol></div>'

    visual_html = ""
    visual = post.get("visual")
    if visual:
        default_file = f"visuels/{post['id']}.png"
        img_url = visual.get("source_url") or f"{kit_base_url}/{visual.get('file', default_file).lstrip('/')}"
        dl_name = post["id"] + (".png" if img_url.endswith(".png") else Path(img_url).suffix or ".jpg")

        variants_html = ""
        for variant in visual.get("variants", []):
            v_rel = variant.get("file", "")
            v_url = f"{kit_base_url}/{v_rel.lstrip('/')}" if v_rel and not v_rel.startswith("http") else v_rel
            v_note = variant.get("note", "")
            note_p = f'<p class="variant-note">{html.escape(v_note)}</p>' if v_note else ""
            variants_html += f"""
        <div class="visual-variant">
          <p class="variant-label">{html.escape(variant.get("label", "Variante"))}</p>
          {note_p}
          <figure class="visual-preview visual-preview--variant">
            <img src="{html.escape(v_url)}" alt="{html.escape(variant.get('label', ''))}" loading="lazy" width="200" height="200">
          </figure>
          <div class="visual-actions">
            <a class="btn-dl" href="{html.escape(v_url)}" download="{html.escape(post['id'])}-{html.escape(variant.get('id', 'variant'))}.png">Télécharger</a>
            <button type="button" class="btn-copy" data-copy-value="{html.escape(v_url)}" data-copy-label="Copier l'URL">Copier l'URL</button>
          </div>
        </div>"""

        carousel_files = visual.get("carousel_files", [])
        carousel_imgs = ""
        if carousel_files:
            thumbs = "".join(
                (
                    lambda url: (
                        f'<a href="{html.escape(url)}" target="_blank" rel="noopener" class="carousel-thumb">'
                        f'<img src="{html.escape(url)}" alt="Slide {i}" loading="lazy"></a>'
                    )
                )(f"{kit_base_url}/{cf.lstrip('/')}")
                for i, cf in enumerate(carousel_files, start=1)
            )
            carousel_imgs = f"""
        <div class="carousel-visuals">
          <h4>Slides carrousel LinkedIn</h4>
          <div class="carousel-grid">{thumbs}</div>
        </div>"""

        variants_section = ""
        if variants_html:
            variants_section = f"""
        <div class="visual-variants">
          <h4>Variantes (test)</h4>
          {variants_html}
        </div>"""

        visual_html = f"""
      <section class="visual-block">
        <h3>Visuel réseaux sociaux</h3>
        <p class="visual-meta">{html.escape(visual.get("format", "Image article"))} · même visuel pour LinkedIn, X et Facebook</p>
        <figure class="visual-preview">
          <img src="{html.escape(img_url)}" alt="{html.escape(visual.get("alt", post["title"]))}" loading="lazy" width="600" height="400">
          <figcaption>{html.escape(visual.get("alt", post["title"]))}</figcaption>
        </figure>
        <div class="visual-actions">
          <a class="btn-dl" href="{html.escape(img_url)}" download="{html.escape(dl_name)}" target="_blank" rel="noopener">Télécharger</a>
          <button type="button" class="btn-copy" data-copy-value="{html.escape(img_url)}" data-copy-label="Copier l'URL">Copier l'URL</button>
        </div>
        {variants_section}
        {carousel_imgs}
      </section>"""

    reviews = post.get("reviews")
    reviews_html = ""
    if reviews:
        items = "".join(
            f"""<blockquote class="review-quote">
          <p>{html.escape(r.get("text", ""))}</p>
          <cite>— {html.escape(r.get("author", ""))} · {html.escape(str(r.get("rating", 5)))}★</cite>
        </blockquote>"""
            for r in reviews
        )
        reviews_html = f"""
      <section class="reviews-block">
        <h3>Avis publiés (référence)</h3>
        <p class="reviews-source">Source : <a href="{html.escape(post.get("reviews_url", post["url"]))}#section-reviews" target="_blank" rel="noopener">fiche produit — section avis</a></p>
        {items}
      </section>"""

    return f"""
    <article class="post-card" id="{post['id']}" data-post-id="{post['id']}">
      <header class="post-header">
        <div class="post-header-top">
          <div class="post-date">
            <span class="day">{html.escape(post['day'])}</span>
            <span class="date">{html.escape(post['date'])}</span>
            <span class="time">{html.escape(post['time'])}</span>
          </div>
          <button type="button" class="btn-done" data-post-id="{post['id']}" aria-pressed="false" title="Masquer ce post une fois publié">
            Marquer traité
          </button>
        </div>
        <div class="post-meta">
          <span class="badge badge-main">📰 Publication principale</span>
          <span class="badge">{html.escape(post['type'])}</span>
          {f'<span class="badge badge-tone">Récit</span>' if post.get("tone") == "storytelling" else ""}
          <span class="objective">🎯 {html.escape(post['objective'])}</span>
        </div>
        <h2>{html.escape(post['title'])}</h2>
        <p class="hashtags">{html.escape(hashtags)}</p>
      </header>
      {note_html}
      {visual_html}
      {carousel_html}
      {reviews_html}
      <div class="networks">
        {''.join(networks)}
      </div>
    </article>"""


def render_daily_post(post: dict, meta: dict, campaign: str, kit_base_url: str) -> str:
    hashtags = " ".join(post.get("hashtags", []))
    fmt = post.get("format", "astuce")
    fmt_labels = {
        "astuce": "💡 Astuce",
        "anecdote": "📖 Anecdote",
        "fait": "📌 Fait terrain",
    }
    fmt_badge = fmt_labels.get(fmt, fmt.capitalize())
    visual_html = ""
    visual = post.get("visual") or {}
    visual_file = visual.get("file") or (f"visuels/{post['id']}.png" if post.get("has_visual") else "")
    if post.get("has_visual") and visual_file:
        img_url = (
            visual.get("source_url")
            or f"{kit_base_url}/{visual_file.lstrip('/')}"
        )
        dl_name = Path(visual_file).name or f"{post['id']}.jpg"
        alt = visual.get("alt") or post["title"]
        visual_format = visual.get("format") or "1080×1080 PNG — LinkedIn, X et Facebook"
        visual_html = f"""
      <section class="visual-block visual-block--daily">
        <h3>Visuel réseaux sociaux</h3>
        <p class="visual-meta">{html.escape(visual_format)}</p>
        <figure class="visual-preview">
          <img src="{html.escape(img_url)}" alt="{html.escape(alt)}" loading="lazy" width="600" height="600">
          <figcaption>{html.escape(alt)}</figcaption>
        </figure>
        <div class="visual-actions">
          <a class="btn-dl" href="{html.escape(img_url)}" download="{html.escape(dl_name)}" target="_blank" rel="noopener">Télécharger</a>
          <button type="button" class="btn-copy" data-copy-value="{html.escape(img_url)}" data-copy-label="Copier l'URL">Copier l'URL</button>
        </div>
      </section>"""

    networks = []
    for net, key, label in [
        ("linkedin", "linkedin", "LinkedIn"),
        ("x", "x", "X"),
        ("facebook", "facebook", "Facebook"),
    ]:
        body, link_url = build_daily_social_body(post[key], net, post, meta)
        networks.append(
            f"""
      <div class="network network--compact" data-network="{net}">
        <div class="network-head">
          <h4>{label}</h4>
          <button type="button" class="btn-copy" data-copy-target="copy-{post['id']}-{net}">Copier</button>
        </div>
        <pre id="copy-{post['id']}-{net}" class="copy-block copy-block--compact">{html.escape(body)}</pre>
        <p class="utm-hint">Lien : <a href="{html.escape(link_url)}" target="_blank" rel="noopener">{html.escape(link_url)}</a></p>
      </div>"""
        )

    return f"""
    <article class="post-card post-card--daily" id="{post['id']}" data-post-id="{post['id']}">
      <header class="post-header post-header--daily">
        <div class="post-header-top">
          <div class="post-date">
            <span class="day">{html.escape(post['day'])}</span>
            <span class="date">{html.escape(post['date'])}</span>
            <span class="time">{html.escape(post['time'])}</span>
          </div>
          <button type="button" class="btn-done" data-post-id="{post['id']}" aria-pressed="false" title="Masquer ce post une fois publié">
            Marquer traité
          </button>
        </div>
        <div class="post-meta">
          <span class="badge badge-daily">{fmt_badge}</span>
          <span class="badge badge-category">{html.escape(post.get('category', ''))}</span>
          {'<span class="badge badge-visual">🖼 Visuel suggéré</span>' if post.get('has_visual') else '<span class="badge badge-text">Texte seul</span>'}
        </div>
        <h2>{html.escape(post['title'])}</h2>
        <p class="hashtags">{html.escape(hashtags)}</p>
      </header>
      {visual_html}
      <div class="networks">
        {''.join(networks)}
      </div>
    </article>"""


def build_page(data: dict, kit_base_url: str) -> str:
    meta = data["meta"]
    footers = data["footer"]
    campaign = meta["utm_campaign"]
    daily_posts = data.get("daily_posts", [])
    feed = merge_chronological_feed(data["posts"], daily_posts)
    total_posts = len(feed)

    nav_items = "".join(
        f'<a href="#{p["id"]}" data-post-id="{p["id"]}" class="nav-{p.get("feed_kind", "main")}">'
        f'{p.get("date", "")[8:10]}/{p.get("date", "")[5:7]} {p.get("time", "")} · '
        f'{"📰" if p.get("feed_kind") == "main" else "💬"} '
        f'{html.escape(p["title"][:28])}{"…" if len(p["title"]) > 28 else ""}</a>'
        for p in feed
    )
    feed_html = "".join(
        render_feed_item(p, footers, campaign, kit_base_url, meta) for p in feed
    )
    hero_desc = meta.get(
        "description",
        "Planning réseaux sociaux en un seul flux chronologique — publications principales et posts quotidiens.",
    )
    status_badge = ""
    if meta.get("status") == "draft":
        status_badge = '<span class="pill pill-draft">📝 Brouillon — non publié</span>'

    return f"""<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
  <meta name="googlebot" content="noindex, nofollow">
  <title>{html.escape(meta['title'])} | Arnaud Merigeau</title>
  <link rel="icon" href="https://www.arnaud-merigeau.fr/wp-content/uploads/2024/07/cropped-favicon-orange-1-32x32.png">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&display=swap" rel="stylesheet">
  <style>
    :root {{
      --orange: #e98030;
      --orange-dark: #d06a1a;
      --orange-light: #fff4eb;
      --black: #111;
      --gray-700: #404040;
      --gray-500: #6b6b6b;
      --gray-200: #e8e8e8;
      --gray-100: #f8f8f8;
      --white: #fff;
      --shadow: 0 12px 40px rgba(17,17,17,.08);
      --radius: 16px;
      --max: 960px;
    }}
    * {{ box-sizing: border-box; margin: 0; padding: 0; }}
    body {{
      font-family: 'Montserrat', sans-serif;
      color: var(--gray-700);
      background: linear-gradient(180deg, #fff 0%, var(--gray-100) 100%);
      line-height: 1.6;
      -webkit-font-smoothing: antialiased;
    }}
    a {{ color: var(--orange); text-decoration: none; }}
    a:hover {{ color: var(--orange-dark); }}
    .container {{ width: min(100% - 2rem, var(--max)); margin-inline: auto; }}
    .site-header {{
      position: sticky; top: 0; z-index: 50;
      background: rgba(255,255,255,.94);
      backdrop-filter: blur(12px);
      border-bottom: 1px solid var(--gray-200);
    }}
    .site-header .inner {{
      display: flex; align-items: center; justify-content: space-between;
      padding: .85rem 0; gap: 1rem;
    }}
    .logo {{ font-weight: 800; color: var(--black); font-size: .95rem; }}
    .logo span {{ color: var(--orange); }}
    .hero {{ padding: 2.5rem 0 1.5rem; }}
    .hero h1 {{ font-size: clamp(1.6rem, 4vw, 2.4rem); color: var(--black); font-weight: 800; margin-bottom: .75rem; }}
    .hero p {{ max-width: 40rem; }}
    .stats {{
      display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.2rem;
    }}
    .pill {{
      background: var(--white); border: 1px solid var(--gray-200);
      padding: .45rem .85rem; border-radius: 999px; font-size: .82rem; font-weight: 600;
    }}
    .nav-posts {{
      position: sticky; top: 58px; z-index: 40;
      background: var(--white); border-bottom: 1px solid var(--gray-200);
      overflow-x: auto; white-space: nowrap;
      padding: .6rem 0;
    }}
    .nav-posts .container {{ display: flex; gap: .5rem; }}
    .nav-posts a {{
      display: inline-block; padding: .4rem .75rem; border-radius: 999px;
      font-size: .78rem; font-weight: 600; background: var(--gray-100);
      color: var(--gray-700); border: 1px solid transparent;
    }}
    .nav-posts a:hover {{ border-color: var(--orange); color: var(--orange-dark); }}
    .nav-posts a.nav-main {{ border-left: 3px solid var(--orange); }}
    .nav-posts a.nav-daily {{ border-left: 3px solid #90caf9; }}
    .posts {{ padding: 1.5rem 0 3rem; display: grid; gap: 1.5rem; }}
    .post-card {{
      background: var(--white); border: 1px solid var(--gray-200);
      border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
    }}
    .post-header {{ padding: 1.25rem 1.25rem .5rem; }}
    .post-header-top {{
      display: flex; align-items: flex-start; justify-content: space-between;
      gap: 1rem; margin-bottom: .6rem;
    }}
    .post-date {{
      display: flex; align-items: baseline; gap: .6rem;
      font-size: .85rem; font-weight: 700; color: var(--orange-dark);
    }}
    .post-date .date {{ color: var(--black); font-size: 1rem; }}
    .post-meta {{ display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: .6rem; font-size: .8rem; }}
    .badge {{
      background: var(--orange-light); color: var(--orange-dark);
      padding: .2rem .6rem; border-radius: 999px; font-weight: 700;
    }}
    .badge-tone {{ background: #e8f4fd; color: #1a5f8a; }}
    .badge-main {{ background: var(--orange); color: var(--white); }}
    .objective {{ color: var(--gray-500); }}
    .post-card h2 {{ font-size: 1.15rem; color: var(--black); margin-bottom: .4rem; }}
    .hashtags {{ font-size: .78rem; color: var(--gray-500); }}
    .post-note {{
      margin: 0 1.25rem .75rem; padding: .65rem .85rem;
      background: #fffbe6; border-left: 3px solid #f0c040; border-radius: 0 8px 8px 0;
      font-size: .85rem;
    }}
    .carousel-block {{
      margin: 0 1.25rem 1rem; padding: .85rem 1rem;
      background: var(--gray-100); border-radius: 10px; font-size: .88rem;
    }}
    .carousel-block h4 {{ margin-bottom: .5rem; font-size: .85rem; }}
    .carousel-block ol {{ padding-left: 1.2rem; }}
    .carousel-block li {{ margin-bottom: .35rem; }}
    .visual-block {{
      margin: 0 1.25rem 1rem; padding: 1rem;
      background: var(--orange-light); border: 1px solid rgba(233,128,48,.25);
      border-radius: 12px;
    }}
    .visual-block h3 {{ font-size: .95rem; color: var(--black); margin-bottom: .35rem; }}
    .visual-meta {{ font-size: .78rem; color: var(--gray-500); margin-bottom: .85rem; }}
    .visual-preview {{
      display: grid; grid-template-columns: 200px 1fr; gap: 1rem; align-items: center;
      margin-bottom: .85rem;
    }}
    .visual-preview img {{
      width: 200px; height: auto; max-height: 280px; object-fit: contain;
      border-radius: 12px; border: 2px solid var(--white);
      box-shadow: 0 8px 24px rgba(17,17,17,.12);
    }}
    .visual-preview:not(.visual-preview--variant) img {{
      width: min(100%, 520px); max-height: 360px;
    }}
    .visual-preview figcaption {{ font-size: .85rem; line-height: 1.5; }}
    .visual-variants {{ margin-top: 1.25rem; padding-top: 1rem; border-top: 1px dashed rgba(233,128,48,.4); }}
    .visual-variants h4 {{ font-size: .85rem; margin-bottom: .75rem; }}
    .visual-variant {{ margin-bottom: 1rem; }}
    .variant-label {{ font-weight: 700; font-size: .82rem; color: var(--black); margin-bottom: .25rem; }}
    .variant-note {{ font-size: .75rem; color: var(--gray-500); margin-bottom: .5rem; }}
    .visual-actions {{ display: flex; flex-wrap: wrap; gap: .5rem; }}
    .btn-dl {{
      display: inline-flex; align-items: center;
      background: var(--black); color: var(--white);
      padding: .45rem .9rem; border-radius: 999px;
      font-size: .75rem; font-weight: 700;
    }}
    .btn-dl:hover {{ background: var(--orange-dark); color: var(--white); }}
    .carousel-visuals {{ margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(233,128,48,.3); }}
    .carousel-visuals h4 {{ font-size: .82rem; margin-bottom: .6rem; }}
    .carousel-grid {{ display: flex; flex-wrap: wrap; gap: .5rem; }}
    .carousel-thumb img {{
      width: 96px; height: 96px; object-fit: cover; border-radius: 8px;
      border: 2px solid var(--white); box-shadow: 0 4px 12px rgba(17,17,17,.1);
    }}
    .networks {{ border-top: 1px solid var(--gray-200); }}
    .network {{ padding: 1rem 1.25rem; border-bottom: 1px solid var(--gray-200); }}
    .network:last-child {{ border-bottom: none; }}
    .network-head {{
      display: flex; align-items: center; justify-content: space-between; margin-bottom: .6rem;
    }}
    .network h4 {{ font-size: .9rem; color: var(--black); }}
    .btn-copy {{
      background: var(--orange); color: var(--white); border: none;
      padding: .4rem .8rem; border-radius: 999px; font-size: .75rem;
      font-weight: 700; cursor: pointer; font-family: inherit;
    }}
    .btn-copy:hover {{ background: var(--orange-dark); }}
    .btn-copy.copied {{ background: #2d8a4e; }}
    .btn-done {{
      flex-shrink: 0;
      background: var(--white); color: var(--gray-500);
      border: 1px solid var(--gray-200);
      padding: .35rem .75rem; border-radius: 999px;
      font-size: .72rem; font-weight: 700; cursor: pointer; font-family: inherit;
      transition: background .15s, color .15s, border-color .15s;
    }}
    .btn-done:hover {{ border-color: #2d8a4e; color: #2d8a4e; }}
    .btn-done.is-done {{
      background: #e8f5ec; color: #2d8a4e; border-color: #b8dfc4;
    }}
    .post-card.is-done {{ display: none; }}
    body.show-done .post-card.is-done {{
      display: block; opacity: .72;
      border-color: #b8dfc4;
    }}
    body.show-done .post-card.is-done .btn-done {{ opacity: 1; }}
    .nav-posts a.is-done {{ display: none; }}
    body.show-done .nav-posts a.is-done {{
      display: inline-block; opacity: .55; text-decoration: line-through;
    }}
    .done-toolbar {{
      display: flex; flex-wrap: wrap; align-items: center; gap: .75rem;
      margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--gray-200);
    }}
    .done-toolbar label {{
      display: inline-flex; align-items: center; gap: .45rem;
      font-size: .82rem; font-weight: 600; cursor: pointer; user-select: none;
    }}
    .done-count {{ font-size: .82rem; color: var(--gray-500); font-weight: 600; }}
    .btn-reset-done {{
      background: none; border: none; color: var(--gray-500);
      font-size: .75rem; font-weight: 600; cursor: pointer; font-family: inherit;
      text-decoration: underline; padding: 0;
    }}
    .btn-reset-done:hover {{ color: var(--orange-dark); }}
    .pill-draft {{ background: #fff8e6; border-color: #f0c040; color: #8a6d00; }}
    .reviews-block {{
      margin: 0 1.25rem 1rem; padding: 1rem;
      background: #f0faf9; border: 1px solid rgba(0, 140, 140, .25);
      border-radius: 12px;
    }}
    .reviews-block h3 {{ font-size: .9rem; color: var(--black); margin-bottom: .35rem; }}
    .reviews-source {{ font-size: .75rem; color: var(--gray-500); margin-bottom: .85rem; }}
    .review-quote {{
      margin: 0 0 .75rem; padding: .75rem .9rem;
      background: var(--white); border-left: 3px solid #008c8c;
      border-radius: 0 8px 8px 0; font-size: .85rem;
    }}
    .review-quote p {{ margin-bottom: .4rem; font-style: italic; }}
    .review-quote cite {{ font-size: .78rem; color: var(--gray-500); font-style: normal; font-weight: 700; }}
    .copy-block {{
      white-space: pre-wrap; word-break: break-word;
      background: var(--gray-100); border-radius: 10px;
      padding: .85rem 1rem; font-size: .82rem; line-height: 1.55;
      font-family: inherit; color: var(--gray-700); max-height: 320px; overflow: auto;
    }}
    .utm-hint {{ margin-top: .5rem; font-size: .72rem; color: var(--gray-500); word-break: break-all; }}
    .footer-note {{
      text-align: center; padding: 2rem 0 3rem; font-size: .8rem; color: var(--gray-500);
    }}
    .section-title {{
      font-size: 1.25rem; color: var(--black); font-weight: 800;
      margin: 2rem 0 .35rem; padding-top: .5rem;
    }}
    .section-desc {{ font-size: .85rem; color: var(--gray-500); margin-bottom: 1.25rem; }}
    .feed-legend {{
      display: flex; flex-wrap: wrap; gap: 1rem; font-size: .8rem; color: var(--gray-500);
      margin-bottom: 1.25rem; padding: .65rem .85rem; background: var(--white);
      border: 1px solid var(--gray-200); border-radius: 10px;
    }}
    .feed-legend span {{ display: inline-flex; align-items: center; gap: .35rem; }}
    .feed-legend .dot-main {{ width: 10px; height: 10px; border-radius: 50%; background: var(--orange); }}
    .feed-legend .dot-daily {{ width: 10px; height: 10px; border-radius: 50%; background: #64b5f6; }}
    .posts-section--daily {{
      margin-top: 2.5rem; padding-top: 1.5rem;
      border-top: 2px solid var(--orange-light);
    }}
    .post-card--daily {{
      border-left: 3px solid var(--orange);
    }}
    .post-header--daily h2 {{ font-size: 1rem; }}
    .badge-daily {{ background: #e8f4fd; color: #1565c0; }}
    .badge-category {{ background: var(--gray-100); color: var(--gray-700); }}
    .badge-visual {{ background: #f3e8ff; color: #6b21a8; }}
    .badge-text {{ background: var(--gray-100); color: var(--gray-500); }}
    .daily-visual-idea {{
      margin: 0 1.25rem 1rem; padding: .75rem .9rem;
      background: #faf5ff; border: 1px dashed #c4b5fd; border-radius: 10px; font-size: .82rem;
    }}
    .daily-visual-idea h4 {{ font-size: .78rem; color: #6b21a8; margin-bottom: .35rem; }}
    .network--compact {{ padding: .75rem 1.25rem; }}
    .copy-block--compact {{ max-height: 200px; font-size: .8rem; }}
    .nav-daily-posts {{
      overflow-x: auto; margin-bottom: 1rem; padding-bottom: .25rem;
    }}
    .nav-daily-inner {{ display: flex; gap: .35rem; flex-wrap: wrap; }}
    .nav-daily-posts a {{
      display: inline-block; padding: .3rem .55rem; border-radius: 8px;
      font-size: .72rem; font-weight: 600; background: var(--orange-light);
      color: var(--orange-dark); border: 1px solid rgba(233,128,48,.2);
    }}
    .nav-daily-posts a:hover {{ background: var(--orange); color: var(--white); }}
    @media (max-width: 600px) {{
      .post-date {{ flex-wrap: wrap; }}
      .post-header-top {{ flex-direction: column; align-items: stretch; }}
      .btn-done {{ align-self: flex-start; }}
      .visual-preview {{ grid-template-columns: 1fr; }}
      .visual-preview img {{ width: 100%; height: auto; max-width: 280px; }}
    }}
  </style>
</head>
<body>
  <header class="site-header">
    <div class="container inner">
      <div class="logo">Kit social <span>été 2026</span></div>
      <div style="font-size:.78rem;color:var(--gray-500)">Usage interne · noindex</div>
    </div>
  </header>

  <section class="hero">
    <div class="container">
      <h1>{html.escape(meta['title'])}</h1>
      <p>{html.escape(hero_desc)}</p>
      <div class="stats">
        {status_badge}
        <span class="pill">📅 {html.escape(meta.get('period', '—'))}</span>
        <span class="pill">📆 <span id="posts-total">{total_posts}</span> publications</span>
        <span class="pill">📰 {len(data['posts'])} principaux + {len(daily_posts)} quotidiens</span>
        <span class="pill">⏱ {html.escape(meta.get('cadence', '—'))}</span>
        <span class="pill">🔗 UTM : {html.escape(campaign)}</span>
        <span class="pill" id="posts-remaining-pill">✅ <span id="posts-remaining">{total_posts}</span> restant(s)</span>
      </div>
      <div class="done-toolbar">
        <label>
          <input type="checkbox" id="toggle-show-done">
          Afficher les posts traités (<span id="posts-done-count">0</span>)
        </label>
        <button type="button" class="btn-reset-done" id="reset-done" hidden>Réinitialiser tout</button>
      </div>
    </div>
  </section>

  <nav class="nav-posts" aria-label="Navigation chronologique">
    <div class="container">{nav_items}</div>
  </nav>

  <main class="posts container">
    <div class="feed-legend">
      <span><span class="dot-main"></span> Publication principale (article / actualité)</span>
      <span><span class="dot-daily"></span> Post quotidien (astuce / anecdote / fait)</span>
      <span>↑ du plus proche au plus lointain</span>
    </div>
    {feed_html}
  </main>

  <p class="footer-note container">
    Page protégée par mot de passe · non indexée · générée depuis <code>exports/planning-social-ete-2026/posts.json</code>
  </p>

  <script>
    (function() {{
      var STORAGE_KEY = 'kit-social-ete-2026-done';
      var SHOW_KEY = 'kit-social-ete-2026-show-done';

      function loadDone() {{
        try {{ return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }}
        catch (e) {{ return []; }}
      }}

      function saveDone(ids) {{
        localStorage.setItem(STORAGE_KEY, JSON.stringify(ids));
      }}

      function isDone(id, doneIds) {{
        return doneIds.indexOf(id) !== -1;
      }}

      function updateUI() {{
        var doneIds = loadDone();
        var total = document.querySelectorAll('.post-card[data-post-id]').length;
        var doneCount = doneIds.length;
        var remaining = total - doneCount;

        document.querySelectorAll('.post-card[data-post-id]').forEach(function(card) {{
          var id = card.getAttribute('data-post-id');
          var done = isDone(id, doneIds);
          card.classList.toggle('is-done', done);
          var btn = card.querySelector('.btn-done');
          if (btn) {{
            btn.classList.toggle('is-done', done);
            btn.setAttribute('aria-pressed', done ? 'true' : 'false');
            btn.textContent = done ? 'Traité ✓' : 'Marquer traité';
          }}
        }});

        document.querySelectorAll('.nav-posts a[data-post-id]').forEach(function(link) {{
          var id = link.getAttribute('data-post-id');
          link.classList.toggle('is-done', isDone(id, doneIds));
        }});

        var remainingEl = document.getElementById('posts-remaining');
        var doneCountEl = document.getElementById('posts-done-count');
        var resetBtn = document.getElementById('reset-done');
        if (remainingEl) remainingEl.textContent = String(remaining);
        if (doneCountEl) doneCountEl.textContent = String(doneCount);
        if (resetBtn) resetBtn.hidden = doneCount === 0;
      }}

      function toggleDone(id) {{
        var doneIds = loadDone();
        var idx = doneIds.indexOf(id);
        if (idx === -1) doneIds.push(id);
        else doneIds.splice(idx, 1);
        saveDone(doneIds);
        updateUI();
      }}

      document.querySelectorAll('.btn-done').forEach(function(btn) {{
        btn.addEventListener('click', function(e) {{
          e.stopPropagation();
          toggleDone(btn.getAttribute('data-post-id'));
        }});
      }});

      var toggleShow = document.getElementById('toggle-show-done');
      if (toggleShow) {{
        toggleShow.checked = localStorage.getItem(SHOW_KEY) === '1';
        document.body.classList.toggle('show-done', toggleShow.checked);
        toggleShow.addEventListener('change', function() {{
          document.body.classList.toggle('show-done', toggleShow.checked);
          localStorage.setItem(SHOW_KEY, toggleShow.checked ? '1' : '0');
        }});
      }}

      var resetBtn = document.getElementById('reset-done');
      if (resetBtn) {{
        resetBtn.addEventListener('click', function() {{
          if (!confirm('Réafficher tous les posts et effacer les marquages ?')) return;
          localStorage.removeItem(STORAGE_KEY);
          updateUI();
        }});
      }}

      updateUI();
    }})();

    document.querySelectorAll('.btn-copy').forEach(function(btn) {{
      btn.addEventListener('click', function() {{
        var text = btn.getAttribute('data-copy-value');
        if (!text) {{
          var id = btn.getAttribute('data-copy-target');
          var el = document.getElementById(id);
          if (!el) return;
          text = el.textContent;
        }}
        var label = btn.getAttribute('data-copy-label') || 'Copier';
        navigator.clipboard.writeText(text).then(function() {{
          btn.textContent = 'Copié ✓';
          btn.classList.add('copied');
          setTimeout(function() {{
            btn.textContent = label;
            btn.classList.remove('copied');
          }}, 2000);
        }});
      }});
    }});
  </script>
</body>
</html>"""


def main() -> int:
    kit_arg = Path(sys.argv[1]) if len(sys.argv) > 1 else None
    _, out_dir, out_html, kit_base_url = resolve_kit_paths(kit_arg)
    data = load_kit_data(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    out_html.write_text(build_page(data, kit_base_url), encoding="utf-8")
    # Sync JSON sources into kit folder only for the default planning kit.
    if kit_arg is None:
        src_posts = ROOT / "exports/planning-social-ete-2026/posts.json"
        src_daily = ROOT / "exports/planning-social-ete-2026/daily_posts.json"
        if src_posts.exists():
            (out_dir / "posts.json").write_text(src_posts.read_text(encoding="utf-8"), encoding="utf-8")
        if src_daily.exists():
            (out_dir / "daily_posts.json").write_text(src_daily.read_text(encoding="utf-8"), encoding="utf-8")
    print(f"Généré : {out_html} ({len(data['posts'])} principaux + {len(data.get('daily_posts', []))} quotidiens)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
