// ============================================================================
// Trip state + connector status layer
// ----------------------------------------------------------------------------
// This is the single source of truth the UI reads from. Every screen should
// read `useTripState()` / `useConnector('weather')` / etc., never poke data
// globals directly. Under the hood this maps onto:
//
//   - window.TRIP / DAYS / POINTS / WEATHER / HOTELS / FLIGHTS  (SSOT JSON)
//   - Cloudflare Worker endpoints for weather (Open-Meteo), nearby
//     (Overpass / OSM), POIs (Tripadvisor)
//
// For the prototype, connectors are simulated — but the shape (phase, status,
// ts, error) is exactly what a Worker fetch wrapper would return, so real
// endpoints can swap in without UI changes.
// ============================================================================

const { createContext: tsCC, useContext: tsUC, useState: tsS, useEffect: tsE, useMemo: tsM, useCallback: tsCB } = React;

// ---- Demo clock ------------------------------------------------------------
// Override the "now" used by the app so the prototype can demo both pre-trip
// and in-trip behaviour. Real build reads actual Date.now().
const DEMO_MODE_KEY = 'v2:demoMode';
const DEMO_MODES = {
  'pre-trip':   { label: 'Pre-trip · 7 days out',   iso: '2026-04-18T14:30:00Z' },
  'travel-day': { label: 'Day 1 · WAW → EWR',       iso: '2026-04-25T10:00:00Z' },
  'in-trip':    { label: 'Day 5 · Grand Canyon',    iso: '2026-04-29T20:45:00Z' }, // 13:45 local PT
  'post-trip':  { label: 'Post-trip · home',        iso: '2026-05-09T18:00:00Z' },
};
const DEFAULT_DEMO_MODE = 'pre-trip';

function currentDemoMode() {
  try {
    const saved = localStorage.getItem(DEMO_MODE_KEY);
    if (saved && DEMO_MODES[saved]) return saved;
  } catch {}
  return DEFAULT_DEMO_MODE;
}
function setDemoMode(mode) {
  try { localStorage.setItem(DEMO_MODE_KEY, mode); } catch {}
  window.dispatchEvent(new CustomEvent('v2:demoModeChanged', { detail: mode }));
}

// ---- Phase + today resolution ---------------------------------------------
function resolveTripPhase(now) {
  const days = window.DAYS || [];
  const trip = window.TRIP || {};
  if (!days.length || !trip.startDate) return { phase: 'unknown', today: null, daysToStart: null };

  const todayStr = now.toISOString().slice(0, 10);
  const todayMin = now.getUTCHours() * 60 + now.getUTCMinutes();

  if (todayStr < trip.startDate) {
    const start = new Date(trip.startDate + 'T00:00:00Z');
    const diff = Math.ceil((start - now) / 86400000);
    return { phase: 'pre-trip', today: null, daysToStart: diff, nowMin: todayMin };
  }
  if (todayStr >= trip.endDate) {
    return { phase: 'post-trip', today: null, daysToStart: 0, nowMin: todayMin };
  }
  const match = days.find((d) => d.date === todayStr) || days[days.length - 1];
  // A day counts as travel-day if any event has kind: 'flight' or 'airport'
  const hasFlight = (match.events || []).some((e) => e.kind === 'flight' || e.kind === 'airport');
  return { phase: hasFlight ? 'travel-day' : 'in-trip', today: match, daysToStart: 0, nowMin: todayMin };
}

// ---- Connector registry ----------------------------------------------------
// Status model mirrors what a Cloudflare Worker wrapper would return:
//   { status, ts, ttl, source, error? }
// status: 'ok' | 'loading' | 'stale' | 'offline' | 'error'
// ts:     when data was last refreshed (ms epoch)
// source: 'live' | 'cache' | 'ssot'

const DEFAULT_CONNECTORS = {
  ssot:       { status: 'ok',      ts: Date.now(), source: 'ssot',  label: 'Trip data',         ttl: null },
  weather:    { status: 'ok',      ts: Date.now() - 11 * 60 * 1000, source: 'live',  label: 'Open-Meteo',        ttl: 30 * 60 * 1000 },
  nearby:     { status: 'stale',   ts: Date.now() - 3 * 3600 * 1000, source: 'cache', label: 'OSM · Overpass',    ttl: 2 * 3600 * 1000 },
  poi:        { status: 'ok',      ts: Date.now() - 45 * 60 * 1000, source: 'live',  label: 'Tripadvisor',       ttl: 6 * 3600 * 1000 },
  flights:    { status: 'ok',      ts: Date.now() - 120 * 60 * 1000, source: 'cache', label: 'Air France',        ttl: 12 * 3600 * 1000 },
};

// ---- Network state ---------------------------------------------------------
function initialNetwork() {
  if (typeof navigator === 'undefined') return { online: true, type: 'unknown' };
  return { online: navigator.onLine !== false, type: 'unknown' };
}

// ---- Context --------------------------------------------------------------
const TripStateContext = tsCC(null);

function TripStateProvider({ children }) {
  const [demoMode, setMode] = tsS(currentDemoMode);
  const [tick, setTick] = tsS(0);
  const [network, setNetwork] = tsS(initialNetwork);
  const [connectors, setConnectors] = tsS(DEFAULT_CONNECTORS);

  // Listen for demo-mode changes
  tsE(() => {
    const h = (e) => setMode(e.detail);
    window.addEventListener('v2:demoModeChanged', h);
    return () => window.removeEventListener('v2:demoModeChanged', h);
  }, []);

  // Tick every 30s so "X min ago" updates without being wasteful
  tsE(() => {
    const id = setInterval(() => setTick((t) => t + 1), 30_000);
    return () => clearInterval(id);
  }, []);

  // Browser online/offline events
  tsE(() => {
    const on = () => setNetwork((n) => ({ ...n, online: true }));
    const off = () => setNetwork((n) => ({ ...n, online: false }));
    window.addEventListener('online', on);
    window.addEventListener('offline', off);
    return () => {
      window.removeEventListener('online', on);
      window.removeEventListener('offline', off);
    };
  }, []);

  const now = tsM(() => {
    const iso = DEMO_MODES[demoMode]?.iso;
    return iso ? new Date(iso) : new Date();
  }, [demoMode, tick]);

  const phase = tsM(() => resolveTripPhase(now), [now]);

  // Manual connector refresh — real build would fetch from Worker
  const refreshConnector = tsCB((key) => {
    setConnectors((c) => ({
      ...c,
      [key]: { ...c[key], status: 'loading' },
    }));
    setTimeout(() => {
      setConnectors((c) => ({
        ...c,
        [key]: { ...c[key], status: 'ok', ts: Date.now(), source: 'live' },
      }));
    }, 800);
  }, []);

  const value = tsM(() => ({
    now,
    phase: phase.phase,
    today: phase.today,
    daysToStart: phase.daysToStart,
    nowMin: phase.nowMin,
    demoMode, setDemoMode,
    connectors, refreshConnector,
    network,
  }), [now, phase, demoMode, connectors, network]);

  return React.createElement(TripStateContext.Provider, { value }, children);
}

function useTripState() {
  const v = tsUC(TripStateContext);
  if (!v) throw new Error('useTripState must be used inside TripStateProvider');
  return v;
}
function useConnector(key) {
  const { connectors, refreshConnector } = useTripState();
  return { ...(connectors[key] || { status: 'unknown', ts: null }), refresh: () => refreshConnector(key) };
}

// ---- Helpers ---------------------------------------------------------------
function formatAgo(ts, now = Date.now()) {
  if (!ts) return '—';
  const diff = now - ts;
  if (diff < 60_000) return 'just now';
  if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m ago`;
  if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h ago`;
  return `${Math.floor(diff / 86400_000)}d ago`;
}

function connectorDot(status) {
  // Returns a CSS color var reference for a status dot.
  switch (status) {
    case 'ok':       return 'var(--status-ok)';
    case 'loading':  return 'var(--status-load)';
    case 'stale':    return 'var(--status-stale)';
    case 'offline':  return 'var(--status-off)';
    case 'error':    return 'var(--status-err)';
    default:         return 'var(--ink-4)';
  }
}

Object.assign(window, {
  TripStateProvider, useTripState, useConnector,
  formatAgo, connectorDot, DEMO_MODES, setDemoMode,
});
