// live-data.jsx — Replaces data.js with live Supabase-backed data when configured.
// Falls back to the mock window.PH365 (already loaded from data.js) when Supabase is unavailable.
// Exposes window.PH365_LIVE { ready, loadAll, refreshWaits } and overwrites window.PH365 in place
// once load completes.
(function () {
  const supabase = window.PH365_SUPABASE;
  const MOCK = window.PH365;  // populated by data.js

  // No Supabase config? Stay on mock.
  if (!supabase) {
    console.info('[PH365] No Supabase configured — using mock data.');
    window.PH365_LIVE = { ready: false, source: 'mock', refreshWaits: async () => {} };
    return;
  }

  // ── Helpers ─────────────────────────────────────────────────────────────
  function landsByPark(landsArr) {
    const out = {};
    for (const l of landsArr) out[l.id] = { id: l.id, name: l.name, park: l.park_id };
    return out;
  }

  // Convert Supabase row → the shape data.js exposed (so screens don't need to change).
  // Falls back to MOCK lat/lon when the DB column is null, so the map keeps working
  // for any attraction we have coordinates for in code even before the DB migration runs.
  function transformAttractions(rows, latestWaits, parkLookup, landLookup) {
    const mockById = new Map((MOCK?.ATTRACTIONS ?? []).map(a => [a.id, a]));
    return rows.map(a => {
      const wait = latestWaits.get(a.id);
      const mock = mockById.get(a.id);
      return {
        id: a.id,
        name: a.name,
        land: a.land_id,                     // screens already key by land id
        park: parkLookup[landLookup[a.land_id]?.park]?.code,
        type: a.type,
        wait: wait?.wait_minutes ?? 0,
        favorite: false,                      // populated from user data once auth exists
        favoriteScore: 0.5,
        height: a.height_requirement ?? 'Any',
        rating: a.rating != null ? Number(a.rating) : null,
        rides: 0,                             // populated from user_visits
        blurb: a.blurb ?? '',
        lastRidden: null,
        history: [],                          // hydrated lazily on demand
        visits: [],
        queueTimesId: a.queue_times_id,
        // Coordinates: prefer DB column, fall back to data.js (mock) so the
        // map renders pins even on schemas that haven't run the lat/lon migration.
        lat: a.lat != null ? Number(a.lat) : (mock?.lat ?? null),
        lon: a.lon != null ? Number(a.lon) : (mock?.lon ?? null),
        // Editorial baseline (1-10) used by the Go-To recommender when no
        // user rating exists. Lives only in data.js — Supabase doesn't store it.
        baselineScore: mock?.baselineScore ?? null,
        isOpen: wait ? wait.wait_minutes != null : true,
      };
    });
  }

  function transformChecklist(rows) {
    // Default-template checklist — until we have per-user checklist rows
    return rows.map(c => ({
      id: c.id,
      name: c.name,
      category: c.category,
      land: c.land_id,
      done: false,
      verb: c.verb,
    }));
  }

  // ── Loader ──────────────────────────────────────────────────────────────
  async function loadAll() {
    try {
      const [parksRes, landsRes, attractionsRes, achievementsRes, checklistRes, waitsRes] = await Promise.all([
        supabase.from('parks').select('*'),
        supabase.from('lands').select('*'),
        supabase.from('attractions').select('*'),
        supabase.from('achievements').select('*'),
        supabase.from('checklist_templates').select('*'),
        // Latest wait per attraction (server-side ordered, dedupe client-side)
        supabase.from('wait_times')
          .select('attraction_id, wait_minutes, recorded_at')
          .order('recorded_at', { ascending: false })
          .limit(500),
      ]);

      for (const r of [parksRes, landsRes, attractionsRes, achievementsRes, checklistRes, waitsRes]) {
        if (r.error) throw r.error;
      }

      // Build latest-wait map (first-seen wins because rows are newest-first)
      const latestWaits = new Map();
      for (const w of waitsRes.data) {
        if (!latestWaits.has(w.attraction_id)) latestWaits.set(w.attraction_id, w);
      }

      const PARKS = {};
      for (const p of parksRes.data) {
        PARKS[p.id] = { code: p.code, name: p.name, short: p.short_name, hours: p.hours };
      }

      const LANDS = {};
      for (const l of landsRes.data) {
        LANDS[l.id] = { id: l.id, name: l.name, park: l.park_id };
      }

      const ATTRACTIONS  = transformAttractions(attractionsRes.data, latestWaits, PARKS, LANDS);
      const ACHIEVEMENTS = achievementsRes.data.map(a => ({
        id: a.id,
        name: a.name,
        desc: a.description,
        tier: a.tier,
        progress: 0,
        total: a.total,
        unlocked: false,
        rare: a.is_rare,
      }));
      const CHECKLIST = transformChecklist(checklistRes.data ?? []);

      // Pull user-specific data only when authenticated
      const { data: { user } } = await supabase.auth.getUser();
      let USER = guestUser();
      let TODAY = { date: friendlyDate(), ridesToday: 0, ridesYear: 0 };

      if (user) {
        const [profileRes, visitsRes, userAchsRes, ratingsRes, userChecklistRes] = await Promise.all([
          supabase.from('user_profiles').select('*').eq('id', user.id).maybeSingle(),
          supabase.from('user_visits').select('attraction_id, visited_at').eq('user_id', user.id),
          supabase.from('user_achievements').select('achievement_id, progress, unlocked, unlocked_at').eq('user_id', user.id),
          supabase.from('user_ratings').select('attraction_id, stars').eq('user_id', user.id),
          supabase.from('user_checklist').select('*').eq('user_id', user.id),
        ]);

        const visits   = visitsRes.data ?? [];
        const userAchs = userAchsRes.data ?? [];
        const ratings  = ratingsRes.data ?? [];
        const userCl   = userChecklistRes.data ?? [];
        const profile  = profileRes.data;

        // Compute counts
        const today = new Date(); today.setHours(0, 0, 0, 0);
        const yearStart = new Date(today.getFullYear(), 0, 1);

        const ridesToday = visits.filter(v => new Date(v.visited_at) >= today).length;
        const ridesYTD   = visits.filter(v => new Date(v.visited_at) >= yearStart).length;
        const daysYTD    = new Set(
          visits
            .filter(v => new Date(v.visited_at) >= yearStart)
            .map(v => new Date(v.visited_at).toISOString().slice(0, 10))
        ).size;
        const unlockedCount = userAchs.filter(a => a.unlocked).length;

        // Per-ride counters: how many times has user ridden each
        const ridesByAttr = new Map();
        const lastRiddenByAttr = new Map();
        for (const v of visits) {
          ridesByAttr.set(v.attraction_id, (ridesByAttr.get(v.attraction_id) ?? 0) + 1);
          const cur = lastRiddenByAttr.get(v.attraction_id);
          if (!cur || new Date(v.visited_at) > new Date(cur)) {
            lastRiddenByAttr.set(v.attraction_id, v.visited_at);
          }
        }
        // Per-ride user rating
        const myStarsByAttr = new Map(ratings.map(r => [r.attraction_id, r.stars]));

        // Mutate ATTRACTIONS to include user-specific overlay fields.
        // Auto-favorite: 3+ rides marks it as a personal favorite, and the
        // bullet-progress on cards uses ride count (capped at 8) as the score.
        // This activates the heart icon + bullet UI that data.js had hardcoded
        // to false — the signal is already in user_visits, just wasn't read.
        for (const a of ATTRACTIONS) {
          a.rides       = ridesByAttr.get(a.id) ?? 0;
          a.lastRidden  = lastRiddenByAttr.get(a.id) ? formatShortDate(lastRiddenByAttr.get(a.id)) : null;
          a.favorite    = a.rides >= 3;
          a.favoriteScore = Math.min(1, a.rides / 8);
          if (myStarsByAttr.has(a.id)) a.userStars = myStarsByAttr.get(a.id);
        }

        // Overlay achievement progress
        const userAchById = new Map(userAchs.map(a => [a.achievement_id, a]));
        for (const ach of ACHIEVEMENTS) {
          const u = userAchById.get(ach.id);
          if (u) {
            ach.progress = u.progress;
            ach.unlocked = u.unlocked;
            ach.date = u.unlocked_at ? formatShortDate(u.unlocked_at) : undefined;
          }
        }

        // Overlay user's checklist toggles onto the templates. Match by
        // template_id first (the post-2026-05-03 source of truth — see
        // 20260503030000_user_checklist_unique.sql) and fall back to name
        // for legacy rows written before template_id was set.
        const doneByTemplate = new Map();
        const doneByName     = new Map();
        for (const u of userCl) {
          if (u.template_id) doneByTemplate.set(u.template_id, u);
          if (u.name)        doneByName.set(u.name, u);
        }
        for (const c of CHECKLIST) {
          const u = doneByTemplate.get(c.id) || doneByName.get(c.name);
          if (u) {
            c.done = u.done;
            c.verb = u.verb ?? c.verb;
            c.date = u.completed_at ? formatShortDate(u.completed_at) : undefined;
            c.userChecklistId = u.id;
          }
        }

        USER = {
          id: user.id,
          name: profile?.display_name || user.email?.split('@')[0] || 'Member',
          email: user.email,
          joined: profile?.joined_at || (user.created_at ?? '').slice(0, 10),
          passType: profile?.pass_type || 'Member',
          passNumber: profile?.pass_number || null,
          visits: visits.length,
          ridesYTD,
          achievements: unlockedCount,
          daysYTD,
          isGuest: false,
          isAdmin: profile?.is_admin === true,
        };
        TODAY = { date: friendlyDate(), ridesToday, ridesYear: ridesYTD };
      } else {
        // Signed-out guest: hydrate from localStorage so checklist toggles
        // and ride check-ins still survive a refresh on a guest device.
        // user-actions.jsx writes to these same keys when there's no auth.
        const guestToday = applyLocalGuestOverlay({ ATTRACTIONS, CHECKLIST });
        TODAY = { date: friendlyDate(), ridesToday: guestToday.ridesToday, ridesYear: guestToday.ridesYear };
      }

      // Overwrite the global so existing screens just work
      window.PH365 = {
        ...MOCK,
        PARKS,
        LANDS,
        ATTRACTIONS,
        ACHIEVEMENTS,
        CHECKLIST,
        TODAY,
        USER,
      };

      window.PH365_LIVE = {
        ready: true,
        source: 'supabase',
        loadedAt: new Date(),
        refreshWaits,
        reloadUser: () => loadAll(),  // call after sign-in / sign-out
      };

      // Notify any listeners (App component) that fresh data arrived
      window.dispatchEvent(new CustomEvent('ph365-data-ready'));
      console.info(`[PH365] Loaded: ${ATTRACTIONS.length} attractions, ${parksRes.data.length} parks, user=${USER.isGuest ? 'guest' : USER.email}`);
    } catch (err) {
      console.error('[PH365] Live data load failed, staying on mock:', err);
      window.PH365_LIVE = { ready: false, source: 'mock', error: err.message, refreshWaits: async () => {} };
    }
  }

  function guestUser() {
    return {
      name: 'Guest', email: null, joined: null, passType: 'Visitor',
      visits: 0, ridesYTD: 0, achievements: 0, daysYTD: 0, isGuest: true,
    };
  }

  // Read localStorage entries written by user-actions when no Supabase
  // session exists, and overlay them onto the in-memory ATTRACTIONS /
  // CHECKLIST so the UI shows the same "done" / "rides" state on refresh
  // as it did before the reload. Returns ride-count summaries for the
  // TODAY card so the home screen still reflects guest activity.
  function applyLocalGuestOverlay({ ATTRACTIONS, CHECKLIST }) {
    let ridesToday = 0;
    let ridesYear  = 0;
    try {
      const visits = JSON.parse(localStorage.getItem('ph365_local_visits') || '[]');
      const today = new Date(); today.setHours(0, 0, 0, 0);
      const yearStart = new Date(today.getFullYear(), 0, 1);
      const ridesByAttr = new Map();
      const lastRiddenByAttr = new Map();
      for (const v of visits) {
        if (!v?.attraction_id) continue;
        ridesByAttr.set(v.attraction_id, (ridesByAttr.get(v.attraction_id) ?? 0) + 1);
        const cur = lastRiddenByAttr.get(v.attraction_id);
        if (!cur || new Date(v.visited_at) > new Date(cur)) {
          lastRiddenByAttr.set(v.attraction_id, v.visited_at);
        }
        const when = new Date(v.visited_at);
        if (when >= today)     ridesToday++;
        if (when >= yearStart) ridesYear++;
      }
      for (const a of ATTRACTIONS) {
        const r = ridesByAttr.get(a.id);
        if (r) {
          a.rides = r;
          a.lastRidden = formatShortDate(lastRiddenByAttr.get(a.id));
          a.favorite = r >= 3;
          a.favoriteScore = Math.min(1, r / 8);
        }
      }
    } catch (e) { console.warn('[PH365] guest visits overlay failed:', e); }

    try {
      const dict = JSON.parse(localStorage.getItem('ph365_local_checklist') || '{}');
      // dict keys are templateId|name; match against either.
      for (const c of CHECKLIST) {
        const u = dict[c.id] || dict[c.name];
        if (u) {
          c.done = !!u.done;
          c.verb = u.verb ?? c.verb;
          c.date = u.completedAt ? formatShortDate(u.completedAt) : undefined;
        }
      }
    } catch (e) { console.warn('[PH365] guest checklist overlay failed:', e); }

    return { ridesToday, ridesYear };
  }

  function formatShortDate(iso) {
    try {
      return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
    } catch { return ''; }
  }

  // ── Wait-time refresh (called every 60s while app is foregrounded) ─────
  // Race the Supabase query against a 6s timeout. Without this, a hung query
  // leaves UI buttons (GoToScreen "Refresh") stuck on "Refreshing…" forever
  // because their finally-block awaits this promise. Trigger the parallel
  // queue-times fetch regardless — that is now the actual source of truth
  // for live wait minutes; this Supabase call is best-effort overlay only.
  async function refreshWaits() {
    const supabasePart = (async () => {
      const timeout = new Promise((_, rej) =>
        setTimeout(() => rej(new Error('supabase wait_times timeout')), 6000));
      const query = supabase
        .from('wait_times')
        .select('attraction_id, wait_minutes, recorded_at')
        .order('recorded_at', { ascending: false })
        .limit(500);
      try {
        const { data, error } = await Promise.race([query, timeout]);
        if (error) throw error;
        if (!data?.length) return;  // No Supabase wait data — leave queue-times values alone

        const latest = new Map();
        for (const w of data) if (!latest.has(w.attraction_id)) latest.set(w.attraction_id, w);

        const NEW = window.PH365.ATTRACTIONS.map(a => {
          const w = latest.get(a.id);
          if (!w) return a;
          return { ...a, wait: w.wait_minutes ?? a.wait, isOpen: w.wait_minutes != null };
        });
        window.PH365 = { ...window.PH365, ATTRACTIONS: NEW };
        window.dispatchEvent(new CustomEvent('ph365-waits-refreshed'));
      } catch (err) {
        console.warn('[PH365] refreshWaits (supabase) failed:', err.message ?? err);
      }
    })();

    // Always also trigger queue-times. It's the live source and finishes
    // even if Supabase is down. Fire-and-await alongside the Supabase part.
    const qtPart = (async () => {
      try { await window.PH365_QT?.refresh?.(); }
      catch (err) { console.warn('[PH365] refreshWaits (queue-times) failed:', err.message ?? err); }
    })();

    await Promise.allSettled([supabasePart, qtPart]);
  }

  function friendlyDate() {
    return new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
  }

  // Kick off
  loadAll();

  // Reload when auth state changes (sign-in / sign-out)
  if (supabase) {
    supabase.auth.onAuthStateChange((event, _session) => {
      if (event === 'SIGNED_IN' || event === 'SIGNED_OUT' || event === 'USER_UPDATED') {
        console.info(`[PH365] Auth event: ${event} — reloading user data`);
        loadAll();
      }
    });
  }
})();
