// Events store — backed by the Cloudflare Workers API.
//
// The hooks keep the same shapes the UI expects:
//   useEventsStore()    → [events, mutators]   (admin-aware: returns full list incl. archived)
//   useActiveEvents()   → events filtered to !archived (landing page)
//   useEventByCode(code)→ single row from the full list
//
// Internally a singleton in-memory cache keeps every consumer in sync. On
// mount the first hook fetches the appropriate endpoint; subsequent mutations
// patch the cache and fire a 'eventschange' event so other mounted hooks
// re-render.

const _eventsCache = { list: null, loading: false };
const EVENTS_PUBLIC_URL = '/api/events';
const EVENTS_ADMIN_URL  = '/api/admin/events';

async function _fetchEvents(adminMode) {
  const path = adminMode ? EVENTS_ADMIN_URL : EVENTS_PUBLIC_URL;
  const data = await window.api.apiGet(path);
  return Array.isArray(data?.events) ? data.events : [];
}

function _publishEvents(list) {
  _eventsCache.list = list;
  window.dispatchEvent(new CustomEvent('eventschange', { detail: list }));
}

// ── public hook ────────────────────────────────────────────────────────────
function useActiveEvents() {
  const [events, setEvents] = React.useState(() => _eventsCache.list || []);
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(_eventsCache.list === null);

  React.useEffect(() => {
    const onChange = (e) => setEvents(e.detail || []);
    window.addEventListener('eventschange', onChange);

    if (_eventsCache.list === null && !_eventsCache.loading) {
      _eventsCache.loading = true;
      _fetchEvents(false)
        .then(list => { _publishEvents(list); setError(null); })
        .catch(err => setError(err))
        .finally(() => { _eventsCache.loading = false; setLoading(false); });
    } else {
      setLoading(false);
    }

    return () => window.removeEventListener('eventschange', onChange);
  }, []);

  // Filter out archived even though the public endpoint already excludes them
  // — admin-mode loads inject archived rows into the cache.
  return events.filter(e => !e.archived);
}

// ── admin hook ─────────────────────────────────────────────────────────────
function useEventsStore() {
  const [events, setEvents] = React.useState(() => _eventsCache.list || []);
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(_eventsCache.list === null);
  const adminMode = window.api.hasAdminToken();

  React.useEffect(() => {
    const onChange = (e) => setEvents(e.detail || []);
    window.addEventListener('eventschange', onChange);

    // Admin hook always re-fetches via the admin endpoint to pick up archived
    // rows that the public endpoint hid.
    if (_eventsCache.list === null || (adminMode && !_eventsCache._adminFetched)) {
      _eventsCache.loading = true;
      _fetchEvents(adminMode)
        .then(list => {
          _eventsCache._adminFetched = adminMode;
          _publishEvents(list);
          setError(null);
        })
        .catch(err => setError(err))
        .finally(() => { _eventsCache.loading = false; setLoading(false); });
    } else {
      setLoading(false);
    }

    return () => window.removeEventListener('eventschange', onChange);
  }, [adminMode]);

  // Normalize a partial PATCH response back into the cache.
  const _replace = (next) => {
    const list = (_eventsCache.list || []).map(e => e.code === next.code ? next : e);
    if (!list.some(e => e.code === next.code)) list.push(next);
    _publishEvents(list);
  };

  const update = React.useCallback(async (code, patch) => {
    const r = await window.api.apiPatch(`/api/admin/events/${encodeURIComponent(code)}`, patch);
    if (r?.event) _replace(r.event);
  }, []);

  const archive = React.useCallback(async (code) => {
    const r = await window.api.apiPost(`/api/admin/events/${encodeURIComponent(code)}/archive`);
    if (r?.event) _replace(r.event);
  }, []);
  const unarchive = React.useCallback(async (code) => {
    const r = await window.api.apiPost(`/api/admin/events/${encodeURIComponent(code)}/unarchive`);
    if (r?.event) _replace(r.event);
  }, []);

  const create = React.useCallback(async (event) => {
    const r = await window.api.apiPost('/api/admin/events', event);
    if (r?.event) {
      const list = [...(_eventsCache.list || []), r.event];
      _publishEvents(list);
    }
  }, []);

  // remove(code) blocks if signups exist (server returns HAS_SIGNUPS).
  // remove(code, { force: true }) cascades and deletes signups + waivers.
  const remove = React.useCallback(async (code, opts = {}) => {
    const qs = opts.force ? '?force=true' : '';
    await window.api.apiDelete(`/api/admin/events/${encodeURIComponent(code)}${qs}`);
    const list = (_eventsCache.list || []).filter(e => e.code !== code);
    _publishEvents(list);
  }, []);

  // No-op in API mode: defaults are seeded server-side via npm run db:seed.
  // Keep the function so existing UI doesn't NPE.
  const reset = React.useCallback(async () => {
    if (!window.confirm('Reset events to defaults? Run `npm run db:reset:local` on the backend first — this hook is now a no-op.')) return;
  }, []);

  return [events, { update, archive, unarchive, create, remove, reset, error, loading }];
}

// Lookup by code from the full list.
function useEventByCode(code) {
  const [events] = useEventsStore();
  return events.find(e => e.code === code) || null;
}

Object.assign(window, {
  useEventsStore,
  useActiveEvents,
  useEventByCode,
});
