// Global state, DB persistence, date utilities, and Rebuild scheduling.

const IST_OFFSET_MIN = 330;
const REBUILD_INTERVALS = [0, 1, 3, 7, 14, 30, 60, 120];

const CATEGORY_LABELS = {
  python: 'Python',
  api: 'API',
  azure: 'Azure',
  genai: 'GenAI',
  enterprise_repo: 'Enterprise Repo',
  weak: 'Weak',
};

function defaultRebuildState() {
  return {
    version: 1,
    topics: {},
    attempts: {},
    settings: {
      dailyBudgetMinutes: 30,
      focusCount: 4,
    },
    bankFingerprint: '',
    bankSyncedAt: '',
  };
}

function defaultState() {
  return {
    days: {},
    pending: {},
    rebuild: defaultRebuildState(),
    view: 'today',
    _updatedAt: null,
  };
}

function normalizeState(state) {
  const base = defaultState();
  const rebuild = { ...defaultRebuildState(), ...(state?.rebuild || {}) };
  rebuild.topics = rebuild.topics || {};
  rebuild.attempts = rebuild.attempts || {};
  rebuild.settings = { ...defaultRebuildState().settings, ...(rebuild.settings || {}) };
  return {
    ...base,
    ...(state || {}),
    days: state?.days || {},
    pending: state?.pending || {},
    rebuild,
  };
}

function hasMeaningfulState(state) {
  return !!(
    Object.keys(state.days || {}).length ||
    Object.keys(state.pending || {}).length ||
    Object.keys(state.rebuild?.topics || {}).length ||
    Object.keys(state.rebuild?.attempts || {}).length
  );
}

const StoreContext = React.createContext(null);

function StoreProvider({ children }) {
  const remoteHydratedRef = React.useRef(false);
  const saveTimerRef = React.useRef(null);
  const sessionRef = React.useRef(null);
  const [state, setState] = React.useState(() => normalizeState(defaultState()));
  const [sync, setSync] = React.useState({
    configured: !!window.ScheduleSync?.configured,
    signedIn: false,
    email: '',
    status: window.ScheduleSync?.configured ? 'Checking sync...' : 'Cloudflare required',
    pendingEmail: false,
    error: '',
    lastSyncedAt: null,
    hydrated: false,
  });

  const update = React.useCallback((patch) => {
    setState(prev => {
      const next = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch };
      return normalizeState({ ...next, _updatedAt: new Date().toISOString() });
    });
  }, []);

  const pullFromCloud = React.useCallback(async ({ silent = false } = {}) => {
    const syncer = window.ScheduleSync;
    if (!syncer?.configured || !sessionRef.current) return;
    if (!silent) setSync(s => ({ ...s, status: 'Loading cloud data...', error: '' }));
    try {
      const remote = await syncer.loadState();
      if (remote?.payload) {
        const next = normalizeState(remote.payload);
        setState(next);
      }
      remoteHydratedRef.current = true;
      setSync(s => ({ ...s, status: 'Synced', error: '', hydrated: true, lastSyncedAt: new Date().toISOString() }));
    } catch (err) {
      setSync(s => ({ ...s, status: 'Sync error', error: err.message || String(err) }));
    }
  }, []);

  React.useEffect(() => {
    const syncer = window.ScheduleSync;
    if (!syncer?.configured) return;

    let cancelled = false;
    let pollTimer = null;

    async function hydrate(session) {
      if (cancelled) return;
      sessionRef.current = session || null;
      if (!session) {
        remoteHydratedRef.current = false;
        setState(normalizeState(defaultState()));
        setSync(s => ({ ...s, signedIn: false, email: '', status: 'Sign in to load DB', error: '', hydrated: false }));
        return;
      }

      setSync(s => ({
        ...s,
        signedIn: true,
        email: session.user.email || '',
        status: 'Loading cloud data...',
        error: '',
      }));

      try {
        const remote = await syncer.loadState();
        if (cancelled) return;
        setState(remote?.payload ? normalizeState(remote.payload) : normalizeState(defaultState()));
        remoteHydratedRef.current = true;
        setSync(s => ({ ...s, status: 'Synced', error: '', hydrated: true, lastSyncedAt: new Date().toISOString() }));
      } catch (err) {
        setSync(s => ({ ...s, status: 'Sync error', error: err.message || String(err) }));
      }

      if (pollTimer) clearInterval(pollTimer);
      pollTimer = setInterval(() => {
        if (document.visibilityState === 'visible') pullFromCloud({ silent: true });
      }, 30000);
    }

    syncer.getSession().then(hydrate).catch(() => {
      setSync(s => ({ ...s, signedIn: false, status: 'Sign in to sync', error: '' }));
    });
    const unsubscribeAuth = syncer.onAuthChange(hydrate);

    function onFocus() {
      if (sessionRef.current && document.visibilityState === 'visible') pullFromCloud({ silent: true });
    }
    window.addEventListener('focus', onFocus);
    document.addEventListener('visibilitychange', onFocus);

    return () => {
      cancelled = true;
      unsubscribeAuth();
      if (pollTimer) clearInterval(pollTimer);
      window.removeEventListener('focus', onFocus);
      document.removeEventListener('visibilitychange', onFocus);
    };
  }, [pullFromCloud]);

  React.useEffect(() => {
    const syncer = window.ScheduleSync;
    if (!syncer?.configured || !sync.signedIn || !remoteHydratedRef.current) return;
    clearTimeout(saveTimerRef.current);
    setSync(s => ({ ...s, status: 'Saving...' }));
    saveTimerRef.current = setTimeout(() => {
      syncer.saveState(normalizeState(state))
        .then(() => setSync(s => ({ ...s, status: 'Synced', error: '', lastSyncedAt: new Date().toISOString() })))
        .catch(err => setSync(s => ({ ...s, status: 'Sync error', error: err.message || String(err) })));
    }, 800);
    return () => clearTimeout(saveTimerRef.current);
  }, [state, sync.signedIn]);

  const signInToSync = React.useCallback(async (email, password) => {
    const syncer = window.ScheduleSync;
    if (!syncer?.configured) throw new Error('Cloud DB sync is required. Use the deployed Cloudflare Pages site.');
    setSync(s => ({ ...s, pendingEmail: true, status: 'Signing in...', error: '' }));
    try {
      await syncer.signInWithEmail(email, password);
      setSync(s => ({ ...s, pendingEmail: false, status: 'Loading cloud data...', error: '' }));
    } catch (err) {
      setSync(s => ({ ...s, pendingEmail: false, status: 'Sync error', error: err.message || String(err) }));
      throw err;
    }
  }, []);

  const signOutOfSync = React.useCallback(async () => {
    const syncer = window.ScheduleSync;
    clearTimeout(saveTimerRef.current);
    sessionRef.current = null;
    remoteHydratedRef.current = false;
    try {
      if (syncer?.configured) await syncer.signOut();
    } finally {
      window.location.reload();
    }
  }, []);

  const refreshFromCloud = React.useCallback(() => pullFromCloud({ silent: false }), [pullFromCloud]);

  const value = { state, update, sync, signInToSync, signOutOfSync, refreshFromCloud };
  return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>;
}

function useStore() {
  return React.useContext(StoreContext);
}

function nowInIST() {
  const now = new Date();
  const utcMs = now.getTime() + now.getTimezoneOffset() * 60000;
  return new Date(utcMs + IST_OFFSET_MIN * 60000);
}

function fmtDate(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const da = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${da}`;
}

function parseDate(s) {
  const [y, m, d] = String(s || '').split('-').map(Number);
  return new Date(y, m - 1, d);
}

function addDays(dateText, days) {
  const base = parseDate(dateText);
  base.setDate(base.getDate() + Number(days || 0));
  return fmtDate(base);
}

function daysBetween(a, b) {
  const left = new Date(a.getFullYear(), a.getMonth(), a.getDate());
  const right = new Date(b.getFullYear(), b.getMonth(), b.getDate());
  return Math.round((right - left) / (1000 * 60 * 60 * 24));
}

function useToday() {
  const [today, setToday] = React.useState(() => {
    const ist = nowInIST();
    return new Date(ist.getFullYear(), ist.getMonth(), ist.getDate());
  });
  React.useEffect(() => {
    function tick() {
      const ist = nowInIST();
      const next = new Date(ist.getFullYear(), ist.getMonth(), ist.getDate());
      setToday(prev => prev.getTime() === next.getTime() ? prev : next);
    }
    const id = setInterval(tick, 60000);
    window.addEventListener('focus', tick);
    document.addEventListener('visibilitychange', tick);
    return () => {
      clearInterval(id);
      window.removeEventListener('focus', tick);
      document.removeEventListener('visibilitychange', tick);
    };
  }, []);
  return today;
}

function currentPhaseFor(today) {
  const key = fmtDate(today);
  const phases = window.PLAN_DATA.phases || [];
  return phases.find(p => key >= p.start && key <= p.end) || (key < phases[0]?.start ? phases[0] : phases[phases.length - 1]);
}

function plannedDay(dateText) {
  return (window.PLAN_DATA.calendar || []).find(day => day.date === dateText) || null;
}

function dayIsActive(dayState) {
  return !!(dayState && (dayState.done || dayState.note?.trim() || Number(dayState.actualHours || 0) > 0));
}

function rebuildFingerprint(bank) {
  const text = JSON.stringify(bank?.topics || []);
  let hash = 5381;
  for (let i = 0; i < text.length; i += 1) hash = ((hash * 33) ^ text.charCodeAt(i)) >>> 0;
  return `fp_${hash.toString(16)}_${text.length}`;
}

function normalizeBankTopic(topic) {
  return {
    id: topic.id,
    title: topic.title,
    category: topic.category || 'weak',
    priority: topic.priority || 'medium',
    firstRebuildDate: topic.firstRebuildDate,
    occurrenceDates: Array.isArray(topic.occurrenceDates) ? topic.occurrenceDates : [],
    artifactPrompt: topic.artifactPrompt || '',
    questions: Array.isArray(topic.questions) ? topic.questions : [],
    status: 'active',
    stage: 0,
    dueDate: topic.firstRebuildDate,
    started: false,
    weak: false,
    lastRating: '',
    lastReviewedAt: '',
    attemptCount: 0,
  };
}

function syncBankIntoState(prev, bank) {
  const fingerprint = rebuildFingerprint(bank);
  const existing = prev.rebuild?.topics || {};
  const nextTopics = { ...existing };
  (bank?.topics || []).forEach(topic => {
    const normalized = normalizeBankTopic(topic);
    nextTopics[topic.id] = {
      ...normalized,
      ...(existing[topic.id] || {}),
      title: normalized.title,
      category: normalized.category,
      priority: normalized.priority,
      firstRebuildDate: normalized.firstRebuildDate,
      occurrenceDates: normalized.occurrenceDates,
      artifactPrompt: normalized.artifactPrompt,
      questions: normalized.questions,
    };
  });
  return normalizeState({
    ...prev,
    rebuild: {
      ...prev.rebuild,
      topics: nextTopics,
      bankFingerprint: fingerprint,
      bankSyncedAt: new Date().toISOString(),
    },
  });
}

function rebuildTopicsArray(state) {
  return Object.values(state.rebuild?.topics || {}).filter(Boolean);
}

function rebuildAttemptsArray(state) {
  return Object.values(state.rebuild?.attempts || {}).filter(Boolean);
}

function rebuildTopicDue(topic, todayText) {
  return topic.status !== 'archived' && topic.dueDate && topic.dueDate <= todayText;
}

function rebuildDueTopics(state, todayText) {
  return rebuildTopicsArray(state)
    .filter(topic => rebuildTopicDue(topic, todayText))
    .sort((a, b) => {
      if (a.dueDate !== b.dueDate) return String(a.dueDate).localeCompare(String(b.dueDate));
      const weight = { core: 0, high: 1, medium: 2, supporting: 3 };
      const aw = weight[a.priority] ?? 2;
      const bw = weight[b.priority] ?? 2;
      if (aw !== bw) return aw - bw;
      return String(a.title).localeCompare(String(b.title));
    });
}

function nextRebuildSchedule(topic, rating, reviewDate) {
  const stage = Math.max(0, Number(topic.stage || 0));
  const last = REBUILD_INTERVALS.length - 1;
  if (rating === 'weak') {
    return { nextStage: Math.max(1, stage), dueDate: addDays(reviewDate, 1), weak: true };
  }
  const nextStage = Math.min(last, stage + 1);
  if (stage >= last) return { nextStage: last, dueDate: '', weak: rating !== 'strong' };
  const planned = topic.occurrenceDates?.[nextStage] || addDays(reviewDate, REBUILD_INTERVALS[nextStage]);
  if (rating === 'shaky') {
    const shorter = Math.max(1, Math.ceil((REBUILD_INTERVALS[nextStage] || 1) / 2));
    return { nextStage, dueDate: addDays(reviewDate, shorter), weak: true };
  }
  return { nextStage, dueDate: planned > reviewDate ? planned : addDays(reviewDate, Math.max(1, REBUILD_INTERVALS[nextStage] || 1)), weak: false };
}

function completeRebuildTopic(prev, topicId, rating, payload) {
  const topic = prev.rebuild.topics[topicId];
  if (!topic) return prev;
  const reviewDate = payload.reviewDate || fmtDate(nowInIST());
  const schedule = nextRebuildSchedule(topic, rating, reviewDate);
  const attemptId = `att_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
  const attempt = {
    id: attemptId,
    topicId,
    rating,
    stage: Number(topic.stage || 0),
    nextStage: schedule.nextStage,
    dueDate: schedule.dueDate,
    reviewDate,
    artifactText: payload.artifactText || '',
    notes: payload.notes || '',
    answers: payload.answers || {},
    createdAt: new Date().toISOString(),
  };
  return normalizeState({
    ...prev,
    rebuild: {
      ...prev.rebuild,
      attempts: { ...(prev.rebuild.attempts || {}), [attemptId]: attempt },
      topics: {
        ...(prev.rebuild.topics || {}),
        [topicId]: {
          ...topic,
          started: true,
          weak: schedule.weak,
          stage: schedule.nextStage,
          dueDate: schedule.dueDate,
          status: schedule.dueDate ? 'active' : 'completed',
          lastRating: rating,
          lastReviewedAt: new Date().toISOString(),
          attemptCount: Number(topic.attemptCount || 0) + 1,
        },
      },
    },
  });
}

function createPendingItem(title, sourceDate, patch = {}) {
  const id = `p_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
  return {
    id,
    title: String(title || '').trim(),
    sourceDate: sourceDate || fmtDate(nowInIST()),
    targetDate: '',
    status: 'open',
    priority: 'normal',
    note: '',
    createdAt: new Date().toISOString(),
    completedAt: '',
    ...patch,
  };
}

Object.assign(window, {
  StoreProvider,
  useStore,
  fmtDate,
  parseDate,
  addDays,
  daysBetween,
  nowInIST,
  useToday,
  currentPhaseFor,
  plannedDay,
  dayIsActive,
  REBUILD_INTERVALS,
  CATEGORY_LABELS,
  rebuildFingerprint,
  syncBankIntoState,
  rebuildTopicsArray,
  rebuildAttemptsArray,
  rebuildDueTopics,
  completeRebuildTopic,
  createPendingItem,
});
