/* Report Builder — slide + block renderers ==============================
   Pure presentation: SlideRenderer(def, slide, dataset, theme) → 16:9 DOM.
   The SAME DOM is the live preview and the PDF snapshot source; the PPTX
   exporter re-derives the same numbers via reportMetrics selectors.
   Slides render at a fixed logical 1280×720 and scale via CSS transform.
======================================================================== */

const R_SLIDE_W = 1280, R_SLIDE_H = 720;
const RCx = (typeof Recharts !== 'undefined') ? Recharts : window.Recharts;

/* ---------- Theme from a brand kit ------------------------------------ */
function rTheme(kit, lang){
  kit = kit || {};
  const colors = (kit.colors && kit.colors.length===3) ? kit.colors
    : ['#233433','#45464B','#488D82'];
  /* Direction follows the REPORT language; kit.rtl only informs the default
     font (an English report with an Arabic brand kit stays LTR).
     'both' (bilingual) lays out RTL — Arabic-first, Saudi audience. */
  const rtl = lang === 'ar' || lang === 'both';
  return {
    kit, rtl, lang: lang || 'en',
    main: colors[0], secondary: colors[1], tertiary: colors[2],
    colors,
    ramp: rRamp(colors, 8),
    onMain: rContrastText(colors[0]),
    onSecondary: rContrastText(colors[1]),
    font: kit.font || (rtl || kit.rtl ? "'Sakkal Majalla','Segoe UI',Tahoma,sans-serif" : "Inter,Helvetica,Arial,sans-serif"),
    logo: kit.logo_path || null,
    logoDark: kit.logo_dark_path || kit.logo_path || null,   // for dark/branded slides
    bg: kit.bg_path || null,
    bgFlipped: kit.bg_path_flipped || null,
  };
}
/* Branded background for cover/divider/thank_you. Illustration must sit
   OPPOSITE the text: LTR text-left → original (illustration right);
   RTL text-right → flipped (illustration left). */
function rBrandedBg(theme){
  if (!theme.bg) return null;
  return theme.rtl ? (theme.bgFlipped || theme.bg) : theme.bg;
}

/* ---------- Shared bits ------------------------------------------------ */
function RNum({ children }){        // keep numerals LTR inside RTL text
  return <span dir="ltr" style={{unicodeBidi:'isolate'}}>{children}</span>;
}
/* Official platform logo (user-supplied PNG); BrandIcon SVG as fallback. */
function RPlatLogo({ platform, size }){
  const src = typeof rPlatformLogo==='function' ? rPlatformLogo(platform) : null;
  if (src) return <img src={src} alt={platform}
    style={{width:size, height:size, objectFit:'contain', flexShrink:0}}/>;
  return <BrandIcon name={rPlatform(platform).icon} size={size}/>;
}
function RDeltaChip({ cur, prev, mkey, lang }){
  const d = rDelta(cur, prev, mkey);
  if (!d) return null;
  const tones = { good:{bg:'#e7f6ee',fg:'#0c7a43'}, bad:{bg:'#fdecec',fg:'#b42323'},
                  neutral:{bg:'#f0f0ee',fg:'#5e5e55'} };
  const t = tones[d.tone];
  return (
    <span style={{background:t.bg,color:t.fg,borderRadius:99,padding:'2px 8px',
      fontSize:11,fontWeight:700,display:'inline-flex',alignItems:'center',gap:3}}>
      <RNum>{d.up?'▲':'▼'} {Math.abs(d.pct).toFixed(1)}%</RNum>
    </span>
  );
}
function REmpty({ lang, note }){
  return (
    <div style={{display:'grid',placeItems:'center',minHeight:170,height:'100%',color:'#8b8b80',
      fontSize:14.5,textAlign:'center',border:'1px dashed #d9d9d4',borderRadius:12,padding:16}}>
      <div>{note || rStr('no_data', lang)}</div>
    </div>
  );
}
const R_AXIS = '#8b8b80', R_GRID = 'rgba(0,0,0,.07)';

/* ---------- Blocks ------------------------------------------------------ */
function RScorecard({ block, ds, theme, currency, tall }){
  const lang = theme.lang;
  const b = block.binding || {};
  const t = b.platform ? rTotals(ds, b.platform) : rTotals(ds);
  const metrics = (b.metrics && b.metrics.length ? b.metrics : ['spend','conversions','cpa','roas']);
  if (rMetricValue(t.cur,'spend')===0 && rMetricValue(t.cur,'impressions')===0)
    return <REmpty lang={lang}/>;
  const cols = metrics.length <= 4 ? metrics.length : Math.ceil(metrics.length/2);
  const big = tall && metrics.length <= 4;      // solo scorecard row → fill the slide
  return (
    <div style={{display:'grid',gridTemplateColumns:`repeat(${cols},1fr)`,gap:big?18:14,
      height: tall ? '100%' : undefined, alignContent: tall ? 'center' : undefined}}>
      {metrics.map((k,mi)=>{
        const m = rMetric(k); if (!m) return null;
        /* Total Followers is a BRAND metric — comes from brand_metrics_daily
           (aggregated across platforms), not from ad-spend sums. */
        if (k === 'followers'){
          const br = rBrand(ds);
          const total = br.totalFollowers || 0;
          return (
            <div key={k} style={{border:'1px solid #e7e7e2',borderRadius:16,
              padding: big?'30px 24px':'18px 18px',
              background:'#fff',boxShadow:'0 1px 3px rgba(0,0,0,.05)',
              display:'flex',flexDirection:'column',justifyContent:'center'}}>
              <div style={{width:big?52:40,height:big?5:4,borderRadius:99,marginBottom:big?14:10,
                background:theme.ramp[mi % theme.ramp.length]}}/>
              <div style={{fontSize:big?15:13.5,fontWeight:600,color:'#6d6d64',marginBottom:big?12:8}}>
                {rMetricLabel('followers', lang)}
              </div>
              <div style={{fontSize:big?42:32,fontWeight:800,letterSpacing:-0.5,color:theme.main,lineHeight:1.05}}>
                <RNum>{total ? rCompact(total,'int',currency,lang) : (br.totalGained ? '+'+rCompact(br.totalGained,'int',currency,lang) : '—')}</RNum>
              </div>
              <div style={{marginTop:big?14:10,fontSize:big?12:11,color:'#0c7a43',fontWeight:700}}>
                {br.totalGained ? <RNum>{'+'+br.totalGained.toLocaleString('en-US')}</RNum> : <span style={{color:'#9a9a90',fontWeight:400}}>—</span>}
                <span style={{color:'#9a9a90',fontWeight:400}}> {rStr('followers_by_platform', lang)}</span>
              </div>
            </div>
          );
        }
        const cur = rMetricValue(t.cur,k), prev = rMetricValue(t.prev,k);
        return (
          <div key={k} style={{border:'1px solid #e7e7e2',borderRadius:16,
            padding: big?'30px 24px':'18px 18px',
            background:'#fff',boxShadow:'0 1px 3px rgba(0,0,0,.05)',
            display:'flex',flexDirection:'column',justifyContent:'center'}}>
            <div style={{width:big?52:40,height:big?5:4,borderRadius:99,marginBottom:big?14:10,
              background:theme.ramp[mi % theme.ramp.length]}}/>
            <div style={{fontSize:big?15:13.5,fontWeight:600,color:'#6d6d64',marginBottom:big?12:8}}>
              {rMetricLabel(k, lang)}
            </div>
            <div style={{fontSize:big?42:32,fontWeight:800,letterSpacing:-0.5,color:theme.main,lineHeight:1.05}}>
              <RNum>{rCompact(cur, m.fmt, currency, lang)}</RNum>
            </div>
            <div style={{marginTop:big?14:10,display:'flex',alignItems:'center',gap:7,flexWrap:'wrap'}}>
              <RDeltaChip cur={cur} prev={prev} mkey={k} lang={lang}/>
              <span style={{fontSize:big?12:11,color:'#9a9a90'}}>{rStr('vs_prev', lang)}</span>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function RTrend({ block, ds, theme, currency, editable, onBinding, tall }){
  const lang = theme.lang;
  const b = block.binding || {};
  const keys = (b.metrics && b.metrics.length ? b.metrics : ['spend']);
  const showCompare = block.options && block.options.showCompare !== false;
  const data = rSeries(ds, keys, b.platform);
  if (!data.length) return <REmpty lang={lang}/>;
  /* dual axis: money-scale metrics left, ratio/small metrics right */
  const isBig = (k)=>{ const m=rMetric(k); return m && (m.fmt==='currency'||m.fmt==='int'); };
  const left = keys.filter(isBig), right = keys.filter(k=>!isBig(k));
  const colorOf = (k)=> theme.ramp[keys.indexOf(k) % theme.ramp.length];
  const toggle = (k)=>{
    if (!editable || !onBinding) return;
    const cur = new Set(keys);
    cur.has(k) && cur.size>1 ? cur.delete(k) : cur.add(k);
    onBinding({ metrics:[...cur] });
  };
  const all = rAllMetrics().filter(m=>m.tier!==3 && m.agg!=='last');
  return (
    <div>
      <div style={{display:'flex',gap:6,flexWrap:'wrap',marginBottom:8}}>
        {(editable ? all.map(m=>m.key) : keys).map(k=>{
          const on = keys.includes(k);
          if (!editable && !on) return null;
          return (
            <button key={k} onClick={()=>toggle(k)}
              style={{fontSize:12.5,fontWeight:600,padding:'4px 12px',borderRadius:99,
                cursor:editable?'pointer':'default',
                border:'1px solid ' + (on ? colorOf(k) : '#dddad2'),
                background: on ? colorOf(k) : '#fff',
                color: on ? rContrastText(colorOf(k)) : '#8b8b80'}}>
              {rMetricLabel(k, lang)}
            </button>
          );
        })}
      </div>
      <div style={{height: tall ? 480 : 300, direction:'ltr'}}>
        <RCx.ResponsiveContainer width="100%" height="100%">
          <RCx.LineChart data={data} margin={{top:6,right:12,bottom:2,left:0}}>
            <RCx.CartesianGrid stroke={R_GRID} vertical={false}/>
            <RCx.XAxis dataKey="label" tick={{fontSize:12,fill:R_AXIS}} tickLine={false} axisLine={{stroke:R_GRID}}/>
            <RCx.YAxis yAxisId="l" tick={{fontSize:12,fill:R_AXIS}} tickLine={false} axisLine={false} width={58}
              tickFormatter={(v)=>rCompact(v, 'int', currency, 'en')}/>
            {right.length>0 && <RCx.YAxis yAxisId="r" orientation="right" tick={{fontSize:12,fill:R_AXIS}}
              tickLine={false} axisLine={false} width={48}/>}
            <RCx.Tooltip formatter={(v,name)=>{
              const key = String(name).replace('_cmp_','');
              const m = rMetric(key);
              return [rFmt(v, m?m.fmt:'int', currency, lang), rMetricLabel(key,lang) + (String(name).startsWith('_cmp_')?' (prev)':'')];
            }}/>
            {keys.map(k=>(
              <RCx.Line isAnimationActive={false} key={k} yAxisId={isBig(k)?'l':'r'} type="monotone" dataKey={k}
                stroke={colorOf(k)} strokeWidth={2.4} dot={false}/>
            ))}
            {showCompare && keys.map(k=>(
              <RCx.Line isAnimationActive={false} key={'c'+k} yAxisId={isBig(k)?'l':'r'} type="monotone" dataKey={'_cmp_'+k}
                stroke={colorOf(k)} strokeWidth={1.4} strokeDasharray="5 4" dot={false} opacity={0.45}/>
            ))}
          </RCx.LineChart>
        </RCx.ResponsiveContainer>
      </div>
    </div>
  );
}

function RBar({ block, ds, theme, currency, tall }){
  const lang = theme.lang;
  const b = block.binding || {};
  const key = b.metric || 'spend';
  const m = rMetric(key);
  let data;
  if ((b.dimension||'platform') === 'platform'){
    data = rByPlatform(ds).map(p=>({
      name: lang==='ar' ? rPlatform(p.platform).ar : rPlatform(p.platform).label,
      value: rMetricValue(p.cur, key), tone: rPlatform(p.platform).tone,
    }));
  } else {
    const { rows } = rEntityRows(ds, b.level||'campaign', { platform:b.platform, sortKey:key, topN:b.topN||8 });
    data = rows.map(r=>({ name:r.name, value:rMetricValue(r.cur,key) }));
  }
  data = data.filter(d=>d.value>0);
  if (!data.length) return <REmpty lang={lang}/>;
  return (
    <div style={{height: tall ? 460 : 300, direction:'ltr'}}>
      <RCx.ResponsiveContainer width="100%" height="100%">
        <RCx.BarChart data={data} margin={{top:6,right:8,bottom:2,left:0}}>
          <RCx.CartesianGrid stroke={R_GRID} vertical={false}/>
          <RCx.XAxis dataKey="name" tick={{fontSize:12,fill:R_AXIS}} tickLine={false} axisLine={{stroke:R_GRID}}
            interval={0} angle={data.length>6?-18:0} height={data.length>6?50:28}/>
          <RCx.YAxis tick={{fontSize:12,fill:R_AXIS}} tickLine={false} axisLine={false} width={58}
            tickFormatter={(v)=>rCompact(v,'int',currency,'en')}/>
          <RCx.Tooltip formatter={(v)=>[rFmt(v, m?m.fmt:'int', currency, lang), rMetricLabel(key,lang)]}/>
          <RCx.Bar isAnimationActive={false} dataKey="value" radius={[6,6,0,0]}>
            {data.map((d,i)=><RCx.Cell key={i} fill={theme.ramp[i % theme.ramp.length]}/>)}
          </RCx.Bar>
        </RCx.BarChart>
      </RCx.ResponsiveContainer>
    </div>
  );
}

function RDonut({ block, ds, theme, currency, tall }){
  const lang = theme.lang;
  const key = (block.binding && block.binding.metric) || 'spend';
  const m = rMetric(key);
  const data = rByPlatform(ds)
    .map(p=>({ id:p.platform, name: lang==='ar'?rPlatform(p.platform).ar:rPlatform(p.platform).label,
      value: rMetricValue(p.cur,key) }))
    .filter(d=>d.value>0);
  if (!data.length) return <REmpty lang={lang}/>;
  const total = data.reduce((a,b)=>a+b.value,0);
  return (
    <div>
      <div style={{fontSize:14.5,fontWeight:700,color:'#4b4b44',marginBottom:6}}>{rStr('spend_share',lang)}</div>
      <div style={{display:'flex',alignItems:'center',gap:16}}>
        <div style={{width:tall?250:200,height:tall?250:200,direction:'ltr',flexShrink:0,position:'relative'}}>
          {/* center total */}
          <div style={{position:'absolute',inset:0,display:'grid',placeItems:'center',pointerEvents:'none'}}>
            <div style={{textAlign:'center'}}>
              <div style={{fontSize:tall?22:17,fontWeight:800,color:theme.main,lineHeight:1.1}}>
                {rCompact(total, m?m.fmt:'currency', currency, lang)}
              </div>
              <div style={{fontSize:tall?11.5:10,color:'#9a9a90',fontWeight:600}}>{rMetricLabel(key,lang)}</div>
            </div>
          </div>
          <RCx.ResponsiveContainer width="100%" height="100%">
            <RCx.PieChart>
              <RCx.Pie isAnimationActive={false} data={data} dataKey="value" nameKey="name"
                innerRadius={tall?70:56} outerRadius={tall?118:94}
                paddingAngle={2} strokeWidth={0}>
                {data.map((d,i)=><RCx.Cell key={i} fill={theme.ramp[i % theme.ramp.length]}/>)}
              </RCx.Pie>
              <RCx.Tooltip formatter={(v)=>[rFmt(v, m?m.fmt:'currency', currency, lang)]}/>
            </RCx.PieChart>
          </RCx.ResponsiveContainer>
        </div>
        <div style={{display:'grid',gap:9,minWidth:0,flex:1}}>
          {data.map((d,i)=>(
            <div key={d.id} style={{display:'flex',alignItems:'center',gap:9,fontSize:13.5}}>
              <span style={{width:12,height:12,borderRadius:3,background:theme.ramp[i%theme.ramp.length],flexShrink:0}}/>
              <span style={{fontWeight:600,color:'#3d3d37'}}>{d.name}</span>
              <span style={{color:'#8b8b80'}}><RNum>{(d.value/total*100).toFixed(1)}%</RNum></span>
              <span style={{color:'#5e5e55',fontWeight:600,marginInlineStart:'auto'}}>
                <RNum>{rCompact(d.value, m?m.fmt:'currency', currency, lang)}</RNum>
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function RBrandBlock({ ds, theme, lang }){
  lang = lang || theme.lang;
  const b = rBrand(ds);
  if (!b.hasData) return <REmpty lang={lang}
    note={lang==='ar' ? 'لا توجد بيانات متابعين — أضفها يدوياً من إعدادات التقرير.'
      : 'No follower data — connect a social source or add numbers in report setup.'}/>;
  return (
    <div>
      <div style={{fontSize:14.5,fontWeight:700,color:'#4b4b44',marginBottom:10}}>{rStr('followers_by_platform',lang)}</div>
      <div style={{display:'grid',gap:10}}>
        {b.platforms.map(p=>(
          <div key={p} style={{display:'flex',alignItems:'center',gap:9,fontSize:14}}>
            <span style={{display:'inline-flex',width:20,justifyContent:'center'}}><RPlatLogo platform={p} size={17}/></span>
            <span style={{fontWeight:600,color:'#3d3d37'}}>{lang==='ar'?rPlatform(p).ar:rPlatform(p).label}</span>
            <span style={{marginInlineStart:'auto',fontWeight:700,color:theme.main}}>
              <RNum>{b.gained[p] ? '+'+ (b.gained[p]).toLocaleString('en-US') : '—'}</RNum>
            </span>
            {b.latest[p] != null && <span style={{fontSize:11,color:'#8b8b80'}}>
              <RNum>({Number(b.latest[p]).toLocaleString('en-US')})</RNum></span>}
          </div>
        ))}
      </div>
      <div style={{marginTop:12,paddingTop:12,borderTop:'1px solid #ecece7',display:'flex',fontSize:14.5}}>
        <span style={{fontWeight:700,color:'#4b4b44'}}>{rStr('total_followers',lang)}</span>
        <span style={{marginInlineStart:'auto',fontWeight:800,color:theme.main}}>
          <RNum>{b.totalFollowers ? b.totalFollowers.toLocaleString('en-US') : '+' + b.totalGained.toLocaleString('en-US')}</RNum>
        </span>
      </div>
    </div>
  );
}

function RTable({ block, ds, theme, currency, editable, onBinding }){
  const lang = theme.lang;
  const b = block.binding || {};
  const metrics = b.metrics && b.metrics.length ? b.metrics
    : ['spend','impressions','clicks','ctr','conversions','cpa'];
  const res = rEntityRows(ds, b.level||'campaign', {
    platform:b.platform, sortKey:b.sortKey||'spend', sortDir:b.sortDir||'desc',
    topN:b.topN||10, filters:b.filters||[] });
  const rows = res.rows;
  if (!rows.length) return <REmpty lang={lang}/>;
  /* 👑 best performer of this level+scope (objective-aware), unless disabled */
  const perf = (block.options && block.options.crown === false) ? null
    : rPerfInsights(ds, b.level||'campaign', { platform:b.platform, lang, currency });
  const crownId = perf ? perf.bestId : null;
  const heat = (block.options && block.options.heatmap) || [];
  const maxOf = {};
  heat.forEach(k=>{ maxOf[k] = Math.max(...rows.map(r=>rMetricValue(r.cur,k)), 0.0001); });
  const sortOn = (k)=>{
    if (!editable || !onBinding) return;
    onBinding(b.sortKey===k ? { sortDir: b.sortDir==='asc'?'desc':'asc' } : { sortKey:k, sortDir:'desc' });
  };
  const th = { fontSize:12, textTransform:'uppercase', letterSpacing:0.4, color:'#8b8b80',
    fontWeight:700, padding:'10px 12px', whiteSpace:'nowrap',
    textAlign: theme.rtl?'right':'left', cursor: editable?'pointer':'default' };
  const allRows = res.others ? [...rows, res.others] : rows;
  return (
    <div style={{border:'1px solid #e7e7e2',borderRadius:12,overflow:'hidden'}}>
      <table style={{width:'100%',borderCollapse:'collapse',fontSize:13.5}}>
        <thead>
          <tr style={{background:rMix('#ffffff', theme.main, 0.08)}}>
            <th style={th} onClick={()=>{}}>{
              b.level==='ad' ? rStr('ads',lang) : b.level==='adset' ? rStr('adsets',lang) : rStr('campaigns',lang)
            }</th>
            {metrics.map(k=>(
              <th key={k} style={{...th,textAlign:theme.rtl?'left':'right'}} onClick={()=>sortOn(k)}>
                {rMetricLabel(k,lang)}{b.sortKey===k ? (b.sortDir==='asc'?' ↑':' ↓') : ''}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {allRows.map((r,ri)=>(
            <tr key={r.id||ri} style={{borderTop:'1px solid #efefe9',
              background: r.id===crownId ? rMix('#ffffff', theme.secondary, 0.16)
                : (r._others?'#fafaf7':(ri%2===1?'#fafaf8':undefined)),
              fontStyle:r._others?'italic':undefined}}>
              <td style={{padding:'10px 12px',fontWeight:600,color:'#33332e',maxWidth:320,
                overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
                {r.id===crownId && <span title="Top performer" style={{marginInlineEnd:6}}>👑</span>}
                {r.name}
              </td>
              {metrics.map(k=>{
                const m = rMetric(k);
                const v = rMetricValue(r.cur,k);
                let bg;
                if (heat.includes(k) && !r._others){
                  const t = Math.min(1, v / maxOf[k]);
                  bg = rMix('#ffffff', theme.tertiary, 0.14 + 0.5*t);
                }
                return (
                  <td key={k} style={{padding:'10px 12px',textAlign:theme.rtl?'left':'right',
                    fontVariantNumeric:'tabular-nums', background:bg,
                    color: bg ? rContrastText(bg) : '#3d3d37', fontWeight: heat.includes(k)?600:400}}>
                    <RNum>{rFmt(v, m?m.fmt:'int', currency, lang)}</RNum>
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
      {res.total > rows.length && !res.others &&
        <div style={{fontSize:10.5,color:'#9a9a90',padding:'5px 10px'}}>
          {lang==='ar' ? `عرض ${rows.length} من ${res.total}` : `Showing ${rows.length} of ${res.total}`}
        </div>}
    </div>
  );
}

/* Day-of-week × week heatmap (data is daily — honest granularity). */
function RHeatmap({ block, ds, theme, currency, tall }){
  const lang = theme.lang;
  const key = (block.binding && block.binding.metric) || 'spend';
  const m = rMetric(key);
  const series = rSeries(ds, [key], block.binding && block.binding.platform);
  if (!series.length) return <REmpty lang={lang}/>;
  const weeks = {};   // weekStart(iso) -> [7 values]
  series.forEach(r=>{
    const d = new Date(r.date+'T00:00:00');
    const dow = d.getDay();
    const ws = new Date(d); ws.setDate(d.getDate()-dow);
    const wk = ws.toISOString().slice(0,10);
    (weeks[wk] = weeks[wk] || Array(7).fill(null))[dow] = r[key];
  });
  const wkeys = Object.keys(weeks).sort();
  const max = Math.max(...series.map(r=>r[key]||0), 0.0001);
  const days = lang==='ar' ? ['أحد','إثن','ثلا','أرب','خمي','جمع','سبت']
    : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  return (
    <div style={{direction:'ltr'}}>
      <div style={{fontSize:14.5,fontWeight:700,color:'#4b4b44',marginBottom:10}}>
        {rMetricLabel(key,lang)} — {rStr('daily',lang)}
      </div>
      <div style={{display:'grid',gridTemplateColumns:`44px repeat(${wkeys.length},1fr)`,gap:3}}>
        <div/>
        {wkeys.map(w=>(
          <div key={w} style={{fontSize:9.5,color:'#9a9a90',textAlign:'center'}}>
            {new Date(w+'T00:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric'})}
          </div>
        ))}
        {days.map((dl,dow)=>(
          <React.Fragment key={dow}>
            <div style={{fontSize:11.5,color:'#8b8b80',display:'flex',alignItems:'center'}}>{dl}</div>
            {wkeys.map(w=>{
              const v = weeks[w][dow];
              const bg = v==null ? '#f4f4f0' : rMix('#ffffff', theme.main, 0.12 + 0.72*Math.min(1, v/max));
              return (
                <div key={w} title={v==null?'':rFmt(v, m?m.fmt:'int', currency, lang)}
                  style={{height:tall?52:34,borderRadius:6,background:bg,display:'grid',placeItems:'center'}}>
                  {v!=null && v/max>0.02 &&
                    <span style={{fontSize:tall?11:9.5,fontWeight:600,color:rContrastText(bg),opacity:0.85}}>
                      {rCompact(v,'int',currency,'en')}
                    </span>}
                </div>
              );
            })}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

/* Ranked list — from the Al Munawarah deck: rank badge + name + share + secondary */
function RRankedList({ block, ds, theme, currency }){
  const lang = theme.lang;
  const b = block.binding || {};
  const key = b.metric || 'impressions', sec = b.secondary || 'ctr';
  const mk = rMetric(key), ms = rMetric(sec);
  const res = rEntityRows(ds, b.level||'campaign', { platform:b.platform, sortKey:key, sortDir:'desc', topN:b.topN||5 });
  const rows = res.rows;
  if (!rows.length) return <REmpty lang={lang}/>;
  const total = rows.reduce((a,r)=>a+rMetricValue(r.cur,key),0) +
    (res.others ? rMetricValue(res.others.cur,key) : 0);
  return (
    <div style={{display:'grid',gap:10}}>
      {rows.map((r,i)=>{
        const v = rMetricValue(r.cur,key), sv = rMetricValue(r.cur,sec);
        const share = total>0 ? v/total*100 : 0;
        return (
          <div key={r.id} style={{display:'flex',alignItems:'center',gap:18,border:'1px solid #e7e7e2',
            borderRadius:14,padding:'14px 18px',background:'#fff'}}>
            <span style={{width:44,height:44,borderRadius:'50%',flexShrink:0,display:'grid',placeItems:'center',
              background:theme.secondary,color:rContrastText(theme.secondary),fontWeight:800,fontSize:19}}>
              <RNum>{i+1}</RNum>
            </span>
            <div style={{minWidth:0,flex:1}}>
              <div style={{fontWeight:700,fontSize:16.5,color:'#2b2b26',overflow:'hidden',
                textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{r.name}</div>
              <div style={{fontSize:13,color:'#7c7c72',marginTop:3}}>
                <RNum>{rCompact(v, mk?mk.fmt:'int', currency, lang)}</RNum>
                {' — '}<RNum>{share.toFixed(1)}%</RNum>
                {' • '}{rMetricLabel(sec,lang)} <RNum>{rFmt(sv, ms?ms.fmt:'pct', currency, lang)}</RNum>
              </div>
              <div style={{height:7,borderRadius:99,background:'#efefe9',marginTop:8,direction:'ltr'}}>
                <div style={{height:7,borderRadius:99,width:share+'%',background:theme.main}}/>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* Analysis flags — WHY the best ad/ad set wins and the worst is failing. */
function RPerfFlags({ block, ds, theme, currency }){
  const lang = theme.lang;
  const b = block.binding || {};
  const perf = rPerfInsights(ds, b.level||'ad', { platform:b.platform, lang, currency });
  if (!perf) return <REmpty lang={lang}
    note={lang==='ar' ? 'يتطلب التحليل عنصرين نشطين على الأقل بإنفاق كافٍ'
      : 'Analysis needs at least two entries with meaningful spend'}/>;
  const Card = ({ icon, tone, label, name, reasons })=>(
    <div style={{flex:1, minWidth:0, border:'1px solid ' + tone.border, background:tone.bg,
      borderRadius:14, padding:'16px 18px'}}>
      <div style={{fontSize:12, fontWeight:800, letterSpacing:0.6, textTransform:'uppercase',
        color:tone.fg, marginBottom:8}}>{icon} {label}</div>
      <div style={{fontSize:16, fontWeight:700, color:'#26261f', marginBottom:8,
        overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{name}</div>
      <div style={{display:'flex', flexWrap:'wrap', gap:6}}>
        {reasons.map((t,i)=>(
          <span key={i} style={{fontSize:11.5, fontWeight:600, color:tone.fg, background:'#ffffffB0',
            border:'1px solid ' + tone.border, borderRadius:99, padding:'3px 10px'}}>
            <RNum>{t}</RNum>
          </span>
        ))}
      </div>
    </div>
  );
  return (
    <div style={{display:'flex', gap:14}}>
      <Card icon="👑" label={lang==='ar' ? 'الأفضل أداءً' : (lang==='both' ? 'الأفضل أداءً · Top performer' : 'Top performer')}
        tone={{ bg:'#f2f9f2', border:'#bfe3bf', fg:'#1d7a34' }}
        name={perf.best.name} reasons={perf.best.reasons}/>
      <Card icon="⚠️" label={lang==='ar' ? 'يحتاج معالجة' : (lang==='both' ? 'يحتاج معالجة · Needs attention' : 'Needs attention')}
        tone={{ bg:'#fdf5f0', border:'#f0cdb8', fg:'#b4560f' }}
        name={perf.worst.name} reasons={perf.worst.reasons}/>
    </div>
  );
}

/* Key Priorities — rich, data-driven action cards (Next Steps slide). */
function RPriorities({ block, ds, theme, currency, tall }){
  const lang = theme.lang;
  const custom = block.options && block.options.items;
  const items = (custom && custom.length) ? custom : rRecommendations(ds, lang, currency);
  if (!items.length) return <REmpty lang={lang}/>;
  const PRI = {
    high:{ label: lang==='ar'?'عالية':'HIGH',   bar:'#d9482b', bg:'#fdf3f0', icon:'#d9482b' },
    med: { label: lang==='ar'?'متوسطة':'MED',   bar:'#e2a615', bg:'#fdf9ee', icon:'#b3820a' },
    low: { label: lang==='ar'?'منخفضة':'LOW',   bar:'#4f9a67', bg:'#f2f8f3', icon:'#3d7d51' },
  };
  return (
    <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:14,
      height: tall ? '100%' : undefined, alignContent: tall ? 'stretch' : 'start'}}>
      {items.map((it,i)=>{
        const p = PRI[it.pri] || PRI.med;
        return (
          <div key={i} style={{display:'flex', border:'1px solid #e7e7e2', borderRadius:14,
            overflow:'hidden', background:'#fff', minWidth:0}}>
            <div style={{width:6, background:p.bar, flexShrink:0}}/>
            <div style={{padding:'13px 16px', minWidth:0, flex:1}}>
              <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:6}}>
                <span style={{width:26, height:26, borderRadius:8, background:p.bg, flexShrink:0,
                  display:'grid', placeItems:'center', color:p.icon}}>
                  <Icon name={it.icon||'sparkles'} size={14}/>
                </span>
                <span style={{fontSize:14.5, fontWeight:800, color:'#26261f', minWidth:0,
                  overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{it.title}</span>
                <span style={{marginInlineStart:'auto', flexShrink:0, fontSize:9.5, fontWeight:800,
                  letterSpacing:0.6, color:p.bar, border:'1.5px solid '+p.bar, borderRadius:99,
                  padding:'2px 8px'}}>{p.label}</span>
              </div>
              <div style={{fontSize:12, lineHeight:1.55, color:'#55554e', whiteSpace:'pre-line'}}>
                <RNum>{it.why}</RNum>
              </div>
              <div style={{fontSize:12, fontWeight:700, color:theme.main, marginTop:7,
                display:'flex', gap:6, whiteSpace:'pre-line'}}>
                <span style={{flexShrink:0}}>→</span><RNum>{it.action}</RNum>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function RText({ block, ds, theme, currency, editable, onOptions }){
  const lang = theme.lang;
  const o = block.options || {};
  let bullets = o.bullets && o.bullets.length ? o.bullets : null;
  if (!bullets && o.autoInsights) bullets = rAutoInsights(ds, lang, currency);
  const text = o.text || '';
  if (!bullets && !text) return <REmpty lang={lang}
    note={lang==='ar'?'أضف نصاً من لوحة التحرير':'Add text from the edit panel'}/>;
  return (
    <div style={{fontSize:17,lineHeight:1.85,color:'#3a3a34'}}>
      {text && <p style={{margin:'0 0 12px'}}>{text}</p>}
      {bullets && (
        <ul style={{margin:0,paddingInlineStart:24,display:'grid',gap:12}}>
          {bullets.map((t,i)=><li key={i} style={{listStyle:'disc'}}>{t}</li>)}
        </ul>
      )}
      {o.autoInsights && !o.bullets?.length &&
        <div style={{marginTop:10,fontSize:10.5,color:'#9a9a90'}}>
          {lang==='ar'?'نقاط مُولّدة تلقائياً من البيانات — قابلة للتحرير.':'Auto-generated from the data — editable.'}
        </div>}
    </div>
  );
}

function RImage({ block }){
  const o = block.options || {};
  if (!o.src) return <REmpty lang="en" note="No image selected"/>;
  return <div style={{display:'grid',placeItems:'center'}}>
    <img src={o.src} style={{maxWidth:'100%',maxHeight:380,objectFit:o.fit||'contain',borderRadius:10}}/>
  </div>;
}

const R_BLOCKS = {
  scorecard:  { comp:RScorecard,  span:12, label:'Scorecards',   ar:'بطاقات المؤشرات', icon:'sparkles' },
  metric_grid:{ comp:RScorecard,  span:12, label:'Metric grid',  ar:'شبكة مؤشرات',     icon:'columns' },
  trend:      { comp:RTrend,      span:12, label:'Trend chart',  ar:'مخطط الاتجاه',    icon:'zap' },
  bar:        { comp:RBar,        span:6,  label:'Bar chart',    ar:'مخطط أعمدة',      icon:'columns' },
  donut:      { comp:RDonut,      span:6,  label:'Share donut',  ar:'مخطط دائري',      icon:'sparkles' },
  table:      { comp:RTable,      span:12, label:'Data table',   ar:'جدول بيانات',     icon:'columns' },
  heatmap:    { comp:RHeatmap,    span:12, label:'Heatmap',      ar:'خريطة حرارية',    icon:'calendar' },
  ranked_list:{ comp:RRankedList, span:12, label:'Ranked list',  ar:'قائمة مرتبة',     icon:'star' },
  perf_flags: { comp:RPerfFlags,  span:12, label:'Analysis flags', ar:'تحليل الأداء',  icon:'sparkles' },
  priorities: { comp:RPriorities, span:12, label:'Priorities',   ar:'الأولويات',       icon:'rocket' },
  brand:      { comp:RBrandBlock, span:6,  label:'Followers',    ar:'المتابعون',        icon:'users' },
  text:       { comp:RText,       span:12, label:'Text / insights', ar:'نص / رؤى',     icon:'file' },
  image:      { comp:RImage,      span:12, label:'Image',        ar:'صورة',            icon:'image' },
};

/* ---------- Slide chrome ------------------------------------------------ */
function RSlideHeader({ def, slide, theme }){
  const lang = theme.lang;
  const periodStr = def.scope.start ?
    rDateLabel(def.scope.start,lang) + ' – ' + rDateLabel(def.scope.end,lang) : '';
  return (
    <div style={{display:'flex',alignItems:'center',gap:16,marginBottom:22}}>
      <div style={{width:6,height:38,borderRadius:99,background:theme.main,flexShrink:0}}/>
      <div style={{minWidth:0}}>
        <div style={{fontSize:29,fontWeight:800,color:'#22221e',letterSpacing:-0.4,lineHeight:1.1}}>{slide.title}</div>
        {periodStr && <div style={{fontSize:12.5,color:'#9a9a90',marginTop:3}}><RNum>{periodStr}</RNum></div>}
      </div>
      <div style={{marginInlineStart:'auto',flexShrink:0,display:'flex',alignItems:'center',gap:12}}>
        {slide.platform && <RPlatLogo platform={slide.platform} size={30}/>}
        {theme.logo && <img src={theme.logo} style={{height:40,maxWidth:140,objectFit:'contain'}}/>}
      </div>
    </div>
  );
}

/* Branded (cover / divider / thank_you) slide surface. Kits without a
   background image get brand-derived geometry instead of a flat fill. */
function RBrandedSurface({ theme, children }){
  const bg = rBrandedBg(theme);
  return (
    <div style={{position:'absolute',inset:0,
      background: bg ? theme.main
        : `linear-gradient(135deg, ${theme.main} 0%, ${rMix(theme.main,'#000000',0.32)} 100%)`,
      color: theme.onMain, overflow:'hidden'}}>
      {bg && <img src={bg} style={{position:'absolute',inset:0,width:'100%',height:'100%',objectFit:'cover'}}/>}
      {!bg && (
        <React.Fragment>
          <div style={{position:'absolute',width:620,height:620,borderRadius:'50%',
            background:theme.secondary,opacity:0.14,insetInlineEnd:-200,top:-220}}/>
          <div style={{position:'absolute',width:430,height:430,borderRadius:'50%',
            border:`2.5px solid ${theme.tertiary}`,opacity:0.4,insetInlineEnd:90,bottom:-170}}/>
          <div style={{position:'absolute',width:180,height:180,borderRadius:'50%',
            background:theme.tertiary,opacity:0.18,insetInlineEnd:330,top:110}}/>
        </React.Fragment>
      )}
      <div style={{position:'relative',width:'100%',height:'100%'}}>{children}</div>
    </div>
  );
}

/* Agency credit ("Prepared by FLNT") — separate from the client brand. */
function RAgencyCredit({ def, size, center }){
  const a = def.agency;
  if (!a || !a.enabled) return null;
  return (
    <div style={{display:'flex',alignItems:'center',gap:8,opacity:0.85,
      justifyContent:center?'center':undefined,fontSize:size||13,fontWeight:500}}>
      <span>Prepared by</span>
      {a.logo && <img src={a.logo} style={{height:(size||13)*1.5,maxWidth:110,objectFit:'contain'}}/>}
      <span style={{fontWeight:700,letterSpacing:0.5}}>{a.name || 'FLNT'}</span>
    </div>
  );
}

function RSlide({ def, slide, ds, theme, currency, editable, selectedBlockId, onSelectBlock, onBinding }){
  const lang = theme.lang;
  const middle = def.slides.filter(s=>!['cover','agenda','thank_you'].includes(s.type));
  const base = {
    width:R_SLIDE_W, height:R_SLIDE_H, background:'#fbfbf9', position:'relative',
    fontFamily: theme.font, overflow:'hidden', flexShrink:0,
  };
  const pad = { position:'absolute', inset:0, padding:'40px 52px 50px', display:'flex', flexDirection:'column' };
  const periodStr = def.scope.start ?
    rDateLabel(def.scope.start,lang) + ' – ' + rDateLabel(def.scope.end,lang) : '';

  let content = null;
  if (slide.type === 'cover'){
    content = (
      <RBrandedSurface theme={theme}>
        <div style={{...pad, justifyContent:'center'}}>
          {theme.logoDark && <img src={theme.logoDark}
            style={{height:96,alignSelf:theme.rtl?'flex-start':'flex-start',objectFit:'contain',marginBottom:40, maxWidth:320}}/>}
          <div style={{fontSize:18,fontWeight:700,opacity:0.8,letterSpacing:2,textTransform:'uppercase'}}>
            {def.brandName || ''}
          </div>
          <div style={{fontSize:66,fontWeight:800,lineHeight:1.1,letterSpacing:-1,maxWidth:760,marginTop:12}}>
            {slide.title || rStr('performance_report', lang)}
          </div>
          {periodStr && <div style={{fontSize:20,opacity:0.9,marginTop:22,fontWeight:500}}>
            {rStr('exec_summary',lang)} — <RNum>{periodStr}</RNum>
          </div>}
          <div style={{width:96,height:6,borderRadius:99,background:theme.secondary,marginTop:30}}/>
          <div style={{position:'absolute',bottom:40,insetInlineStart:52}}>
            <RAgencyCredit def={def} size={13}/>
          </div>
        </div>
      </RBrandedSurface>
    );
  } else if (slide.type === 'thank_you'){
    content = (
      <RBrandedSurface theme={theme}>
        <div style={{...pad, alignItems:'center', justifyContent:'center', textAlign:'center'}}>
          {theme.logoDark && <img src={theme.logoDark} style={{height:112,objectFit:'contain',marginBottom:36,maxWidth:360}}/>}
          <div style={{fontSize:60,fontWeight:800,letterSpacing:-0.8}}>{slide.title || rStr('thank_you',lang)}</div>
          {def.brandName && <div style={{fontSize:19,opacity:0.85,marginTop:18,fontWeight:500}}>{def.brandName}</div>}
          <div style={{marginTop:26}}><RAgencyCredit def={def} size={13} center/></div>
        </div>
      </RBrandedSurface>
    );
  } else if (slide.type === 'divider'){
    const dividers = def.slides.filter(s=>s.type==='divider');
    const secNum = String(dividers.indexOf(slide)+1).padStart(2,'0');
    content = (
      <RBrandedSurface theme={theme}>
        {/* oversized section-number watermark */}
        <div style={{position:'absolute',insetInlineEnd:30,bottom:-72,fontSize:300,fontWeight:800,
          lineHeight:1,opacity:0.09,letterSpacing:-10,userSelect:'none'}} dir="ltr">{secNum}</div>
        <div style={{...pad, justifyContent:'center'}}>
          {slide.platform && (
            <span style={{width:96,height:96,borderRadius:26,background:'rgba(255,255,255,.92)',
              display:'grid',placeItems:'center',marginBottom:28,
              boxShadow:'0 4px 18px rgba(0,0,0,.25)'}}>
              <RPlatLogo platform={slide.platform} size={62}/>
            </span>
          )}
          <div style={{fontSize:15,fontWeight:700,opacity:0.65,letterSpacing:3,marginBottom:10}}
            dir="ltr">{(theme.rtl?'':'SECTION ')}{secNum}</div>
          <div style={{fontSize:58,fontWeight:800,letterSpacing:-1}}>{slide.title}</div>
          <div style={{width:100,height:6,borderRadius:99,background:theme.secondary,marginTop:26}}/>
        </div>
      </RBrandedSurface>
    );
  } else if (slide.type === 'agenda'){
    content = (
      <div style={pad}>
        <RSlideHeader def={def} slide={{...slide, title: slide.title || rStr('agenda',lang)}} theme={theme}/>
        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'12px 52px',marginTop:10,
          alignContent: middle.length<=10 ? 'space-evenly' : 'start', flex:1}}>
          {middle.map((s,i)=>(
            <div key={s.id} style={{display:'flex',alignItems:'center',gap:16,padding:'13px 0',
              borderBottom:'1px solid #ecece6'}}>
              <span style={{width:38,height:38,borderRadius:11,background:theme.main,flexShrink:0,
                color:theme.onMain,display:'grid',placeItems:'center',fontWeight:800,fontSize:15.5}}>
                <RNum>{String(i+1).padStart(2,'0')}</RNum>
              </span>
              {s.type==='divider' && s.platform &&
                <span style={{display:'inline-flex',flexShrink:0}}>
                  <RPlatLogo platform={s.platform} size={20}/></span>}
              <span style={{fontSize:17.5,fontWeight:600,color:'#33332e',overflow:'hidden',
                textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
                {s.type==='divider' ? (lang==='ar'?'قسم: ':'Section: ') + s.title : s.title}
              </span>
            </div>
          ))}
        </div>
      </div>
    );
  } else {
    /* exec_summary + content: block grid — a single block stretches to fill
       the slide (no dead space); multiple blocks distribute evenly. */
    const blocksArr = slide.blocks || [];
    const solo = blocksArr.length === 1;
    content = (
      <div style={pad}>
        <RSlideHeader def={def} slide={slide} theme={theme}/>
        <div style={{display:'grid',gridTemplateColumns:'repeat(12,1fr)',gap:16,flex:1,
          alignContent: solo ? 'stretch' : 'space-evenly', overflow:'hidden'}}>
          {blocksArr.map(blk=>{
            const meta = R_BLOCKS[blk.type];
            if (!meta) return null;
            const Comp = meta.comp;
            const span = (blk.options && blk.options.span) || meta.span;
            const sel = editable && selectedBlockId === blk.id;
            return (
              <div key={blk.id} onClick={editable ? (e)=>{ e.stopPropagation(); onSelectBlock && onSelectBlock(blk.id); } : undefined}
                style={{gridColumn:`span ${span}`, minWidth:0, position:'relative',
                  borderRadius:12, outline: sel ? `2px solid ${theme.tertiary}` : 'none',
                  outlineOffset:3, cursor: editable?'pointer':'default'}}>
                <Comp block={blk} ds={ds} def={def} theme={theme} currency={currency} lang={lang}
                  editable={editable} tall={solo}
                  onBinding={editable && onBinding ? (patch)=>onBinding(blk.id, patch) : undefined}/>
              </div>
            );
          })}
          {(!slide.blocks || !slide.blocks.length) &&
            <div style={{gridColumn:'span 12'}}>
              <REmpty lang={lang} note={lang==='ar'?'أسحب عنصراً من مكتبة العناصر':'Add a block from the block library'}/>
            </div>}
        </div>
      </div>
    );
  }

  const light = !['cover','thank_you','divider'].includes(slide.type);
  return (
    <div className="r-slide" data-slide-id={slide.id} dir={theme.rtl?'rtl':'ltr'} style={base}>
      {/* brand accent strip across the top of light slides */}
      {light && <div style={{position:'absolute',top:0,left:0,right:0,height:5,zIndex:2,
        background:`linear-gradient(90deg, ${theme.main} 0%, ${theme.tertiary} 60%, ${theme.secondary} 100%)`}}/>}
      {content}
      {/* footer: brand · agency credit · page number */}
      {light && (
        <div style={{position:'absolute',bottom:0,left:0,right:0,zIndex:2,
          padding:'9px 52px',display:'flex',alignItems:'center',gap:12,
          borderTop:'1px solid #ecece6',fontSize:10.5,color:'#a3a39a',background:'rgba(251,251,249,.92)'}}>
          <span style={{fontWeight:600}}>{def.brandName}</span>
          <span style={{marginInlineStart:'auto',display:'flex',alignItems:'center',gap:6}}>
            {def.agency && def.agency.enabled && (
              <span style={{display:'flex',alignItems:'center',gap:5}} dir="ltr">
                Prepared by
                {def.agency.logo && <img src={def.agency.logo} style={{height:13,maxWidth:70,objectFit:'contain'}}/>}
                <b>{def.agency.name || 'FLNT'}</b>
                <span style={{opacity:0.5}}>·</span>
              </span>
            )}
            <RNum>{def.slides.indexOf(slide)+1} / {def.slides.length}</RNum>
          </span>
        </div>
      )}
    </div>
  );
}

/* Scaled preview wrapper: renders the 1280×720 slide at any width. */
function RSlideScaled({ width, children }){
  const scale = width / R_SLIDE_W;
  return (
    <div style={{width, height: R_SLIDE_H*scale, overflow:'hidden', position:'relative',
      borderRadius:10, boxShadow:'0 1px 4px rgba(0,0,0,.12)'}}>
      <div style={{transform:`scale(${scale})`, transformOrigin:'top left', width:R_SLIDE_W, height:R_SLIDE_H}}>
        {children}
      </div>
    </div>
  );
}

Object.assign(window, {
  R_SLIDE_W, R_SLIDE_H, R_BLOCKS, rTheme, rBrandedBg,
  RSlide, RSlideScaled, RDeltaChip, REmpty, RPlatLogo,
});
