/* Data layer — LIVE ===================================================
   No synthetic numbers. All account/KPI/campaign/creative data is read
   from RelayLive (populated by /api/data/bootstrap). When nothing has been
   ingested yet the accessors return zeros / empty arrays, so the UI shows
   honest empty states. Pure math helpers (funnel, CI, projections) operate
   on the real data.
=================================================================== */

/* ---------- Accounts (loaded from the API at sign-in) -------------- */
let ACCOUNTS = [];
function setAccounts(list){ ACCOUNTS = Array.isArray(list) ? list : []; window.ACCOUNTS = ACCOUNTS; }

/* ---------- Platforms (presentation metadata only) ---------------- */
const PLATFORMS = [
  { id: 'overview',         label: 'Overview',   short: 'Blended',  glyph: '✦', icon:'relay',     tone: '#8351ff' },
  { id: 'meta',             label: 'Meta',       short: 'Meta',     glyph: 'M', icon:'meta',      tone: '#2f7bff' },
  { id: 'facebook',         label: 'Facebook',   short: 'FB',       glyph: 'f', icon:'facebook',  tone: '#1877F2' },
  { id: 'instagram',        label: 'Instagram',  short: 'IG',       glyph: '◉', icon:'instagram', tone: '#E1306C' },
  { id: 'google',           label: 'Google Ads', short: 'Google',   glyph: 'G', icon:'google',    tone: '#34A853' },
  { id: 'google_analytics', label: 'Analytics',  short: 'GA4',      glyph: '▲', icon:'ga',        tone: '#E37400' },
  { id: 'tiktok',           label: 'TikTok',     short: 'TikTok',   glyph: 'T', icon:'tiktok',    tone: '#111111' },
  { id: 'snapchat',         label: 'Snapchat',   short: 'Snap',     glyph: 'S', icon:'snapchat',  tone: '#F7C700' },
  { id: 'x',                label: 'X',          short: 'X',        glyph: '𝕏', icon:'x',         tone: '#111111' },
  { id: 'linkedin',         label: 'LinkedIn',   short: 'LinkedIn', glyph: 'in',icon:'linkedin',  tone: '#0A66C2' },
];

/* ---------- Deterministic PRNG (used only for sparkline shaping) --- */
function mulberry(seed){
  return function(){
    let t = (seed += 0x6D2B79F5);
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
function strSeed(s){
  let h = 5381;
  for (let i=0;i<s.length;i++) h = ((h<<5) + h + s.charCodeAt(i)) | 0;
  return Math.abs(h) || 1;
}

/* ---------- KPI definitions (labels/format; deltas come live) ------ */
const KPI_DEFS = [
  { id:'spend',    label:'Total Spend',     format:'currency',  good:'down' },
  { id:'cpa',      label:'Blended CPA',     format:'currency',  good:'down' },
  { id:'roas',     label:'Blended ROAS',    format:'x',         good:'up'   },
  { id:'cpc',      label:'CPC',             format:'currency2', good:'down' },
  { id:'ctr',      label:'CTR',             format:'pct',       good:'up'   },
];

const EMPTY_SPARK = { spend:[], cpa:[], roas:[], cpc:[], ctr:[] };

/* ---------- Live accessors (read from RelayLive) ------------------- */
function kpiSnapshot(accId, platId){
  const b = RelayLive.get(accId, platId);
  if (!b || !b.kpis) {
    return { spend:0, cpa:0, roas:0, cpc:0, ctr:0, spark:EMPTY_SPARK, deltas:{}, empty:true };
  }
  return Object.assign({ spark: b.spark || EMPTY_SPARK, deltas: b.deltas || {}, empty:false }, b.kpis);
}

function timeSeriesFor(accId, platId){
  const b = RelayLive.get(accId, platId);
  return (b && b.timeseries) ? b.timeseries : [];
}

function getCampaigns(accId, platId){
  const b = RelayLive.get(accId, platId);
  return (b && b.campaigns) ? b.campaigns : [];
}

function getCreatives(accId, platId){
  const b = RelayLive.get(accId, platId);
  return (b && b.creatives) ? b.creatives : [];
}

/* Per-platform spend/roas breakdown for the donut + reallocator. */
function platformBreakdown(accId){
  const b = RelayLive.get(accId, 'overview');
  return (b && b.platforms) ? b.platforms : [];
}

/* Live alerts for an account (from the warehouse). */
function getAlerts(accId){
  const b = RelayLive.get(accId, 'overview');
  return (b && b.alerts) ? b.alerts : [];
}

/* ---------- Default Automations rules (starter templates) --------- */
const DEFAULT_RULES = [];
const DEFAULT_CUSTOM_COLS = [];

/* ---------- Currency (real, per-account) --------------------------- */
/* The shell sets the active account's currency on every account switch so
   money renders in the account's real currency (e.g. SAR, EGP) instead of
   always '$'. fmt() reads ACTIVE_CURRENCY; pass an explicit code as the 3rd
   arg to override (e.g. mixed-currency tables). */
let ACTIVE_CURRENCY = 'USD';
const CURRENCY_SYMBOL = { USD:'$', EUR:'€', GBP:'£' };
function setActiveCurrency(code){
  ACTIVE_CURRENCY = (typeof code === 'string' && /^[A-Za-z]{3}$/.test(code)) ? code.toUpperCase() : 'USD';
  window.ACTIVE_CURRENCY = ACTIVE_CURRENCY;
  return ACTIVE_CURRENCY;
}
/* Prefix used before an amount. USD/EUR/GBP use their symbol; everything else
   (SAR, EGP, AED, …) uses the ISO code + non-breaking space — unambiguous. */
function curPrefix(code){
  const c = (typeof code === 'string' && /^[A-Za-z]{3}$/.test(code) ? code : ACTIVE_CURRENCY).toUpperCase();
  return CURRENCY_SYMBOL[c] || (c + ' ');
}

/* ---------- Formatting helpers ------------------------------------- */
function fmt(v, kind, curr){
  if (v === null || v === undefined || Number.isNaN(v)) return '—';
  if (kind === 'currency')  return curPrefix(curr) + v.toLocaleString('en-US', {maximumFractionDigits:0});
  if (kind === 'currency2') return curPrefix(curr) + v.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2});
  if (kind === 'pct')       return v.toFixed(2) + '%';
  if (kind === 'x')         return v.toFixed(2) + '×';
  if (kind === 'int')       return Math.round(v).toLocaleString('en-US');
  return v;
}

/* Dark-mode-safe display tone: lift near-black brand colors so they stay visible. */
function displayTone(tone){
  const dark = typeof document!=='undefined' && document.documentElement.classList.contains('dark');
  if (!dark) return tone;
  const h = String(tone||'').replace('#','');
  if (h.length>=6){
    const r=parseInt(h.slice(0,2),16), g=parseInt(h.slice(2,4),16), b=parseInt(h.slice(4,6),16);
    const lum = 0.299*r+0.587*g+0.114*b;
    if (lum < 40) return '#d4d4d4';
  }
  return tone;
}

/* ---------- Platform-derived helpers (operate on real data) ------- */

/* Per-source freshness — real sync lag from connected integrations. */
function platformFreshness(){
  const f = (typeof RelayLive !== 'undefined') ? RelayLive.freshness() : {};
  return f || {};
}

/* Conversion funnel — ONLY values algebraically derived from reported
   metrics (impressions/clicks reconstructed from spend·cpc·ctr, conversions
   as reported). No fabricated intermediate stages. */
function campaignFunnel(c){
  const cpm = (c.cpc / Math.max(c.ctr,0.01)) * 100;
  const impressions = Math.round((c.spend / Math.max(cpm,1)) * 1000);
  const clicks      = Math.round(c.spend / Math.max(c.cpc, 0.01));
  return [
    { stage:'Impressions', short:'Impr',  value: impressions },
    { stage:'Clicks',      short:'Clicks',value: clicks },
    { stage:'Conversions', short:'Conv',  value: c.conv },
  ];
}

/* Project next n days of spend/roas as the flat trailing-7-day mean of real
   data. Deterministic — no synthetic noise; always flagged `projected`. */
function projectedDays(accountId, platformId, n=7){
  const ts = timeSeriesFor(accountId, platformId);
  if (!ts.length) return [];
  const last7 = ts.slice(-7);
  const avgSpend = last7.reduce((s,d)=>s+d.spend,0)/last7.length;
  const avgRoas  = last7.reduce((s,d)=>s+d.roas, 0)/last7.length;
  const lastDate = new Date(ts[ts.length-1].date+'T00:00:00Z');
  return Array.from({length:n}, (_,i)=>{
    const d = new Date(lastDate); d.setUTCDate(lastDate.getUTCDate() + (i+1));
    return {
      date: d.toISOString().slice(0,10),
      label: d.toLocaleDateString('en-US',{month:'short',day:'numeric'}),
      spend: +avgSpend.toFixed(0),
      roas:  +avgRoas.toFixed(2),
      projected: true,
    };
  });
}

/* 95% CI on ROAS — proxy from conversion volume; widens for tiny samples. */
function roasCI(roas, conv){
  const n = Math.max(conv, 1);
  const margin = roas * (0.45 / Math.sqrt(n));
  return {
    lo: Math.max(0, +(roas - margin).toFixed(2)),
    hi: +(roas + margin).toFixed(2),
    margin: +margin.toFixed(2),
    wide: margin > roas * 0.25,
  };
}

/* Diminishing-returns projection from a real base spend/roas. */
function projectedRevenueFrom(baseSpend, baseRoas, newSpend){
  if (baseSpend <= 0) return 0;
  const currentRev = baseSpend * baseRoas;
  return currentRev * Math.pow(newSpend / baseSpend, 0.78);
}
function projectedRevenue(accountId, platformId, spendUSD){
  const k = kpiSnapshot(accountId, platformId);
  return projectedRevenueFrom(k.spend, k.roas, spendUSD);
}

/* ---------- Export to window --------------------------------------- */
Object.assign(window, {
  ACCOUNTS, setAccounts, PLATFORMS, KPI_DEFS,
  kpiSnapshot, timeSeriesFor, getCampaigns, getCreatives, platformBreakdown, getAlerts,
  DEFAULT_RULES, DEFAULT_CUSTOM_COLS,
  fmt, curPrefix, setActiveCurrency, mulberry, strSeed, displayTone,
  platformFreshness, campaignFunnel, projectedDays, roasCI,
  projectedRevenue, projectedRevenueFrom,
});
