// ============================================================================
// PhotoQuestProvider — state + upload queue + offline persistence
// ----------------------------------------------------------------------------
// Shape of the world:
//   submissions: {
//     [dayId]: {
//       [slotId]: [ { id, memberId, ts, localBlob?, url?, status } ]
//     }
//   }
//
// Submission flow:
//   1. User picks/takes a photo for slot → createSubmission(dayId, slotId, memberId, blob)
//   2. We stash it in IndexedDB-like localStorage manifest with status:'queued'
//   3. Upload worker picks it up when online and calls QUEST_ADAPTER.upload
//   4. Adapter returns { url } → status:'uploaded', blob removed
//   5. On network loss, items stay queued; retry on 'online' event.
//
// The adapter is a single swap point for Cloudflare R2 + Images:
//   - POST /api/quest/upload  (multipart)
//   - Worker uploads to R2, pipes through Images API for resize/variants
//   - Returns { url, variants: { thumb, full } }
//
// For the prototype, the adapter stores blobs as data: URLs in memory so the
// UI renders real preview images; deploying against R2 only requires changing
// QUEST_ADAPTER.upload — nothing else.
// ============================================================================

const { createContext: pqCC, useContext: pqUC, useState: pqS, useEffect: pqE, useMemo: pqM, useCallback: pqCB, useRef: pqR } = React;

const SUBMISSIONS_KEY = 'v2:quest:submissions';
const QUEUE_KEY       = 'v2:quest:queue';
const BADGES_KEY      = 'v2:quest:badges';

// ---- Adapter ---------------------------------------------------------------
const QUEST_ADAPTER = {
  // Upload one submission. Real impl: multipart POST to Worker.
  async upload(submission) {
    // Simulate 600-1800ms network. Sometimes fails so we can see retries.
    await new Promise((r) => setTimeout(r, 600 + Math.random() * 1200));
    if (Math.random() < 0.05) throw new Error('network timeout');
    // Return the object URL already in the submission as "uploaded url" for demo.
    return { url: submission.localBlobUrl, variants: { thumb: submission.localBlobUrl, full: submission.localBlobUrl } };
  },
};

// ---- Storage helpers -------------------------------------------------------
function loadJSON(key, fallback) {
  try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : fallback; }
  catch { return fallback; }
}
function saveJSON(key, v) {
  try { localStorage.setItem(key, JSON.stringify(v)); } catch {}
}

// ---- Member currently acting-as --------------------------------------------
// Reads from the shared v2:actor key written by AccessGate on login.
const ACTOR_KEY = 'v2:actor';
function currentActor() {
  try { return localStorage.getItem(ACTOR_KEY) || (window.TEAM && window.TEAM[0] && window.TEAM[0].id) || 'robert'; }
  catch { return 'robert'; }
}
function setActor(id) {
  try { localStorage.setItem(ACTOR_KEY, id); } catch {}
  window.dispatchEvent(new CustomEvent('v2:actorChanged', { detail: id }));
}

// ---- Context ---------------------------------------------------------------
const PhotoQuestContext = pqCC(null);

function PhotoQuestProvider({ children }) {
  const [submissions, setSubmissions] = pqS(() => loadJSON(SUBMISSIONS_KEY, {}));
  const [queue, setQueue] = pqS(() => loadJSON(QUEUE_KEY, []));
  const [badges, setBadges] = pqS(() => loadJSON(BADGES_KEY, {}));
  const [actor, setActorState] = pqS(currentActor);
  const [online, setOnline] = pqS(() => (typeof navigator !== 'undefined' ? navigator.onLine !== false : true));
  const [completionEvent, setCompletionEvent] = pqS(null); // transient, drives confetti overlay
  const processingRef = pqR(false);

  // Persist
  pqE(() => saveJSON(SUBMISSIONS_KEY, submissions), [submissions]);
  pqE(() => saveJSON(QUEUE_KEY, queue), [queue]);
  pqE(() => saveJSON(BADGES_KEY, badges), [badges]);

  // Actor sync
  pqE(() => {
    const h = (e) => setActorState(e.detail);
    window.addEventListener('v2:actorChanged', h);
    return () => window.removeEventListener('v2:actorChanged', h);
  }, []);

  // Network
  pqE(() => {
    const up = () => setOnline(true);
    const down = () => setOnline(false);
    window.addEventListener('online', up);
    window.addEventListener('offline', down);
    return () => { window.removeEventListener('online', up); window.removeEventListener('offline', down); };
  }, []);

  // Process queue
  const processQueue = pqCB(async () => {
    if (processingRef.current) return;
    if (!online) return;
    processingRef.current = true;
    try {
      let q = [...queue];
      while (q.length) {
        const item = q[0];
        // Mark the matching submission as 'uploading'
        setSubmissions((s) => mutSubStatus(s, item, 'uploading'));
        try {
          const { url } = await QUEST_ADAPTER.upload(item);
          setSubmissions((s) => mutSubFinalize(s, item, url));
          q = q.slice(1);
          setQueue(q);
        } catch (err) {
          // Backoff retry: move to end with bumped attempts
          const bumped = { ...item, attempts: (item.attempts || 0) + 1, lastError: String(err) };
          if (bumped.attempts >= 5) {
            // Give up
            setSubmissions((s) => mutSubStatus(s, item, 'error'));
            q = q.slice(1);
          } else {
            q = [...q.slice(1), bumped];
          }
          setQueue(q);
          await new Promise((r) => setTimeout(r, 1000 * bumped.attempts));
        }
      }
    } finally { processingRef.current = false; }
  }, [queue, online]);

  // Kick the queue whenever online flips true or queue changes
  pqE(() => { if (online && queue.length) processQueue(); }, [online, queue.length]); // eslint-disable-line

  // ---- Mutations exposed to UI ---------------------------------------------
  const createSubmission = pqCB(async ({ dayId, slotId, blob }) => {
    const id = 'sub_' + Math.random().toString(36).slice(2, 10);
    const memberId = actor;
    const localBlobUrl = blob ? URL.createObjectURL(blob) : null;
    const sub = { id, dayId, slotId, memberId, ts: Date.now(), localBlobUrl, url: null, status: online ? 'queued' : 'offline-queued' };
    setSubmissions((s) => {
      const day = { ...(s[dayId] || {}) };
      const slot = [...(day[slotId] || []), sub];
      day[slotId] = slot;
      return { ...s, [dayId]: day };
    });
    setQueue((q) => [...q, { submissionId: id, dayId, slotId, memberId, localBlobUrl }]);
    // Check for day completion after insert (use next tick to let state settle)
    setTimeout(() => maybeCompleteDay(dayId), 40);
    return id;
  }, [actor, online]);

  const deleteSubmission = pqCB(({ dayId, slotId, submissionId }) => {
    setSubmissions((s) => {
      const day = { ...(s[dayId] || {}) };
      day[slotId] = (day[slotId] || []).filter((x) => x.id !== submissionId);
      if (!day[slotId].length) delete day[slotId];
      return { ...s, [dayId]: day };
    });
    setQueue((q) => q.filter((x) => x.submissionId !== submissionId));
  }, []);

  const maybeCompleteDay = pqCB((dayId) => {
    const quest = (window.PHOTO_QUESTS || {})[dayId];
    if (!quest) return;
    // Count filled slots (at least 1 submission per slot from ANY member)
    const daySubs = (submissionsRef.current || submissions)[dayId] || {};
    const allSlots = [...quest.concrete, ...quest.creative].map((s) => s.id);
    const filled = allSlots.filter((id) => (daySubs[id] || []).length > 0).length;
    if (filled === allSlots.length && !badges[dayId]) {
      setBadges((b) => ({ ...b, [dayId]: { ts: Date.now() } }));
      setCompletionEvent({ dayId, ts: Date.now() });
    }
  }, [submissions, badges]);

  // Keep a ref of submissions so completion check sees latest value
  const submissionsRef = pqR(submissions);
  pqE(() => { submissionsRef.current = submissions; }, [submissions]);

  // ---- Selectors -----------------------------------------------------------
  const selectors = pqM(() => ({
    getDaySummary(dayId) {
      const quest = (window.PHOTO_QUESTS || {})[dayId];
      if (!quest) return { total: 0, filled: 0, pct: 0, concreteFilled: 0, creativeFilled: 0 };
      const daySubs = submissions[dayId] || {};
      const concreteFilled = quest.concrete.filter((s) => (daySubs[s.id] || []).length > 0).length;
      const creativeFilled = quest.creative.filter((s) => (daySubs[s.id] || []).length > 0).length;
      const filled = concreteFilled + creativeFilled;
      return { total: quest.total, filled, pct: quest.total ? Math.round((filled / quest.total) * 100) : 0, concreteFilled, creativeFilled };
    },
    getSlotSubmissions(dayId, slotId) {
      return (submissions[dayId] || {})[slotId] || [];
    },
    getMemberStats(memberId) {
      let total = 0;
      Object.values(submissions).forEach((day) => {
        Object.values(day).forEach((slot) => {
          slot.forEach((s) => { if (s.memberId === memberId) total++; });
        });
      });
      return { contributions: total };
    },
    getLeaderboard() {
      const team = window.TEAM || [];
      return team.map((m) => ({ ...m, contributions: this.getMemberStats(m.id).contributions }))
        .sort((a, b) => b.contributions - a.contributions);
    },
    getTodayDayId() {
      // Find first day matching today's date, else first day.
      const todayStr = new Date().toISOString().slice(0, 10);
      const days = window.DAYS || [];
      const match = days.find((d) => d.date === todayStr);
      return (match || days[0] || { id: null }).id;
    },
    hasBadge(dayId) { return !!badges[dayId]; },
    getAllBadges() { return Object.keys(badges); },
  }), [submissions, badges]);

  const value = pqM(() => ({
    submissions, queue, badges, actor, online,
    setActor,
    createSubmission, deleteSubmission,
    completionEvent,
    clearCompletionEvent: () => setCompletionEvent(null),
    ...selectors,
  }), [submissions, queue, badges, actor, online, completionEvent, selectors]);

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

// Helper mutators used by processQueue
function mutSubStatus(s, item, status) {
  const day = { ...(s[item.dayId] || {}) };
  const slot = (day[item.slotId] || []).map((x) => x.id === item.submissionId ? { ...x, status } : x);
  day[item.slotId] = slot;
  return { ...s, [item.dayId]: day };
}
function mutSubFinalize(s, item, url) {
  const day = { ...(s[item.dayId] || {}) };
  const slot = (day[item.slotId] || []).map((x) => x.id === item.submissionId ? { ...x, status: 'uploaded', url } : x);
  day[item.slotId] = slot;
  return { ...s, [item.dayId]: day };
}

function usePhotoQuest() {
  const v = pqUC(PhotoQuestContext);
  if (!v) throw new Error('usePhotoQuest must be used inside PhotoQuestProvider');
  return v;
}

Object.assign(window, { PhotoQuestProvider, usePhotoQuest, QUEST_ADAPTER });
