// data.jsx — Event Plus dashboard engine
// Reads data.json (built from Meta Marketing API by build_data.py) and exposes
// helpers used by all tabs: overview, campaigns, creatives.

const RANGES = {
  '7d':   { label: 'Last 7 days',   days: 7,    buckets: { daily: 7,  weekly: 1, monthly: 1, quarterly: 1 }, sub: 'vs previous 7 days' },
  '14d':  { label: 'Last 14 days',  days: 14,   buckets: { daily: 14, weekly: 2, monthly: 1, quarterly: 1 }, sub: 'vs previous 14 days' },
  '30d':  { label: 'Last 30 days',  days: 30,   buckets: { daily: 30, weekly: 4, monthly: 1, quarterly: 1 }, sub: 'vs previous 30 days' },
  '90d':  { label: 'Last 90 days',  days: 90,   buckets: { daily: 90, weekly: 13, monthly: 3, quarterly: 1 }, sub: 'vs previous 90 days' },
  'mtd':  { label: 'Month to date', days: 'mtd',buckets: { daily: 30, weekly: 5, monthly: 1, quarterly: 1 }, sub: 'vs previous MTD' },
  'all':  { label: 'All time',      days: 'all',buckets: { daily: 90, weekly: 13, monthly: 12, quarterly: 4 }, sub: '(no comparison period)' },
  'custom': { label: 'Custom range',days: 0,    buckets: { daily: 90, weekly: 26, monthly: 12, quarterly: 4 }, sub: 'vs previous period' },
};

let RAW = null;
let MIN_DATE = null, MAX_DATE = null;

function parseISO(s) { const [y, m, d] = s.split('-').map(Number); return new Date(Date.UTC(y, m - 1, d)); }
function fmtISO(d)   { return d.toISOString().slice(0, 10); }
function addDays(d, n) { const x = new Date(d.getTime()); x.setUTCDate(x.getUTCDate() + n); return x; }

function loadReal() {
  return fetch('data.json', { cache: 'no-cache' })
    .then(r => r.json())
    .then(d => { RAW = d; MIN_DATE = parseISO(d.meta.minDate); MAX_DATE = parseISO(d.meta.maxDate); return d; });
}

function rangeBounds(range, refDate = MAX_DATE, customStart = null, customEnd = null) {
  if (range === 'custom' && customStart && customEnd) {
    const start = parseISO(customStart), end = parseISO(customEnd);
    const days = Math.round((end - start) / 86400000) + 1;
    const prevEnd = addDays(start, -1);
    const prevStart = addDays(prevEnd, -(days - 1));
    return { start, end, prevStart, prevEnd, days };
  }
  const end = new Date((refDate || MAX_DATE).getTime());
  if (range === 'mtd') {
    const start = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), 1));
    const days = Math.round((end - start) / 86400000) + 1;
    const prevStart = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth() - 1, 1));
    const prevEnd   = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth() - 1, end.getUTCDate()));
    return { start, end, prevStart, prevEnd, days };
  }
  if (range === 'all') {
    const start = new Date(MIN_DATE.getTime());
    const days = Math.round((end - start) / 86400000) + 1;
    return { start, end, prevStart: start, prevEnd: start, days };
  }
  const days = RANGES[range].days;
  const start = addDays(end, -(days - 1));
  const prevEnd = addDays(start, -1);
  const prevStart = addDays(prevEnd, -(days - 1));
  return { start, end, prevStart, prevEnd, days };
}

function rowsBetween(start, end) {
  const s = fmtISO(start), e = fmtISO(end);
  return (RAW.daily || []).filter(r => r.d >= s && r.d <= e);
}

function sumRows(rows) {
  const o = { spend: 0, impressions: 0, reach: 0, clicks: 0, leads: 0,
              linkClicks: 0, videoViews: 0, lpViews: 0, msgConvos: 0 };
  for (const r of rows) {
    o.spend       += r.spend       || 0;
    o.impressions += r.impressions || 0;
    o.reach       += r.reach       || 0;
    o.clicks      += r.clicks      || 0;
    o.leads       += r.leads       || 0;
    o.linkClicks  += r.linkClicks  || 0;
    o.videoViews  += r.videoViews  || 0;
    o.lpViews     += r.lpViews     || 0;
    o.msgConvos   += r.msgConvos   || 0;
  }
  o.ctr = o.impressions > 0 ? (o.clicks / o.impressions) * 100 : 0;
  o.cpc = o.clicks > 0 ? o.spend / o.clicks : 0;
  o.cpm = o.impressions > 0 ? (o.spend / o.impressions) * 1000 : 0;
  o.cpl = o.leads > 0 ? o.spend / o.leads : 0;
  o.frequency = o.reach > 0 ? o.impressions / o.reach : 0;
  o.linkClickRate = o.clicks > 0 ? (o.linkClicks / o.clicks) * 100 : 0;
  o.formCVR = o.linkClicks > 0 ? (o.leads / o.linkClicks) * 100 : 0;
  return o;
}

function bucketRows(rows, granularity, start, end) {
  if (!rows.length) return { labels: [], rows: [] };
  const dayCount = Math.round((end - start) / 86400000) + 1;
  let bucketSize;
  if (granularity === 'daily')      bucketSize = 1;
  else if (granularity === 'weekly') bucketSize = 7;
  else if (granularity === 'monthly') bucketSize = Math.max(28, Math.round(dayCount / 12));
  else if (granularity === 'quarterly') bucketSize = Math.max(60, Math.round(dayCount / 4));
  else bucketSize = 1;
  const out = [];
  for (let cursor = new Date(start.getTime()); cursor <= end; ) {
    const bEnd = addDays(cursor, bucketSize - 1);
    const stop = bEnd > end ? end : bEnd;
    const bRows = rows.filter(r => r.d >= fmtISO(cursor) && r.d <= fmtISO(stop));
    out.push({ start: new Date(cursor.getTime()), end: stop, agg: sumRows(bRows) });
    cursor = addDays(stop, 1);
  }
  const M = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  const labelFor = (s, e, g) => {
    if (g === 'monthly')   return `${M[s.getUTCMonth()]} ${String(s.getUTCFullYear()).slice(-2)}`;
    if (g === 'quarterly') return `Q${Math.floor(s.getUTCMonth() / 3) + 1} ${String(s.getUTCFullYear()).slice(-2)}`;
    return `${s.getUTCDate()}/${s.getUTCMonth() + 1}`;
  };
  return {
    labels: out.map(b => labelFor(b.start, b.end, granularity)),
    rows: out.map(b => b.agg),
  };
}

// ---- High level helpers ----

function realKPIs(range, customStart = null, customEnd = null) {
  const b = rangeBounds(range, undefined, customStart, customEnd);
  const cur  = sumRows(rowsBetween(b.start, b.end));
  const prev = sumRows(rowsBetween(b.prevStart, b.prevEnd));
  return { cur, prev, bounds: b };
}

function realSeries(range, granularity, customStart = null, customEnd = null) {
  const b = rangeBounds(range, undefined, customStart, customEnd);
  const rows = rowsBetween(b.start, b.end);
  const { labels, rows: bk } = bucketRows(rows, granularity, b.start, b.end);
  return {
    labels,
    spend:       bk.map(r => Math.round(r.spend)),
    leads:       bk.map(r => r.leads),
    impressions: bk.map(r => r.impressions),
    reach:       bk.map(r => r.reach),
    clicks:      bk.map(r => r.clicks),
    linkClicks:  bk.map(r => r.linkClicks),
    cpl:         bk.map(r => +r.cpl.toFixed(0)),
    cpc:         bk.map(r => +r.cpc.toFixed(2)),
    ctr:         bk.map(r => +r.ctr.toFixed(2)),
    videoViews:  bk.map(r => r.videoViews),
    msgConvos:   bk.map(r => r.msgConvos),
  };
}

function realSparkline(metric, days = 14) {
  const end = MAX_DATE;
  const start = addDays(end, -(days - 1));
  const rows = rowsBetween(start, end);
  return rows.map(r => {
    if (metric === 'cpl')  return r.cpl || 0;
    if (metric === 'ctr')  return r.ctr || 0;
    if (metric === 'cpc')  return r.cpc || 0;
    return r[metric] || 0;
  });
}

// Filter campaigns / ads to a date window using their per-row daily array
function filterEntityByRange(entity, b) {
  const s = fmtISO(b.start), e = fmtISO(b.end);
  const inRange = (entity.daily || []).filter(r => r.d >= s && r.d <= e);
  const tot = sumRows(inRange);
  return { ...entity, totals: tot, dailyInRange: inRange };
}

function realCampaigns(range, customStart = null, customEnd = null) {
  const b = rangeBounds(range, undefined, customStart, customEnd);
  return (RAW.campaigns || []).map(c => filterEntityByRange(c, b))
    .filter(c => c.totals.spend > 0 || c.totals.impressions > 0);
}

function realAds(range, customStart = null, customEnd = null) {
  const b = rangeBounds(range, undefined, customStart, customEnd);
  return (RAW.ads || []).map(a => filterEntityByRange(a, b))
    .filter(a => a.totals.spend > 0 || a.totals.impressions > 0);
}

function realAudience() {
  // Audience breakdowns are pre-aggregated for the full Mar 1 → today period.
  // They do NOT respect the active range — they're a snapshot of all-time
  // breakdown distribution. This keeps the visual stable; tighter ranges would
  // need additional API calls (Meta breakdowns can't be sliced from daily totals).
  return (RAW && RAW.audience) || { age: [], gender: [], region: [], country: [], device: [], placement: [] };
}

window.EPData = {
  RANGES,
  loadReal,
  rangeBounds,
  realKPIs, realSeries, realSparkline,
  realCampaigns, realAds, realAudience,
  rowsBetween, sumRows,
  RAW_META: () => (RAW && RAW.meta) || null,
  CURRENCY: 'INR',
  CURRENCY_SYMBOL: '₹',
};
