<?php
declare(strict_types=1);

// Função auxiliar para clarear/escurar cores
function shadeColor($color, $percent) {
    $color = trim($color, '#');
    if (strlen($color) == 6) {
        $rgb = str_split($color, 2);
        $r = hexdec($rgb[0]); $g = hexdec($rgb[1]); $b = hexdec($rgb[2]);

        $r = min(255, $r + $r * $percent / 100);
        $g = min(255, $g + $g * $percent / 100);
        $b = min(255, $b + $b * $percent / 100);

        return '#' . str_pad(dechex($r), 2, '0', STR_PAD_LEFT) .
                   str_pad(dechex($g), 2, '0', STR_PAD_LEFT) .
                   str_pad(dechex($b), 2, '0', STR_PAD_LEFT);
    }
    return '#'.$color;
}

// Função auxiliar para validar cores hexadecimais
if (!function_exists('isValidColor')) {
    function isValidColor($color) {
        return preg_match('/^#[0-9A-Fa-f]{6}$/i', $color);
    }
}

/**
 * Gera /public/roletas/{slug}.php a partir de /public/template_roleta.html
 * Mantém compatibilidade de caminhos (../../uploads/...),
 * injeta PRIZES, áudios, background, título, SPIN_TEXT, subtítulo
 * e configura ANIM_SPINS/ANIM_SPEED (fator) conforme o banco.
 * Adições:
 * - Injeta Google Fonts (font_family) no <head> e aplica no CSS e canvas;
 * - Injeta POINTER_COLOR e POINTER_POSITION no template.
 * - Injeta configurações do aro da roleta (rim_style, rim_light_color, rim_mid_color, rim_dark_color, rim_solid_color)
 * - Injeta margens da roleta e controles de visibilidade mobile
 * - Injeta hide_text_all para ocultar textos do segmento
 * - ADICIONADO: Funcionalidade para desabilitar console e inspeção de código
 */
function generate_roulette_file(PDO $pdo, int $rouletteId): string
{
    $ROOT = realpath(__DIR__ . '/..');
    if ($ROOT === false) throw new RuntimeException('ROOT inválido');

    $PUBLIC   = $ROOT . '/public';
    $TEMPLATE = $PUBLIC . '/template_roleta.html';
    $OUT_DIR  = $PUBLIC . '/roletas';

    if (!is_file($TEMPLATE)) throw new RuntimeException("Template não encontrado: {$TEMPLATE}");
    if (!is_dir($OUT_DIR) && !@mkdir($OUT_DIR, 0775, true)) {
        throw new RuntimeException("Não foi possível criar {$OUT_DIR}");
    }

    // ---------- Roleta ----------
    $st = $pdo->prepare("
        SELECT id, name, slug, spin_button_text, subtitle, anim_spins, anim_speed,
               title_color, title_size, subtitle_color, subtitle_size,
               spin_text_color, spin_text_size, spin_button_color,
               pointer_color, pointer_position,
               font_family, wheel_scale_pct,
               rim_style, rim_light_color, rim_mid_color, rim_dark_color, rim_solid_color,
               wheel_margin_top_desktop, wheel_margin_bottom_desktop,
               wheel_margin_top_mobile, wheel_margin_bottom_mobile,
               hide_title_desktop, hide_subtitle_desktop, hide_title_mobile, hide_subtitle_mobile,
               disable_inspect 
        FROM roulettes WHERE id=?
    ");
    $st->execute([$rouletteId]);
    $R = $st->fetch(PDO::FETCH_ASSOC);
    if (!$R) throw new RuntimeException("Roleta #{$rouletteId} não encontrada.");

    $id     = (int)$R['id'];
    $name   = (string)($R['name'] ?? 'Roleta');
    $slug   = trim((string)$R['slug']);
    if ($slug === '') throw new RuntimeException("Slug vazio para a roleta #{$rouletteId}.");

    $spin_text   = (string)($R['spin_button_text'] ?? 'GIRAR');
    if ($spin_text === '') $spin_text = 'GIRAR';
    $subtitle    = (string)($R['subtitle'] ?? '');
    $anim_spins  = max(0, min(20, (int)($R['anim_spins'] ?? 2)));
    $anim_speed  = max(20, min(300, (int)($R['anim_speed'] ?? 100))); // %
    $speed_factor = $anim_speed / 100.0;

    // NOVO CAMPO: Desabilitar console e inspeção
    $disable_inspect = (int)($R['disable_inspect'] ?? 0);

    // Novos campos para margens e ocultação
    $wheel_margin_top_desktop    = (int)($R['wheel_margin_top_desktop'] ?? 0);
    $wheel_margin_bottom_desktop = (int)($R['wheel_margin_bottom_desktop'] ?? 0);
    $wheel_margin_top_mobile     = (int)($R['wheel_margin_top_mobile'] ?? 0);
    $wheel_margin_bottom_mobile  = (int)($R['wheel_margin_bottom_mobile'] ?? 0);
    $hide_title_mobile           = (int)($R['hide_title_mobile'] ?? 0);
    $hide_subtitle_mobile        = (int)($R['hide_subtitle_mobile'] ?? 0);
    $hide_title_desktop    = (int)($R['hide_title_desktop'] ?? 0);
    $hide_subtitle_desktop = (int)($R['hide_subtitle_desktop'] ?? 0);

    // Validar valores máximos
    $wheel_margin_top_desktop    = min(250, max(0, $wheel_margin_top_desktop));
    $wheel_margin_bottom_desktop = min(250, max(0, $wheel_margin_bottom_desktop));
    $wheel_margin_top_mobile     = min(250, max(0, $wheel_margin_top_mobile));
    $wheel_margin_bottom_mobile  = min(250, max(0, $wheel_margin_bottom_mobile));

    // Estilos do título e subtítulo
    $title_color    = (string)($R['title_color'] ?? '#ffffff');
    $title_size     = (int)($R['title_size'] ?? 30);
    $subtitle_color = (string)($R['subtitle_color'] ?? '#eef3ff');
    $subtitle_size  = (int)($R['subtitle_size'] ?? 16);

    // Estilos do texto SPIN (botão central)
    $spin_text_color   = (string)($R['spin_text_color'] ?? '#ffffff');
    $spin_text_size    = (int)($R['spin_text_size'] ?? 24);
    $spin_button_color = (string)($R['spin_button_color'] ?? '#f59e0b');

    // ===== Ponteiro =====
    $pointer_color = (string)($R['pointer_color'] ?? '#A855F7');
    $pointer_color = ltrim($pointer_color, '#');
    if (!preg_match('/^[0-9a-fA-F]{6}$/', $pointer_color)) {
        $pointer_color = 'A855F7';
    }
    $pointer_color = '#'.$pointer_color;

    $pointer_position = strtolower((string)($R['pointer_position'] ?? 'right'));
    if (!in_array($pointer_position, ['top','right','bottom','left'], true)) {
        $pointer_position = 'right';
    }

    // ===== Configurações do Aro da Roleta =====
    $rim_style = (string)($R['rim_style'] ?? 'gradient');
    $rim_light_color = (string)($R['rim_light_color'] ?? '#c3cad6');
    $rim_mid_color = (string)($R['rim_mid_color'] ?? '#7b8396');
    $rim_dark_color = (string)($R['rim_dark_color'] ?? '#3b4356');
    $rim_solid_color = (string)($R['rim_solid_color'] ?? '#3b4356');

    // Validar cores do aro
    if (!isValidColor($rim_light_color)) $rim_light_color = '#c3cad6';
    if (!isValidColor($rim_mid_color)) $rim_mid_color = '#7b8396';
    if (!isValidColor($rim_dark_color)) $rim_dark_color = '#3b4356';
    if (!isValidColor($rim_solid_color)) $rim_solid_color = '#3b4356';
    if (!in_array($rim_style, ['gradient', 'solid'])) $rim_style = 'gradient';

    // ===== Fonte (Google Fonts) =====
    $font_family = trim((string)($R['font_family'] ?? 'Poppins'));
    if ($font_family === '') $font_family = 'Poppins';
    $gf_family_param = preg_replace('~\s+~', '+', preg_replace('~[^0-9a-zA-Z\s]~', '', $font_family));
    $gf_href = "https://fonts.googleapis.com/css2?family={$gf_family_param}:wght@400;600;700;800;900&display=swap";
    $font_stack_css = "'" . addslashes($font_family) . "', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";

    // ---------- Segmentos ----------
    $st = $pdo->prepare("
        SELECT segment_order, type, label_primary, label_unit, spin_count, color, weight,
               game_name, game_url, image_path, image_fit, image_opacity,
               text_color, text_size, unit_text_size, hide_text_all
        FROM roulette_segments
        WHERE roulette_id=? ORDER BY segment_order ASC
    ");
    $st->execute([$rouletteId]);
    $rows = $st->fetchAll(PDO::FETCH_ASSOC);
    if (!$rows) throw new RuntimeException("Roleta #{$rouletteId} sem segmentos.");

    // Caminhos no mesmo padrão que funcionava
    // Caminhos relativos do ponto de vista de /public/roletas
    $normalize = static function (string $p): string {
        $p = trim($p);
        if ($p === '') return $p;

        // URLs absolutas (http/https) ou caminho absoluto do site: mantém
        if (preg_match('~^(https?:)?//~i', $p) || str_starts_with($p, '/')) {
            return $p;
        }

        // Normaliza variações antigas para "uploads/…"
        // ex.: "../public/uploads/..." -> "uploads/...", "public/uploads/..." -> "uploads/..."
        $p = preg_replace('#^(?:\.\./)+public/#', '', $p);
        $p = preg_replace('#^public/#', '', $p);
        $p = preg_replace('#^(?:\.\./)+uploads/#', 'uploads/', $p);

        // Do arquivo /public/roletas/*.php para /public/uploads -> prefixo correto é "../"
        if (str_starts_with($p, 'uploads/')) return '../' . $p;

        // Já está como "../uploads/..."? deixa como está
        if (str_starts_with($p, '../uploads/')) return $p;

        // Qualquer outro relativo recebe "../" por segurança
        return '../' . ltrim($p, './');
    };

    $prizes = [];
    foreach ($rows as $r) {
        $primary = (string)($r['label_primary'] ?? '');
        if ($primary === '' && $r['spin_count'] !== null) {
            $primary = (string)$r['spin_count'];
        }
        $type = strtoupper((string)($r['type'] ?? 'LOSE'));
        $prizes[] = [
            'primary'      => $primary,
            'unit'         => (string)($r['label_unit'] ?? ''),
            'color'        => (string)($r['color'] ?? '#ffffff'),
            'weight'       => (int)($r['weight'] ?? 1),
            'kind'         => $type,
            'type'         => $type,
            'game'         => (string)($r['game_name'] ?? ''),
            'url'          => $normalize((string)($r['game_url'] ?? '')),
            'image'        => $normalize((string)($r['image_path'] ?? '')),
            'imageFit'     => (string)($r['image_fit'] ?? 'cover'),
            'imageOpacity' => (int)($r['image_opacity'] ?? 95),
            // Estilos do texto do segmento
            'text_color'     => (string)($r['text_color'] ?? '#ffffff'),
            'text_size'      => (int)($r['text_size'] ?? 24),
            'unit_text_size' => (int)($r['unit_text_size'] ?? 13),
            // Novo campo: hide_text_all
            'hide_text_all'  => (int)($r['hide_text_all'] ?? 0),
        ];
    }
    $prizesJS = "const PRIZES = " . json_encode($prizes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ";";
    // Log de pesos para debugging
    $prizesJS .= "\n// DEBUG: Segment weights analysis";
    $prizesJS .= "\nconsole.log('🎰 ROULETTE SEGMENT WEIGHTS:');";
    $prizesJS .= "\nPRIZES.forEach((p, i) => {";
    $prizesJS .= "\n  console.log(`Segment \${i}: Weight \${p.weight} -> Effective: \${p.weight <= 1 ? 0 : p.weight} - \${p.primary} \${p.unit} - HideText: \${p.hide_text_all}`);";
    $prizesJS .= "\n});";

    // ---------- Assets ----------
    $assetUrl = ['music'=>'','tick'=>'','inicio'=>'','ganhou'=>'','perdeu'=>'','background'=>'','background_mobile'=>''];
    try {
        $st = $pdo->prepare("SELECT asset_key, file_path, COALESCE(is_muted,0) AS is_muted FROM roulette_assets WHERE roulette_id=?");
        $st->execute([$rouletteId]);
        while ($a = $st->fetch(PDO::FETCH_ASSOC)) {
            $isMuted = isset($a['is_muted']) ? (int)$a['is_muted'] : 0;
            if ($isMuted === 1) { continue; }

            $k = (string)$a['asset_key'];
            if (array_key_exists($k, $assetUrl)) {
                $assetUrl[$k] = $normalize((string)$a['file_path']);
            }
        }
    } catch (\Throwable $e) { /* tolerante */ }

    // ---------- Template ----------
    $html = (string)file_get_contents($TEMPLATE);

    // depois de montar $html
$html = preg_replace('/(--overlay\s*:\s*)[^;]+;/i', '$1none;', $html);
// ou remova a camada do CSS:
$html = str_replace('background-image: var(--overlay), var(--bg-image);',
                    'background-image: var(--bg-image);', $html);

    // 1) PRIZES
    $html = preg_replace(
        '~/\* ===== PRIZES \(o generator substitui esta linha inteira\) ===== \*/\s*const PRIZES = \[\];~',
        '/* ===== PRIZES (gerado) ===== */' . "\n" . $prizesJS,
        $html,
        1
    ) ?? $html;

    // 2) SPIN_TEXT
    $spinJSON = json_encode($spin_text, JSON_UNESCAPED_UNICODE);
    $html = preg_replace(
        "/const\\s+SPIN_TEXT\\s*=\\s*(['\"]).*?\\1\\s*;/",
        "const SPIN_TEXT = {$spinJSON};",
        $html,
        1
    ) ?? $html;

    // 2.1) ===== FONTE (declara em JS para uso no canvas) =====
    $fontJS  = "/* ===== FONTE (gerado) ===== */\n";
    $fontJS .= "const FONT_FAMILY = " . json_encode($font_family, JSON_UNESCAPED_UNICODE) . ";\n";
    $fontJS .= "const FONT_STACK  = \"'\" + FONT_FAMILY + \"', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\";\n";
    $html_try = preg_replace(
        "/(const\\s+SPIN_TEXT\\s*=\\s*['\"].*?['\"];)/",
        "$1\n".$fontJS,
        $html,
        1
    );
    if ($html_try !== null) $html = $html_try;

// 3) ANIMAÇÃO (ANIM_SPINS / ANIM_SPEED)
$anim_spins   = max(0, min(100, (int)($R['anim_spins']  ?? 2)));      // voltas
$anim_speed   = max(20, min(300,(int)($R['anim_speed'] ?? 100)));    // em %
$speed_factor = $anim_speed / 100.0;

// Substitui a linha do ANIM_SPINS (qualquer valor anterior)
$html = preg_replace(
    '/const\s+ANIM_SPINS\s*=\s*[^;]*;/',
    'const ANIM_SPINS = ' . $anim_spins . ';',
    $html,
    1
) ?? $html;

// Substitui a linha do ANIM_SPEED (qualquer valor anterior)
$html = preg_replace(
    '/const\s+ANIM_SPEED\s*=\s*[^;]*;/',
    'const ANIM_SPEED = ' . rtrim(rtrim(sprintf("%.4F", $speed_factor), '0'), '.') . ';',
    $html,
    1
) ?? $html;

    // 4) Áudios
    $map = ['music'=>'bgm','tick'=>'tick','inicio'=>'inicio','ganhou'=>'ganhou','perdeu'=>'perdeu'];
    foreach ($map as $key => $idAttr) {
        if (!empty($assetUrl[$key])) {
            $html = preg_replace(
                '#(<audio\s+[^>]*id="'.$idAttr.'"\s+[^>]*src=")[^"]*(")#i',
                '$1'.addcslashes($assetUrl[$key], '\\$').'$2',
                $html,
                1
            ) ?? $html;
        }
    }

// 4.1) Se um áudio foi marcado como mudo/ausente (sem URL em $assetUrl),
// limpa o src no HTML para não carregar arquivos padrão do template.
(function () use (&$html, $assetUrl) {
    $__map = ['music'=>'bgm','tick'=>'tick','inicio'=>'inicio','ganhou'=>'ganhou','perdeu'=>'perdeu'];
    foreach ($__map as $__key => $__id) {
        if (!empty($assetUrl[$__key])) continue;
        // Remove src="..."
        $html = preg_replace(
            '#(<audio\s+[^>]*id="'. $__id .'"\s+[^>]*src=")[^"]*(")#i',
            '$1$2',
            $html,
            1
        ) ?? $html;
        // Força preload=none
        $html = preg_replace(
            '#(<audio\s+[^>]*id="'. $__id .'"\s+[^>]*)(preload="[^"]*")#i',
            '$1preload="none"',
            $html,
            1
        ) ?? $html;
        // Remove loop do BGM se não houver música
        if ($__id === 'bgm') {
            $html = preg_replace('#(<audio\s+[^>]*id="bgm"[^>]*?)\s+loop#i', '$1', $html, 1) ?? $html;
        }
    }
})();

    // 5) Background
    if (!empty($assetUrl['background'])) {
        $html = preg_replace(
            "#(--bg-image:\s*url\\(')[^']*('\\))#",
            '${1}'.addcslashes($assetUrl['background'], '\\$').'${2}',
            $html,
        1
        ) ?? $html;
    }

    // 5.1) Background Mobile (CSS media query)
    $mobileBgCSS = "";
    if (!empty($assetUrl['background_mobile'])) {
        $mobileBgCSS = "
            <style>
            @media (max-width: 768px) {
                body::before {
                    background-image: url('" . addcslashes($assetUrl['background_mobile'], '\\$') . "') !important;
                }
            }
            </style>
        ";
    } else if (!empty($assetUrl['background'])) {
        // Se não tem background mobile, usa o normal também no mobile
        $mobileBgCSS = "
            <style>
            @media (max-width: 768px) {
                body::before {
                    background-image: url('" . addcslashes($assetUrl['background'], '\\$') . "') !important;
                }
            }
            </style>
        ";
    }

    if ($mobileBgCSS) {
        $html = preg_replace('~</head>~i', $mobileBgCSS . '</head>', $html, 1) ?? $html;
    }

    // 6) <title> e subtítulo
    if ($name !== '') {
        $html = preg_replace(
            '/<title>.*?<\/title>/i',
            '<title>'.htmlspecialchars($name, ENT_QUOTES, 'UTF-8').'</title>',
            $html,
            1
        ) ?? $html;
    }
    if ($subtitle !== '') {
        $subEsc = htmlspecialchars($subtitle, ENT_QUOTES, 'UTF-8');
        $html = preg_replace(
            '~<div[^>]*id=["\']subtitle["\'][^>]*>.*?</div>~is',
            '<div class="subtitle" id="subtitle">'.$subEsc.'</div>',
            $html,
            1
        ) ?? $html;
    }

    // 7) Estilos do título e subtítulo (agora com font-family)
    $html = preg_replace(
        '#\.title\s*\{[^}]*\}#',
        ".title{\n    text-align:center; font-weight:1000; letter-spacing:.18em; text-transform:uppercase;\n    font-size:clamp(14px,2.6vw,{$title_size}px); margin:0 0 4px 0;\n    color:{$title_color};\n    font-family: {$font_stack_css};\n    text-shadow:0 2px 10px rgba(0,0,0,.6), 0 0 12px rgba(96,165,250,.45);\n}",
        $html,
        1
    ) ?? $html;

    $html = preg_replace(
        '#\.subtitle\s*\{[^}]*\}#',
        ".subtitle{\n    margin-top:-4px; text-align:center; opacity:.85; font-weight:700;\n    font-size:clamp(11px,1.8vw,{$subtitle_size}px); letter-spacing:.05em;\n    color:{$subtitle_color};\n    font-family: {$font_stack_css};\n}",
        $html,
        1
    ) ?? $html;

    // 8) ===== SUBSTITUIÇÃO DO ARO DA ROLETA =====
    if ($rim_style === 'solid') {
        // Aro sólido - mantém o formato circular usando a mesma cor para todos os stops do gradiente
        $html = preg_replace(
            '/const g1=ctx\.createRadialGradient\(0,0,inner,0,0,outer\+10\);.*?g1\.addColorStop\(1,\'#[0-9A-Fa-f]{6}\'\);/s',
            'const g1=ctx.createRadialGradient(0,0,inner,0,0,outer+10);' .
            'g1.addColorStop(0,\''.$rim_solid_color.'\'); ' .
            'g1.addColorStop(0.55,\''.$rim_solid_color.'\'); ' .
            'g1.addColorStop(1,\''.$rim_solid_color.'\');',
            $html,
            1
        ) ?? $html;
    } else {
        // Aro com gradiente metálico (3 cores diferentes)
        $html = preg_replace(
            '/const g1=ctx\.createRadialGradient\(0,0,inner,0,0,outer\+10\);.*?g1\.addColorStop\(1,\'#[0-9A-Fa-f]{6}\'\);/s',
            'const g1=ctx.createRadialGradient(0,0,inner,0,0,outer+10);' .
            'g1.addColorStop(0,\''.$rim_light_color.'\'); ' .
            'g1.addColorStop(0.55,\''.$rim_mid_color.'\'); ' .
            'g1.addColorStop(1,\''.$rim_dark_color.'\');',
            $html,
            1
        ) ?? $html;
    }

    // 8.1) Modificar a função drawWheel para usar estilos dos segmentos + FONTE personalizada + hide_text_all
    $drawWheelReplacement = '// ===== IMAGEM DO SEGMENTO =====
const img = SEG_IMAGES[i];
const hasImg = !!img;
const hideText = p.hide_text_all;

// ===== IMAGEM CONTIDA ACIMA DO TEXTO =====
if (hasImg){
  const fit = String(p.imageFit||\'contain\').toLowerCase();
  const alpha = Math.max(0, Math.min(100, +p.imageOpacity||95))/100;
  const aMid = (a0 + a1) / 2;

  if (IMAGE_STYLE === \'ring\'){
    // Estilo anel - mantido original
    const rInner = Math.max(0, r * 0.27 - 2);
    const rOuter = r;
    const bandH  = Math.max(2, rOuter - rInner);
    const bias   = (typeof p.imageBiasIn === \'number\'
                    ? Math.max(0, Math.min(1, p.imageBiasIn))
                    : 0.40);
    const rc     = rInner + bandH * bias;
    const arcLen = Math.max(2, 2 * rc * Math.tan(SL/2));

    const iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height;
    const ir = iw/ih, tr = arcLen/bandH;
    let sx=0, sy=0, sw=iw, sh=ih;
    if ((fit === \'cover\'  && ir < tr) || (fit === \'contain\' && ir > tr)) { sw = ih * tr; sx = (iw - sw) * 0.5; }
    else { sh = iw / tr; sy = (ih - sh) * 0.5; }

    ctx.save();
    (function clipAnnularWedge(ctx, inner, outer, a0, a1){
      ctx.beginPath();
      ctx.arc(0,0, outer, a0, a1, false);
      ctx.arc(0,0, inner, a1, a0, true);
      ctx.closePath();
      ctx.clip();
    })(ctx, rInner, rOuter, a0+0.003, a1-0.003);

    ctx.rotate(aMid);
    ctx.translate(rc, 0);
    ctx.rotate(Math.PI/2);

    const prevA = ctx.globalAlpha; ctx.globalAlpha = alpha;
    ctx.drawImage(img, sx, sy, sw, sh, -arcLen/2, -bandH/2, arcLen, bandH);
    ctx.globalAlpha = prevA;

    ctx.restore();
  } else {
    // ===== IMAGEM SLICE - AJUSTE CONFORME hide_text_all =====
    let boxW, boxH, rPos;

    if (hideText) {
      // Quando texto oculto: imagem ocupa quase todo o segmento
      boxW = Math.min(r * 0.95, (a1-a0) * r * 0.90);
      boxH = r * 0.70; // Altura maior
      rPos = r * 0.65; // Posição mais central
    } else {
      // Com texto: imagem menor acima do texto
      boxW = Math.min(r*0.80, (a1-a0)*r*0.85);
      boxH = r * 0.35;
      rPos = r * 0.80;
    }

    const iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height;
    const ir = iw/ih;
    
    let dw, dh, dx, dy;
    
    // SEMPRE usa contain para imagem inteira sem cortes
    if (ir > boxW/boxH) {
      dw = boxW;
      dh = dw / ir;
    } else {
      dh = boxH;
      dw = dh * ir;
    }
    dx = -dw/2;
    dy = -dh/2;

    ctx.save();
    ctx.rotate(aMid);
    ctx.translate(rPos, 0);
    ctx.rotate(Math.PI/2);

    const prevA = ctx.globalAlpha; ctx.globalAlpha = alpha;
    ctx.drawImage(img, dx, dy, dw, dh);
    ctx.globalAlpha = prevA;
    ctx.restore();
  }
}

// ===== TEXTO (somente se não estiver oculto) =====
if (!hideText) {
  // ... resto do código de texto permanece igual ...
  const textR = hasImg ? r * TEXT_R_POS : r * TEXT_CENTER_POS;

  ctx.save();
  ctx.rotate(mid);
  ctx.translate(textR,0);
  ctx.rotate(Math.PI/2);
  ctx.textAlign=\'center\';
  ctx.textBaseline=\'middle\';

  // === Estilos do texto do segmento ===
  const text_color = p.text_color || \'#ffffff\';
  ctx.fillStyle = text_color;

  // === Mobile font boost (reaproveita IS_MOBILE já definido no topo da drawWheel) ===
  const __IS_MOBILE    = IS_MOBILE;
  const __PRIMARY_BOOST = 1.15; // ex.: +15% para a linha de cima (primary) no mobile
  const __UNIT_BOOST    = 1.25; // ex.: +25% para a linha de baixo (unit) no mobile

  const text_size = Math.round(
    (p.text_size || Math.max(24, Math.round(r * 0.09))) *
    (__IS_MOBILE ? __PRIMARY_BOOST : 1)
  );

  const unit_text_size = Math.round(
    (p.unit_text_size || Math.max(13, Math.round(r * 0.045))) *
    (__IS_MOBILE ? __UNIT_BOOST : 1)
  );

  ctx.fillStyle = text_color;

  // PRIMARY sempre 1 linha (como original)
  ctx.font = `900 ${text_size}px ${FONT_STACK}`;
  ctx.shadowColor=\'rgba(0,0,0,0.6)\';
  ctx.shadowBlur=4;
  ctx.shadowOffsetX=1;
  ctx.shadowOffsetY=1;
  ctx.strokeStyle=\'rgba(0,0,0,0.8)\';
  ctx.lineWidth=Math.max(4, r*0.018);

  const primaryY = hasImg ? -6 : -6;
  if (p.primary){
    ctx.strokeText(p.primary, 0, primaryY);
    ctx.fillText  (p.primary, 0, primaryY);
  }

  // UNIT: quebra por palavras só se necessário e só fora do mobile
  const unit = String(p.unit || \'\').trim();
  if (unit){
    ctx.font = `800 ${unit_text_size}px ${FONT_STACK}`;
    ctx.strokeStyle=\'rgba(0,0,0,0.8)\';
    ctx.lineWidth=Math.max(3, r*0.015);

    const pad = Math.max(6, r*0.02);
    const maxW = maxTextWidthAtRadius(textR, SL, pad);
    const yCenter = hasImg ? 18 : 16;

    if (!IS_MOBILE && ctx.measureText(unit).width > maxW && unit.includes(\' \')){
      const lh = Math.round(unit_text_size * 1.12);
      const lines = wrapByWords(ctx, unit, maxW);
      if (lines.length === 1){
        ctx.strokeText(lines[0], 0, yCenter);
        ctx.fillText  (lines[0], 0, yCenter);
      } else {
        // duas linhas: centraliza ao redor do yCenter
        const y1 = yCenter - lh/2;
        const y2 = yCenter + lh/2;
        ctx.strokeText(lines[0], 0, y1);
        ctx.fillText  (lines[0], 0, y1);
        ctx.strokeText(lines[1], 0, y2);
        ctx.fillText  (lines[1], 0, y2);
      }
    } else {
      // mobile ou coube em 1 linha
      ctx.strokeText(unit, 0, yCenter);
      ctx.fillText  (unit, 0, yCenter);
    }
  }

  // Limpa sombras
  ctx.shadowColor=\'transparent\';
  ctx.shadowBlur=0;
  ctx.shadowOffsetX=0;
  ctx.shadowOffsetY=0;

  ctx.restore();
}';

    // Substituir a seção de desenho da imagem e texto
    $html = preg_replace(
        '/(\/\/ ===== IMAGEM DO SEGMENTO =====.*?)(\/\/ ===== TEXTO.*?ctx\.restore\(\);)/s',
        $drawWheelReplacement,
        $html,
        1
    ) ?? $html;

    // 8.2) Garantia extra: qualquer outro ctx.font=`...px system-ui` vira ${FONT_STACK}
    $html = preg_replace(
        '~ctx\.font=`([^`]*?)px system-ui`;~',
        'ctx.font=`$1px ${FONT_STACK}`;',
        $html
    ) ?? $html;

// 9) Botão GIRAR — fundo/cores do BD + contorno nítido + tamanho fixo
$html = preg_replace(
    // pega do início do desenho do botão até o fillText do SPIN_TEXT
    '/const cr=r\\*0\\.27; ctx\\.rotate\\(-angle\\);.*?ctx\\.fillText\\(SPIN_TEXT,0,0\\);/s',
    // ---------- replacement ----------
    'const cr=r*0.27; ctx.rotate(-angle);' .
    // Fundo do botão (cor cadastrada)
    'ctx.beginPath(); ctx.arc(0,0,cr,0,TAU); ctx.fillStyle=\''.$spin_button_color.'\'; ctx.fill();' .
    // Aro opcional (comente as 2 linhas abaixo se quiser 100% flat)
    'ctx.lineWidth=Math.max(2,cr*0.075); ctx.strokeStyle=\'rgba(255,255,255,.92)\'; ctx.beginPath(); ctx.arc(0,0,cr-ctx.lineWidth*0.55,0,TAU); ctx.stroke();' .

    // Texto: alinhamento e tamanho EXATO do painel
    'ctx.textAlign=\'center\'; ctx.textBaseline=\'middle\';' .
    'ctx.shadowColor=\'transparent\'; ctx.shadowBlur=0; ctx.shadowOffsetX=0; ctx.shadowOffsetY=0;' .
    'const spinSize = '.$spin_text_size.';' . // <= usa exatamente o valor salvo (ex.: 40)
    'ctx.font=`900 ${spinSize}px ${FONT_STACK}`;' .

    // Contorno adaptativo (igual aos segmentos, mais nítido)
    'const __txtColor = \''.$spin_text_color.'\';' .
    'const __tc=__txtColor.replace(\'#\',\'\');' .
    'const __tr=parseInt(__tc.substring(0,2),16), __tg=parseInt(__tc.substring(2,4),16), __tb=parseInt(__tc.substring(4,6),16);' .
    'const __tl=0.2126*__tr+0.7152*__tg+0.0722*__tb;' .
    'const __haloMain=(__tl>160)?\'rgba(0,0,0,0.70)\':\'rgba(255,255,255,0.90)\';' .
    'const __haloSub =( __tl>160)?\'rgba(0,0,0,0.34)\':\'rgba(255,255,255,0.42)\';' .
    'ctx.lineJoin=\'round\'; ctx.miterLimit=2;' .
    'ctx.strokeStyle=__haloMain; ctx.lineWidth=Math.max(2, r*0.014); ctx.strokeText(SPIN_TEXT,0,0);' .
    'ctx.strokeStyle=__haloSub;  ctx.lineWidth=Math.max(1, r*0.008); ctx.strokeText(SPIN_TEXT,0,0);' .
    'ctx.fillStyle=__txtColor; ctx.fillText(SPIN_TEXT,0,0);',
    $html,
    1
) ?? $html;

    // 10) Margens da roleta (desktop e mobile)
    $wheelbox_margin_desktop = '';
    if ($wheel_margin_top_desktop > 0 || $wheel_margin_bottom_desktop > 0) {
        $wheelbox_margin_desktop = "margin: {$wheel_margin_top_desktop}px auto {$wheel_margin_bottom_desktop}px;";
    }

    $wheelbox_margin_mobile = '';
    if ($wheel_margin_top_mobile > 0 || $wheel_margin_bottom_mobile > 0) {
        $wheelbox_margin_mobile = "margin: {$wheel_margin_top_mobile}px auto {$wheel_margin_bottom_mobile}px;";
    }

    $html = preg_replace(
        '/\.wheelbox\{[^}]*\}/',
        ".wheelbox{ position:relative; width:var(--wheel-size); height:var(--wheel-size); {$wheelbox_margin_desktop} cursor: pointer; }",
        $html,
        1
    ) ?? $html;

    // 10.0) CSS de Desktop: ocultação de título/subtítulo e centralização
    $desktop_css = '';
    if ($hide_title_desktop) {
        $desktop_css .= "h1.title{ display: none !important; }";
    } else {
        $desktop_css .= "h1.title{ font-size: var(--title-size, {$title_size}px) !important; }";
    }
    if ($hide_subtitle_desktop) {
        $desktop_css .= ".subtitle{ display: none !important; }";
    } else {
        $desktop_css .= ".subtitle{ font-size: var(--subtitle-size, {$subtitle_size}px) !important; }";
    }

    // Centralização horizontal da roleta - MANTIDA
    $desktop_css .= ".wheelbox{ display:block; margin-left:auto; margin-right:auto; }";

    // CORREÇÃO: Remove os overrides problemáticos das margens
    // As margens configuradas pelo usuário devem ser sempre respeitadas
    if ($desktop_css !== '') {
        $html = preg_replace('/<\/style>/', $desktop_css . "\n</style>", $html, 1);
    }
    
    // 10.1) Media query (mobile) com margens e ocultação
    $mobile_css = '@media (max-width: 768px){';

    // Margens mobile
    if ($wheelbox_margin_mobile) {
        $mobile_css .= ".wheelbox{ {$wheelbox_margin_mobile} }";
    }

    // Ocultar título/subtítulo
    if ($hide_title_mobile) {
        $mobile_css .= "h1.title{ display: none !important; }";
    } else {
        $mobile_css .= "h1.title{ font-size: calc(var(--title-size, {$title_size}px) * 0.80) !important; }";
    }

    if ($hide_subtitle_mobile) {
        $mobile_css .= ".subtitle{ display: none !important; }";
    } else {
        $mobile_css .= ".subtitle{ font-size: calc(var(--subtitle-size, {$subtitle_size}px) * 0.40) !important; }";
    }

    $mobile_css .= '}';

    $html = preg_replace(
        '/<\/style>/',
        $mobile_css . "\n</style>",
        $html
    );

    // 11) ====== POINTER: cor e posição (injeta as const) ======
    $pointerBlock =
        "/* ===== POINTER (gerado) ===== */\n".
        "const POINTER_COLOR = " . json_encode($pointer_color) . ";\n".
        "const POINTER_POSITION = " . json_encode($pointer_position) . ";";

    // Tenta substituir bloco inteiro se existir
    $html_try = preg_replace(
        '~/\* ===== POINTER.*? ===== \*/.*?const\s+POINTER_COLOR[^;]*;.*?const\s+POINTER_POSITION[^;]*;~s',
        $pointerBlock,
        $html,
        1
    );
    if ($html_try !== null) $html = $html_try;

    // Garante substituição individual mesmo sem o bloco
    $html = preg_replace(
        "/const\\s+POINTER_COLOR\\s*=\\s*(['\"]).*?\\1\\s*;/",
        "const POINTER_COLOR = " . json_encode($pointer_color) . ";",
        $html,
        1
    ) ?? $html;
    $html = preg_replace(
        "/const\\s+POINTER_POSITION\\s*=\\s*(['\"]).*?\\1\\s*;/",
        "const POINTER_POSITION = " . json_encode($pointer_position) . ";",
        $html,
        1
    ) ?? $html;

    // 12) Injeta Google Fonts <link> no <head>
    $fontsLinks = "\n<!-- Google Fonts (gerado) -->\n".
                  '<link rel="preconnect" href="https://fonts.googleapis.com">'."\n".
                  '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'."\n".
                  '<link rel="stylesheet" href="'.htmlspecialchars($gf_href, ENT_QUOTES, 'UTF-8').'">'."\n";
    $html = preg_replace('~</head>~i', $fontsLinks.'</head>', $html, 1) ?? $html;

// ✅ INJETA ROULETTE_ID NO PRIMEIRO <script> (Solução Corrigida)
$pos = strpos($html, '<script>');
if ($pos !== false) {
    $html = substr_replace($html, "<script>\nwindow.ROULETTE_ID = " . $rouletteId . ";\n", $pos, strlen('<script>'));
}

    // 13) Escreve arquivo final
    
    // === OVERRIDE FINAL (cores do botão + tamanho da roleta) ===
    if (isset($R)) {
        $spin_text_color   = (string)($R['spin_text_color']   ?? '#ffffff');
        $spin_button_color = (string)($R['spin_button_color'] ?? '#f59e0b');
        $spin_text_size    = (int)   ($R['spin_text_size']    ?? 0);
        $wheel_scale_pct   = (int)   ($R['wheel_scale_pct']   ?? 100);
        if ($wheel_scale_pct < 40) $wheel_scale_pct = 40;
        if ($wheel_scale_pct > 100) $wheel_scale_pct = 100;

        foreach (['spin_button_color','spin_text_color'] as $__k) {
            $__v = $$__k; $__v = trim((string)$__v);
            if ($__v !== '' && $__v[0] !== '#') $__v = '#'.$__v;
            if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $__v)) {
                $__v = ($__k==='spin_button_color') ? '#f59e0b' : '#ffffff';
            }
            $$__k = $__v;
        }

        // Substituições determinísticas nas constantes do template
        $html = str_replace("const BUTTON_COLOR = '#f59e0b';",
                            "const BUTTON_COLOR = '{$spin_button_color}';", $html);
        $html = str_replace("const SPIN_TEXT_COLOR = '#ffffff';",
                            "const SPIN_TEXT_COLOR = '{$spin_text_color}';", $html);
        if ($spin_text_size > 0) {
            $html = str_replace("const SPIN_TEXT_PX = null;",
                                "const SPIN_TEXT_PX = ".$spin_text_size.";", $html);
        }
        // Tamanho (40..100%)
        if (strpos($html, "const WHEEL_SCALE_PCT") !== false) {
            $html = str_replace("const WHEEL_SCALE_PCT = 100;",
                                "const WHEEL_SCALE_PCT = ".$wheel_scale_pct.";", $html);
        } else {
            // Template antigo: injeta a constante no primeiro <script>
            $html = preg_replace('/<script>/', "<script>\\nconst WHEEL_SCALE_PCT = ".$wheel_scale_pct.";\\n", $html, 1) ?? $html;
        }
    }

    // === ÁUDIOS: monta manifesto e ignora mutados na página gerada (não quebra nada existente) ===
    try {
        $roulette_id_for_assets = (int)($R['id'] ?? ($roulette['id'] ?? 0));
        if ($roulette_id_for_assets > 0 && isset($pdo)) {
            $__stmt = $pdo->prepare("SELECT id, roulette_id, mime_type, url, file_path, file_name, COALESCE(is_muted,0) AS is_muted
                                     FROM roulette_assets
                                     WHERE roulette_id = :rid
                                     ORDER BY id ASC");
            $__stmt->execute([':rid' => $roulette_id_for_assets]);
            $__rows = $__stmt->fetchAll(PDO::FETCH_ASSOC);

            // helper para detectar áudio por MIME/extensão
            $__isAudio = function(array $r): bool {
                $mt = isset($r['mime_type']) ? (string)$r['mime_type'] : '';
                if ($mt !== '' && stripos($mt, 'audio/') === 0) return true;
                $u  = (string)($r['url'] ?? $r['file_path'] ?? $r['file_name'] ?? '');
                return (bool)preg_match('~\.(mp3|wav|ogg|m4a|aac)$~i', $u);
            };

            $__onlyAudio = array_values(array_filter($__rows, $__isAudio));

            // normaliza URL para frontend (preferimos 'url' se houver)
            $__toWeb = function(array $r) {
                $u = (string)($r['url'] ?? '');
                if ($u !== '') return $u;
                $rid = (int)($r['roulette_id'] ?? 0);
                $fn  = (string)($r['file_name'] ?? '');
                if ($fn !== '' && $rid > 0) {
                    return "/uploads/roulettes/{$rid}/{$fn}";
                }
                $fp = (string)($r['file_path'] ?? '');
                return $fp;
            };

            $__all = [];
            $__active = [];
            foreach ($__onlyAudio as $row) {
                $item = [
                    'id'   => (int)$row['id'],
                    'url'  => $__toWeb($row),
                    'mime' => (string)($row['mime_type'] ?? ''),
                    'muted'=> (int)$row['is_muted'] === 1 ? 1 : 0,
                ];
                $__all[] = $item;
                if (!$item['muted']) $__active[] = $item;
            }

            // Anexa manifesto JS não intrusivo
            $html .= "\n<script>window.ROULETTE_AUDIO=" . json_encode(['active'=>$__active,'all'=>$__all], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) . ";</script>\n";
        }
    } catch (Throwable $__e) {
        // silencioso: não interrompe a geração mesmo se a tabela/coluna não existir
    }

    // 14) ===== DESABILITAR CONSOLE E INSPEÇÃO =====
    if ($disable_inspect) {
        $inspectScript = "
<!-- 🔒 PROTEÇÃO CONTRA INSPEÇÃO -->
<script>
(function() {
    'use strict';
    
    // Bloqueia menu de contexto (botão direito)
    document.addEventListener('contextmenu', function(e) {
        e.preventDefault();
        showWarning('Inspeção desabilitada');
        return false;
    });
    
    // Bloqueia atalhos do desenvolvedor
    const blockedKeys = new Set(['F12', 'I', 'J', 'C', 'U', 'S']);
    const blockedCombinations = [
        { ctrl: true, shift: true, key: 'I' },
        { ctrl: true, shift: true, key: 'J' },
        { ctrl: true, shift: true, key: 'C' },
        { ctrl: true, key: 'U' },  // Ctrl+U - View Source
        { ctrl: true, key: 'S' },  // Ctrl+S - Save Page
        { meta: true, alt: true, key: 'I' },
        { meta: true, alt: true, key: 'J' },
        { meta: true, alt: true, key: 'C' }
    ];
    
    document.addEventListener('keydown', function(e) {
        // Bloqueia teclas individuais
        if (blockedKeys.has(e.key)) {
            if (e.key === 'F12' || (e.ctrlKey && e.key === 'u') || (e.ctrlKey && e.key === 's')) {
                e.preventDefault();
                showWarning('Ação não permitida');
                return false;
            }
        }
        
        // Bloqueia combinações
        for (const combo of blockedCombinations) {
            const ctrlMatch = combo.ctrl === undefined || combo.ctrl === e.ctrlKey;
            const shiftMatch = combo.shift === undefined || combo.shift === e.shiftKey;
            const altMatch = combo.alt === undefined || combo.alt === e.altKey;
            const metaMatch = combo.meta === undefined || combo.meta === e.metaKey;
            const keyMatch = combo.key === e.key;
            
            if (ctrlMatch && shiftMatch && altMatch && metaMatch && keyMatch) {
                e.preventDefault();
                showWarning('Atalho do desenvolvedor bloqueado');
                return false;
            }
        }
        
        // Proteção extra para Ctrl+U (View Source)
        if (e.ctrlKey && e.key === 'u') {
            e.preventDefault();
            showWarning('Visualização do código fonte desabilitada');
            return false;
        }
    });
    
    // Remove console methods de forma segura
    function disableConsole() {
        try {
            const noop = function(){};
            const methods = ['log', 'debug', 'info', 'warn', 'error', 'table', 'group', 'groupEnd', 'clear'];
            
            methods.forEach(method => {
                if (window.console && typeof console[method] === 'function') {
                    console[method] = noop;
                }
            });
            
            // Tenta remover completamente sem causar erros
            Object.defineProperty(window, 'console', {
                get: function() {
                    return {
                        log: noop, debug: noop, info: noop, warn: noop, error: noop,
                        table: noop, group: noop, groupEnd: noop, clear: noop,
                        assert: noop, count: noop, dir: noop, dirxml: noop,
                        profile: noop, profileEnd: noop, time: noop, timeEnd: noop,
                        timeStamp: noop, trace: noop
                    };
                },
                set: function() {}
            });
        } catch (e) {
            // Silencia erros para não quebrar a aplicação
        }
    }
    
    // Mostra aviso
    function showWarning(message) {
        try {
            // Cria um toast notification
            const toast = document.createElement('div');
            toast.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: #ef4444;
                color: white;
                padding: 12px 16px;
                border-radius: 8px;
                z-index: 10000;
                font-family: system-ui;
                font-weight: bold;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            `;
            toast.textContent = '🔒 ' + message;
            document.body.appendChild(toast);
            
            setTimeout(() => {
                if (toast.parentNode) {
                    toast.parentNode.removeChild(toast);
                }
            }, 3000);
        } catch (e) {
            // Silencia erros
        }
    }
    
    // Inicializa proteções de forma segura
    // Aguarda a página carregar completamente antes de aplicar as proteções
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            setTimeout(disableConsole, 100);
        });
    } else {
        setTimeout(disableConsole, 100);
    }
    
    // Log inicial (antes de desabilitar o console)
    if (typeof console !== 'undefined' && typeof console.log === 'function') {
        console.log('🔒 Modo de proteção ativado');
    }
})();
</script>
";

        // Insere o script antes do </body>
        $html = preg_replace('~</body>~i', $inspectScript . '</body>', $html, 1) ?? $html;
    }

    // ✅ CORREÇÃO: INJEÇÃO DE CÓDIGO DE SINCRONIZAÇÃO NO FINAL DO BODY
    $syncFix = "
<script>
// ✅ CORREÇÃO DE SINCRONIZAÇÃO - Garantir que frontend e backend usem a mesma lógica
function calculateSegmentIndex(angle, pointerOffset, slice, numSegments) {
    const normalized = ((pointerOffset - angle) % TAU + TAU) % TAU;
    return Math.floor(normalized / slice) % numSegments;
}

// ✅ OVERRIDE da função de normalização para garantir consistência
window.robustNormalize = function(angle) {
    angle = angle % TAU;
    if (angle < 0) angle += TAU;
    return angle;
};

console.log('✅ Sincronização frontend/backend carregada');
</script>";

    // ✅ CORREÇÃO: Usar método seguro para adicionar antes do </body>
    $html = str_replace('</body>', $syncFix . '</body>', $html);

    // ✅ CORREÇÃO: Adicionar anti-cache headers no início
    $antiCacheHeaders = "<?php
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');
?>
";

    // Adicionar no início do conteúdo
    $html = $antiCacheHeaders . $html;

    $outfile = $OUT_DIR . '/' . $slug . '.php';
    
    if (file_put_contents($outfile, $html) === false) {
        throw new RuntimeException("Falha ao escrever {$outfile}");
    }

    return $outfile;
}