/* ============================================================
   Glafia Dex — app shell, state, persistence, keyboard
============================================================ */
const NEWBOX_EMOJI = ['📦','🍱','🥡','🫕','🧆','🍲','🥗','🍰','🌙','🔥'];
const BOX_COLORS = ['#2f9fd0','#e8896b','#f0b41f','#6cbf4a','#a877c8','#bf9243','#5aa0a0','#d06a9a','#7c9cd0'];
function loadJSON(key, fb){ try { const v = JSON.parse(localStorage.getItem(key)); return v == null ? fb : v; } catch { return fb; } }

function App(){
  const [recipes, setRecipes] = React.useState(() => loadJSON('glafia-recipes', window.DEX.recipes));
  const [collections, setCollections] = React.useState(() => {
    const saved = loadJSON('glafia-collections', window.DEX.collections);
    return saved.map((c, i) => ({ ...c, color: c.color || BOX_COLORS[i % BOX_COLORS.length] }));
  });
  const [pantry, setPantry] = React.useState(() => loadJSON('glafia-pantry', window.DEX.pantry));
  const [favourites, setFavourites] = React.useState(() => new Set(loadJSON('glafia-favs', window.DEX.favourites)));
  const [have, setHave] = React.useState(() => new Set(loadJSON('glafia-have', [])));
  const [collIndex, setCollIndex] = React.useState(0);
  const [selectedId, setSelectedId] = React.useState((window.DEX.favourites[0]) || window.DEX.recipes[0].id);
  const [view, setView] = React.useState('box');
  const [editId, setEditId] = React.useState(null);
  const [showBoxList, setShowBoxList] = React.useState(false);
  const [toast, setToast] = React.useState(null);
  const isMobile = useIsMobile();
  const applyingRef = React.useRef(false);
  const firstRef = React.useRef(true);

  React.useEffect(() => { try { localStorage.setItem('glafia-recipes', JSON.stringify(recipes)); } catch {} }, [recipes]);
  React.useEffect(() => { try { localStorage.setItem('glafia-collections', JSON.stringify(collections)); } catch {} }, [collections]);
  React.useEffect(() => { try { localStorage.setItem('glafia-pantry', JSON.stringify(pantry)); } catch {} }, [pantry]);
  React.useEffect(() => { try { localStorage.setItem('glafia-favs', JSON.stringify([...favourites])); } catch {} }, [favourites]);
  React.useEffect(() => { try { localStorage.setItem('glafia-have', JSON.stringify([...have])); } catch {} }, [have]);

  // ---------- automatic cross-device sync (Cloudflare D1 via /api/data) ----------
  // One shared cookbook for this site — every device reads & writes the same data.
  const SYNC_KEY = 'glafia-cookbook';
  function bundle(){ return { recipes, collections, pantry, favourites: [...favourites], have: [...have] }; }
  function applyRemote(data){
    if (!data) return;
    applyingRef.current = true;
    if (data.recipes) setRecipes(data.recipes);
    if (data.collections) setCollections(data.collections.map((c, i) => ({ ...c, color: c.color || BOX_COLORS[i % BOX_COLORS.length] })));
    if (data.pantry) setPantry(data.pantry);
    if (data.favourites) setFavourites(new Set(data.favourites));
    if (data.have) setHave(new Set(data.have));
    setTimeout(() => { applyingRef.current = false; }, 0);
  }
  async function pull(){
    try {
      const r = await fetch('/api/data', { headers: { 'x-sync-code': SYNC_KEY } });
      if (!r.ok) return;
      const j = await r.json();
      const localUpdated = +(localStorage.getItem('glafia-updated') || 0);
      if (j.data && (j.updated || 0) >= localUpdated) applyRemote(j.data);
      else if (!j.data) push(); // nothing stored yet — seed it from this device
    } catch (e) { /* offline / no D1 yet — stays local */ }
  }
  async function push(){
    try {
      const r = await fetch('/api/data', { method: 'PUT', headers: { 'content-type': 'application/json', 'x-sync-code': SYNC_KEY }, body: JSON.stringify({ data: bundle() }) });
      if (!r.ok) return;
      const j = await r.json();
      localStorage.setItem('glafia-updated', String(j.updated || Date.now()));
    } catch (e) { /* offline / no D1 yet — stays local */ }
  }
  React.useEffect(() => { pull(); }, []);
  React.useEffect(() => {
    if (firstRef.current) { firstRef.current = false; return; }
    if (applyingRef.current) return;
    localStorage.setItem('glafia-updated', String(Date.now()));
    clearTimeout(window.__sync);
    window.__sync = setTimeout(() => push(), 1200);
  }, [recipes, collections, pantry, favourites, have]);

  function flash(msg){ setToast(msg); clearTimeout(window.__t); window.__t = setTimeout(() => setToast(null), 2600); }
  const recipeById = id => recipes.find(r => r.id === id);
  const pagerBoxes = [{ id: '__all', name: 'All recipes', emoji: '✦', virtual: true, members: recipes.map(r => r.id) }, ...collections];

  const api = {
    recipes, recipeById, collections, pantry, favourites, have,
    selectedId, setSelected: setSelectedId, view, setView,
    collIndex, setCollIndex, pagerBoxes, editId, flash, isMobile,
    colorOf: (id) => { const c = collections.find(c => c.members.includes(id)); return c ? c.color : '#c9a36a'; },
    boxesOf: (id) => collections.filter(c => c.members.includes(id)),
    openBoxList: () => setShowBoxList(true),
    openRecipe: (id) => { setSelectedId(id); setView('recipe'); },
    startAdd: () => { setEditId(null); setView('edit'); },
    startEdit: (id) => { setEditId(id); setView('edit'); },
    setHave,
    toggleFav: (id) => setFavourites(p => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; }),
    renameCollection: (id, name) => setCollections(cs => cs.map(c => c.id === id ? { ...c, name } : c)),
    editCollection: (id, patch) => setCollections(cs => cs.map(c => c.id === id ? { ...c, ...patch } : c)),
    addCollection: (name) => {
      const id = 'box-' + Date.now();
      setCollections(cs => {
        const next = [...cs, { id, name: name.trim(), emoji: NEWBOX_EMOJI[cs.length % NEWBOX_EMOJI.length], color: BOX_COLORS[cs.length % BOX_COLORS.length], members: [] }];
        setCollIndex(cs.length + 1); // pager index of the new box (virtual at 0)
        return next;
      });
      return id;
    },
    deleteCollection: (id) => setCollections(cs => {
      const next = cs.filter(c => c.id !== id);
      setCollIndex(i => Math.min(i, next.length));
      return next;
    }),
    toggleMember: (collId, recipeId) => setCollections(cs => cs.map(c => {
      if (c.id !== collId) return c;
      const has = c.members.includes(recipeId);
      return { ...c, members: has ? c.members.filter(m => m !== recipeId) : [...c.members, recipeId] };
    })),
    editPantry: (key, patch) => setPantry(ps => ps.map(p => p.key === key ? { ...p, ...patch } : p)),
    addPantry: (label, cat) => {
      const key = 'custom-' + window.slug(label) + '-' + Math.random().toString(36).slice(2, 5);
      setPantry(ps => [...ps, { key, label: label.trim(), emoji: '', cat: cat || 'custom', custom: true }]);
      setHave(h => { const n = new Set(h); n.add(key); return n; });
    },
    removePantry: (key) => { setPantry(ps => ps.filter(p => p.key !== key)); setHave(h => { const n = new Set(h); n.delete(key); return n; }); },
    saveRecipe: (draft, boxIds) => {
      let rec = draft;
      if (!rec.id) {
        const maxNo = recipes.reduce((m, r) => Math.max(m, r.no || 0), 0);
        rec = { ...rec, id: 'user-' + Date.now(), no: maxNo + 1 };
      }
      setRecipes(rs => rs.some(r => r.id === rec.id) ? rs.map(r => r.id === rec.id ? rec : r) : [...rs, rec]);
      setCollections(cs => cs.map(c => {
        const should = boxIds.includes(c.id), has = c.members.includes(rec.id);
        if (should && !has) return { ...c, members: [...c.members, rec.id] };
        if (!should && has) return { ...c, members: c.members.filter(m => m !== rec.id) };
        return c;
      }));
      setSelectedId(rec.id); setEditId(null); setView('box'); flash('Saved to your dex! 🐾');
    },
    deleteRecipe: (id) => {
      setRecipes(rs => rs.filter(r => r.id !== id));
      setCollections(cs => cs.map(c => ({ ...c, members: c.members.filter(m => m !== id) })));
      setFavourites(f => { const n = new Set(f); n.delete(id); return n; });
      setEditId(null); setView('box'); flash('Released. 👋');
    },
  };

  // ---------- keyboard (Game Boy feel) ----------
  React.useEffect(() => {
    function onKey(e){
      const t = e.target;
      if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return;
      if (showBoxList) { if (e.key === 'Escape') setShowBoxList(false); return; }
      if (view === 'recipe' || view === 'search' || view === 'edit') {
        if (e.key === 'Escape' || e.key === 'b' || e.key === 'B') { e.preventDefault(); setView('box'); }
        return;
      }
      // box view
      if (e.key === 'ArrowLeft')  { e.preventDefault(); setCollIndex(i => (i - 1 + pagerBoxes.length) % pagerBoxes.length); }
      else if (e.key === 'ArrowRight') { e.preventDefault(); setCollIndex(i => (i + 1) % pagerBoxes.length); }
      else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
        e.preventDefault();
        const members = pagerBoxes[Math.min(collIndex, pagerBoxes.length - 1)].members;
        if (!members.length) return;
        let pos = members.indexOf(selectedId);
        pos = e.key === 'ArrowDown' ? (pos + 1) % members.length : (pos - 1 + members.length) % members.length;
        if (pos < 0) pos = 0;
        setSelectedId(members[pos]);
      }
      else if (e.key === 'Enter' || e.key === 'a' || e.key === 'A') { if (selectedId) { e.preventDefault(); setSelectedId(selectedId); setView('recipe'); } }
      else if (e.key === 'f' || e.key === 'F') { if (selectedId) api.toggleFav(selectedId); }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [view, collIndex, selectedId, showBoxList, pagerBoxes.length]);

  const sel = recipeById(selectedId);

  return (
    <div className="gameframe">
      <div className="cmdbar top">
        <div className="brandlogo"><span className="ball"></span> Glafia Dex</div>
        <div className="cmdgroup right">
          <button className={'cmdbtn' + (view === 'box' ? ' act' : '')} onClick={() => setView('box')}><span className="glyph x">▦</span><span className="lab">Boxes</span></button>
          <button className={'cmdbtn' + (view === 'search' ? ' act' : '')} onClick={() => setView('search')}><span className="glyph y">⌕</span><span className="lab">Search</span></button>
          <button className="cmdbtn" onClick={() => api.startAdd()}><span className="glyph plus">＋</span><span className="lab">Add recipe</span></button>
        </div>
      </div>

      {view === 'box' && <BoxScreen api={api} />}
      {view === 'recipe' && <RecipeFull api={api} />}
      {view === 'search' && <SearchScreen api={api} />}
      {view === 'edit' && <AddEditScreen api={api} />}

      <div className="cmdbar bottom">
        <button className="cmdbtn" onClick={() => sel && api.openRecipe(sel.id)}><span className="glyph a">A</span><span className="lab">Open</span></button>
        <button className="cmdbtn" onClick={() => view === 'box' ? null : setView('box')}><span className="glyph b">B</span><span className="lab">Back</span></button>
        <button className="cmdbtn" onClick={() => sel && api.toggleFav(sel.id)}><span className="glyph y">★</span><span className="lab">{sel && favourites.has(sel.id) ? 'Unfav' : 'Favourite'}</span></button>
        <button className="cmdbtn" onClick={() => setShowBoxList(true)}><span className="glyph x">▦</span><span className="lab">Boxes</span></button>
      </div>

      {showBoxList && <BoxListModal api={api} onClose={() => setShowBoxList(false)} />}
      {toast && <div className="toast">{toast}</div>}
      <Mascot />
    </div>
  );
}

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