// app.jsx — Pathline learning tracker: state, routing, theme, assembly

const { useState, useEffect, useCallback } = React;
const TITLES = { map:'Learning map', today:'Today', topics:'Topics', topicDetail:'Topic',
  materials:'Study materials', review:'Study materials', friends:'Friends', friendDetail:'Friends', ideas:'Ideas', rocky:'Ask Rocky', settings:'Settings' };

// Content signature for a material, used to detect duplicates. Concept maps are
// stored per-topic, so any two map materials for one topic are the same content.
function matContentSig(m) {
  try {
    if (m.type === 'deck') return JSON.stringify((window.FLASHCARDS || {})[m.id] || null);
    if (m.type === 'summary') return JSON.stringify((window.SUMMARIES || {})[m.id] || null);
    if (m.type === 'map') return JSON.stringify((window.CONCEPT_MAPS || {})[m.topic] || null);
  } catch (e) {}
  return null;
}
// Collapse duplicate materials: same topic + type with identical generated
// content become one (keep the first / newest). Materials generated from "My
// Collection" and "Study Materials" land on the same topic, so this merges them.
function dedupeMaterials(list) {
  const seen = new Set();
  const out = [];
  for (const m of list) {
    if (m.type === 'map') { const k = 'map|' + m.topic; if (seen.has(k)) continue; seen.add(k); out.push(m); continue; }
    const sig = matContentSig(m);
    const k = m.topic + '|' + m.type + '|' + (sig == null ? '#' + m.id : sig);
    if (seen.has(k)) continue;
    seen.add(k);
    out.push(m);
  }
  return out;
}

function App() {
  const [route, setRoute] = useState(() => {
    const r = localStorage.getItem('pl-route') || 'map';
    return (r === 'review' || r === 'topicDetail' || r === 'friendDetail') ? (r === 'friendDetail' ? 'friends' : 'map') : r;
  });
  const [topicId, setTopicId] = useState('cell');
  const [friendId, setFriendId] = useState('mara');
  const [material, setMaterial] = useState(null);
  const [query, setQuery] = useState('');
  const [filterDomain, setFilterDomain] = useState('all');
  const [materials, setMaterials] = useState(() => dedupeMaterials([...MATERIALS]));
  const [topics, setTopics] = useState(() => [...TOPICS]);
  const addTopic = useCallback((name, domain) => {
    const t = { id:'t-' + Date.now(), domain, name:name.trim(), notes:0, progress:0, due:0,
      pos:[20 + Math.random()*60, 20 + Math.random()*60] };
    // Register in the global arrays too, so topicById / materialsOf / the map see it.
    try { if (!TOPICS.some(x => x.id === t.id)) TOPICS.push(t); } catch (e) {}
    setTopics(prev => [...prev, t]);
    return t;
  }, []);
  const addMaterials = useCallback((mats) => {
    try { mats.forEach(m => { if (!MATERIALS.some(x => x.id === m.id)) MATERIALS.unshift(m); }); const dd = dedupeMaterials(MATERIALS); MATERIALS.length = 0; MATERIALS.push(...dd); } catch (e) {}
    setMaterials(prev => dedupeMaterials([...mats.filter(m => !prev.some(p => p.id === m.id)), ...prev]));
  }, []);
  const [domainV, setDomainV] = useState(0); // bump to re-render after a custom domain is added
  // Record a user-defined domain into the global DOMAINS so it shows everywhere
  // (topic chips, filters, the learning map). Returns the new or existing domain.
  const addDomain = useCallback((name) => {
    const nm = (name || '').trim();
    if (!nm) return null;
    const existing = DOMAINS.find(d => d.name.toLowerCase() === nm.toLowerCase());
    if (existing) return existing;
    const palette = Object.values(ACCENT);
    const d = { id: 'd-' + Date.now(), name: nm, accent: palette[DOMAINS.length % palette.length], pos: [20 + Math.random() * 60, 20 + Math.random() * 60] };
    try { DOMAINS.push(d); } catch (e) {}
    setDomainV(v => v + 1);
    return d;
  }, []);
  const editTopic = useCallback((id, patch) => {
    const apply = (t) => t.id === id ? { ...t, ...patch, name: (patch.name != null ? patch.name.trim() : t.name) || t.name } : t;
    try { const gi = TOPICS.findIndex(x => x.id === id); if (gi >= 0) TOPICS[gi] = apply(TOPICS[gi]); } catch (e) {}
    setTopics(prev => prev.map(apply));
  }, []);
  const deleteTopic = useCallback((id) => {
    try { const gi = TOPICS.findIndex(x => x.id === id); if (gi >= 0) TOPICS.splice(gi, 1); } catch (e) {}
    try { for (let i = MATERIALS.length - 1; i >= 0; i--) if (MATERIALS[i].topic === id) MATERIALS.splice(i, 1); } catch (e) {}
    setTopics(prev => prev.filter(t => t.id !== id));
    setMaterials(prev => prev.filter(m => m.topic !== id));
  }, []);
  const deleteMaterial = useCallback((id) => {
    try { const gi = MATERIALS.findIndex(x => x.id === id); if (gi >= 0) MATERIALS.splice(gi, 1); } catch (e) {}
    setMaterials(prev => prev.filter(m => m.id !== id));
  }, []);
  const [themeMode, setThemeMode] = useState(() => localStorage.getItem('pl-theme') || 'light');
  const [theme, setTheme] = useState('light');
  const [accent, setAccent] = useState(() => localStorage.getItem('pl-accent') || 'blue');
  const [profile, setProfile] = useState(() => {
    let p = { name: 'Avery Reyes', email: 'avery@example.com', avatar: null };
    try { const s = JSON.parse(localStorage.getItem('pl-profile')); if (s) p = { ...p, ...s }; } catch (e) {}
    return p;
  });
  // Dyslexia-friendly font: swaps the whole app to OpenDyslexic via a <html> flag.
  const [dyslexia, setDyslexia] = useState(() => { try { return localStorage.getItem('pl-dyslexia') === '1'; } catch (e) { return false; } });
  // "Read it for me": when on, summaries get a podcast-style audio narration player.
  const [readAloud, setReadAloud] = useState(() => { try { return localStorage.getItem('pl-readaloud') === '1'; } catch (e) { return false; } });
  useEffect(() => { try { localStorage.setItem('pl-readaloud', readAloud ? '1' : '0'); } catch (e) {} }, [readAloud]);
  useEffect(() => {
    try { localStorage.setItem('pl-dyslexia', dyslexia ? '1' : '0'); } catch (e) {}
    document.documentElement.setAttribute('data-dyslexia', dyslexia ? '1' : '0');
  }, [dyslexia]);
  useEffect(() => { try { localStorage.setItem('pl-profile', JSON.stringify(profile)); } catch (e) {} }, [profile]);
  const [streakOpen, setStreakOpen] = useState(true);
  const [capture, setCapture] = useState(false);
  const [gen, setGen] = useState(null); // { topicId, type } | null
  const [toastMsg, setToastMsg] = useState('');
  const [ideaCaptured, setIdeaCaptured] = useState(false);
  const ideaConfirm = useCallback(() => {
    setIdeaCaptured(true);
    window.clearTimeout(ideaConfirm._t); ideaConfirm._t = window.setTimeout(() => setIdeaCaptured(false), 2200);
  }, []);
  // Navigation history — lets a "← Back" return to wherever the user actually came from.
  const [history, setHistory] = useState([]);
  const viewRef = React.useRef({});
  viewRef.current = { route, topicId, friendId, material, filterDomain };

  // theme resolution
  useEffect(() => {
    const mq = window.matchMedia('(prefers-color-scheme: dark)');
    const resolve = () => themeMode === 'system' ? (mq.matches ? 'dark' : 'light') : themeMode;
    const apply = () => { const t = resolve(); setTheme(t); document.documentElement.setAttribute('data-theme', t); };
    apply();
    localStorage.setItem('pl-theme', themeMode);
    if (themeMode === 'system') { mq.addEventListener('change', apply); return () => mq.removeEventListener('change', apply); }
  }, [themeMode]);

  useEffect(() => { localStorage.setItem('pl-route', route); }, [route]);

  // accent color — apply chosen accent to --blue (brighter variant in dark mode)
  useEffect(() => {
    const a = ACCENTS.find(x => x.id === accent) || ACCENTS[0];
    const root = document.documentElement;
    root.style.setProperty('--blue', theme === 'dark' ? a.dark : a.light);
    root.style.setProperty('--blue-ink', theme === 'dark' ? a.dark : a.ink);
    localStorage.setItem('pl-accent', accent);
  }, [accent, theme]);

  const toast = useCallback((msg) => {
    setToastMsg(msg);
    window.clearTimeout(toast._t); toast._t = window.setTimeout(() => setToastMsg(''), 2400);
  }, []);

  const [session, setSession] = useState(null); // { ids:[...], i } sequential review session
  const go = useCallback((r) => { setHistory(h => [...h, viewRef.current]); setCapture(false); setGen(null); setRoute(r); document.querySelector('.content')?.scrollTo(0,0); }, []);
  const goBack = useCallback(() => {
    setHistory(h => {
      setCapture(false); setGen(null);
      const prev = h[h.length - 1];
      if (!prev) { setRoute('map'); return h; }
      setRoute(prev.route);
      if (prev.topicId !== undefined) setTopicId(prev.topicId);
      if (prev.friendId !== undefined) setFriendId(prev.friendId);
      setMaterial(prev.material);
      if (prev.filterDomain !== undefined) setFilterDomain(prev.filterDomain);
      document.querySelector('.content')?.scrollTo(0,0);
      return h.slice(0, -1);
    });
  }, []);
  const openTopic = useCallback((id) => { setTopicId(id); go('topicDetail'); }, [go]);
  const openDomain = useCallback((id) => { setFilterDomain(id); go('topics'); }, [go]);
  const openMaterial = useCallback((idOrObj) => {
    const m = typeof idOrObj === 'string'
      ? (materials.find(x => x.id === idOrObj) || MATERIALS.find(x => x.id === idOrObj)) : idOrObj;
    if (!m) return;
    setSession(null); setMaterial(m); go('review');
  }, [materials, go]);
  // Route a notification's nav descriptor to the right destination.
  const notifNav = useCallback((nav) => {
    if (!nav) { go('today'); return; }
    if (nav.type === 'material') openMaterial(nav.id);
    else if (nav.type === 'topic') openTopic(nav.id);
    else go(nav.route || 'today');
  }, [openMaterial, openTopic, go]);
  // Sequential review session: walk a list of material ids one by one.
  const startSession = useCallback((ids, startIdx = 0) => {
    const list = (ids || []).filter(Boolean);
    if (!list.length) return;
    const i = Math.max(0, Math.min(list.length - 1, startIdx));
    const m = materials.find(x => x.id === list[i]) || MATERIALS.find(x => x.id === list[i]);
    setSession({ ids: list, i });
    if (m) { setMaterial(m); go('review'); }
  }, [materials, go]);
  const sessionGo = useCallback((delta) => {
    setSession(s => {
      if (!s) return s;
      const ni = Math.max(0, Math.min(s.ids.length - 1, s.i + delta));
      if (ni === s.i) return s;
      const m = materials.find(x => x.id === s.ids[ni]) || MATERIALS.find(x => x.id === s.ids[ni]);
      if (m) setMaterial(m);
      return { ...s, i: ni };
    });
  }, [materials]);
  const endSession = useCallback(() => {
    setSession(s => {
      if (s && s.ids) { s.ids.forEach(id => markReviewed(id)); try { window.dispatchEvent(new CustomEvent('pl-reviewed')); } catch (e) {} }
      return null;
    });
    go('today');
  }, [go]);
  const openGenerate = useCallback((tid, type, presetName, replaceId) => setGen({ topicId: tid || null, type, presetName: presetName || null, replaceId: replaceId || null }), []);
  const openFriend = useCallback((id) => { setFriendId(id); go('friendDetail'); }, [go]);
  const openSearchResult = useCallback((r) => {
    if (r.kind === 'topic' || r.kind === 'note') openTopic(r.id);
    else if (r.kind === 'material') openMaterial(r.id);
    else if (r.kind === 'domain') { setFilterDomain(r.id); go('topics'); }
  }, [openTopic, openMaterial, go]);
  const toggleTheme = useCallback(() => setThemeMode(t => (t === 'dark' ? 'light' : 'dark')), []);

  const commitMaterials = useCallback((mats) => {
    const list = Array.isArray(mats) ? mats : (mats ? [mats] : []);
    if (!list.length) return;
    setMaterials(prev => dedupeMaterials([...list, ...prev.filter(p => !list.some(m => m.id === p.id))]));
    try {
      list.forEach(m => { const gi = MATERIALS.findIndex(x => x.id === m.id); if (gi >= 0) MATERIALS.splice(gi, 1); MATERIALS.unshift(m); });
      const dd = dedupeMaterials(MATERIALS); MATERIALS.length = 0; MATERIALS.push(...dd);
    } catch (e) {}
  }, []);
  const onGenDone = (mats, openTarget) => {
    const list = Array.isArray(mats) ? mats : [mats];
    if (!list.length) { setGen(null); return; }
    commitMaterials(list);
    setGen(null);
    openMaterial(openTarget || list[0]);
  };

  let screen;
  if (route === 'map') screen = <MapScreen openTopic={openTopic} openDomain={openDomain} onCapture={() => setCapture(true)} go={go} openMaterial={openMaterial} profile={profile} />;
  else if (route === 'today') screen = <TodayScreen openMaterial={openMaterial} startSession={startSession} openTopic={openTopic} go={go} streakOpen={streakOpen} dismissStreak={() => setStreakOpen(false)} profile={profile} />;
  else if (route === 'topics') screen = <TopicsScreen query={query} openTopic={openTopic} filterDomain={filterDomain} setFilterDomain={setFilterDomain} onCapture={() => setCapture(true)} go={go} goBack={goBack} topics={topics} onAddTopic={addTopic} onAddMaterials={addMaterials} onEditTopic={editTopic} onDeleteTopic={deleteTopic} onAddDomain={addDomain} domainV={domainV} toast={toast} />;
  else if (route === 'topicDetail') screen = <TopicDetailScreen topicId={topicId} topics={topics} materials={materials} openMaterial={openMaterial} openGenerate={openGenerate} go={go} goBack={goBack} onEditTopic={editTopic} onDeleteTopic={deleteTopic} toast={toast} />;
  else if (route === 'materials') screen = <LibraryScreen query={query} materials={materials} openMaterial={openMaterial} openGenerate={openGenerate} onDeleteMaterial={deleteMaterial} toast={toast} />;
  else if (route === 'friends') screen = <FriendsScreen openFriend={openFriend} toast={toast} profile={profile} accent={accent} />;
  else if (route === 'friendDetail') screen = <FriendDetailScreen friendId={friendId} back={() => go('friends')} toast={toast} profile={profile} accent={accent} />;
  else if (route === 'review') screen = material ? <ReviewScreen material={material} go={go} goBack={goBack} openTopic={openTopic} openMaterial={openMaterial} openGenerate={openGenerate} toast={toast} session={session} sessionGo={sessionGo} endSession={endSession} readAloud={readAloud} /> : null;
  else if (route === 'ideas') screen = <IdeasPage toast={toast} openGenerate={openGenerate} />;
  else if (route === 'rocky') screen = <RockyPage toast={toast} />;
  else if (route === 'settings') screen = <SettingsScreen themeMode={themeMode} setThemeMode={setThemeMode} toast={toast}
    accent={accent} setAccent={setAccent} profile={profile} setProfile={setProfile} dyslexia={dyslexia} setDyslexia={setDyslexia} readAloud={readAloud} setReadAloud={setReadAloud} />;

  // map "review"/"topicDetail" to their nav root for active states
  const navRoute = route === 'topicDetail' ? 'topics' : route === 'review' ? 'materials' : route === 'friendDetail' ? 'friends' : route;

  return (
    <div className="app">
      <NavWidget route={navRoute} go={go} onCapture={() => setCapture(true)} dueCount={REVIEW_QUEUE.length} profile={profile} />
      <div className="main">
        <Topbar title={TITLES[route]} query={query} setQuery={setQuery} theme={theme} toggleTheme={toggleTheme}
          onCapture={() => setCapture(true)} go={go} onPick={openSearchResult} profile={profile} onNotifNav={notifNav} />
        <MobileTopbar query={query} setQuery={setQuery} onCapture={() => setCapture(true)} theme={theme} toggleTheme={toggleTheme} go={go} onPick={openSearchResult} onNotifNav={notifNav} />
        <div className="content">{screen}</div>
      </div>
      <MobileNav route={navRoute} go={go} />

      {capture && <QuickCapture onClose={() => setCapture(false)} onSave={(text, meta) => {
        try {
          const note = (text || '').trim();
          if (note) {
            const title = note.length > 72 ? note.slice(0, 72).replace(/\s+\S*$/, '') + '…' : note;
            const idea = {
              id: 'i-c' + Date.now(),
              domain: (meta && meta.domain) || 'sci',
              title,
              blurb: meta && meta.topicName ? ('Captured note · filed under ' + meta.topicName) : 'A note you captured from Quick capture.',
              custom: true, note,
            };
            const prev = JSON.parse(localStorage.getItem('pl-idea-custom') || '[]');
            localStorage.setItem('pl-idea-custom', JSON.stringify([idea, ...prev]));
            try {
              const newList = JSON.parse(localStorage.getItem('pl-idea-new') || '[]');
              localStorage.setItem('pl-idea-new', JSON.stringify([idea.id, ...newList]));
            } catch (e) {}
            window.dispatchEvent(new CustomEvent('pl-idea-added', { detail: idea }));
          }
        } catch (e) {}
        setCapture(false);
        ideaConfirm();
      }} />}
      {gen && <GenerateModal topicId={gen.topicId} presetType={gen.type} presetName={gen.presetName} replaceId={gen.replaceId} materials={materials} onAddTopic={addTopic}
        onClose={() => setGen(null)} onDone={onGenDone} onCommit={commitMaterials} toast={toast} />}
      {toastMsg && <div className="toast"><Icon name="check" size={16} style={{ color:'var(--positive)' }} /> {toastMsg}</div>}
      {ideaCaptured && (
        <div className="capture-confirm" role="status" aria-live="polite">
          <div className="cc-card">
            <span className="cc-ic"><Icon name="check" size={26} /></span>
            <div className="cc-title">Idea captured</div>
            <div className="cc-sub">Saved to your Ideas page</div>
          </div>
        </div>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
