// data.jsx — Sri Sri Plywoods data engine
// Loads data.json (built from the Google Sheet by pull_data.py).

const RANGES = {
  '7d':  { label: 'Last 7 days',    days: 7,   buckets: { daily: 7,  weekly: 1,  monthly: 1 }, sub: 'vs previous 7 days'    },
  '30d': { label: 'Last 30 days',   days: 30,  buckets: { daily: 30, weekly: 4,  monthly: 1 }, sub: 'vs previous 30 days'   },
  '90d': { label: 'Last 90 days',   days: 90,  buckets: { daily: 90, weekly: 13, monthly: 3 }, sub: 'vs previous 90 days'   },
  'all': { label: 'All time',       days: 'all', buckets: { daily: 90, weekly: 26, monthly: 12 }, sub: 'since launch' },
  'custom': { label: 'Custom range', days: 0,  buckets: { daily: 90, weekly: 52, monthly: 12 }, sub: 'vs previous period' },
};

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

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

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 rangeBounds(range, 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), prevStart = addDays(prevEnd,-(days-1));
    return { start, end, prevStart, prevEnd, days };
  }
  if (range === 'all') {
    const start = MIN_DATE, end = MAX_DATE;
    const days = Math.round((end-start)/86400000)+1;
    const prevEnd = addDays(start,-1), prevStart = addDays(prevEnd,-(days-1));
    return { start, end, prevStart, prevEnd, days };
  }
  const end = MAX_DATE;
  const days = RANGES[range].days;
  const start = addDays(end, -(days-1));
  const prevEnd = addDays(start,-1), prevStart = addDays(prevEnd,-(days-1));
  return { start, end, prevStart, prevEnd, days };
}

function rowsBetween(arr, start, end) {
  const s = fmtISO(start), e = fmtISO(end);
  return arr.filter(r => r.date >= s && r.date <= e);
}

// ───────────── Aggregators ─────────────
function sumMeta(rows) {
  const o = { spend:0, impressions:0, clicks:0, link_clicks:0, leads:0, reach:0 };
  for (const r of rows) for (const k of Object.keys(o)) o[k] += +r[k] || 0;
  o.ctr = o.impressions > 0 ? (o.clicks/o.impressions)*100 : 0;
  o.cpc = o.clicks > 0 ? o.spend/o.clicks : 0;
  o.cpl = o.leads > 0 ? o.spend/o.leads : 0;
  o.cpm = o.impressions > 0 ? (o.spend/o.impressions)*1000 : 0;
  return o;
}
function sumGoogle(rows) {
  const o = { spend:0, impressions:0, clicks:0, conversions:0, store_visits:0, conv_value:0 };
  for (const r of rows) for (const k of Object.keys(o)) o[k] += +r[k] || 0;
  o.ctr = o.impressions > 0 ? (o.clicks/o.impressions)*100 : 0;
  o.cpc = o.clicks > 0 ? o.spend/o.clicks : 0;
  o.cost_per_conv = o.conversions > 0 ? o.spend/o.conversions : 0;
  o.cpm = o.impressions > 0 ? (o.spend/o.impressions)*1000 : 0;
  return o;
}
function sumOverview(metaRows, googleRows) {
  const m = sumMeta(metaRows), g = sumGoogle(googleRows);
  return {
    meta: m, google: g,
    spend: m.spend + g.spend,
    impressions: m.impressions + g.impressions,
    clicks: m.clicks + g.clicks,
    conversions: m.leads + g.conversions,        // total: meta leads + google conversions
    metaLeads: m.leads, googleConv: g.conversions,
    metaSpend: m.spend, googleSpend: g.spend,
    cpc: (m.clicks+g.clicks) > 0 ? (m.spend+g.spend)/(m.clicks+g.clicks) : 0,
    cpa: (m.leads+g.conversions) > 0 ? (m.spend+g.spend)/(m.leads+g.conversions) : 0,
    ctr: (m.impressions+g.impressions) > 0 ? ((m.clicks+g.clicks)/(m.impressions+g.impressions))*100 : 0,
  };
}

// ───────────── Time bucketing ─────────────
function bucketRows(rows, granularity, start, end, kind /* 'meta'|'google' */) {
  const sumFn = kind === 'google' ? sumGoogle : sumMeta;
  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 bucketSize = 1;
  const buckets = [];
  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.date >= fmtISO(cursor) && r.date <= fmtISO(stop));
    buckets.push({ start: new Date(cursor.getTime()), end: stop, agg: sumFn(bRows) });
    cursor = addDays(stop, 1);
  }
  const labels = buckets.map(b => labelFor(b.start, b.end, granularity));
  return { labels, rows: buckets.map(b => b.agg) };
}
function labelFor(start, end, gran) {
  const M = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  if (gran === 'daily') return `${start.getUTCDate()}/${start.getUTCMonth()+1}`;
  if (gran === 'weekly') return `${start.getUTCDate()}/${start.getUTCMonth()+1}`;
  if (gran === 'monthly') return `${M[start.getUTCMonth()]} ${String(start.getUTCFullYear()).slice(-2)}`;
  return fmtISO(start);
}

// ───────────── High-level helpers ─────────────
function metaRowsRange(b)   { return rowsBetween(RAW.meta_daily, b.start, b.end); }
function googleRowsRange(b) { return rowsBetween(RAW.google_daily, b.start, b.end); }
function creativeRowsRange(b) { return rowsBetween(RAW.creative, b.start, b.end); }

function realKPIs(range, customStart=null, customEnd=null) {
  const b = rangeBounds(range, customStart, customEnd);
  const cur = sumOverview(metaRowsRange(b), googleRowsRange(b));
  const prev = sumOverview(
    rowsBetween(RAW.meta_daily, b.prevStart, b.prevEnd),
    rowsBetween(RAW.google_daily, b.prevStart, b.prevEnd)
  );
  return { cur, prev, bounds: b };
}
function realMetaSeries(range, granularity, customStart=null, customEnd=null) {
  const b = rangeBounds(range, customStart, customEnd);
  const { labels, rows } = bucketRows(metaRowsRange(b), granularity, b.start, b.end, 'meta');
  return {
    labels,
    spend:       rows.map(r => Math.round(r.spend)),
    impressions: rows.map(r => r.impressions),
    clicks:      rows.map(r => r.clicks),
    leads:       rows.map(r => r.leads),
    cpl:         rows.map(r => +r.cpl.toFixed(0)),
    ctr:         rows.map(r => +r.ctr.toFixed(2)),
  };
}
function realGoogleSeries(range, granularity, customStart=null, customEnd=null) {
  const b = rangeBounds(range, customStart, customEnd);
  const { labels, rows } = bucketRows(googleRowsRange(b), granularity, b.start, b.end, 'google');
  return {
    labels,
    spend:       rows.map(r => Math.round(r.spend)),
    impressions: rows.map(r => r.impressions),
    clicks:      rows.map(r => r.clicks),
    conversions: rows.map(r => r.conversions),
    cost_per_conv: rows.map(r => +r.cost_per_conv.toFixed(0)),
    ctr:         rows.map(r => +r.ctr.toFixed(2)),
  };
}
function realCombinedSeries(range, granularity, customStart=null, customEnd=null) {
  const m = realMetaSeries(range, granularity, customStart, customEnd);
  const g = realGoogleSeries(range, granularity, customStart, customEnd);
  // Align by labels (same ranges always produce same labels)
  const labels = m.labels.length >= g.labels.length ? m.labels : g.labels;
  const pad = (arr, len) => arr.length === len ? arr : Array(len-arr.length).fill(0).concat(arr);
  return {
    labels,
    metaSpend:    pad(m.spend, labels.length),
    googleSpend:  pad(g.spend, labels.length),
    metaLeads:    pad(m.leads, labels.length),
    googleConv:   pad(g.conversions, labels.length),
  };
}
function campaignBreakdown(kind, range, customStart=null, customEnd=null) {
  const b = rangeBounds(range, customStart, customEnd);
  const rows = kind === 'google' ? googleRowsRange(b) : metaRowsRange(b);
  const sumFn = kind === 'google' ? sumGoogle : sumMeta;
  const map = {};
  for (const r of rows) {
    const k = r.campaign_name || '(unnamed)';
    map[k] = map[k] || { name: k, _rows: [] };
    map[k]._rows.push(r);
  }
  return Object.values(map).map(c => ({ name: c.name, agg: sumFn(c._rows) }))
    .sort((a,b) => b.agg.spend - a.agg.spend);
}
function creativeBreakdown(range, customStart=null, customEnd=null) {
  const b = rangeBounds(range, customStart, customEnd);
  const rows = creativeRowsRange(b);
  const map = {};
  for (const r of rows) {
    const k = r.ad_id;
    map[k] = map[k] || { ad_id:k, ad_name:r.ad_name, campaign_name:r.campaign_name, adset_name:r.adset_name,
                         thumbnail_url:r.thumbnail_url, creative_id:r.creative_id, _rows: [] };
    map[k]._rows.push(r);
  }
  return Object.values(map).map(c => {
    const a = sumMeta(c._rows);
    return { ad_id:c.ad_id, ad_name:c.ad_name, campaign_name:c.campaign_name, adset_name:c.adset_name,
             thumbnail_url:c.thumbnail_url, creative_id:c.creative_id, agg:a };
  }).sort((a,b) => b.agg.spend - a.agg.spend);
}
function realSparkline(metric, days=14) {
  const end = MAX_DATE, start = addDays(end, -(days-1));
  const days_arr = [];
  for (let d = new Date(start.getTime()); d <= end; d = addDays(d,1)) days_arr.push(fmtISO(d));
  return days_arr.map(date => {
    const m = RAW.meta_daily.filter(r => r.date === date);
    const g = RAW.google_daily.filter(r => r.date === date);
    const ms = sumMeta(m), gs = sumGoogle(g);
    if (metric === 'spend') return Math.round(ms.spend + gs.spend);
    if (metric === 'leads') return ms.leads + gs.conversions;
    if (metric === 'clicks') return ms.clicks + gs.clicks;
    if (metric === 'impressions') return ms.impressions + gs.impressions;
    if (metric === 'cpa') {
      const conv = ms.leads + gs.conversions;
      return conv > 0 ? +((ms.spend+gs.spend)/conv).toFixed(0) : 0;
    }
    return 0;
  });
}

window.SriSriData = {
  RANGES,
  loadReal,
  rangeBounds,
  realKPIs, realMetaSeries, realGoogleSeries, realCombinedSeries,
  campaignBreakdown, creativeBreakdown, realSparkline,
  RAW_META: () => (RAW && RAW.meta) || null,
  sumMeta, sumGoogle, sumOverview,
};
