// Florio Analytics — Gamification tab (real data via window.FlorioAPI).
// Mirrors the existing js/pages/gamification.js data, rebuilt in the new design kit.
(function () {
  const NS = window.HyperChartsDesignSystem_e51a95;
  const { LineChart, BarChart, ProgressRing, Badge } = NS;
  const I = window.HyperIcons;
  const { useState, useEffect } = React;

  const rangeToDays = (r) => ({ Day: 1, Week: 7, Month: 30, Year: 365 }[r] || 30);

  // ── range-independent fetch cache (survives range changes; no failure cached) ──
  const _cache = {};
  async function cached(key, fn, fallback) {
    if (key in _cache) return _cache[key];
    try { const v = await fn(); _cache[key] = v; return v; }
    catch (e) { console.error('[Gamification] ' + key + ' failed:', e); return fallback; }
  }

  const shortDate = (s) => { const d = new Date(s + 'T00:00:00'); return `${d.getDate()}/${d.getMonth() + 1}`; };
  const streakOf = (u) => (typeof u.streakCount === 'number' ? u.streakCount : 0);

  // formatNumber from the data layer (window.FlorioAPI may not be ready at definition time → lazy).
  function fmt(n) { return (window.FlorioAPI && window.FlorioAPI.formatNumber) ? window.FlorioAPI.formatNumber(n) : String(n ?? 0); }

  // Empty-state block used inside ChartCards when a series has no data.
  function Empty({ note }) {
    return (
      <div style={{ minHeight: 140, display: 'grid', placeItems: 'center', textAlign: 'center', gap: 8, padding: 22 }}>
        <span style={{ color: 'var(--text-muted)', display: 'flex' }}>{I.sparkle ? I.sparkle({ size: 24 }) : null}</span>
        <span style={{ fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--text-muted)' }}>{note || 'No data yet'}</span>
      </div>
    );
  }

  function Gamification({ range }) {
    const P = window.PA;
    const [d, setD] = useState(null);

    useEffect(() => {
      let alive = true;
      setD(null);
      (async () => {
        await window.FlorioReady;
        const API = window.FlorioAPI;
        const dates = (range && range.start && range.end)
          ? API.getDateRangeBetween(range.start, range.end)
          : API.getDateRange(rangeToDays(range && range.key ? range.key : range));
        const days = dates.length;

        // Range-DEPENDENT: daily aggregates for the selected range (Total XP, XP/day, Coins/day).
        const aggP = API.fetchDailyAggregates(dates).catch((e) => { console.error('[Gamification] aggregates', e); return {}; });

        // Range-INDEPENDENT (cached across range changes):
        //  • users           → avg streak + streak % thresholds
        //  • recent14        → streak distribution (active last 14 days)
        //  • secure          → level distribution + coin-balance distribution
        //  • recent30 + plantCounts → plant-count distribution (active last 30 days)
        const [agg, users, recent14, secure, plantData] = await Promise.all([
          aggP,
          cached('users', () => API.fetchUsers(10000), []),
          cached('recent14', () => API.fetchRecentUsers(14), []),
          cached('secure', () => API.fetchUserSecureDocs(10000), []),
          cached('plantDist', async () => {
            const ru = await API.fetchRecentUsers(30);
            const counts = await API.fetchPlantCounts(ru);
            return counts;
          }, []),
        ]);
        if (!alive) return;

        // ── KPI stats (from full users list) ──
        const totalUsers = users.length;
        const streaks = users.map(streakOf);
        const streakSum = streaks.reduce((a, b) => a + b, 0);
        const avgStreak = totalUsers > 0 ? (streakSum / totalUsers) : 0;
        const above3 = streaks.filter((s) => s >= 3).length;
        const above7 = streaks.filter((s) => s >= 7).length;
        const pct3 = totalUsers > 0 ? (above3 / totalUsers) * 100 : 0;
        const pct7 = totalUsers > 0 ? (above7 / totalUsers) * 100 : 0;

        // ── Total XP (range-dependent) ──
        const totalXp = API.sumDailyField(agg, 'xpGained');

        // ── XP / coins per day (range-dependent series) ──
        const xpSeries = dates.map((x) => agg[x]?.xpGained ?? 0);
        const coinSeries = dates.map((x) => agg[x]?.coinsEarned ?? 0);
        const xpTotal = xpSeries.reduce((a, b) => a + b, 0);
        const coinTotal = coinSeries.reduce((a, b) => a + b, 0);

        // ── Streak length distribution (active last 14 days, streak ≥ 1), bucketed to
        //    match the kit's compact 5-bar design (full per-value data is summarised here) ──
        const recentStreaks = recent14.map(streakOf).filter((s) => s >= 1);
        const streakItems = [
          { label: '1–2 d', value: recentStreaks.filter((s) => s <= 2).length },
          { label: '3–6 d', value: recentStreaks.filter((s) => s >= 3 && s <= 6).length },
          { label: '7–13 d', value: recentStreaks.filter((s) => s >= 7 && s <= 13).length },
          { label: '14–29 d', value: recentStreaks.filter((s) => s >= 14 && s <= 29).length },
          { label: '30 d+', value: recentStreaks.filter((s) => s >= 30).length },
        ];

        // ── Level distribution (userSecure): bucket by garden.level ──
        const levelCounts = {};
        for (const u of secure) { const lvl = u.garden?.level ?? 0; levelCounts[lvl] = (levelCounts[lvl] || 0) + 1; }
        const sortedLevels = Object.keys(levelCounts).map(Number).sort((a, b) => a - b);
        const levelData = sortedLevels.map((l) => levelCounts[l]);
        const levelLabels = sortedLevels.map((l) => 'Lv ' + l);

        // ── Coin balance distribution (userSecure): buckets of 10 ──
        const coinValues = secure.map((u) => (typeof u.coins === 'number' ? u.coins : 0));
        const maxCoins = Math.max(...coinValues, 0);
        const coinBuckets = Math.floor(maxCoins / 10) + 1;
        const coinDist = new Array(coinBuckets).fill(0);
        for (const c of coinValues) coinDist[Math.floor(c / 10)]++;
        const coinBalData = coinDist;
        const coinBalLabels = coinDist.map((_, i) => `${i * 10}-${i * 10 + 9}`);

        // ── Plant count distribution (active last 30 days, >=1 plant): one bucket per plant count ──
        const withPlants = plantData.filter((c) => c >= 1);
        const maxPlants = Math.max(...withPlants, 0);
        const plantDist = new Array(maxPlants).fill(0);
        for (const c of withPlants) plantDist[c - 1]++;
        const plantData2 = plantDist;
        const plantLabels = plantDist.map((_, i) => String(i + 1));

        setD({
          totalUsers, avgStreak, above3, above7, pct3, pct7,
          totalXp,
          xpSeries, coinSeries, xpTotal, coinTotal, labels: dates.map(shortDate), dayCount: days,
          streakItems, streakHasData: streakItems.some((b) => b.value > 0),
          levelData, levelLabels, levelHasData: levelData.length > 0 && levelData.some((v) => v > 0),
          coinBalData, coinBalLabels, coinBalHasData: coinBalData.length > 0 && coinBalData.some((v) => v > 0),
          plantData: plantData2, plantLabels, plantHasData: plantData2.length > 0 && plantData2.some((v) => v > 0),
        });
      })();
      return () => { alive = false; };
    }, [range]);

    if (!d) {
      return <div style={{ display: 'grid', placeItems: 'center', minHeight: '60vh' }}><div className="pa-spin" style={{ width: 38, height: 38, borderRadius: '50%', border: '3px solid var(--border-default)', borderTopColor: 'var(--app-accent)' }} /></div>;
    }

    const rk = (range && range.key === 'custom') ? (range.start + '_' + range.end) : String((range && range.key) || range); // stable redraw key per range
    // Per-user averages for KPI subtext (guard /0).
    const xpPerUser = d.totalUsers > 0 ? (d.totalXp / d.totalUsers) : 0;
    const xpAvgPerDay = d.dayCount > 0 ? Math.round(d.xpTotal / d.dayCount) : 0;
    const coinAvgPerDay = d.dayCount > 0 ? Math.round(d.coinTotal / d.dayCount) : 0;
    const xpToday = d.xpSeries.length ? d.xpSeries[d.xpSeries.length - 1] : 0;
    const coinToday = d.coinSeries.length ? d.coinSeries[d.coinSeries.length - 1] : 0;

    const lineAction = (val, sub) => (
      <div style={{ textAlign: 'right' }}>
        <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 20, letterSpacing: '-.02em', color: 'var(--text-primary)' }}>{fmt(val)}</div>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)' }}>{sub}</div>
      </div>
    );

    return (
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(12, minmax(0,1fr))', gap: 20 }}>
        {/* KPI row — 5 stat tiles */}
        <div style={{ gridColumn: 'span 12', display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 16 }}>
          <P.StatTile icon={I.flame({ size: 18 })} label="Average streak" value={`${d.avgStreak.toFixed(1)} days`} sub={`${fmt(d.totalUsers)} users`} accent="#FE7E07" />
          <P.StatTile icon={I.target({ size: 18 })} label="Users > 3-day streak" value={`${d.pct3.toFixed(1)}%`} sub={`${fmt(d.above3)} users`} accent="#2ACF56" />
          <P.StatTile icon={I.trophy({ size: 18 })} label="Users > 7-day streak" value={`${d.pct7.toFixed(1)}%`} sub={`${fmt(d.above7)} users`} accent="#B771F3" />
          <P.StatTile icon={I.sparkle({ size: 18 })} label="Total XP earned" value={fmt(d.totalXp)} sub={`${fmt(Math.round(xpPerUser))} avg per user`} accent="#2BB0A6" />
          <P.StatTile icon={I.coin({ size: 18 })} label="Total coins earned" value={fmt(d.coinTotal)} sub={`${fmt(coinAvgPerDay)} / day avg`} accent="#5B8DEF" />
        </div>

        {/* Streak distribution (rings + bars) + Level distribution */}
        <P.ChartCard title="Streak retention" eyebrow="Users active in last 14 days" span={4}>
          <div style={{ display: 'flex', gap: 14, justifyContent: 'space-around', paddingTop: 2, marginBottom: 16 }}>
            <ProgressRing key={'s3' + rk} value={Number(d.pct3.toFixed(1))} size={116} label="3-DAY+" color="#2ACF56" />
            <ProgressRing key={'s7' + rk} value={Number(d.pct7.toFixed(1))} size={116} label="7-DAY+" color="#B771F3" />
          </div>
          <div style={{ fontFamily: 'var(--font-mono-display)', fontSize: 10, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--text-muted)', borderTop: '1px solid var(--border-subtle)', paddingTop: 14, marginBottom: 12 }}>Streak length</div>
          {d.streakHasData
            ? <P.DistBars color="#FE7E07" items={d.streakItems} />
            : <Empty note="No active users with streaks yet" />}
        </P.ChartCard>

        <P.ChartCard title="Level distribution" eyebrow="Users per garden level" span={8}>
          {d.levelHasData
            ? <BarChart key={'lv' + rk} width={680} height={220} showValues={false}
                gradient={['#8338EC', '#C9A9FF']}
                data={d.levelData}
                labels={d.levelLabels}
                style={{ width: '100%' }} />
            : <Empty note="No level data yet" />}
        </P.ChartCard>

        {/* XP & coins per day (range-dependent) */}
        <P.ChartCard title="XP earned per day" eyebrow={`Last ${d.dayCount} days`} span={6} action={lineAction(xpToday, 'latest')}>
          {d.xpTotal > 0
            ? <React.Fragment>
                <P.LineChart series={[{ data: d.xpSeries, color: '#2BB0A6', name: 'XP' }]} labels={d.labels} height={200} />
                <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 12, fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--text-muted)' }}>
                  <span>range total <b style={{ color: 'var(--text-secondary)' }}>{fmt(d.xpTotal)} XP</b></span>
                  <span>avg <b style={{ color: 'var(--text-secondary)' }}>{fmt(xpAvgPerDay)} / day</b></span>
                </div>
              </React.Fragment>
            : <Empty note="No XP earned in range" />}
        </P.ChartCard>

        <P.ChartCard title="Coins earned per day" eyebrow={`Last ${d.dayCount} days`} span={6} action={lineAction(coinToday, 'latest')}>
          {d.coinTotal > 0
            ? <React.Fragment>
                <P.LineChart series={[{ data: d.coinSeries, color: '#FE7E07', name: 'Coins' }]} labels={d.labels} height={200} />
                <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 12, fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--text-muted)' }}>
                  <span>range total <b style={{ color: 'var(--text-secondary)' }}>{fmt(d.coinTotal)}</b></span>
                  <span>avg <b style={{ color: 'var(--text-secondary)' }}>{fmt(coinAvgPerDay)} / day</b></span>
                </div>
              </React.Fragment>
            : <Empty note="No coins earned in range" />}
        </P.ChartCard>

        {/* Distributions */}
        <P.ChartCard title="Coin balance distribution" eyebrow="All users · buckets of 10" span={6}>
          {d.coinBalHasData
            ? <BarChart key={'cb' + rk} width={520} height={212} showValues gap={0.4} radius={7}
                gradient={['#5B8DEF', '#AEC8F8']}
                data={d.coinBalData}
                labels={d.coinBalLabels}
                style={{ width: '100%' }} />
            : <Empty note="No coin data yet" />}
        </P.ChartCard>

        <P.ChartCard title="Plant count distribution" eyebrow="Users active in last 30 days" span={6}>
          {d.plantHasData
            ? <BarChart key={'pc' + rk} width={520} height={212} showValues gap={0.4} radius={7}
                gradient={['#2ACF56', '#9BE8B6']}
                data={d.plantData}
                labels={d.plantLabels}
                style={{ width: '100%' }} />
            : <Empty note="No plant data yet" />}
        </P.ChartCard>
      </div>
    );
  }

  // Register under a Florio-owned namespace. NOTE: _ds_bundle.js itself defines a
  // MOCK window.PAGamification — keying off that would leak fake data, so we must NOT.
  (window.FlorioTabs = window.FlorioTabs || {}).gamification = Gamification;
})();
