// Deterministic demo dataset — realistic-looking GA4-style data so the app
// can be explored before real properties are connected. Same output shapes
// as the real GA4 layer in ga4.js.

function hashStr(s) {
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}

function mulberry32(seed) {
  let a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

const CHANNELS = ['Organic Search', 'Direct', 'Paid Search', 'Organic Social', 'Referral', 'Email', 'Paid Social', 'Display'];
const DEVICES = ['desktop', 'mobile', 'tablet'];
const COUNTRIES = ['United Kingdom', 'United States', 'Ireland', 'Germany', 'France', 'Netherlands', 'Spain', 'Australia', 'Canada', 'Italy'];
const PAGE_POOLS = [
  ['/', '/releases/1652', '/about', '/services', '/contact', '/blog', '/pricing', '/blog/top-tips', '/case-studies', '/faq', '/team'],
  ['/', '/releases/1687', '/shop', '/shop/new-in', '/product/best-seller', '/basket', '/checkout', '/sale', '/blog', '/delivery', '/returns'],
  ['/', '/releases/1701', '/book-online', '/services', '/opening-hours', '/reviews', '/gallery', '/offers', '/contact', '/about-us', '/news'],
];

function dayKey(d) {
  return d.toISOString().slice(0, 10);
}

// Per-site stable profile: scale, growth, channel mix, device mix.
function siteProfile(site) {
  const rnd = mulberry32(hashStr(site.id));
  const scale = 80 + Math.floor(rnd() * 2400);            // avg daily sessions
  const growth = 0.85 + rnd() * 0.5;                       // yearly growth factor
  const weekendDip = 0.55 + rnd() * 0.4;
  const convRate = 0.008 + rnd() * 0.045;
  const engRate = 0.45 + rnd() * 0.35;
  const bounceish = 1 - engRate;
  const avgDur = 45 + rnd() * 180;
  // Channel mix (normalized weights)
  let w = CHANNELS.map(() => 0.2 + rnd());
  // Bias: organic + direct dominate for most sites
  w[0] *= 2.6; w[1] *= 1.8;
  const wsum = w.reduce((a, b) => a + b, 0);
  const channelMix = w.map((x) => x / wsum);
  const mobileShare = 0.35 + rnd() * 0.4;
  const deviceMix = [1 - mobileShare - 0.06, mobileShare, 0.06];
  const pages = PAGE_POOLS[hashStr(site.id) % PAGE_POOLS.length];
  return { scale, growth, weekendDip, convRate, engRate, bounceish, avgDur, channelMix, deviceMix, pages, rnd };
}

// Daily sessions for a site on a given date (deterministic).
function sessionsOn(site, profile, date) {
  const t = date.getTime() / 86400000; // days since epoch
  const seed = hashStr(site.id + '|' + dayKey(date));
  const noise = 0.75 + mulberry32(seed)() * 0.5;
  const dow = date.getUTCDay();
  const weekday = dow === 0 || dow === 6 ? profile.weekendDip : 1;
  // Gentle seasonality + long-term growth
  const season = 1 + 0.18 * Math.sin((2 * Math.PI * (t % 365.25)) / 365.25 + hashStr(site.id) % 7);
  const now = Date.now() / 86400000;
  const ageYears = (now - t) / 365.25;
  const trend = Math.pow(profile.growth, -ageYears);
  return Math.max(3, Math.round(profile.scale * weekday * season * trend * noise));
}

function metricsFromSessions(site, profile, date, sessions) {
  const seed = hashStr('m' + site.id + dayKey(date));
  const r = mulberry32(seed);
  const users = Math.round(sessions * (0.72 + r() * 0.16));
  const newUsers = Math.round(users * (0.35 + r() * 0.35));
  const pageViews = Math.round(sessions * (1.7 + r() * 2.2));
  const engaged = Math.round(sessions * (profile.engRate * (0.9 + r() * 0.2)));
  const conversions = Math.round(sessions * profile.convRate * (0.7 + r() * 0.6));
  const avgSessionDuration = profile.avgDur * (0.85 + r() * 0.3);
  return { sessions, totalUsers: users, newUsers, screenPageViews: pageViews, engagedSessions: engaged, conversions, avgSessionDuration };
}

function eachDay(startDate, endDate, fn) {
  const d = new Date(startDate + 'T00:00:00Z');
  const end = new Date(endDate + 'T00:00:00Z');
  while (d <= end) {
    fn(new Date(d));
    d.setUTCDate(d.getUTCDate() + 1);
  }
}

export function demoTimeseries(site, startDate, endDate) {
  const profile = siteProfile(site);
  const rows = [];
  eachDay(startDate, endDate, (date) => {
    const s = sessionsOn(site, profile, date);
    rows.push({ date: dayKey(date), ...metricsFromSessions(site, profile, date, s) });
  });
  return rows;
}

export function demoTotals(site, startDate, endDate) {
  const rows = demoTimeseries(site, startDate, endDate);
  const t = rows.reduce(
    (acc, r) => {
      acc.sessions += r.sessions;
      acc.totalUsers += r.totalUsers;
      acc.newUsers += r.newUsers;
      acc.screenPageViews += r.screenPageViews;
      acc.engagedSessions += r.engagedSessions;
      acc.conversions += r.conversions;
      acc._dur += r.avgSessionDuration * r.sessions;
      return acc;
    },
    { sessions: 0, totalUsers: 0, newUsers: 0, screenPageViews: 0, engagedSessions: 0, conversions: 0, _dur: 0 }
  );
  const totals = {
    sessions: t.sessions,
    totalUsers: t.totalUsers,
    newUsers: t.newUsers,
    screenPageViews: t.screenPageViews,
    engagedSessions: t.engagedSessions,
    conversions: t.conversions,
    engagementRate: t.sessions ? t.engagedSessions / t.sessions : 0,
    avgSessionDuration: t.sessions ? t._dur / t.sessions : 0,
  };
  return totals;
}

function splitByWeights(total, labels, weights, seedStr) {
  const r = mulberry32(hashStr(seedStr));
  const jittered = weights.map((w) => w * (0.8 + r() * 0.4));
  const sum = jittered.reduce((a, b) => a + b, 0);
  let remaining = total;
  const rows = labels.map((label, i) => {
    const v = i === labels.length - 1 ? remaining : Math.round((total * jittered[i]) / sum);
    remaining -= v;
    return { label, value: Math.max(0, v) };
  });
  return rows.sort((a, b) => b.value - a.value);
}

export function demoBreakdown(site, dimension, startDate, endDate) {
  const profile = siteProfile(site);
  const totals = demoTotals(site, startDate, endDate);
  const seedStr = site.id + dimension + startDate + endDate;
  if (dimension === 'sessionDefaultChannelGroup') {
    return splitByWeights(totals.sessions, CHANNELS, profile.channelMix, seedStr).map((r) => ({
      dimensionValue: r.label, sessions: r.value,
      conversions: Math.round(r.value * profile.convRate * (r.label.includes('Paid') ? 1.7 : 1)),
    }));
  }
  if (dimension === 'deviceCategory') {
    return splitByWeights(totals.sessions, DEVICES, profile.deviceMix, seedStr).map((r) => ({
      dimensionValue: r.label, sessions: r.value,
    }));
  }
  if (dimension === 'country') {
    const w = COUNTRIES.map((_, i) => Math.pow(0.55, i));
    return splitByWeights(totals.sessions, COUNTRIES, w, seedStr).map((r) => ({
      dimensionValue: r.label, sessions: r.value,
    }));
  }
  if (dimension === 'pagePath') {
    const w = profile.pages.map((_, i) => Math.pow(0.62, i));
    return splitByWeights(totals.screenPageViews, profile.pages, w, seedStr).map((r) => ({
      dimensionValue: r.label, screenPageViews: r.value,
      sessions: Math.round(r.value * 0.62),
    }));
  }
  if (dimension === 'sessionSource') {
    const sources = ['google', '(direct)', 'bing', 'facebook.com', 'instagram.com', 'linkedin.com', 'newsletter', 'partner-site.com'];
    const w = sources.map((_, i) => Math.pow(0.58, i));
    return splitByWeights(totals.sessions, sources, w, seedStr).map((r) => ({
      dimensionValue: r.label, sessions: r.value,
    }));
  }
  // Fallback: single bucket
  return [{ dimensionValue: '(other)', sessions: totals.sessions }];
}

// On-site search terms (deterministic) — what demo visitors "searched" for.
const SEARCH_TERMS = [
  'electric', 'price list', 'press kit', 'new model', 'hybrid', 'images',
  'specification', 'dealer', 'range', 'warranty', 'towing', 'colours',
  'service plan', 'launch date', 'finance',
];
export function demoSearchTerms(site, startDate, endDate) {
  const totals = demoTotals(site, startDate, endDate);
  const base = Math.max(20, Math.round(totals.sessions * 0.035));
  const seedStr = site.id + 'search' + startDate + endDate;
  const w = SEARCH_TERMS.map((_, i) => Math.pow(0.72, i));
  return splitByWeights(base, SEARCH_TERMS, w, seedStr)
    .map((r) => ({ term: r.label, searches: r.value, users: Math.max(1, Math.round(r.value * 0.8)) }))
    .filter((r) => r.searches > 0);
}

// Navigation flows + outbound destinations (deterministic demo).
export function demoFlows(site, startDate, endDate) {
  const profile = siteProfile(site);
  const totals = demoTotals(site, startDate, endDate);
  const pages = profile.pages;
  const seedStr = site.id + 'flows' + startDate;
  const r = mulberry32(hashStr(seedStr));
  const internal = [];
  for (let i = 0; i < Math.min(9, pages.length - 1); i++) {
    const from = pages[i % pages.length];
    const to = pages[(i + 1 + Math.floor(r() * 3)) % pages.length];
    if (from === to) continue;
    internal.push({ from, to, views: Math.max(8, Math.floor(totals.screenPageViews * 0.04 * (1 - i * 0.09) * (0.7 + r() * 0.6))) });
  }
  const sources = ['google.com', 'news.google.com', 'bing.com', 'linkedin.com', 'facebook.com', 't.co'];
  const entries = sources.map((source, i) => ({
    source, to: pages[i % 3 === 0 ? 0 : 1 + (i % (pages.length - 1))],
    views: Math.max(5, Math.floor(totals.sessions * 0.05 * (1 - i * 0.13) * (0.7 + r() * 0.6))),
  }));
  return {
    internal: internal.sort((a, b) => b.views - a.views),
    entries: entries.sort((a, b) => b.views - a.views),
    siteHost: (site.url || '').replace(/^https?:\/\/(www\.)?/, '') || 'demo.example.com',
  };
}

export function demoOutbound(site, startDate, endDate) {
  const totals = demoTotals(site, startDate, endDate);
  const seedStr = site.id + 'ob' + startDate;
  const r = mulberry32(hashStr(seedStr));
  const brand = (site.name || 'brand').toLowerCase().split(' ')[0].replace(/[^a-z]/g, '') || 'brand';
  const doms = [`${brand}.com`, `${brand}-press.co.uk`, 'youtube.com', 'instagram.com', 'linkedin.com', 'twitter.com', 'flickr.com'];
  const domains = doms.map((domain, i) => {
    const clicks = Math.max(3, Math.floor(totals.sessions * 0.06 * (1 - i * 0.14) * (0.7 + r() * 0.6)));
    return { domain, clicks, users: Math.max(2, Math.floor(clicks * 0.8)) };
  }).sort((a, b) => b.clicks - a.clicks);
  const urls = domains.slice(0, 5).flatMap((d) => [
    { url: `https://${d.domain}/`, domain: d.domain, clicks: Math.ceil(d.clicks * 0.6), users: Math.ceil(d.users * 0.6) },
    { url: `https://${d.domain}/models`, domain: d.domain, clicks: Math.floor(d.clicks * 0.4), users: Math.floor(d.users * 0.4) },
  ]);
  return { domains, urls, collected: true };
}

// Filtered breakdowns for drill-downs (sources within a channel, cities within
// a country, sources/countries for one page) — deterministic.
const SOURCES_BY_CHANNEL = {
  'Organic Search': ['google', 'bing', 'duckduckgo', 'yahoo', 'ecosia.org'],
  'Paid Search': ['google', 'bing'],
  'Organic Social': ['linkedin.com', 'facebook.com', 'instagram.com', 't.co', 'youtube.com', 'reddit.com'],
  'Paid Social': ['facebook.com', 'instagram.com', 'linkedin.com', 'tiktok.com'],
  Referral: ['pressport.com', 'journalists-hub.net', 'motoring-news.co.uk', 'partner-site.com', 'newsround.io'],
  Email: ['newsletter', 'press-alert', 'mailchimp'],
  Direct: ['(direct)'],
  Display: ['googleads', 'criteo', 'display-net'],
};
const CITIES_BY_COUNTRY = {
  'United Kingdom': ['London', 'Manchester', 'Birmingham', 'Leeds', 'Glasgow', 'Bristol', 'Edinburgh'],
  'United States': ['New York', 'Los Angeles', 'Chicago', 'San Francisco', 'Austin', 'Seattle'],
  Ireland: ['Dublin', 'Cork', 'Galway'],
  Germany: ['Berlin', 'Munich', 'Hamburg', 'Frankfurt'],
  France: ['Paris', 'Lyon', 'Marseille'],
};
export function demoFiltered(site, dimension, metric, filterField, filterValue, startDate, endDate) {
  const totals = demoTotals(site, startDate, endDate);
  const seedStr = site.id + '|' + dimension + '|' + filterField + '|' + filterValue + '|' + startDate;
  const r = mulberry32(hashStr(seedStr));
  const base = Math.max(25, Math.round((metric === 'screenPageViews' ? totals.screenPageViews : totals.sessions) * (0.02 + r() * 0.18)));
  let labels;
  if (dimension === 'sessionSource') {
    labels = filterField === 'sessionDefaultChannelGroup'
      ? SOURCES_BY_CHANNEL[filterValue] || ['google', '(direct)', 'bing', 'facebook.com']
      : ['google', '(direct)', 'linkedin.com', 'bing', 'newsletter', 'facebook.com', 'partner-site.com'];
  } else if (dimension === 'deviceCategory') {
    labels = ['mobile', 'desktop', 'tablet'];
  } else if (dimension === 'city') {
    labels = CITIES_BY_COUNTRY[filterValue] || ['Capital City', 'Second City', 'Third City', 'Port Town'];
  } else if (dimension === 'country') {
    labels = COUNTRIES.slice(0, 8);
  } else if (dimension === 'landingPage') {
    const profile = siteProfile(site);
    labels = profile.pages.slice(0, 6).concat([profile.pages[0] + 'special']);
  } else {
    labels = ['(other)'];
  }
  const w = labels.map((_, i) => Math.pow(0.58, i));
  return splitByWeights(base, labels, w, seedStr).map((row) => ({ dimensionValue: row.label, [metric]: row.value }));
}

// Daily views for a single page — releases get a publish-spike-and-decay curve.
export function demoPageTrend(site, path, startDate, endDate) {
  const profile = siteProfile(site);
  const seed = hashStr(site.id + '|' + path);
  const isRelease = /\/\d+$/.test(path);
  const publishOffset = seed % 75; // days ago the "release" went out
  const now = Date.now() / 86400000;
  const rows = [];
  eachDay(startDate, endDate, (date) => {
    const t = date.getTime() / 86400000;
    const r = mulberry32(hashStr('pt' + site.id + path + dayKey(date)));
    let v;
    if (isRelease) {
      const age = now - publishOffset - t; // days before(-)/after(+) publish
      if (age > 0) v = 0; // before publish
      else v = Math.round(profile.scale * 0.9 * Math.exp(age / 6) * (0.6 + r() * 0.8));
    } else {
      v = Math.round(sessionsOn(site, profile, date) * (0.12 + (seed % 20) / 100) * (0.8 + r() * 0.4));
    }
    rows.push({ date: dayKey(date), screenPageViews: Math.max(0, v) });
  });
  return rows;
}

// Deterministic sub-pages under a path prefix (for the drill-down in demo mode).
export function demoPagesByPrefix(site, prefix, startDate, endDate) {
  const clean0 = String(prefix || '/').replace(/\/+$/, '');
  // Article-style paths (…/1652) are leaves — no children, like real GA4 data.
  if (/\/\d+$/.test(clean0)) return [];
  const totals = demoTotals(site, startDate, endDate);
  const clean = clean0 || '';
  const r = mulberry32(hashStr(site.id + '|' + clean + '|' + startDate + endDate));
  const n = 6 + Math.floor(r() * 8);
  const budget = Math.round(totals.screenPageViews * (0.15 + r() * 0.2));
  const rows = [];
  const seen = new Set();
  for (let i = 0; i < n; i++) {
    let slug = String(1000 + Math.floor(r() * 9000));
    while (seen.has(slug)) slug = String(1000 + Math.floor(r() * 9000));
    seen.add(slug);
    const v = Math.max(3, Math.round(budget * 0.35 * Math.pow(0.65, i) * (0.8 + r() * 0.4)));
    rows.push({ dimensionValue: `${clean}/${slug}`, screenPageViews: v, sessions: Math.round(v * 0.62) });
  }
  return rows.sort((a, b) => b.screenPageViews - a.screenPageViews);
}
