// realdata.jsx — bridges the dashboard to a REAL Discord bot HTTP API.
//
// When CONN is connected, REALDATA polls the bot's GET endpoints, then MUTATES the
// existing mock globals (BOT, COMMAND_CATEGORIES, ALL_COMMANDS, GUILDS, LATENCY,
// COMMANDS_HOURLY, TOTAL_*) IN PLACE so every page that reads those globals shows
// real data with zero page rewrites. New data the mock can't represent (shifts,
// requests, logs) is stored on REALDATA itself for pages to read directly.
//
// When NOT connected, nothing is mutated and the original mock data is shown.

const REALDATA = (() => {
  const state = {
    connected: false,
    lastFetch: null,
    version: 0,
    raw: { health: null, metrics: null, commands: null, guilds: null, shifts: null, requests: null, logs: null },
  };
  const listeners = new Set();

  // Public read surfaces for data the mock globals can't hold.
  const out = {
    shifts: { active: [], weekly: { users: [], totalShifts: 0, totalMs: 0 }, recentEvents: [] },
    requests: [],
    logs: [],
    totp: { companies: [], serverNow: 0, fetchedAt: 0 },
    totpHistory: [],
    get connected() { return state.connected; },
    get version() { return state.version; },
    get lastFetch() { return state.lastFetch; },
    get raw() { return state.raw; },
  };

  // Rolling latency history (last 60 samples) — fed from metrics.ping each refresh.
  const latencyHistory = [];

  function subscribe(cb) { listeners.add(cb); return () => listeners.delete(cb); }
  function notify() { state.version++; listeners.forEach((cb) => { try { cb(); } catch {} }); }

  function baseUrl() { return (CONN.endpoint || '').replace(/\/$/, ''); }
  function headers() { return CONN.token ? { 'Authorization': 'Bearer ' + CONN.token } : {}; }

  async function getJSON(path) {
    const res = await fetch(baseUrl() + path, { headers: headers() });
    if (!res.ok) throw new Error('HTTP ' + res.status + ' on ' + path);
    return res.json();
  }

  // ── Mutate mock globals in place from real payloads ────────────────────────
  function applyHealth(h) {
    if (!h) return;
    if (typeof window.BOT === 'object' && window.BOT) {
      Object.assign(window.BOT, {
        tag: h.bot || window.BOT.tag,
        name: h.bot ? String(h.bot).split('#')[0] : window.BOT.name,
        id: h.id || window.BOT.id,
        version: h.version || window.BOT.version,
        node: h.node || window.BOT.node,
        library: h.library || window.BOT.library,
        startedAt: typeof h.uptime === 'number' ? (Date.now() - h.uptime * 1000) : window.BOT.startedAt,
      });
    }
  }

  function applyMetrics(m) {
    if (!m) return;
    // Hourly command volume → mutate COMMANDS_HOURLY in place (old→new, 24 buckets).
    if (Array.isArray(m.hourly) && m.hourly.length && Array.isArray(window.COMMANDS_HOURLY)) {
      window.COMMANDS_HOURLY.length = 0;
      window.COMMANDS_HOURLY.push(...m.hourly.map((n) => Math.max(0, Math.round(Number(n) || 0))));
    }
    // Latency history → mutate LATENCY in place (keep last 60).
    if (typeof m.ping === 'number' && Array.isArray(window.LATENCY)) {
      latencyHistory.push(Math.round(m.ping));
      while (latencyHistory.length > 60) latencyHistory.shift();
      window.LATENCY.length = 0;
      window.LATENCY.push(...latencyHistory);
    }
    // 24h / all-time uses are `let` primitives in data.jsx — reassign in shared scope.
    if (typeof m.uses24h === 'number') { try { TOTAL_USES_24H = m.uses24h; } catch {} }
    if (typeof m.totalUses === 'number') { try { TOTAL_USES_ALL = m.totalUses; } catch {} }
  }

  function applyCommands(c) {
    if (!c || !Array.isArray(c.categories)) return;
    // Rebuild COMMAND_CATEGORIES in place.
    const cats = c.categories.map((cat) => ({
      key: cat.key,
      name: cat.name,
      icon: cat.icon || '•',
      color: cat.color || '#9aa4b2',
      items: (cat.items || []).map((it) => ({
        name: it.name,
        desc: it.desc || '',
        uses: Number(it.uses) || 0,
        ownerOnly: !!it.ownerOnly,
        aliases: it.aliases || [],
      })),
    }));
    if (Array.isArray(window.COMMAND_CATEGORIES)) {
      window.COMMAND_CATEGORIES.length = 0;
      window.COMMAND_CATEGORIES.push(...cats);
    }
    // Rebuild ALL_COMMANDS in place (flattened, with category metadata).
    if (Array.isArray(window.ALL_COMMANDS)) {
      const flat = cats.flatMap((cat) =>
        cat.items.map((it) => ({ ...it, category: cat.key, categoryName: cat.name, color: cat.color }))
      );
      window.ALL_COMMANDS.length = 0;
      window.ALL_COMMANDS.push(...flat);
    }
    // Derived `let` primitives — reassign in shared script scope.
    try { TOTAL_COMMANDS = window.ALL_COMMANDS.length; } catch {}
    if (typeof c.total === 'number') { try { TOTAL_COMMANDS = c.total; } catch {} }
    if (typeof c.totalUses === 'number') { try { TOTAL_USES_ALL = c.totalUses; } catch {} }
    else { try { TOTAL_USES_ALL = window.ALL_COMMANDS.reduce((s, x) => s + (x.uses || 0), 0); } catch {} }
    if (typeof c.uses24h === 'number') { try { TOTAL_USES_24H = c.uses24h; } catch {} }
  }

  const GUILD_COLORS = ['#7cffb2', '#c8a8ff', '#5eead4', '#fb7185', '#fbbf24', '#60a5fa', '#9aa4b2'];
  function initials(name) {
    const parts = String(name || '?').trim().split(/\s+/);
    return ((parts[0] || '?')[0] + (parts[1] ? parts[1][0] : '')).toUpperCase();
  }
  function applyGuilds(gs) {
    if (!Array.isArray(gs) || !Array.isArray(window.GUILDS)) return;
    const mapped = gs.map((g, i) => ({
      id: String(g.id),
      name: g.name || 'guild',
      members: Number(g.members) || 0,
      online: Number(g.online) || 0,
      prefix: '/',
      joined: g.joined ? String(g.joined).slice(0, 10) : '—',
      icon: g.icon || initials(g.name),
      iconUrl: g.icon && /^https?:/.test(String(g.icon)) ? g.icon : null,
      color: GUILD_COLORS[i % GUILD_COLORS.length],
      premium: false,
    }));
    window.GUILDS.length = 0;
    window.GUILDS.push(...mapped);
  }

  function applyShifts(s) {
    out.shifts = {
      active: (s && Array.isArray(s.active)) ? s.active : [],
      weekly: (s && s.weekly) ? {
        users: Array.isArray(s.weekly.users) ? s.weekly.users : [],
        totalShifts: Number(s.weekly.totalShifts) || 0,
        totalMs: Number(s.weekly.totalMs) || 0,
      } : { users: [], totalShifts: 0, totalMs: 0 },
      recentEvents: (s && Array.isArray(s.recentEvents)) ? s.recentEvents : [],
    };
  }

  function applyRequests(r) {
    out.requests = Array.isArray(r) ? r : [];
  }

  function applyLogs(l) {
    const logs = (l && Array.isArray(l.logs)) ? l.logs : [];
    out.logs = logs;
    // Feed the live stream so the Logs page + Overview activity feed show real data.
    if (window.LIVE && typeof window.LIVE.ingestReal === 'function') {
      window.LIVE.ingestReal(logs);
    }
  }

  function applyTotp(t) {
    out.totp = {
      companies: (t && Array.isArray(t.companies)) ? t.companies : [],
      serverNow: (t && t.serverNow) || Date.now(),
      fetchedAt: Date.now(),
    };
  }
  function applyTotpHistory(h) {
    out.totpHistory = (h && Array.isArray(h.history)) ? h.history : [];
  }

  /** ดึงเฉพาะ /totp (ใช้ poll เร็วๆ ในหน้า 2FA) */
  async function fetchTotp() {
    if (!CONN.endpoint) return;
    try {
      const t = await getJSON('/totp');
      applyTotp(t);
      notify();
    } catch {}
  }

  // ── Main refresh: fetch all GET endpoints in parallel, apply what succeeds ──
  async function refreshAll() {
    if (!CONN.endpoint) return;
    const results = await Promise.allSettled([
      getJSON('/health'),
      getJSON('/metrics'),
      getJSON('/commands'),
      getJSON('/guilds'),
      getJSON('/shifts'),
      getJSON('/requests'),
      getJSON('/logs?limit=200'),
      getJSON('/totp'),
      getJSON('/totp/history?limit=100'),
    ]);
    const [health, metrics, commands, guilds, shifts, requests, logs, totp, totpHist] = results;

    // If literally everything failed, treat as disconnected and keep last good data.
    const anyOk = results.some((r) => r.status === 'fulfilled');
    if (!anyOk) { state.connected = false; return; }

    if (health.status === 'fulfilled')   { state.raw.health = health.value;     applyHealth(health.value); }
    if (metrics.status === 'fulfilled')  { state.raw.metrics = metrics.value;   applyMetrics(metrics.value); }
    if (commands.status === 'fulfilled') { state.raw.commands = commands.value; applyCommands(commands.value); }
    if (guilds.status === 'fulfilled')   { state.raw.guilds = guilds.value;     applyGuilds(guilds.value); }
    if (shifts.status === 'fulfilled')   { state.raw.shifts = shifts.value;     applyShifts(shifts.value); }
    if (requests.status === 'fulfilled') { state.raw.requests = requests.value; applyRequests(requests.value); }
    if (logs.status === 'fulfilled')     { state.raw.logs = logs.value;         applyLogs(logs.value); }
    if (totp.status === 'fulfilled')     { applyTotp(totp.value); }
    if (totpHist.status === 'fulfilled') { applyTotpHistory(totpHist.value); }

    // Keep the window.* snapshots in sync with the reassigned `let` primitives so
    // code paths that read window.TOTAL_* (e.g. LIVE.cmdsToday) see fresh values.
    try { window.TOTAL_COMMANDS = TOTAL_COMMANDS; } catch {}
    try { window.TOTAL_USES_ALL = TOTAL_USES_ALL; } catch {}
    try { window.TOTAL_USES_24H = TOTAL_USES_24H; } catch {}

    state.connected = true;
    state.lastFetch = new Date();
    notify();
  }

  // ── Owner actions: POST /actions/:name ─────────────────────────────────────
  async function doAction(name, body) {
    const res = await fetch(baseUrl() + '/actions/' + encodeURIComponent(name), {
      method: 'POST',
      headers: Object.assign({ 'Content-Type': 'application/json' }, headers()),
      body: body ? JSON.stringify(body) : undefined,
    });
    let data = null;
    try { data = await res.json(); } catch {}
    if (!res.ok) throw new Error((data && data.error) || ('HTTP ' + res.status));
    return data || { ok: true };
  }

  // ── Request approve / deny: POST /requests/:id/:decision ────────────────────
  async function requestAction(id, decision) {
    const res = await fetch(
      baseUrl() + '/requests/' + encodeURIComponent(id) + '/' + encodeURIComponent(decision),
      { method: 'POST', headers: headers() },
    );
    let data = null;
    try { data = await res.json(); } catch {}
    if (!res.ok) throw new Error((data && data.error) || ('HTTP ' + res.status));
    await refreshAll();
    return data || { ok: true };
  }

  out.subscribe = subscribe;
  out.refreshAll = refreshAll;
  out.doAction = doAction;
  out.requestAction = requestAction;
  out.fetchTotp = fetchTotp;
  return out;
})();

function useRealData() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => REALDATA.subscribe(force), []);
  return REALDATA;
}

// ── Poller: poll every 5s while connected; stop on disconnect ─────────────────
(() => {
  let timer = null;
  let polling = false;

  function startPolling() {
    if (polling) return;
    polling = true;
    REALDATA.refreshAll().catch(() => {}); // immediate first fetch
    timer = setInterval(() => { REALDATA.refreshAll().catch(() => {}); }, 5000);
  }
  function stopPolling() {
    polling = false;
    if (timer) { clearInterval(timer); timer = null; }
    // hand the log stream back to the simulator
    if (window.LIVE && typeof window.LIVE.setRealMode === 'function') window.LIVE.setRealMode(false);
  }

  CONN.subscribe(() => {
    if (CONN.status === 'connected') startPolling();
    else stopPolling();
  });
})();

Object.assign(window, { REALDATA, useRealData });
