/* Report Builder — metric registry, aggregation, i18n + color engine =====
   THE single source of truth for every number a report shows or exports.
   Iron rule: ratio metrics are NEVER averaged — always recomputed from
   summed numerators/denominators at the displayed aggregation level.
   Real data only: everything derives from /api/report/dataset sums.
   (See REPORT_BUILDER_SPEC.md. Do not use metrics.jsx fullMetrics/buildAds
   here — those are synthetic-era generators.)
======================================================================== */

/* ---------- Platforms (report ordering is a locked product decision) --- */
const REPORT_PLATFORMS = [
  { id:'meta',     label:'Meta',          ar:'ميتا',           icon:'meta',     tone:'#2f7bff' },
  { id:'google',   label:'Google Ads',    ar:'إعلانات جوجل',   icon:'google',   tone:'#34A853' },
  { id:'tiktok',   label:'TikTok Ads',    ar:'تيك توك',        icon:'tiktok',   tone:'#111111' },
  { id:'snapchat', label:'Snapchat Ads',  ar:'سناب شات',       icon:'snapchat', tone:'#F7C700' },
  { id:'x',        label:'X Ads',         ar:'منصة إكس',       icon:'x',        tone:'#111111' },
  { id:'linkedin', label:'LinkedIn Ads',  ar:'لينكد إن',       icon:'linkedin', tone:'#0A66C2' },
];
const REPORT_PLATFORM_ORDER = REPORT_PLATFORMS.map(p=>p.id);
const rPlatform = (id)=> REPORT_PLATFORMS.find(p=>p.id===id) ||
  { id, label:id, ar:id, icon:'custom', tone:'#8b8b80' };
/* Official platform logo PNGs (user-supplied, report_assets/platforms/).
   Used on slides + exports; the SVG BrandIcon set stays as fallback. */
const R_PLATFORM_LOGOS = {
  meta:'report_assets/platforms/meta.png',
  google:'report_assets/platforms/google.png',
  tiktok:'report_assets/platforms/tiktok.png',
  snapchat:'report_assets/platforms/snapchat.png',
  x:'report_assets/platforms/x.png',
  linkedin:'report_assets/platforms/linkedin.png',
  facebook:'report_assets/platforms/facebook.png',
  instagram:'report_assets/platforms/instagram.png',
  whatsapp:'report_assets/platforms/whatsapp.png',
  youtube:'report_assets/platforms/youtube.png',
};
const rPlatformLogo = (id)=> R_PLATFORM_LOGOS[id] || null;
/* Sort any platform list into the locked order (unknowns last, alphabetical) */
function rSortPlatforms(ids){
  return [...ids].sort((a,b)=>{
    const ia=REPORT_PLATFORM_ORDER.indexOf(a), ib=REPORT_PLATFORM_ORDER.indexOf(b);
    return (ia<0?99:ia)-(ib<0?99:ib) || String(a).localeCompare(String(b));
  });
}

/* ---------- Metric registry ------------------------------------------- */
/* agg: 'sum' | 'last' | {num, den, mult}  (ratio: sum(num)/sum(den)*mult)
   fmt: currency | currency2 | pct | x | int | freq
   good: 'up' | 'down' | null (no judgement)
   tier: 1 core (all platforms) | 2 platform-specific | 3 exec/brand
   platforms: undefined = all */
const REPORT_METRICS = [
  { key:'spend',        label:'Spend',            ar:'التكلفة الإجمالية', fmt:'currency',  agg:'sum', good:null,  tier:1 },
  { key:'impressions',  label:'Impressions',      ar:'الظهور',            fmt:'int',       agg:'sum', good:'up',  tier:1 },
  { key:'reach',        label:'Reach',            ar:'الوصول',            fmt:'int',       agg:'sum', good:'up',  tier:1,
    note:'Summed daily reach — may overcount unique users across days.' },
  { key:'clicks',       label:'Clicks',           ar:'النقرات',           fmt:'int',       agg:'sum', good:'up',  tier:1 },
  { key:'link_clicks',  label:'Link clicks',      ar:'نقرات الرابط',      fmt:'int',       agg:'sum', good:'up',  tier:2 },
  { key:'conversions',  label:'Results',          ar:'النتائج',           fmt:'int',       agg:'sum', good:'up',  tier:1 },
  { key:'revenue',      label:'Conv. value',      ar:'قيمة التحويلات',    fmt:'currency',  agg:'sum', good:'up',  tier:1 },
  { key:'video_views',  label:'Video views',      ar:'مشاهدات الفيديو',   fmt:'int',       agg:'sum', good:'up',  tier:2 },
  { key:'engagements',  label:'Engagements',      ar:'التفاعلات',         fmt:'int',       agg:'sum', good:'up',  tier:2 },
  { key:'leads',        label:'Leads',            ar:'العملاء المحتملون', fmt:'int',       agg:'sum', good:'up',  tier:2 },
  /* Ratios — recomputed, never averaged */
  { key:'ctr',          label:'CTR',              ar:'نسبة النقر',        fmt:'pct',       agg:{num:'clicks',den:'impressions',mult:100}, good:'up',   tier:1 },
  { key:'cpc',          label:'CPC',              ar:'متوسط تكلفة النقرة',fmt:'currency2', agg:{num:'spend',den:'clicks'},                good:'down', tier:1 },
  { key:'cpm',          label:'CPM',              ar:'تكلفة الألف ظهور',  fmt:'currency2', agg:{num:'spend',den:'impressions',mult:1000}, good:'down', tier:1 },
  { key:'cpa',          label:'Cost / result',    ar:'تكلفة النتيجة',     fmt:'currency2', agg:{num:'spend',den:'conversions'},           good:'down', tier:1 },
  { key:'cvr',          label:'Conv. rate',       ar:'معدل التحويل',      fmt:'pct',       agg:{num:'conversions',den:'clicks',mult:100}, good:'up',   tier:1 },
  { key:'roas',         label:'ROAS',             ar:'العائد على الإنفاق',fmt:'x',         agg:{num:'revenue',den:'spend'},               good:'up',   tier:1 },
  { key:'frequency',    label:'Frequency',        ar:'التكرار',           fmt:'freq',      agg:{num:'impressions',den:'reach'},           good:null,   tier:1 },
  { key:'cpl',          label:'Cost / lead',      ar:'تكلفة العميل المحتمل', fmt:'currency2', agg:{num:'spend',den:'leads'},              good:'down', tier:2 },
  { key:'eng_rate',     label:'Engagement rate',  ar:'معدل التفاعل',      fmt:'pct',       agg:{num:'engagements',den:'impressions',mult:100}, good:'up', tier:2 },
  { key:'cpv',          label:'Cost / video view',ar:'تكلفة المشاهدة',    fmt:'currency2', agg:{num:'spend',den:'video_views'},           good:'down', tier:2 },
  /* Brand (exec summary) */
  { key:'followers_gained', label:'Followers gained', ar:'متابعون جدد',   fmt:'int',       agg:'sum',  good:'up', tier:3 },
  { key:'followers',        label:'Total followers',  ar:'إجمالي المتابعين', fmt:'int',   agg:'last', good:'up', tier:3 },
];
const REPORT_BASE_KEYS = REPORT_METRICS.filter(m=>m.agg==='sum' && m.tier!==3).map(m=>m.key);
let _customMetrics = [];   // injected per account at runtime
let _blockedKeys  = [];    // per-account blocklist (e.g. Al Munawarah: roas)

function rMetric(key){
  return REPORT_METRICS.find(m=>m.key===key) || _customMetrics.find(m=>m.key===key) || null;
}
/* Pickers/lists exclude account-blocked metrics (set via rLoadCustomMetrics). */
function rAllMetrics(){
  return [...REPORT_METRICS, ..._customMetrics].filter(m=>!_blockedKeys.includes(m.key));
}
function rMetricBlockedFor(accountId, key){
  const bl = (window.ACCOUNT_METRIC_BLOCKLIST || {})[accountId];
  return !!bl && bl.includes(String(key||'').toLowerCase());
}
/* Executive Summary default scorecards (product decision 2026-07-10):
   Reach, Impressions, Cost/Result, Clicks, CPC, CTR, Total Followers, Video views. */
function rDefaultExecMetrics(accountId){
  return ['reach','impressions','cpa','clicks','cpc','ctr','followers','video_views']
    .filter(k=>!rMetricBlockedFor(accountId, k));
}

/* Custom metrics: {key,label,ar?,fmt,formula} — formula over base/derived keys,
   e.g. "spend / engagements". Stored per account (localStorage; columnBuilder pattern). */
function rLoadCustomMetrics(accountId){
  _blockedKeys = ((window.ACCOUNT_METRIC_BLOCKLIST || {})[accountId] || []).map(k=>String(k).toLowerCase());
  try {
    const raw = JSON.parse(localStorage.getItem('relay_report_custom_'+accountId) || '[]');
    _customMetrics = (Array.isArray(raw)?raw:[]).filter(m=>m && m.key && m.formula).map(m=>({
      key:String(m.key), label:String(m.label||m.key), ar:m.ar||m.label||m.key,
      fmt:['currency','currency2','pct','x','int'].includes(m.fmt)?m.fmt:'currency2',
      agg:{formula:String(m.formula)}, good:m.good==='down'?'down':(m.good==='up'?'up':null),
      tier:2, custom:true,
    }));
  } catch(e){ _customMetrics = []; }
  return _customMetrics;
}
function rSaveCustomMetric(accountId, m){
  const list = rLoadCustomMetrics(accountId).filter(x=>x.key!==m.key);
  list.push(m);
  localStorage.setItem('relay_report_custom_'+accountId,
    JSON.stringify(list.map(x=>({key:x.key,label:x.label,ar:x.ar,fmt:x.fmt,formula:x.agg.formula,good:x.good}))));
  return rLoadCustomMetrics(accountId);
}

/* ---------- Aggregation ------------------------------------------------ */
const rZeroSums = ()=> Object.fromEntries(REPORT_BASE_KEYS.map(k=>[k,0]));
/* Sum base metric fields across an array of fact rows/objects. */
function rSum(rows){
  const out = rZeroSums();
  (rows||[]).forEach(r=>{ REPORT_BASE_KEYS.forEach(k=>{ out[k] += Number(r && r[k]) || 0; }); });
  return out;
}
/* Safe formula evaluator for custom metrics (numbers + + - * / ( ) only). */
function rEvalFormula(formula, sums){
  const expr = String(formula).replace(/[a-z_][a-z0-9_]*/gi, (name)=>{
    const v = rMetricValue(sums, name);
    return Number.isFinite(v) ? String(v) : '0';
  });
  if (!/^[-+*/().\d\s]+$/.test(expr)) return 0;
  try { const v = Function('"use strict";return (' + expr + ')')(); return Number.isFinite(v)?v:0; }
  catch(e){ return 0; }
}
/* Value of ANY metric key from a base-sums object. Ratios recomputed here. */
function rMetricValue(sums, key){
  if (!sums) return 0;
  if (key in sums && typeof sums[key] === 'number') return sums[key];
  const m = rMetric(key);
  if (!m) return 0;
  if (m.agg === 'sum' || m.agg === 'last') return Number(sums[key]) || 0;
  if (m.agg.formula) return rEvalFormula(m.agg.formula, sums);
  const num = Number(sums[m.agg.num]) || 0, den = Number(sums[m.agg.den]) || 0;
  if (den <= 0) return 0;
  return num / den * (m.agg.mult || 1);
}
/* % delta + tone. good-direction aware (CPA down = positive). */
function rDelta(cur, prev, key){
  const m = rMetric(key);
  if (!Number.isFinite(cur) || !Number.isFinite(prev) || prev === 0) return null;
  const pct = (cur - prev) / Math.abs(prev) * 100;
  const good = m && m.good ? m.good : null;
  const tone = !good || Math.abs(pct) < 0.05 ? 'neutral'
    : ((pct > 0) === (good === 'up') ? 'good' : 'bad');
  return { pct, up: pct > 0, tone };
}

/* ---------- Dataset selectors (input = /api/report/dataset payload) ---- */
function rDatasetPlatforms(ds){
  const set = new Set();
  (ds && ds.daily || []).forEach(r=>set.add(r.platform));
  (ds && ds.entities && ds.entities.campaigns || []).forEach(c=>set.add(c.platform));
  return rSortPlatforms([...set]);
}
/* Totals over the whole scope (or one platform), cur + prev. */
function rTotals(ds, platform){
  const pick = (rows)=> platform ? rows.filter(r=>r.platform===platform) : rows;
  return {
    cur:  rSum(pick(ds.daily || [])),
    prev: rSum(pick(ds.dailyCompare || [])),
  };
}
/* Per-platform totals in locked order: [{platform, cur, prev}] */
function rByPlatform(ds){
  return rDatasetPlatforms(ds).map(p=>({ platform:p, ...rTotals(ds, p) }));
}
/* Entity rows for a level with attached cur/prev, filter/sort/topN.
   opts: {platform, sortKey, sortDir, topN, filters:[{key,op,value}]} */
function rEntityRows(ds, level, opts){
  opts = opts || {};
  const src = level==='adset' ? (ds.entities.adsets||[])
    : level==='ad' ? (ds.entities.ads||[]) : (ds.entities.campaigns||[]);
  let rows = src.filter(e=> (Number(e.cur && e.cur.spend)||0) > 0 ||
                            (Number(e.cur && e.cur.impressions)||0) > 0);
  if (opts.platform) rows = rows.filter(e=>e.platform===opts.platform);
  (opts.filters||[]).forEach(f=>{
    if (!f || !f.key) return;
    rows = rows.filter(e=>{
      const v = rMetricValue(e.cur, f.key);
      const t = Number(f.value);
      if (f.op==='gte') return v >= t;
      if (f.op==='lte') return v <= t;
      if (f.op==='contains') return String(e.name||'').toLowerCase().includes(String(f.value).toLowerCase());
      return true;
    });
  });
  const sk = opts.sortKey || 'spend', dir = opts.sortDir === 'asc' ? 1 : -1;
  rows = [...rows].sort((a,b)=> dir * (rMetricValue(a.cur, sk) - rMetricValue(b.cur, sk)));
  if (opts.topN && rows.length > opts.topN) {
    const top = rows.slice(0, opts.topN);
    const rest = rows.slice(opts.topN);
    const others = { id:'__others', name:`Others (${rest.length})`, platform:opts.platform||'',
      cur:rSum(rest.map(r=>r.cur)), prev:rSum(rest.map(r=>r.prev)), _others:true };
    return { rows: top, others, total: rows.length };
  }
  return { rows, others:null, total: rows.length };
}
/* Daily time series of metric keys (whole scope or one platform).
   Returns [{date, label, <key>: value, _cmp_<key>: comparisonValue}] —
   comparison rows aligned by index (day 1 ↔ day 1). */
function rSeries(ds, keys, platform){
  const group = (rows)=>{
    const by = {};
    (rows||[]).forEach(r=>{
      if (platform && r.platform !== platform) return;
      (by[r.date] = by[r.date] || []).push(r);
    });
    return Object.keys(by).sort().map(d=>{
      const sums = rSum(by[d]);
      const o = { date:d };
      keys.forEach(k=>{ o[k] = +rMetricValue(sums,k); });
      return o;
    });
  };
  const cur = group(ds.daily), prev = group(ds.dailyCompare);
  return cur.map((r,i)=>{
    const o = { ...r, label: new Date(r.date+'T00:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric'}) };
    keys.forEach(k=>{ o['_cmp_'+k] = prev[i] ? prev[i][k] : null; });
    return o;
  });
}
/* Followers: gained per platform in-range + latest totals. */
function rBrand(ds){
  const gained = {};
  (ds.brand && ds.brand.rows || []).forEach(r=>{
    if (ds.range && (r.date < ds.range.start || r.date > ds.range.end)) return;
    gained[r.platform] = (gained[r.platform]||0) + (Number(r.followers_gained)||0);
  });
  const latest = (ds.brand && ds.brand.latest) || {};
  const platforms = rSortPlatforms([...new Set([...Object.keys(gained), ...Object.keys(latest)])]);
  return {
    platforms,
    gained,
    latest,
    totalGained: Object.values(gained).reduce((a,b)=>a+b,0),
    totalFollowers: Object.values(latest).reduce((a,b)=>a+(Number(b)||0),0),
    hasData: platforms.length > 0,
  };
}

/* ---------- Formatting (report layer — NEVER data.jsx's implicit $) ---- */
const R_AR_DIGITS = false;   // Western digits (matches client's own deck) — v1.5 option
function rFmt(v, fmtKind, currency, lang){
  if (v === null || v === undefined || Number.isNaN(v)) return '—';
  const n = Number(v);
  const money = (dec)=>{
    const s = n.toLocaleString('en-US',{minimumFractionDigits:dec,maximumFractionDigits:dec});
    if (lang==='ar' && (currency||'').toUpperCase()==='SAR') return s + ' ريال';
    const sym = { USD:'$', EUR:'€', GBP:'£' }[(currency||'USD').toUpperCase()];
    return sym ? sym + s : (currency||'USD').toUpperCase() + ' ' + s;
  };
  switch (fmtKind){
    case 'currency':  return money(0);
    case 'currency2': return money(2);
    case 'pct':       return n.toFixed(2) + '%';
    case 'x':         return n.toFixed(2) + '×';
    case 'freq':      return n.toFixed(2);
    case 'int':       return Math.round(n).toLocaleString('en-US');
    default:          return String(v);
  }
}
/* Compact display for scorecards: 930K / 1.2M — Arabic unit words for ar. */
function rCompact(v, fmtKind, currency, lang){
  const n = Number(v)||0;
  const abs = Math.abs(n);
  const unit = abs>=1e6 ? [1e6, lang==='ar'?' مليون':'M'] : abs>=1e4 ? [1e3, lang==='ar'?' ألف':'K'] : null;
  const isMoney = fmtKind==='currency'||fmtKind==='currency2';
  if (!unit) return rFmt(v, fmtKind, currency, lang);
  const num = (n/unit[0]).toLocaleString('en-US',{maximumFractionDigits:1});
  const core = num + unit[1];
  if (!isMoney) return core;
  if (lang==='ar' && (currency||'').toUpperCase()==='SAR') return core + ' ريال';
  const sym = { USD:'$', EUR:'€', GBP:'£' }[(currency||'USD').toUpperCase()];
  return sym ? sym + core : (currency||'USD').toUpperCase() + ' ' + core;
}
function rMetricLabel(key, lang){
  const m = rMetric(key);
  if (!m) return key;
  if (lang === 'both') return (m.ar || m.label) + ' · ' + m.label;
  return lang==='ar' ? (m.ar || m.label) : m.label;
}

/* ---------- Static label i18n (RTL-lite v1) ---------------------------- */
const REPORT_STR = {
  agenda:            { en:'Agenda',                ar:'جدول المحتوى' },
  exec_summary:      { en:'Executive Summary',     ar:'الملخص التنفيذي' },
  kpis:              { en:'Key Performance Indicators', ar:'المؤشرات الرئيسية للأداء' },
  thank_you:         { en:'Thank You',             ar:'شكراً لكم' },
  prepared_by:       { en:'Prepared by',           ar:'إعداد' },
  period:            { en:'Reporting period',      ar:'الفترة' },
  performance_report:{ en:'Performance Report',    ar:'تقرير الأداء' },
  campaigns:         { en:'Campaigns',             ar:'الحملات' },
  adsets:            { en:'Ad Sets',               ar:'المجموعات الإعلانية' },
  ads:               { en:'Ads',                   ar:'الإعلانات' },
  vs_prev:           { en:'vs previous period',    ar:'مقارنة بالفترة السابقة' },
  spend_share:       { en:'Spend share',           ar:'توزيع الإنفاق' },
  followers_by_platform:{ en:'Followers gained',   ar:'متابعون جدد' },
  total_followers:   { en:'Combined followers',    ar:'إجمالي المتابعين' },
  others:            { en:'Others',                ar:'أخرى' },
  no_data:           { en:'No data in this range', ar:'لا توجد بيانات في هذه الفترة' },
  next_steps:        { en:'Key Priorities & Next Steps', ar:'الأولويات والخطوات القادمة' },
  highlights:        { en:'Highlights',            ar:'أبرز النتائج' },
  overview:          { en:'Account Overview',      ar:'نظرة عامة على الحساب' },
  trends:            { en:'Performance Trends',    ar:'اتجاهات الأداء' },
  daily:             { en:'Daily performance',     ar:'الأداء اليومي' },
  top_campaigns:     { en:'Top campaigns',         ar:'أفضل الحملات' },
};
/* lang: 'en' | 'ar' | 'both' (bilingual: Arabic first, then English) */
function rStr(key, lang){
  const s = REPORT_STR[key];
  if (!s) return key;
  if (lang === 'both') return s.ar + ' · ' + s.en;
  return lang === 'ar' ? s.ar : s.en;
}
function rDateLabel(iso, lang){
  const d = new Date(iso + 'T00:00:00');
  const ar = lang === 'ar' || lang === 'both';
  return d.toLocaleDateString(ar ? 'ar-SA-u-ca-gregory-nu-latn' : 'en-US',
    { year:'numeric', month:'long', day:'numeric' });
}

/* ---------- Color engine (contrast + tonal ramp) ----------------------- */
function rHexRgb(hex){
  const h = String(hex||'').replace('#','');
  if (h.length < 6) return [0,0,0];
  return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
}
function rRgbHex(r,g,b){
  const c = (x)=> Math.max(0,Math.min(255,Math.round(x))).toString(16).padStart(2,'0');
  return '#' + c(r) + c(g) + c(b);
}
/* WCAG relative luminance → pick readable text color over any background. */
function rLuminance(hex){
  const [r,g,b] = rHexRgb(hex).map(v=>{
    const s = v/255;
    return s <= 0.03928 ? s/12.92 : Math.pow((s+0.055)/1.055, 2.4);
  });
  return 0.2126*r + 0.7152*g + 0.0722*b;
}
function rContrastText(bgHex){ return rLuminance(bgHex) > 0.35 ? '#1a1a17' : '#ffffff'; }
function rMix(hexA, hexB, t){
  const a = rHexRgb(hexA), b = rHexRgb(hexB);
  return rRgbHex(a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t, a[2]+(b[2]-a[2])*t);
}
/* Expand 3 brand seeds into an n-series chart palette (tints/shades). */
function rRamp(seeds, n){
  const base = (seeds && seeds.length ? seeds : ['#233433','#45464B','#488D82']).slice(0,3);
  const out = [...base];
  let i = 0;
  while (out.length < (n||8)){
    const seed = base[i % base.length];
    const step = Math.floor(i / base.length) + 1;
    out.push(step % 2 === 1 ? rMix(seed, '#ffffff', Math.min(0.25*step, 0.7))
                            : rMix(seed, '#000000', Math.min(0.18*step, 0.6)));
    i++;
  }
  return out.slice(0, n||8);
}

/* ---------- Rule-based auto-insights (editable text seeds) ------------- */
function rAutoInsights(ds, lang, currency){
  const out = [];
  /* bilingual: push the Arabic bullet, then its English twin */
  const L = (en, ar)=> lang==='both' ? (out.push(ar), en) : (lang==='ar' ? ar : en);
  const t = rTotals(ds);
  const spend = rMetricValue(t.cur,'spend');
  if (spend <= 0) return out;
  const d = rDelta(spend, rMetricValue(t.prev,'spend'), 'spend');
  if (d) out.push(L(
    `Total spend ${d.up?'increased':'decreased'} ${Math.abs(d.pct).toFixed(1)}% vs the previous period (${rFmt(spend,'currency',currency,lang)}).`,
    `${d.up?'ارتفع':'انخفض'} إجمالي الإنفاق بنسبة ${Math.abs(d.pct).toFixed(1)}٪ مقارنة بالفترة السابقة (${rFmt(spend,'currency',currency,lang)}).`));
  /* biggest platform mover by spend share */
  const plats = rByPlatform(ds);
  if (plats.length > 1){
    let best = null;
    plats.forEach(p=>{
      const dd = rDelta(rMetricValue(p.cur,'spend'), rMetricValue(p.prev,'spend'), 'spend');
      if (dd && (!best || Math.abs(dd.pct) > Math.abs(best.d.pct))) best = { p, d: dd };
    });
    if (best && Math.abs(best.d.pct) >= 10){
      const nm = lang==='ar' ? rPlatform(best.p.platform).ar : rPlatform(best.p.platform).label;
      out.push(L(
        `${nm} was the biggest mover: spend ${best.d.up?'up':'down'} ${Math.abs(best.d.pct).toFixed(0)}% period-over-period.`,
        `${nm} سجلت أكبر تغيّر: الإنفاق ${best.d.up?'ارتفع':'انخفض'} بنسبة ${Math.abs(best.d.pct).toFixed(0)}٪ مقارنة بالفترة السابقة.`));
    }
  }
  /* CPA outliers among campaigns with meaningful spend */
  const { rows } = rEntityRows(ds, 'campaign', {});
  const eligible = rows.filter(r=> rMetricValue(r.cur,'spend') >= Math.max(spend*0.05, 100)
    && rMetricValue(r.cur,'conversions') > 0);
  if (eligible.length >= 2){
    const byCpa = [...eligible].sort((a,b)=> rMetricValue(a.cur,'cpa') - rMetricValue(b.cur,'cpa'));
    const bestC = byCpa[0], worstC = byCpa[byCpa.length-1];
    out.push(L(
      `Best cost-efficiency: “${bestC.name}” at ${rFmt(rMetricValue(bestC.cur,'cpa'),'currency2',currency,lang)} per result; “${worstC.name}” is the most expensive at ${rFmt(rMetricValue(worstC.cur,'cpa'),'currency2',currency,lang)}.`,
      `أفضل كفاءة تكلفة: «${bestC.name}» بتكلفة ${rFmt(rMetricValue(bestC.cur,'cpa'),'currency2',currency,lang)} لكل نتيجة؛ بينما «${worstC.name}» الأعلى تكلفة عند ${rFmt(rMetricValue(worstC.cur,'cpa'),'currency2',currency,lang)}.`));
  }
  const ctr = rMetricValue(t.cur,'ctr'), pctr = rMetricValue(t.prev,'ctr');
  const dctr = rDelta(ctr, pctr, 'ctr');
  if (dctr && Math.abs(dctr.pct) >= 5) out.push(L(
    `Blended CTR ${dctr.up?'improved to':'declined to'} ${ctr.toFixed(2)}% (${dctr.up?'+':''}${dctr.pct.toFixed(1)}%).`,
    `نسبة النقر الإجمالية ${dctr.up?'تحسنت إلى':'تراجعت إلى'} ${ctr.toFixed(2)}٪ (${dctr.up?'+':''}${dctr.pct.toFixed(1)}٪).`));
  return out.slice(0, lang==='both' ? 8 : 4);
}

/* ---------- Performance insights (best/worst per level) ---------------- */
/* Objective-aware primary metric: results exist → CPA (lower = better);
   else clicks exist → CTR (higher = better); else CPM (lower = better). */
function rPrimaryMetric(rows){
  const tot = rSum(rows.map(r=>r.cur));
  if (rMetricValue(tot,'conversions') > 0) return { key:'cpa', best:'min' };
  if (rMetricValue(tot,'clicks') > 0)      return { key:'ctr', best:'max' };
  return { key:'cpm', best:'min' };
}
/* Why is a row good/bad? Compare its ratios against the peer average. */
function rPerfReasons(row, peersSum, peersCount, primary, isBest, lang, currency){
  const L = (en, ar)=> lang==='ar' ? ar : (lang==='both' ? ar + ' · ' + en : en);
  const out = [];
  const avg = (k)=> rMetricValue(peersSum, k);
  const mine = (k)=> rMetricValue(row.cur, k);
  const cmp = (k, goodDir)=>{
    const a = avg(k), m = mine(k);
    if (!(a > 0) || !(m > 0)) return null;
    const pct = Math.round(Math.abs(m - a) / a * 100);
    if (pct < 10) return null;
    const better = goodDir === 'down' ? m < a : m > a;
    return { k, pct, better };
  };
  const checks = [ ['cpa','down'], ['ctr','up'], ['cpc','down'], ['cvr','up'], ['cpm','down'] ];
  for (const [k, dir] of checks){
    const c = cmp(k, dir);
    if (!c) continue;
    if (isBest === c.better){
      const lbl = rMetricLabel(k, lang);
      const val = rFmt(mine(k), rMetric(k).fmt, currency, lang);
      out.push(c.better
        ? L(`${lbl} ${val} — ${c.pct}% better than average`, `${lbl} ${val} — أفضل من المتوسط بنسبة ${c.pct}٪`)
        : L(`${lbl} ${val} — ${c.pct}% worse than average`,  `${lbl} ${val} — أسوأ من المتوسط بنسبة ${c.pct}٪`));
    }
    if (out.length >= 3) break;
  }
  if (!out.length){
    const p = rMetric(primary.key);
    out.push(L(
      `${p.label} ${rFmt(mine(primary.key), p.fmt, currency, lang)} (${isBest?'best':'weakest'} of the group)`,
      `${p.ar} ${rFmt(mine(primary.key), p.fmt, currency, lang)} (${isBest?'الأفضل':'الأضعف'} في المجموعة)`));
  }
  return out;
}
/* Best + worst performer for a level within a platform/report scope. */
function rPerfInsights(ds, level, opts){
  opts = opts || {};
  const { rows } = rEntityRows(ds, level, { platform: opts.platform, sortKey:'spend', sortDir:'desc' });
  const totalSpend = rows.reduce((a,r)=>a + rMetricValue(r.cur,'spend'), 0);
  /* eligibility: enough spend to judge fairly */
  const eligible = rows.filter(r=>rMetricValue(r.cur,'spend') >= Math.max(totalSpend * 0.03, 1));
  if (eligible.length < 2) return null;
  const primary = rPrimaryMetric(eligible);
  const val = (r)=> rMetricValue(r.cur, primary.key);
  const scored = eligible.filter(r=>val(r) > 0);
  if (scored.length < 2) return null;
  const sorted = [...scored].sort((a,b)=> primary.best==='min' ? val(a)-val(b) : val(b)-val(a));
  const best = sorted[0], worst = sorted[sorted.length-1];
  if (best.id === worst.id) return null;
  const lang = opts.lang || 'en', currency = opts.currency || 'USD';
  const peersSum = rSum(scored.map(r=>r.cur));
  return {
    primary: primary.key,
    bestId: best.id,
    best:  { name:best.name,  value:val(best),
             reasons: rPerfReasons(best,  peersSum, scored.length, primary, true,  lang, currency) },
    worst: { name:worst.name, value:val(worst),
             reasons: rPerfReasons(worst, peersSum, scored.length, primary, false, lang, currency) },
    count: scored.length,
  };
}

/* ---------- Structured recommendations (Next Steps slide) -------------- */
/* Rule-based, data-driven action cards: {icon, pri:'high'|'med'|'low',
   title, why, action} — localized, account-blocklist aware. */
function rRecommendations(ds, lang, currency){
  const L = (en, ar)=> lang==='ar' ? ar : (lang==='both' ? ar + '\n' + en : en);
  const out = [];
  const t = rTotals(ds);
  const spend = rMetricValue(t.cur,'spend');
  if (spend <= 0) return out;
  const plats = rByPlatform(ds);
  const perf = rPerfInsights(ds, 'campaign', { lang, currency });
  /* 1. scale the winner */
  if (perf){
    const bestRow = (rEntityRows(ds,'campaign',{}).rows||[]).find(r=>r.id===perf.bestId);
    const share = bestRow ? rMetricValue(bestRow.cur,'spend')/spend : 0;
    if (share > 0 && share < 0.45) out.push({
      icon:'trend-up', pri:'high',
      title: L('Scale the top performer','ضاعف الاستثمار في الأفضل أداءً'),
      why:   L(`“${perf.best.name}” — ${perf.best.reasons[0]||''} with only ${(share*100).toFixed(0)}% of spend.`,
               `«${perf.best.name}» — ${perf.best.reasons[0]||''} مع ${(share*100).toFixed(0)}٪ فقط من الإنفاق.`),
      action:L('Shift 15–20% of budget toward it and monitor for 5–7 days.',
               'انقل ١٥–٢٠٪ من الميزانية إليه وراقب الأداء لمدة ٥–٧ أيام.'),
    });
    /* 2. fix or pause the weakest */
    out.push({
      icon:'alert', pri:'high',
      title: L('Review the weakest performer','عالج الأضعف أداءً'),
      why:   L(`“${perf.worst.name}” — ${perf.worst.reasons[0]||''}.`,
               `«${perf.worst.name}» — ${perf.worst.reasons[0]||''}.`),
      action:L('Refresh its creatives/audience or pause and reallocate.',
               'حدّث التصاميم/الجمهور أو أوقفه وأعد توزيع ميزانيته.'),
    });
  }
  /* 3. spend pacing vs previous period */
  const dSpend = rDelta(spend, rMetricValue(t.prev,'spend'), 'spend');
  if (dSpend && dSpend.pct < -20) out.push({
    icon:'zap', pri:'high',
    title: L('Delivery dropped sharply','انخفاض حاد في الإنفاق'),
    why:   L(`Spend is ${Math.abs(dSpend.pct).toFixed(0)}% below the previous period — check budgets, schedules and disapprovals.`,
             `الإنفاق أقل بنسبة ${Math.abs(dSpend.pct).toFixed(0)}٪ من الفترة السابقة — راجع الميزانيات والجدولة وحالة الإعلانات.`),
    action:L('Verify campaign delivery in each ads manager today.',
             'تحقق من حالة التسليم في مدير الإعلانات اليوم.'),
  });
  /* 4. platform reallocation */
  if (plats.length > 1){
    const withCpa = plats.map(p=>({ p:p.platform, cpa:rMetricValue(p.cur,'cpa'),
      share: rMetricValue(p.cur,'spend')/spend })).filter(x=>x.cpa>0);
    if (withCpa.length > 1){
      const bestP = withCpa.sort((a,b)=>a.cpa-b.cpa)[0];
      if (bestP.share < 0.3) out.push({
        icon:'columns', pri:'med',
        title: L('Rebalance the platform mix','أعد توازن توزيع المنصات'),
        why:   L(`${rPlatform(bestP.p).label} has the lowest cost per result (${rFmt(bestP.cpa,'currency2',currency,lang)}) but only ${(bestP.share*100).toFixed(0)}% of spend.`,
                 `${rPlatform(bestP.p).ar} لديها أقل تكلفة نتيجة (${rFmt(bestP.cpa,'currency2',currency,lang)}) لكنها تحصل على ${(bestP.share*100).toFixed(0)}٪ فقط من الإنفاق.`),
        action:L('Test a gradual budget shift toward it next cycle.',
                 'جرّب نقل الميزانية تدريجياً نحوها في الدورة القادمة.'),
      });
    }
  }
  /* 5. creative fatigue (frequency) */
  const freq = rMetricValue(t.cur,'frequency');
  if (freq > 3) out.push({
    icon:'image', pri:'med',
    title: L('Creative fatigue risk','خطر إجهاد التصاميم'),
    why:   L(`Average frequency is ${freq.toFixed(1)} — audiences see the same ads repeatedly.`,
             `متوسط التكرار ${freq.toFixed(1)} — الجمهور يشاهد الإعلانات نفسها مراراً.`),
    action:L('Rotate in fresh creatives or broaden the audience.',
             'أدخل تصاميم جديدة أو وسّع شريحة الجمهور.'),
  });
  /* 6. weak CTR */
  const ctr = rMetricValue(t.cur,'ctr');
  if (ctr > 0 && ctr < 1) out.push({
    icon:'eye', pri:'med',
    title: L('Hooks need work','معدل النقر يحتاج تحسيناً'),
    why:   L(`Blended CTR is ${ctr.toFixed(2)}% — below the ~1% healthy floor.`,
             `نسبة النقر الإجمالية ${ctr.toFixed(2)}٪ — أقل من الحد الصحي (~١٪).`),
    action:L('A/B test first-3-seconds hooks and thumbnails.',
             'اختبر افتتاحيات الثواني الثلاث الأولى والصور المصغرة.'),
  });
  /* 7. community momentum */
  const br = rBrand(ds);
  if (br.hasData && br.totalGained > 0) out.push({
    icon:'users', pri:'low',
    title: L('Keep the community momentum','حافظ على نمو المجتمع'),
    why:   L(`+${br.totalGained.toLocaleString('en-US')} followers gained this period across platforms.`,
             `+${br.totalGained.toLocaleString('en-US')} متابعاً جديداً هذه الفترة عبر المنصات.`),
    action:L('Sustain the formats driving follows; engage new followers early.',
             'واصل المحتوى الذي يجذب المتابعين وتفاعل معهم مبكراً.'),
  });
  return out.slice(0, 6);
}

Object.assign(window, {
  REPORT_PLATFORMS, REPORT_PLATFORM_ORDER, REPORT_METRICS, REPORT_STR,
  rPerfInsights, rRecommendations, rPrimaryMetric,
  R_PLATFORM_LOGOS, rPlatformLogo,
  rPlatform, rSortPlatforms, rMetric, rAllMetrics, rLoadCustomMetrics, rSaveCustomMetric,
  rMetricBlockedFor, rDefaultExecMetrics,
  rSum, rZeroSums, rMetricValue, rDelta, rTotals, rByPlatform, rEntityRows, rSeries,
  rBrand, rDatasetPlatforms, rFmt, rCompact, rMetricLabel, rStr, rDateLabel,
  rContrastText, rLuminance, rMix, rRamp, rHexRgb, rAutoInsights,
});
