<?php
/**
 * process.php — PoPmyWebsite v9.0 (VPS)
 * v9.0 : Refactor V4 — site.css désormais généré par send.php sur Hostinger
 *        au moment du submit mockup. process.php ne fait plus de génération
 *        CSS dynamique. Changements :
 *        - require_once design_system.php DÉSACTIVÉ (fichier supprimé du VPS,
 *          fonctions migrées vers send.php côté Hostinger)
 *        - Suppression appel generate_site_css() : le fichier site.css existe
 *          déjà dans /preview/{SKU}/site.css quand ce script s'exécute
 *        - Suppression appel pmw_store_deploy() pour site.css : déjà déployé
 *          par send.php
 *        - generate_body_classes() recodée en helper local (lignes 77-95),
 *          version simplifiée sans validation stricte (le mockup V4 contrôle
 *          déjà les valeurs envoyées, fallback safe sur chaque champ)
 *        - Conservation : injection <link> dans <head>, application classes
 *          thème sur <body>, stockage data/design.json pour traçabilité
 *        Avantages : pipeline VPS plus court (~30 secondes gagnées par maquette),
 *        cohérence single-source pour le CSS (mockup preview = maquette = site final),
 *        suppression d'un point de friction (design_system.php était bloquant si
 *        absent depuis sa suppression du VPS).
 *        Version process_site.php parallèle : à aligner V4 prochaine session.
 * v8.9 : Hotfix injection <link> site.css. URL absolue obligatoire car
 *        assemble.php injecte un <base href> vers le dossier du template
 *        (ex: https://popmywebsite.com/templates/restaurant/template-01/),
 *        ce qui faisait chercher site.css dans ce dossier au lieu de
 *        /preview/SKU/. Conséquence : aucune maquette générée depuis
 *        session 4 (06/05/2026 matin) ne chargeait son site.css → choix
 *        design client invisibles. Fix : URL absolue avec BASE_URL et
 *        $folder. Aucun autre changement vs v8.8.
 * v8.8 : Intégration design system v1. Lit les 8 décisions visuelles
 *        depuis le payload (designBase, designPrimary, designSecondary,
 *        designHeadingColor, designCouple, designCorners, designShadows,
 *        designBorders). Si toutes présentes : appelle generate_site_css()
 *        depuis design_system.php pour produire un site.css déployé dans
 *        /preview/SKU/site.css, ajoute <link> dans le HTML, applique les
 *        classes thème sur <body>, stocke les décisions dans data/design.json.
 *        Le bloc legacy <style id="pmw-palette-override"> est supprimé dans
 *        ce cas pour éviter les conflits de variables.
 *        Si décisions design absentes : fallback total sur la logique legacy
 *        (palette + ownColours + style + pmw-palette-override). Aucune
 *        régression sur les maquettes en cours.
 *        Helper inclus : design_system.php (pipeline CSS).
 *        Version process_site.php parallèle : v2.7 (inchangée pour cette session).
 * v8.7 : Désactivation Cloudflare. Le domaine popmywebsite.com n'utilise
 *        plus Cloudflare comme CDN (zone passée en statut "moved" côté
 *        Cloudflare depuis avril 2026). L'appel pmw_cloudflare_purge_prefix()
 *        et le require de cloudflare_purge.php sont commentés (réactivables
 *        en décommentant ces lignes + en restaurant le fichier helper).
 *        Aucun impact fonctionnel : la maquette est servie directement par
 *        Hostinger sans cache CDN. Le pipeline log devient propre (plus
 *        d'erreurs HTTP 403 sur la purge).
 *        Version process_site.php parallèle : v2.7.
 * v8.6 : Architecture JSON modulaire — écriture de la mémoire maquette.
 *        Après le déploiement du HTML, écrit :
 *          - /preview/SKU/site.json (statut "mockup", identité + référence design)
 *          - /preview/SKU/data/design.json (palette, fonts, colors, headerFile,
 *            footerFile, selectedBlocks, headerHtml, footerHtml).
 *        Cette mémoire est lue par process_site.php au moment de la génération
 *        du site final pour reproduire la maquette à l'identique (Bug A).
 *        Échec d'écriture = log WARN, pas blocage du pipeline (la maquette
 *        doit toujours arriver chez le prospect).
 *        Helper inclus : pmw_store.php (couche d'abstraction).
 * v8.5 : Fix défensif duplication SEO/signature — remplacement des
 *        str_replace('</head>', ...) et str_replace('</body>', ...) par une
 *        injection ciblée sur la DERNIÈRE occurrence via strrpos. Évite la
 *        régression silencieuse si un bloc échappe au filtre d'extraction
 *        body de assemble.php.
 * v8.4 : Ajout purge cache Cloudflare après deploy (tâches 1.4 + 1.5)
 * v8.3 : Log dédié process.log, negative_prompt sur product uniquement,
 *        règle prompt : nom exact du produit + description visuelle
 */

define('ANTHROPIC_KEY',       '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');
define('ANTHROPIC_MODEL',     'claude-sonnet-4-6');
define('MAX_TOKENS',          16000);
define('BREVO_API_KEY',       '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');
define('FAL_API_KEY',         '3b3de79c-4838-4d7b-aea9-eb5e95827555:ab404210c91301b1962f77a0e409c67a');
define('FAL_IMAGES_PER_TYPE', 1);
define('LOG_FILE',            '/var/www/html/popmywebsite/process.log');
define('FROM_EMAIL',          'info@popmywebsite.com');
define('FROM_NAME',           'PoPmyWebsite');
define('BASE_URL',            'https://popmywebsite.com');
define('SECRET_TOKEN',        'a3f8c2e1dws97065f2a1c8e3b5d09f26ac2a1b3d6e8f0a2c4e6b8d0f2a4c6e8');

// ── Config centralisée ────────────────────────────────────────────────────────
require_once __DIR__ . '/config.php';
// require_once __DIR__ . '/cloudflare_purge.php';  // Désactivé v8.7 (Cloudflare pas utilisé)
require_once __DIR__ . '/pmw_store.php';  // v8.6 — couche JSON modulaire
// require_once __DIR__ . '/design_system.php';  // v9.0 — désactivé : site.css généré par send.php sur Hostinger

// ── Helper : génère les classes thème à appliquer sur <body> ─────────────────
// v9.0 (May 2026) : reprend la logique de design_system.php::generate_body_classes()
// en version simplifiée (sans validation stricte). Le mockup V4 contrôle déjà les
// valeurs envoyées, donc on fait un fallback safe sur chaque champ et on construit
// la chaîne de classes que template.css / theme files appliqueront.
function generate_body_classes(array $decisions): string {
    $allowedBase    = ['light', 'dark'];
    $allowedCorners = ['sharp', 'soft', 'rounded'];
    $allowedShadows = ['none', 'subtle', 'strong'];
    $allowedBorders = ['none', 'thin', 'strong'];

    $base    = in_array($decisions['base']    ?? '', $allowedBase,    true) ? $decisions['base']    : 'light';
    $corners = in_array($decisions['corners'] ?? '', $allowedCorners, true) ? $decisions['corners'] : 'soft';
    $shadows = in_array($decisions['shadows'] ?? '', $allowedShadows, true) ? $decisions['shadows'] : 'subtle';
    $borders = in_array($decisions['borders'] ?? '', $allowedBorders, true) ? $decisions['borders'] : 'thin';

    return implode(' ', [
        'base-' . $base,
        'theme-corner-' . $corners,
        'theme-shadow-' . $shadows,
        'theme-border-' . $borders,
    ]);
}

// ── Helper : assombrit ou éclaircit une couleur hex ──────────────────────────
function adjustBrightness(string $hex, int $amount): string {
    $hex = ltrim($hex, '#');
    if (strlen($hex) === 3) {
        $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
    }
    $r = max(0, min(255, hexdec(substr($hex,0,2)) + $amount));
    $g = max(0, min(255, hexdec(substr($hex,2,2)) + $amount));
    $b = max(0, min(255, hexdec(substr($hex,4,2)) + $amount));
    return '#' . sprintf('%02x%02x%02x', $r, $g, $b);
}



ignore_user_abort(true);
set_time_limit(300);

function pmw_log(string $msg): void {
    file_put_contents(LOG_FILE, date('[Y-m-d H:i:s] ') . $msg . PHP_EOL, FILE_APPEND | LOCK_EX);
}

// ── CHARGEMENT DU JOB ─────────────────────────────────────────────────────────
if (empty($argv[1]) || !file_exists($argv[1])) {
    pmw_log("[ERROR] Fichier job introuvable");
    exit;
}

$data = json_decode(file_get_contents($argv[1]), true);
unlink($argv[1]);

function clean($v) { return trim(strip_tags((string)$v)); }

// ── PARAMETRES ────────────────────────────────────────────────────────────────
$siteType         = clean($data['siteType']           ?? 'vitrine');
$lang             = clean($data['lang']               ?? 'en');
$folder           = clean($data['folder']             ?? '');
$email            = filter_var(trim($data['email']    ?? ''), FILTER_VALIDATE_EMAIL);
$firstName        = clean($data['firstName']          ?? '');
$projectDisplay   = clean($data['projectDisplay']     ?? '');
$sector           = clean($data['sector']             ?? '');
$description      = clean($data['description']        ?? '');
$palette          = clean($data['palette']            ?? 'blue');
$ownColours       = clean($data['ownColours']         ?? '');
$style            = clean($data['style']              ?? 'modern');
$notes            = clean($data['notes']              ?? '');
$functionalOpts   = clean($data['functionalOptions']  ?? '');
$discoveryAnswers = clean($data['discoveryAnswers']   ?? '');
$selectedPages    = clean($data['selectedPages']      ?? '');
$groupKey         = clean($data['groupKey']           ?? '');
$homepageSections = clean($data['homepageSections']   ?? '');
$ecommerce        = clean($data['ecommerce']          ?? '');
$bizName          = clean($data['bizName']            ?? '');
$bizCity          = clean($data['bizCity']            ?? '');
$bizPhone         = clean($data['bizPhone']           ?? '');
$logoUrl          = filter_var(trim($data['logoUrl']  ?? ''), FILTER_VALIDATE_URL)
                    ? trim($data['logoUrl']) : '';

// ── DESIGN SYSTEM v1 — 8 décisions visuelles (mockup v2.0+) ──────────────────
$designBase         = clean($data['designBase']         ?? '');
$designPrimary      = clean($data['designPrimary']      ?? '');
$designSecondary    = clean($data['designSecondary']    ?? '');
$designHeadingColor = clean($data['designHeadingColor'] ?? '');
$designCouple       = clean($data['designCouple']       ?? '');
$designCorners      = clean($data['designCorners']      ?? '');
$designShadows      = clean($data['designShadows']      ?? '');
$designBorders      = clean($data['designBorders']      ?? '');

// Détection : nouveau pipeline si TOUTES les décisions sont fournies
$useDesignSystem = !empty($designBase) && !empty($designPrimary) && !empty($designSecondary)
    && !empty($designHeadingColor) && !empty($designCouple) && !empty($designCorners)
    && !empty($designShadows) && !empty($designBorders);

pmw_log("v9.0 START — folder=$folder lang=$lang sector=$sector groupKey=$groupKey bizName=$bizName designSystem=" . ($useDesignSystem ? 'v1' : 'legacy'));

if (!preg_match('/^PMW-[A-Z0-9]{8}$/', $folder) || !$email) {
    pmw_log("[ERROR] Parametres invalides");
    exit;
}

$mockupUrl = BASE_URL . '/preview/' . $folder . '/';

// ── NOMS DE LANGUES COMPLETS ──────────────────────────────────────────────────
$langNames = [
    'fr'=>'français','en'=>'English','de'=>'Deutsch','it'=>'italiano',
    'es'=>'español','pt'=>'português','ar'=>'عربي','nl'=>'Nederlands',
];
$langFull = $langNames[$lang] ?? 'English';

// ── 1. APPEL ASSEMBLE.PHP ─────────────────────────────────────────────────────
$assembleUrl = BASE_URL . '/assemble.php?' . http_build_query([
    'token'             => SECRET_TOKEN,
    'type'              => $siteType,
    'lang'              => $lang,
    'palette'           => $palette,
    'ownColours'        => $ownColours,
    'style'             => $style,
    'project'           => $projectDisplay,
    'sector'            => $sector,
    'functionalOptions' => $functionalOpts,
    'discoveryAnswers'  => $discoveryAnswers,
    'selectedPages'     => $selectedPages,
    'sku'               => $folder,
    'mode'              => 'mockup',  // maquette prospect : popup d'interception sera injecté par assemble.php v6.9
    'groupKey'          => $groupKey,
    'homepageSections'  => $homepageSections,
    'ecommerce'         => $ecommerce,
    'bizName'           => $bizName,
    'bizCity'           => $bizCity,
]);

$ch = curl_init($assembleUrl);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 45]);
$assembleResponse = curl_exec($ch);
$assembleCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($assembleCode !== 200 || empty($assembleResponse)) {
    pmw_log("[ERROR] assemble.php : code $assembleCode");
    exit;
}

$assembleData = json_decode($assembleResponse, true);
if (empty($assembleData['ok'])) {
    pmw_log("[ERROR] assemble.php : " . json_encode($assembleData));
    exit;
}

$htmlSkeleton = $assembleData['html'];
$textsMap     = $assembleData['textsMap'] ?? [];

if (empty($textsMap)) {
    pmw_log("[WARN] textsMap vide — fallback sans personnalisation");
    $html = $htmlSkeleton;
} else {

    // ── 2. PROMPT CLAUDE ─────────────────────────────────────────────────────
    $textsJson    = json_encode($textsMap, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    $contextDesc  = $description      ? "Client description: {$description}"    : '';
    $contextDisc  = $discoveryAnswers ? "Discovery answers: {$discoveryAnswers}" : '';
    $contextBiz   = '';
    if ($bizName) $contextBiz .= "Business name: {$bizName}\n";
    if ($bizCity) $contextBiz .= "City: {$bizCity}\n";
    if ($bizPhone) $contextBiz .= "Phone: {$bizPhone}\n";
    $contextNotes = $notes            ? "Additional notes: {$notes}"             : '';

    $schemaTypes = [
        'restaurant'=>'Restaurant','bar'=>'BarOrPub','hotel'=>'LodgingBusiness',
        'beauty'=>'BeautySalon','medical'=>'MedicalBusiness','clinic'=>'MedicalClinic',
        'sport'=>'SportsActivityLocation','coach'=>'ProfessionalService',
        'artisan'=>'HomeAndConstructionBusiness','legal'=>'LegalService',
        'finance'=>'FinancialService','realestate'=>'RealEstateAgent',
        'education'=>'EducationalOrganization','training'=>'EducationalOrganization',
        'elearning'=>'EducationalOrganization','association'=>'NGO',
    ];
    $schemaType = $schemaTypes[$sector] ?? 'LocalBusiness';

    $currencyMap = [
        'fr'=>'€','de'=>'€','it'=>'€','es'=>'€','pt'=>'€','nl'=>'€',
        'pl'=>'zł','sv'=>'kr','da'=>'kr','no'=>'kr','en'=>'£','ar'=>'AED',
    ];
    $currency = $currencyMap[$lang] ?? '€';
    if ($lang === 'en' && isset($data['country']) && !in_array($data['country'], ['GB','IE'])) {
        $currency = '$';
    }

    $phoneMap = [
        'fr'=>'+33 X XX XX XX XX','de'=>'+49 XXX XXXXXXX','it'=>'+39 XXX XXX XXXX',
        'es'=>'+34 XXX XXX XXX','pt'=>'+351 XXX XXX XXX','nl'=>'+31 XX XXXXXXXX',
        'en'=>'+44 XXXX XXXXXX','ar'=>'+971 XX XXX XXXX',
    ];
    $phoneHint = $phoneMap[$lang] ?? '+XX XXX XXX XXX';

    $prompt = <<<PROMPT
You are a senior web copywriter, local SEO specialist, and AI search engine optimization (GEO) expert, specialised in small and medium businesses across all industries.
You master optimization for AI assistants (ChatGPT, Perplexity, Gemini, Claude) — writing structured, factual, answer-ready content that AI models cite and recommend.
You write short, direct, human copy — never generic, never hollow, never robotic.
You never use underscores, em dashes, or special formatting characters inside text values.
You write for the end customer of the website, not for its owner.

━━━ SITE PROFILE ━━━
Project: {$projectDisplay}
Sector: {$sector}
Active functional options: {$functionalOpts}
Site pages: {$selectedPages}
{$contextDesc}
{$contextNotes}

━━━ DISCOVERY ANSWERS — USE EVERY ANSWER CONCRETELY IN THE COPY ━━━
{$contextDisc}
These answers define the real identity of this business. Every text must reflect these choices in a specific, tangible way.
Example: if type = "Brasserie" and ambiance = "Cosy and intimate" — mention a warm atmosphere, not "quality restaurant".
Example: if trade = "Plumbing" and clients = "Residential" — speak directly to homeowners, not companies.
Example: if goal = "Weight loss" — mention results and before/after, not "fitness sessions".
Example: if niche = "DJ" and genre = "Electronic" — write about energy, crowds, and sound, not "music services".

━━━ SECTOR COPYWRITING ANGLES ━━━
- restaurant: sensations, flavours, shared moments, atmosphere — never "quality cuisine"
- bar: cocktail culture, night energy, the right crowd, the vibe
- hotel / concierge: escaping the ordinary, comfort in every detail
- traiteur: the event guests will remember, seamless service
- beauty / spa: visible transformation, personal ritual, the result in the mirror
- medical / clinic: reassurance, visible expertise, proximity
- sport / coach: measurable results, clear method, the person they want to become
- artisan / cleaning: work done right first time, reliability, fast quote
- craftmaker / art: the unique object, the story behind it, the hands that made it
- legal: sharp expertise, cases handled, calm in complex situations
- finance / architecture: concrete outcomes, long-term vision
- decoration / fashion / furniture: aesthetics, uniqueness, curation
- photography / music / influencer / creator: strong visual universe, emotion
- realestate / transport / events / travel: full service, zero stress
- school / training / elearning: measurable transformation, proven method
- association: concrete impact, real numbers, urgency of the cause
- other: direct client benefit, no jargon, immediate value

━━━ VISUAL STYLE TO COPY STYLE ━━━
The chosen visual style ({$style}) must directly influence writing style:
- modern: short rhythmic sentences, active voice, action verbs, warm but precise
- bold: punchy hooks, ultra-short sentences max 7 words, numbers upfront
- elegant: polished sentences, precise vocabulary, slower rhythm
- friendly: warm register, gentle humour, concrete and simple
- minimal: fewest possible words, every word earns its place
- (empty): blend of modern and friendly

━━━ LANGUAGE — ABSOLUTE RULE ━━━
Target language: {$langFull} ({$lang})
Every single JSON value must be written in {$langFull}. Zero exceptions.

━━━ CURRENCY — ABSOLUTE RULE ━━━
Market currency: {$currency}
Every price uses {$currency} exclusively. Never $, never £.
Price = 0 → local word: Gratuit / Free / Kostenlos / Gratuito / Gratis.

━━━ BUSINESS IDENTITY ━━━
{$contextBiz}
Use these real details in the copy whenever relevant.
For any missing information, invent realistic placeholders.

━━━ CONTACT PLACEHOLDERS — ABSOLUTE RULE ━━━
Local phone format: {$phoneHint}
Email: if bizName is provided, derive a professional email from it. Otherwise invent one.
Address: if bizCity is provided, use that city. Otherwise invent a plausible address in {$lang}.
Zero "@example.com", zero "123 Main Street" in the output.

━━━ COPYWRITING RULES ━━━
1. Body text sentences: maximum 18 words each
2. H1 and H2: maximum 8 words — never a question as H1
3. H1: concrete benefit or outcome, not a description of the activity
4. CTAs: action verb + immediate benefit
5. Never repeat the same keyword in adjacent elements
6. No underscores, no em dashes, no special formatting characters
7. Write naturally — varied sentence length
8. For GEO: include at least 2 factual sentences with prices, hours, location or capacity

━━━ BANNED WORDS ━━━
Never use: "professional", "quality", "expert", "passionate", "dedicated", "at your service", "don't hesitate", "innovative solution", "satisfaction guaranteed", "trust us", "Lorem ipsum", "Demo", "PoPmyWebsite", "state of the art", "cutting edge", "world class", "comprehensive", "holistic"

━━━ SIMULATOR BLOCK — SPECIAL RULES ━━━
If the texts contain the key "simulator-01-config", you MUST replace it with a complete JSON config object adapted to this exact sector. Rules:
- "currency": must match the market currency ({$currency} symbol → EUR, GBP, USD, AED, CHF)
- Slider types: use "integer" for counts (sessions, days, weeks, kg, m²), "currency" only for prices
- Slider ranges: realistic for the sector — never use budget ranges for a count slider
- Formula factor: produces a plausible output for the sector
- Result type: "currency" for revenue/cost, "decimal" for physical quantities (kg, km), "integer" for counts

Examples by sector:
  Sport / Coach: s1={séances/sem, integer, min:1, max:7, step:1, value:3}, s2={durée semaines, integer, min:4, max:52, step:1, value:12}, result={decimal, unit:" kg perdus", factor:0.35}
  Finance: s1={budget mensuel, currency, min:500, max:10000, step:100, value:2000}, s2={durée mois, integer, min:6, max:60, step:1, value:24}, result={currency, factor:0.08}
  Immobilier / Conciergerie: s1={loyer nuit, currency, min:50, max:500, step:10, value:120}, s2={nuits/mois, integer, min:5, max:28, step:1, value:18}, result={currency, factor:0.85}
  Artisan: s1={surface m², integer, unit:" m²", min:20, max:300, step:5, value:80}, s2={budget m², currency, min:20, max:200, step:5, value:60}, result={currency, factor:1}
  Restaurant: s1={couverts/soir, integer, unit:" couverts", min:10, max:100, step:5, value:40}, s2={ticket moyen, currency, min:15, max:80, step:5, value:35}, result={currency, factor:0.25}

The JSON must be valid, all strings in {$langFull}.

━━━ TEXTS TO PERSONALISE ━━━
Return ALL keys. Rewrite every value from scratch for this exact business.

{$textsJson}

━━━ SEO AND GEO OPTIMISATION ━━━
Everything in {$langFull}. Title and og_title must NOT contain the prospect first name.
Title: activity + speciality + implicit location — max 60 chars, compelling.
Meta description: concrete benefit + implicit CTA — 140-160 chars.
Schema description: 1 factual sentence citable by an AI assistant.

━━━ IMAGE PROMPTS FOR AI VISUAL GENERATION ━━━
Generate 5 ultra-specific English photo prompts for Flux AI image generation.
Each prompt: 5-8 words, concrete visual English nouns, photo-realistic.

GOLDEN RULE FOR PROMPTS:
Always use the exact product or item name as it is known internationally (in English or its original language if internationally recognised), then add key visual descriptors.
Do NOT translate niche product names into generic English words — this loses precision.

Examples of correct approach:
- Kippa seller → "embroidered kippa collection wooden shelf warm lighting" (keep "kippa" — it is internationally known)
- Moroccan restaurant → "moroccan tagine clay pot steaming herbs restaurant table"
- Resin board maker → "handmade epoxy resin board swirling blue white studio"
- Italian trattoria → "pasta carbonara plate fork rustic italian trattoria table"
- Yoga studio → "yoga class studio wood floor morning light group"

Prompt types:
- hero: wide establishing shot — atmosphere of the place or main activity (16:9)
- gallery: the actual product/dish/item in close-up detail (square)
- about: human element — hands at work, artisan gesture, team moment (portrait)
- card: supporting context — material, tool, ingredient, environment (4:3)
- product: isolated item on white/neutral background, e-commerce style (square)

━━━ RESPONSE FORMAT ━━━
Valid JSON only. Zero markdown, zero comments, zero text outside the JSON block.
{
  "texts": { "element_id": "personalised text in {$langFull}" },
  "seo": {
    "title": "max 60 chars in {$langFull}",
    "description": "140-160 chars in {$langFull}",
    "og_title": "max 70 chars in {$langFull}",
    "og_description": "max 200 chars in {$langFull}",
    "schema_type": "{$schemaType}",
    "schema_name": "{$projectDisplay}",
    "schema_description": "1 factual sentence in {$langFull}"
  },
  "image_prompts": {
    "hero":    "5-8 word English prompt using exact product/place name + visual context",
    "gallery": "5-8 word English prompt using exact product/item name + close-up detail",
    "about":   "5-8 word English prompt — human element, hands, team, artisan",
    "card":    "5-8 word English prompt — material, tool, ingredient, environment",
    "product": "5-8 word English prompt — isolated item, white background, e-commerce"
  }
}
PROMPT;

    // ── 3. APPEL CLAUDE ───────────────────────────────────────────────────────
    pmw_log("Appel Claude API...");
    $ch = curl_init('https://api.anthropic.com/v1/messages');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode([
            'model'      => ANTHROPIC_MODEL,
            'max_tokens' => MAX_TOKENS,
            'messages'   => [['role' => 'user', 'content' => $prompt]],
        ]),
        CURLOPT_HTTPHEADER => [
            'x-api-key: ' . ANTHROPIC_KEY,
            'anthropic-version: 2023-06-01',
            'content-type: application/json',
        ],
        CURLOPT_TIMEOUT => 120,
    ]);
    $claudeResponse = curl_exec($ch);
    $claudeCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($claudeCode !== 200 || empty($claudeResponse)) {
        pmw_log("[ERROR] Claude : code $claudeCode");
        $html = $htmlSkeleton;
    } else {
        $claudeData   = json_decode($claudeResponse, true);
        $rawText      = $claudeData['content'][0]['text'] ?? '';
        $rawText      = preg_replace('/^```(?:json)?\s*/i', '', trim($rawText));
        $rawText      = preg_replace('/\s*```$/i', '', $rawText);
        $claudeResult = json_decode($rawText, true);

        if (!is_array($claudeResult)) {
            pmw_log("[ERROR] Claude JSON invalide : " . substr($rawText, 0, 200));
            $html = $htmlSkeleton;
        } else {
            $replacements = $claudeResult['texts']         ?? [];
            $seo          = $claudeResult['seo']           ?? [];
            $imagePrompts = $claudeResult['image_prompts'] ?? [];
            $html         = $htmlSkeleton;

            pmw_log("Claude OK — " . count($replacements) . " textes, prompts: " . implode(', ', array_keys($imagePrompts)));
            foreach ($imagePrompts as $type => $p) {
                pmw_log("prompt[$type]: $p");
            }

            // ── Remplacement textes ───────────────────────────────────────────
            foreach ($replacements as $editId => $newText) {
                if (empty($editId) || !is_string($newText)) continue;
                $escaped = htmlspecialchars($newText, ENT_NOQUOTES, 'UTF-8');
                $escaped = str_replace(['\\', '$'], ['\\\\', '\\$'], $escaped); // Protege les backreferences regex $X dans preg_replace
                $idQ     = preg_quote($editId, '/');

                if (str_ends_with($editId, '__aria')) {
                    $baseQ = preg_quote(substr($editId, 0, -6), '/');
                    $html  = preg_replace('/(data-edit-id="' . $baseQ . '"[^>]*aria-label=")[^"]*(")/i', '${1}' . $escaped . '${2}', $html);
                    continue;
                }
                if (str_ends_with($editId, '__placeholder')) {
                    $baseQ = preg_quote(substr($editId, 0, -13), '/');
                    $html  = preg_replace('/(data-edit-id="' . $baseQ . '"[^>]*placeholder=")[^"]*(")/i', '${1}' . $escaped . '${2}', $html);
                    continue;
                }
                $html = preg_replace('/(<[^>]+data-edit-id="' . $idQ . '"[^>]*>)\s*([^<]*)\s*(<\/[a-zA-Z0-9]+>)/i', '${1}' . $escaped . '${3}', $html);
            }

            // ── Injection SEO ─────────────────────────────────────────────────
            if (!empty($seo)) {
                $seoHtml = buildSeoTags($seo, $lang, $projectDisplay, $mockupUrl);
                if (!empty($seo['title'])) {
                    $html = preg_replace('/<title>[^<]*<\/title>/i',
                        '<title>' . htmlspecialchars($seo['title'], ENT_QUOTES, 'UTF-8') . '</title>', $html, 1);
                }
                // Injection ciblée sur la DERNIÈRE </head> (défensif anti-duplication)
                $headPos = strrpos($html, '</head>');
                if ($headPos !== false) {
                    $html = substr($html, 0, $headPos) . $seoHtml . "\n" . substr($html, $headPos);
                }
            }

            // ── 4. GÉNÉRATION ET SAUVEGARDE IMAGES FAL.AI ────────────────────
            if (!empty($imagePrompts) && FAL_API_KEY !== 'VOTRE_CLE_FAL_ICI') {
                pmw_log("Génération images fal.ai...");
                $permanentUrls = generateAndSaveFalImages($imagePrompts, $folder);
                if (!empty($permanentUrls)) {
                    $html = injectFalImages($html, $permanentUrls);
                    pmw_log("Images injectées — " . array_sum(array_map('count', $permanentUrls)) . " URLs permanentes");
                }
            } else {
                pmw_log("[WARN] fal.ai ignoré — clé non configurée");
            }
        }
    }
}

// ── INJECTION LOGO ────────────────────────────────────────────────────────────
// Template-01: logo is .nav-logo-text (text) and optionally .nav-logo img
$projectName = htmlspecialchars(trim(preg_replace('/\s*\/.*$/', '', $projectDisplay) ?: 'My Project'), ENT_QUOTES, 'UTF-8');

if ($logoUrl) {
    // Replace existing logo img src
    $html = preg_replace('/(<img[^>]+class="[^"]*logo[^"]*"[^>]*src=")[^"]*(")/i', '$1' . $logoUrl . '$2', $html);
    $html = preg_replace('/(<img[^>]+data-edit-id="[^"]*logo[^"]*"[^>]*src=")[^"]*(")/i', '$1' . $logoUrl . '$2', $html);
    // Also inject logo img into nav-logo if no img tag exists there
    if (!preg_match('/<div[^>]+class="nav-logo[^"]*"[^>]*>\s*<img/i', $html)) {
        $logoImg = '<img src="' . $logoUrl . '" alt="' . $projectName . '" style="height:36px;width:auto;object-fit:contain;display:block">';
        // Replace nav-logo-text content with logo image
        $html = preg_replace(
            '/(<[^>]+class="nav-logo-text"[^>]*>)[^<]*(<\/)/i',
            '$1' . $logoImg . '$2',
            $html
        );
    }
} else {
    // No logo: inject business name into nav-logo-text
    $html = preg_replace('/(<[^>]+class="nav-logo-text"[^>]*>)[^<]*(<\/)/i', '$1' . $projectName . '$2', $html);
    // Fallback: data-edit-id patterns
    $html = preg_replace('/(<[^>]+data-edit-id="[^"]*logo[^"]*text[^"]*"[^>]*>)[^<]*(<\/)/i', '$1' . $projectName . '$2', $html);
}

// ── POST-PROCESSING DEVISE ────────────────────────────────────────────────────
if (!in_array($lang, ['en', 'ar'])) {
    $html = preg_replace('/£\s*([\d][.,\d]*)/u', '$1€', $html);
    $html = preg_replace('/\$\s*([\d][.,\d]*)/u', '$1€', $html);
    $html = preg_replace('/\$0\b/', '0€', $html);
} elseif ($lang === 'en' && ($currency ?? '') === '$') {
    $html = preg_replace('/£\s*([\d][.,\d]*)/u', '\$$1', $html);
}


// ── INJECTION COULEURS PALETTE (template-based) ────────────────────────────────
// Génère 12 variables CSS sémantiques à partir des 3 couleurs du client.
// Règles de contraste automatiques — texte toujours lisible sur n'importe quel fond.
//
// LEGACY UNIQUEMENT : skippé si nouveau design system v1 actif.
if (!$useDesignSystem && !empty($groupKey)) {

    // Map palette key → [primary, secondary, bg]
    $paletteColorMap = [
        'purple' => ['#4f4ce1','#ffca58','#ffffff'],
        'dark'   => ['#1a237e','#ffc107','#ffffff'],
        'green'  => ['#1b5e20','#a5d6a7','#f1f8f1'],
        'orange' => ['#bf360c','#ffccbc','#fbe9e7'],
        'blue'   => ['#0077b6','#90e0ef','#caf0f8'],
        'pink'   => ['#ad1457','#f8bbd0','#fce4ec'],
        'teal'   => ['#00695c','#ff7043','#fff8f6'],
        'red'    => ['#c62828','#ff8a80','#fff5f5'],
        'grey'   => ['#37474f','#ffd700','#f5f5f5'],
        'yellow' => ['#f59e0b','#1a1a2e','#fffbf0'],
    ];

    $cp = $cs = $cbg = null;

    // 1. Own colours (format: "#hex1,#hex2,#hex3")
    if (!empty($ownColours)) {
        $parts = array_map('trim', explode(',', $ownColours));
        if (count($parts) >= 2) {
            $cp  = $parts[0];
            $cs  = $parts[1];
            $cbg = $parts[2] ?? '#ffffff';
        }
    }

    // 2. Palette key fallback
    if (!$cp && isset($paletteColorMap[$palette])) {
        [$cp, $cs, $cbg] = $paletteColorMap[$palette];
    }

    // 3. Default
    if (!$cp) { $cp = '#e63946'; $cs = '#f1a208'; $cbg = '#ffffff'; }

    // ── AUTO CONTRAST RULES ──────────────────────────────────────────────────
    // Returns relative luminance 0–100
    $luminance = function(string $hex): float {
        $hex = ltrim($hex, '#');
        if (strlen($hex) === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
        $r = hexdec(substr($hex,0,2))/255;
        $g = hexdec(substr($hex,2,2))/255;
        $b = hexdec(substr($hex,4,2))/255;
        $toLinear = fn($c) => $c <= 0.03928 ? $c/12.92 : (($c+0.055)/1.055)**2.4;
        return (0.2126*$toLinear($r) + 0.7152*$toLinear($g) + 0.0722*$toLinear($b)) * 100;
    };

    // Rule: text on --pmw-bg
    $bgLum     = $luminance($cbg);
    $ctText    = $bgLum > 50 ? '#1a1a1a' : '#f5f5f5';        // body text
    $ctLight   = $bgLum > 50 ? '#6b7280' : '#a0aec0';        // muted text
    $ctBgAlt   = $bgLum > 50
        ? adjustBrightness($cbg, -8)                           // slightly darker
        : adjustBrightness($cbg, 8);                           // slightly lighter
    $ctBgCard  = $bgLum > 50 ? '#ffffff' : adjustBrightness($cbg, 12);
    $ctBgDark  = $bgLum > 50 ? '#1a1a1a' : '#0d0d0d';

    // Rule: text on --pmw-primary (button text)
    $primLum   = $luminance($cp);
    $ctTextInv = $primLum > 50 ? '#1a1a1a' : '#ffffff';       // button text

    // Rule: border — primary tinted
    $ctBorder  = $bgLum > 50 ? '#e5e7eb' : 'rgba(255,255,255,0.12)';

    // Derived shades
    $cpDark    = adjustBrightness($cp, -22);
    $cpLight   = 'rgba(' . implode(',', array_map(fn($p) => hexdec($p),
        [substr(ltrim($cp,'#'),0,2), substr(ltrim($cp,'#'),2,2), substr(ltrim($cp,'#'),4,2)]
    )) . ',0.10)';

    // Shadow tinted with primary RGB
    $hexRgb = ltrim($cp, '#');
    if (strlen($hexRgb) === 3) $hexRgb = $hexRgb[0].$hexRgb[0].$hexRgb[1].$hexRgb[1].$hexRgb[2].$hexRgb[2];
    $pr = hexdec(substr($hexRgb,0,2)); $pg = hexdec(substr($hexRgb,2,2)); $pb = hexdec(substr($hexRgb,4,2));

    $cssOverride = "<style id=\"pmw-palette-override\">
"
        . ":root {
"
        . "  --pmw-primary:        {$cp};
"
        . "  --pmw-primary-dark:   {$cpDark};
"
        . "  --pmw-primary-light:  rgba({$pr},{$pg},{$pb},0.10);
"
        . "  --pmw-secondary:      {$cs};
"
        . "  --pmw-bg:             {$cbg};
"
        . "  --pmw-bg-alt:         {$ctBgAlt};
"
        . "  --pmw-bg-card:        {$ctBgCard};
"
        . "  --pmw-bg-dark:        {$ctBgDark};
"
        . "  --pmw-text:           {$ctText};
"
        . "  --pmw-text-light:     {$ctLight};
"
        . "  --pmw-text-inv:       {$ctTextInv};
"
        . "  --pmw-text-on-dark:   #f5f5f5;
"
        . "  --pmw-border:         {$ctBorder};
"
        . "  --pmw-border-primary: rgba({$pr},{$pg},{$pb},0.20);
"
        . "  --pmw-overlay:        rgba(0,0,0,0.50);
"
        . "  --pmw-overlay-dark:   rgba(0,0,0,0.70);
"
        . "  --pmw-shadow-sm:      0 2px 8px rgba({$pr},{$pg},{$pb},0.08);
"
        . "  --pmw-shadow-md:      0 8px 24px rgba({$pr},{$pg},{$pb},0.12);
"
        . "  --pmw-shadow-lg:      0 24px 48px rgba({$pr},{$pg},{$pb},0.16);
"
        . "}
"
        . "</style>
";

    $headPos = strrpos($html, '</head>');
    if ($headPos !== false) {
        $html = substr($html, 0, $headPos) . $cssOverride . substr($html, $headPos);
    }
}


// ── DESIGN SYSTEM v1 — INJECTION CSS + APPLICATION CLASSES BODY ──────────────
// v9.0 (May 2026) : refactoré pour V4.
// La génération du site.css est maintenant faite par send.php sur Hostinger (au
// moment du submit mockup). Le fichier est déjà présent dans /preview/{SKU}/site.css
// quand ce code s'exécute. process.php se contente de :
//   1. Injecter le <link rel="stylesheet" href=".../site.css"> dans <head>
//   2. Appliquer les classes thème sur <body>
//   3. Sauvegarder data/design.json pour traçabilité (utile brief.html)
// La fonction local generate_body_classes() est définie en haut de ce fichier.
if ($useDesignSystem) {
    try {
        $designDecisions = [
            'base'           => $designBase,
            'primary'        => $designPrimary,
            'secondary'      => $designSecondary,
            'heading_color'  => $designHeadingColor,
            'couple'         => $designCouple,
            'corners'        => $designCorners,
            'shadows'        => $designShadows,
            'borders'        => $designBorders,
        ];

        $bodyClasses = generate_body_classes($designDecisions);

        // 1. Stockage du module design pour traçabilité (réutilisé par brief.html)
        pmw_store_save_module($folder, 'design', [
            'version'     => 'v1',
            'decisions'   => $designDecisions,
            'bodyClasses' => $bodyClasses,
            'generatedAt' => date('c'),
        ]);

        // 2. Injection <link> dans le <head> du HTML
        // URL absolue obligatoire car assemble.php injecte un <base href> vers le dossier
        // du template, ce qui ferait chercher site.css au mauvais endroit avec une URL relative
        $cssLinkTag = "\n<link rel=\"stylesheet\" href=\"" . BASE_URL . "/preview/" . $folder . "/site.css\">\n";
        $headPos = strrpos($html, '</head>');
        if ($headPos !== false) {
            $html = substr($html, 0, $headPos) . $cssLinkTag . substr($html, $headPos);
            pmw_log("[design-system] <link> site.css injecté dans le head");
        } else {
            pmw_log("[design-system] [WARN] </head> introuvable, <link> non injecté");
        }

        // 3. Application des classes thème sur <body>
        // Pattern : <body[ ATTRS]> → <body class="EXISTING_OR_NEW THEME_CLASSES" ATTRS>
        if (preg_match('/<body([^>]*)>/i', $html, $bodyMatch)) {
            $bodyAttrs = $bodyMatch[1];
            $hasClassAttr = preg_match('/\bclass\s*=\s*["\']([^"\']*)["\']/', $bodyAttrs, $classMatch);
            if ($hasClassAttr) {
                // Body a déjà un class= : on append nos classes
                $existingClasses = trim($classMatch[1]);
                $newClasses = trim($existingClasses . ' ' . $bodyClasses);
                $newBodyAttrs = preg_replace(
                    '/\bclass\s*=\s*["\'][^"\']*["\']/',
                    'class="' . $newClasses . '"',
                    $bodyAttrs
                );
                $html = str_replace('<body' . $bodyAttrs . '>', '<body' . $newBodyAttrs . '>', $html);
            } else {
                // Body sans class : on en ajoute un
                $html = str_replace(
                    '<body' . $bodyAttrs . '>',
                    '<body class="' . $bodyClasses . '"' . $bodyAttrs . '>',
                    $html
                );
            }
            pmw_log("[design-system] body classes appliquées : $bodyClasses");
        } else {
            pmw_log("[design-system] [WARN] tag <body> introuvable dans le HTML");
        }

    } catch (Throwable $e) {
        pmw_log("[design-system] [ERROR] " . $e->getMessage());
        // Pas de blocage : on laisse le HTML partir tel quel sans le design system
    }
}


// ── POPUP MAQUETTE ────────────────────────────────────────────────────────────
// Le popup d'interception des liens (vers /order.html) est désormais injecté
// directement par assemble.php v6.9+ quand mode=mockup. Plus de code popup ici.
// (Ancien bloc retiré : il avait un bug 'http' trop large dans ALLOW qui laissait
// passer toutes les URL externes, et un str_replace cassé dans le onclick.)


// ── MADE BY ───────────────────────────────────────────────────────────────────
// Injection ciblée sur la DERNIÈRE </body> (défensif anti-duplication)
$signature = "\n<div style=\"text-align:center;padding:8px;font-size:11px;color:#aaa\">"
    . "<a href=\"https://popmywebsite.com\" style=\"color:#aaa;text-decoration:none\">Made by PoPmyWebsite</a>"
    . "</div>\n";
$bodyPos = strrpos($html, '</body>');
if ($bodyPos !== false) {
    $html = substr($html, 0, $bodyPos) . $signature . substr($html, $bodyPos);
}

// ── 5. DÉPLOIEMENT ────────────────────────────────────────────────────────────
$deployUrl = BASE_URL . '/deploy_html.php?token=' . urlencode(SECRET_TOKEN) . '&folder=' . urlencode($folder);
$ch = curl_init($deployUrl);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query(['html' => $html]),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
    CURLOPT_TIMEOUT        => 30,
]);
$deployResponse = curl_exec($ch);
$deployCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($deployCode !== 200) {
    pmw_log("[ERROR] deploy : {$deployCode} — {$deployResponse}");
    exit;
}
pmw_log("Maquette déployée : {$mockupUrl}");

// ── 5a. ÉCRITURE MÉMOIRE MAQUETTE (v8.6) ──────────────────────────────────────
// Écrit /preview/SKU/site.json (maître) et /preview/SKU/data/design.json (module)
// pour permettre à process_site.php de reproduire la maquette à l'identique
// lors de la génération du site final (Bug A).
//
// Échec = WARN, pas blocage : la maquette doit arriver chez le prospect coûte
// que coûte. Si la mémoire est absente ou corrompue, process_site.php fera
// un fallback sur les valeurs du brief (comportement legacy préservé).

try {
    // Extraction header/footer rendus depuis le HTML final (post-Claude, post-images, post-logo)
    $mockupHeaderHtml = '';
    $mockupFooterHtml = '';
    $domDoc = new DOMDocument();
    libxml_use_internal_errors(true);
    if ($domDoc->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED)) {
        $xpath = new DOMXPath($domDoc);
        $headerNode = $xpath->query('//header[@data-block]')->item(0);
        if ($headerNode) $mockupHeaderHtml = $domDoc->saveHTML($headerNode);
        $footerNode = $xpath->query('//footer[@data-block]')->item(0);
        if ($footerNode) $mockupFooterHtml = $domDoc->saveHTML($footerNode);
    }
    libxml_clear_errors();

    // Extraction selectedBlocks depuis assembleData
    $mockupSelected = $assembleData['selected'] ?? '';  // chaîne CSV "header-03,hero-07,..."
    $selectedArr    = array_filter(array_map('trim', explode(',', $mockupSelected)));

    // Extraction headerFile / footerFile depuis selectedBlocks
    $mockupHeaderFile = '';
    $mockupFooterFile = '';
    foreach ($selectedArr as $blockName) {
        if (str_starts_with($blockName, 'header-')) $mockupHeaderFile = $blockName;
        if (str_starts_with($blockName, 'footer-')) $mockupFooterFile = $blockName;
    }

    // Construction du module design.json
    $designData = [
        'schemaVer'      => 1,
        'palette'        => $palette,
        'ownColours'     => $ownColours,
        'style'          => $style,
        'preset'         => $assembleData['preset'] ?? '',
        'fonts'          => $assembleData['fonts']  ?? ['heading' => 'Montserrat', 'body' => 'Plus Jakarta Sans'],
        'colors'         => $assembleData['colors'] ?? ['primary' => '#2563eb', 'secondary' => '#1d4ed8'],
        'headerFile'     => $mockupHeaderFile,
        'footerFile'     => $mockupFooterFile,
        'selectedBlocks' => $selectedArr,
        'headerHtml'     => $mockupHeaderHtml,
        'footerHtml'     => $mockupFooterHtml,
    ];

    // Construction du maître site.json
    $siteData = [
        'sku'           => $folder,
        'lang'          => $lang,
        'sector'        => $sector,
        'siteType'      => $siteType,
        'companyName'   => $projectDisplay,
        'domain'        => '',
        'pages'         => ['home'],            // maquette = home seule
        'legalPages'    => [],
        'status'        => 'mockup',
        'modules'       => [
            'design' => 'data/design.json',
        ],
    ];

    // Écriture (module d'abord, maître ensuite — l'ordre garantit que la
    // référence dans modules.design pointe vers un fichier qui existe déjà)
    $okDesign = pmw_store_save_module($folder, 'design', $designData);
    $okMaster = pmw_store_save_master($folder, $siteData);

    if ($okDesign && $okMaster) {
        pmw_log("Mémoire maquette écrite : site.json + data/design.json (" . count($selectedArr) . " blocs, header=$mockupHeaderFile, footer=$mockupFooterFile)");
    } else {
        pmw_log("[WARN] Mémoire maquette partiellement écrite : design=" . ($okDesign ? 'OK' : 'FAIL') . " master=" . ($okMaster ? 'OK' : 'FAIL'));
    }
} catch (\Throwable $e) {
    pmw_log("[WARN] Mémoire maquette : exception " . $e->getMessage());
}

// ── 5b. PURGE CLOUDFLARE — DÉSACTIVÉ v8.7 ─────────────────────────────────────
// Cloudflare n'est plus utilisé (zone "moved" depuis avril 2026).
// Pour réactiver : restaurer cloudflare_purge.php + décommenter ces 2 lignes
// + décommenter les constantes CLOUDFLARE_* dans config.php.
//
// pmw_cloudflare_purge_prefix($folder);

// ── 6. EMAIL BREVO ────────────────────────────────────────────────────────────
$translations = [
    'en'=>['subject'=>'Your free mockup is ready!','h1'=>'Your mockup is ready!','hi'=>'Hi','p1'=>'Your free homepage mockup is ready. Click below to discover it.','btn'=>'View my mockup','p3'=>'This mockup will be available for 5 days.','legal'=>'You received this email because you submitted a mockup request on popmywebsite.com.'],
    'fr'=>['subject'=>'Votre maquette gratuite est prête !','h1'=>'Votre maquette est prête !','hi'=>'Bonjour','p1'=>"Votre maquette de page d'accueil est prête. Cliquez ci-dessous pour la découvrir.",'btn'=>'Voir ma maquette','p3'=>'Votre maquette sera disponible pendant 5 jours.','legal'=>'Vous recevez cet email suite à votre demande de maquette sur popmywebsite.com.'],
    'de'=>['subject'=>'Ihr kostenloses Mockup ist fertig!','h1'=>'Ihr Mockup ist fertig!','hi'=>'Hallo','p1'=>'Ihr kostenloses Homepage-Mockup ist fertig. Klicken Sie unten, um es zu entdecken.','btn'=>'Mein Mockup ansehen','p3'=>'Ihr Mockup ist 5 Tage verfügbar.','legal'=>'Sie erhalten diese E-Mail, weil Sie eine Mockup-Anfrage auf popmywebsite.com gestellt haben.'],
    'it'=>['subject'=>'Il suo mockup gratuito è pronto!','h1'=>'Il suo mockup è pronto!','hi'=>'Gentile','p1'=>'Il suo mockup gratuito della homepage è pronto. Clicchi qui sotto per scoprirlo.','btn'=>'Visualizza il mio mockup','p3'=>'Il suo mockup sarà disponibile per 5 giorni.','legal'=>'Ha ricevuto questa email perché ha inviato una richiesta di mockup su popmywebsite.com.'],
    'es'=>['subject'=>'¡Su maqueta gratuita está lista!','h1'=>'¡Su maqueta está lista!','hi'=>'Estimado/a','p1'=>'Su maqueta gratuita de página de inicio está lista. Haga clic abajo para descubrirla.','btn'=>'Ver mi maqueta','p3'=>'Su maqueta estará disponible durante 5 días.','legal'=>'Recibió este correo porque envió una solicitud de maqueta en popmywebsite.com.'],
    'pt'=>['subject'=>'A sua maquete gratuita está pronta!','h1'=>'A sua maquete está pronta!','hi'=>'Olá','p1'=>'A sua maquete gratuita da página inicial está pronta. Clique abaixo para descobri-la.','btn'=>'Ver a minha maquete','p3'=>'A sua maquete estará disponível durante 5 dias.','legal'=>'Recebeu este email porque enviou um pedido de maquete em popmywebsite.com.'],
];

$tr        = $translations[$lang] ?? $translations['en'];
$emailHtml = '<!DOCTYPE html><html><body style="font-family:Arial,sans-serif;background:#f7f7fc;padding:20px;margin:0">'
    . '<div style="background:#fff;border-radius:12px;max-width:580px;margin:0 auto;overflow:hidden">'
    . '<div style="background:linear-gradient(135deg,#4f4ce1,#3b38c4);padding:36px;text-align:center;color:#fff">'
    . '<h1 style="margin:0;font-size:24px;font-weight:800">' . htmlspecialchars($tr['h1']) . '</h1>'
    . '<p style="margin:10px 0 0;opacity:.8;font-size:15px">' . htmlspecialchars($tr['hi']) . ' ' . htmlspecialchars($firstName) . '</p>'
    . '</div><div style="padding:36px">'
    . '<p style="font-size:15px;color:#3a3a5c;line-height:1.7;margin:0 0 24px">' . htmlspecialchars($tr['p1']) . '</p>'
    . '<a href="' . $mockupUrl . '" style="display:block;background:#4f4ce1;color:#fff;text-align:center;padding:16px 24px;border-radius:100px;font-weight:700;margin:0 0 24px;text-decoration:none;font-size:16px">' . htmlspecialchars($tr['btn']) . '</a>'
    . '<p style="background:#f0effd;border-radius:8px;padding:14px;font-size:13px;color:#4f4ce1;text-align:center;margin:0">' . htmlspecialchars($tr['p3']) . '</p>'
    . '</div><div style="background:#f7f7fc;padding:20px;text-align:center;font-size:12px;color:#aaa;border-top:1px solid #ededfd">'
    . htmlspecialchars($tr['legal']) . '<br><br>'
    . '<a href="https://popmywebsite.com" style="color:#4f4ce1;text-decoration:none">popmywebsite.com</a>'
    . '</div></div></body></html>';

$ch = curl_init('https://api.brevo.com/v3/smtp/email');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'sender'      => ['email' => FROM_EMAIL, 'name' => FROM_NAME],
        'to'          => [['email' => $email, 'name' => $firstName]],
        'subject'     => $tr['subject'],
        'htmlContent' => $emailHtml,
        'replyTo'     => ['email' => FROM_EMAIL, 'name' => FROM_NAME],
    ]),
    CURLOPT_HTTPHEADER => ['api-key: '.BREVO_API_KEY,'Content-Type: application/json','Accept: application/json'],
    CURLOPT_TIMEOUT    => 30,
]);
curl_exec($ch);
$brevoCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($brevoCode < 200 || $brevoCode >= 300) pmw_log("[ERROR] Brevo : {$brevoCode}");
else pmw_log("Email envoyé à {$email}");

pmw_log("v9.0 END — {$folder}");

// ══════════════════════════════════════════════════════════════════════════════
// HELPERS
// ══════════════════════════════════════════════════════════════════════════════

/**
 * Génère une image via fal.ai Flux Dev.
 * negative_prompt uniquement sur le type 'product'.
 */
function generateFalImage(string $prompt, string $imageSize = 'landscape_16_9', string $type = ''): string
{
    $payload = ['prompt' => $prompt, 'image_size' => $imageSize, 'num_images' => 1];

    // Negative prompt uniquement sur product — fond neutre, pas de personnes
    if ($type === 'product') {
        $payload['negative_prompt'] = 'people, person, hands, cluttered background, text, watermark, logo, deformed, blurry';
    }

    $ch = curl_init('https://fal.run/fal-ai/flux/dev');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_HTTPHEADER     => ['Authorization: Key ' . FAL_API_KEY, 'Content-Type: application/json'],
        CURLOPT_TIMEOUT        => 60,
    ]);
    $response = curl_exec($ch);
    $code     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code !== 200 || empty($response)) {
        pmw_log("[ERROR] fal.ai $code — prompt: $prompt");
        return '';
    }
    $data = json_decode($response, true);
    $url  = $data['images'][0]['url'] ?? '';
    if ($url) pmw_log("fal.ai OK [$type]: $url");
    return $url;
}

/**
 * Sauvegarde une URL fal.ai sur Hostinger via save_image.php.
 */
function saveFalImageToHostinger(string $falUrl, string $folder, int $index): string
{
    if (empty($falUrl)) return '';

    $ch = curl_init(BASE_URL . '/save_image.php?token=' . urlencode(SECRET_TOKEN));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query(['folder' => $folder, 'image_url' => $falUrl, 'index' => $index]),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
        CURLOPT_TIMEOUT        => 45,
    ]);
    $response = curl_exec($ch);
    $code     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code !== 200 || empty($response)) {
        pmw_log("[ERROR] save_image $code idx=$index");
        return $falUrl;
    }
    $data = json_decode($response, true);
    if (!empty($data['ok']) && !empty($data['url'])) {
        pmw_log("save_image OK idx=$index : " . $data['url'] . " (" . ($data['size'] ?? 0) . " bytes)");
        return $data['url'];
    }
    pmw_log("[ERROR] save_image réponse invalide : $response");
    return $falUrl;
}

/**
 * Génère et sauvegarde toutes les images.
 */
function generateAndSaveFalImages(array $prompts, string $folder): array
{
    $sizeMap = [
        'hero'    => 'landscape_16_9',
        'gallery' => 'square_hd',
        'about'   => 'portrait_4_3',
        'card'    => 'landscape_4_3',
        'product' => 'square_hd',
    ];
    $result = [];
    $index  = 0;

    foreach ($prompts as $type => $prompt) {
        if (empty($prompt) || !is_string($prompt)) continue;
        $count = in_array($type, ['gallery', 'product']) ? 2 : FAL_IMAGES_PER_TYPE;
        for ($i = 0; $i < $count; $i++) {
            $falUrl = generateFalImage($prompt, $sizeMap[$type] ?? 'landscape_16_9', $type);
            if (empty($falUrl)) continue;
            $permanentUrl = saveFalImageToHostinger($falUrl, $folder, $index);
            if (!empty($permanentUrl)) {
                $result[$type][] = $permanentUrl;
                $index++;
            }
        }
    }
    return $result;
}

/**
 * Injecte les URLs permanentes dans le HTML par type de bloc.
 */
function injectFalImages(string $html, array $generatedUrls): string
{
    if (empty($generatedUrls)) return $html;

    $blockTypeMap = [
        'hero'=>'hero','header'=>'hero','gallery'=>'gallery','about'=>'about',
        'ecommerce'=>'product','services'=>'card','cta'=>'card','blog'=>'card',
        'contact'=>'about','pricing'=>'card','faq'=>'card','footer'=>'card',
        'interactive'=>'card','teaser'=>'card','content'=>'card',
    ];

    $counters = array_fill_keys(array_keys($generatedUrls), 0);

    // Étape 1 — overrides CSS hero background
    if (!empty($generatedUrls['hero'])) {
        $html = preg_replace_callback(
            '/(#block-(?:hero|header)-\d+\s*\{[^}]*background-image\s*:\s*url\()[\'"]?https?:\/\/[^\s\'"\)]+[\'"]?(\)[^}]*\})/i',
            function($m) use ($generatedUrls, &$counters) {
                $url = $generatedUrls['hero'][$counters['hero'] % count($generatedUrls['hero'])];
                $counters['hero']++;
                return $m[1] . $url . $m[2];
            },
            $html
        );
    }

    // Étape 2 — remplacement segment par segment
    $segments    = preg_split('/(data-block="[a-z]+-\d+")/i', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
    $result      = '';
    $currentType = 'card';

    foreach ($segments as $seg) {
        if (preg_match('/^data-block="([a-z]+)-\d+"/i', $seg, $m)) {
            $currentType = $blockTypeMap[strtolower($m[1])] ?? 'card';
            $result     .= $seg;
            continue;
        }
        $urls = $generatedUrls[$currentType] ?? $generatedUrls['card'] ?? [];
        if (!empty($urls)) {
            $t = $currentType;
            $seg = preg_replace_callback(
                '/src="https?:\/\/(?:images\.)?unsplash\.com\/[^"]+"/i',
                function() use ($urls, &$counters, $t) {
                    if (!isset($counters[$t])) $counters[$t] = 0;
                    $url = $urls[$counters[$t] % count($urls)];
                    $counters[$t]++;
                    return 'src="' . $url . '"';
                },
                $seg
            );
            $seg = preg_replace_callback(
                '/url\([\'"]?https?:\/\/(?:images\.)?unsplash\.com\/[^\'")\s]+[\'"]?\)/i',
                function() use ($urls, &$counters, $t) {
                    if (!isset($counters[$t])) $counters[$t] = 0;
                    $url = $urls[$counters[$t] % count($urls)];
                    $counters[$t]++;
                    return 'url(' . $url . ')';
                },
                $seg
            );
        }
        $result .= $seg;
    }
    return $result;
}

/**
 * Construit les balises SEO + Schema.org.
 */
function buildSeoTags(array $seo, string $lang, string $project, string $url): string
{
    $out = "\n<!-- SEO généré par Claude — PoPmyWebsite -->\n";
    if (!empty($seo['description']))    $out .= "<meta name=\"description\" content=\"" . htmlspecialchars($seo['description'], ENT_QUOTES, 'UTF-8') . "\">\n";
    if (!empty($seo['og_title']))       $out .= "<meta property=\"og:title\" content=\"" . htmlspecialchars($seo['og_title'], ENT_QUOTES, 'UTF-8') . "\">\n";
    if (!empty($seo['og_description'])) $out .= "<meta property=\"og:description\" content=\"" . htmlspecialchars($seo['og_description'], ENT_QUOTES, 'UTF-8') . "\">\n";
    $out .= "<meta property=\"og:type\" content=\"website\">\n";
    $out .= "<meta property=\"og:url\" content=\"" . htmlspecialchars($url, ENT_QUOTES) . "\">\n";
    $out .= "<meta property=\"og:locale\" content=\"{$lang}\">\n";
    $out .= "<meta name=\"twitter:card\" content=\"summary_large_image\">\n";
    if (!empty($seo['og_title'])) $out .= "<meta name=\"twitter:title\" content=\"" . htmlspecialchars($seo['og_title'], ENT_QUOTES, 'UTF-8') . "\">\n";
    $schema = [
        '@context'    => 'https://schema.org',
        '@type'       => $seo['schema_type'] ?? 'LocalBusiness',
        'name'        => $seo['schema_name'] ?? $project,
        'description' => $seo['schema_description'] ?? '',
        'url'         => $url,
        'inLanguage'  => $lang,
    ];
    $out .= "<script type=\"application/ld+json\">\n" . json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n</script>\n";
    return $out;
}
