// ─────────────────────────────────────────────────────────────
// GITWRAP — utilities: seeded stats, count-up hook, data fetch
// ─────────────────────────────────────────────────────────────
const { useState, useEffect, useRef, useLayoutEffect, useCallback } = React;

// --- deterministic hash → seeded PRNG (mulberry32) ----------------
function hashStr(s) {
  let h = 1779033703 ^ s.length;
  for (let i = 0; i < s.length; i++) {
    h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
    h = (h << 13) | (h >>> 19);
  }
  return (h ^= h >>> 16) >>> 0;
}
function mulberry32(a) {
  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;
  };
}

// Range config: multipliers + sparkline resolution + label
const RANGES = {
  daily:   { key: 'daily',   label: 'Daily',   sub: 'last 24 hours', mult: 1,   points: 24, axis: '24h' },
  weekly:  { key: 'weekly',  label: 'Weekly',  sub: 'last 7 days',   mult: 6.5, points: 7,  axis: '7d'  },
  monthly: { key: 'monthly', label: 'Monthly', sub: 'last 30 days',  mult: 27,  points: 30, axis: '30d' },
};

// Fetch real contribution data via GitHub GraphQL API.
// Requires a PAT with read:user scope. Returns null on any failure.
async function fetchRealStats(username, rangeKey, token) {
  const to = new Date();
  const from = new Date(to);
  if (rangeKey === 'daily')       from.setDate(from.getDate() - 1);
  else if (rangeKey === 'weekly') from.setDate(from.getDate() - 7);
  else                            from.setDate(from.getDate() - 30);

  const query = `query($login:String!,$from:DateTime!,$to:DateTime!){user(login:$login){contributionsCollection(from:$from,to:$to){totalCommitContributions totalPullRequestContributions contributionCalendar{weeks{contributionDays{contributionCount date}}}}}}`;
  try {
    const res = await fetch('https://api.github.com/graphql', {
      method: 'POST',
      headers: { Authorization: `bearer ${token}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ query, variables: { login: username, from: from.toISOString(), to: to.toISOString() } }),
    });
    if (!res.ok) return null;
    const json = await res.json();
    if (json.errors || !json.data?.user) return null;

    const col = json.data.user.contributionsCollection;
    const days = col.contributionCalendar.weeks.flatMap(w => w.contributionDays);

    const sorted = [...days].sort((a, b) => b.date.localeCompare(a.date));
    let streak = 0;
    for (const day of sorted) {
      if (day.contributionCount > 0) streak++;
      else break;
    }

    const counts = days.map(d => d.contributionCount);
    const peak = Math.max(...counts, 1);
    return {
      commits: col.totalCommitContributions,
      prs: col.totalPullRequestContributions,
      streak,
      series: counts.map(c => c / peak),
    };
  } catch (e) {
    return null;
  }
}

// Build the full stat object deterministically from a username + range.
// `real` is the (optional) live GitHub profile used to color the synthetic data.
// `realStats` is the (optional) live contribution data from fetchRealStats.
function buildStats(username, rangeKey, real, realStats) {
  const u = (username || 'torvalds').toLowerCase();
  const range = RANGES[rangeKey] || RANGES.monthly;
  const rand = mulberry32(hashStr(u + '|' + rangeKey));
  const base = (lo, hi) => lo + Math.floor(rand() * (hi - lo + 1));

  // intensity scaled gently by real follower/repo signal if present
  const signal = real ? Math.min(2.4, 0.6 + Math.log10((real.followers || 0) + (real.public_repos || 0) + 10) / 2) : 1;
  const m = range.mult * (0.7 + rand() * 0.6) * signal;

  const commits = Math.max(1, Math.round(base(3, 14) * m));
  const prs     = Math.max(0, Math.round(base(0, 4) * m * 0.5));
  const streak  = rangeKey === 'daily' ? base(1, 1) : Math.min(range.points, base(2, range.points));
  const loc     = Math.round(commits * base(40, 220)); // lines of code (PAT-gated)

  // sparkline — n points, smooth-ish walk, peaks correlate with commit volume
  const n = range.points;
  const series = [];
  let v = rand();
  for (let i = 0; i < n; i++) {
    v += (rand() - 0.45) * 0.5;
    v = Math.max(0.05, Math.min(1, v));
    // occasional spike
    if (rand() > 0.86) v = Math.min(1, v + 0.4);
    series.push(v);
  }
  // normalize so max hits ~1
  const peak = Math.max(...series);
  const norm = series.map((x) => x / peak);

  const langs = ['TypeScript','Rust','Go','Python','C','Swift','Kotlin','Ruby','Elixir','Zig'];
  const topLang = langs[Math.floor(rand() * langs.length)];

  return {
    username: real?.login || username || 'torvalds',
    name: real?.name || null,
    avatar: real?.avatar_url || `https://github.com/${encodeURIComponent(u)}.png?size=160`,
    range,
    topLang,
    series: realStats?.series ?? norm,
    tiles: [
      { key: 'commits', label: 'Commits',       value: realStats?.commits ?? commits },
      { key: 'prs',     label: 'Pull Requests',  value: realStats?.prs     ?? prs },
      { key: 'streak',  label: 'Day Streak',     value: realStats?.streak  ?? streak },
    ],
    loc,
    realData: !!realStats,
  };
}

// --- count-up: animates 0 → target with easeOutCubic via rAF -------
function useCountUp(target, run, opts = {}) {
  const { duration, delay = 0 } = opts;
  const [val, setVal] = useState(0);
  const raf = useRef(0);
  useEffect(() => {
    if (!run) { setVal(0); return; }
    const dur = duration != null ? duration : Math.min(1200, Math.max(450, target * 2));
    let start = 0;
    const timer = setTimeout(() => {
      const tick = (t) => {
        if (!start) start = t;
        const p = Math.min(1, (t - start) / dur);
        const eased = 1 - Math.pow(1 - p, 3);
        setVal(Math.round(target * eased));
        if (p < 1) raf.current = requestAnimationFrame(tick);
      };
      raf.current = requestAnimationFrame(tick);
    }, delay);
    // safety net: guarantee final value even if rAF never ticks (hidden tab / export)
    const settle = setTimeout(() => setVal(target), delay + dur + 120);
    return () => { clearTimeout(timer); clearTimeout(settle); cancelAnimationFrame(raf.current); };
  }, [target, run, duration, delay]);
  return val;
}

function fmt(n) { return (n ?? 0).toLocaleString('en-US'); }

// --- live profile lookup. Resolves to {status, profile} ------------
// status: 'ok' | 'notfound' | 'offline'  (offline → use seeded mock, no error)
async function lookupProfile(username) {
  const u = (username || '').trim();
  if (!u) return { status: 'notfound' };
  try {
    const res = await fetch(`https://api.github.com/users/${encodeURIComponent(u)}`, {
      headers: { Accept: 'application/vnd.github+json' },
    });
    if (res.status === 404) return { status: 'notfound' };
    if (res.status === 403) return { status: 'offline' }; // rate-limited → graceful mock
    if (!res.ok) return { status: 'offline' };
    const profile = await res.json();
    return { status: 'ok', profile };
  } catch (e) {
    return { status: 'offline' };
  }
}

Object.assign(window, {
  hashStr, mulberry32, RANGES, buildStats, fetchRealStats, useCountUp, fmt, lookupProfile,
  useState, useEffect, useRef, useLayoutEffect, useCallback,
});
