// Main app shell — nav, state, tweaks
const { useState, useEffect, useMemo, useCallback, useRef, useContext } = React;

const DEFAULT_PREFS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "unitsVol": "oz",
  "unitsMass": "g",
  "showMicros": true,
  "goalProfile": "performance",
  "goalsCustom": null
}/*EDITMODE-END*/;

function useToasts() {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((message, type = 'ok') => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, message, type }]);
  }, []);
  const dismiss = useCallback((id) => setToasts(t => t.filter(x => x.id !== id)), []);
  return { toasts, push, dismiss };
}

const INIT_KEY = 'nutrition_user_initialized';

// True if window.api is present AND has not failed a prior call. Set to false
// once the first failure happens so subsequent writes use the localStorage
// fallback path without retrying the network.
function hasBackend() {
  if (typeof window === 'undefined' || !window.api) return false;
  return window.__backendOK !== false;
}

function App() {
  // ---------- entries ----------
  // Initial state strategy:
  //  - If we have a backend, start with cached entries (instant paint) then
  //    refetch from server in an effect.
  //  - If we don't, fall back to the original localStorage + seed behavior.
  const [entries, setEntries] = useState(() => {
    if (hasBackend()) {
      return window.api.cachedEntries() || [];
    }
    const stored = Storage.get(STORAGE_KEY, null);
    if (stored && Array.isArray(stored) && stored.length) return stored;
    const initialized = Storage.get(INIT_KEY, false);
    if (initialized) return [];
    const seed = window.SEED_ENTRIES || [];
    Storage.set(STORAGE_KEY, seed);
    return seed;
  });

  const [entriesLoaded, setEntriesLoaded] = useState(!hasBackend());

  // Refetch from server on mount when backend is present.
  // On failure (e.g. running without the Cloudflare backend / in preview),
  // fall back to the seed+localStorage behavior so the app stays usable.
  useEffect(() => {
    if (!hasBackend()) return;
    let cancelled = false;
    window.api.listEntries()
      .then(rows => {
        if (cancelled) return;
        setEntries(rows);
        setEntriesLoaded(true);
        window.__backendOK = true;
      })
      .catch(err => {
        console.warn('No backend available, falling back to localStorage:', err.message);
        if (cancelled) return;
        window.__backendOK = false;
        const cached = window.api.cachedEntries();
        if (cached && cached.length) { setEntries(cached); setEntriesLoaded(true); return; }
        const stored = Storage.get(STORAGE_KEY, null);
        if (stored && stored.length) { setEntries(stored); setEntriesLoaded(true); return; }
        const initialized = Storage.get(INIT_KEY, false);
        if (!initialized && window.SEED_ENTRIES) {
          Storage.set(STORAGE_KEY, window.SEED_ENTRIES);
          setEntries(window.SEED_ENTRIES);
        }
        setEntriesLoaded(true);
      });
    return () => { cancelled = true; };
  }, []);

  // Always mirror entries to localStorage. With backend up this is just a
  // hot offline cache; without backend it's the primary store.
  useEffect(() => { Storage.set(STORAGE_KEY, entries); }, [entries]);

  const addEntries = useCallback(async (items) => {
    const stamped = items.map(it => ({
      ...it,
      id: it.id || `e_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      createdAt: it.createdAt || Date.now()
    }));
    setEntries(prev => [...prev, ...stamped]);
    if (hasBackend()) {
      try { await window.api.addEntries(stamped); }
      catch (e) {
        console.warn('addEntries fell back to localStorage:', e.message);
        window.__backendOK = false;
      }
    }
    Storage.set(INIT_KEY, true);
  }, []);

  const deleteEntry = useCallback(async (id) => {
    setEntries(prev => prev.filter(e => e.id !== id));
    if (hasBackend()) {
      try { await window.api.deleteEntry(id); }
      catch (e) {
        console.warn('deleteEntry fell back:', e.message);
        window.__backendOK = false;
      }
    }
    Storage.set(INIT_KEY, true);
  }, []);

  const updateEntry = useCallback(async (id, patch) => {
    setEntries(prev => prev.map(e => e.id === id ? { ...e, ...patch } : e));
    if (hasBackend()) {
      try { await window.api.updateEntry(id, patch); }
      catch (e) {
        console.warn('updateEntry fell back:', e.message);
        window.__backendOK = false;
      }
    }
    Storage.set(INIT_KEY, true);
  }, []);

  const replaceAllEntries = useCallback(async (newEntries) => {
    setEntries(newEntries);
    if (hasBackend()) {
      try { await window.api.addEntries(newEntries); }
      catch (e) {
        console.warn('replaceAllEntries fell back:', e.message);
        window.__backendOK = false;
      }
    }
    Storage.set(INIT_KEY, true);
  }, []);

  // ---------- prefs / tweaks ----------
  const [prefs, setPrefs] = useState(() => {
    if (hasBackend()) {
      return { ...DEFAULT_PREFS, ...(window.api.cachedPrefs() || {}) };
    }
    return { ...DEFAULT_PREFS, ...(Storage.get(PREFS_KEY, {}) || {}) };
  });

  // Don't PUT prefs back to the server until we've successfully GET'd them —
  // otherwise the first render would overwrite server state with the client's
  // cached defaults.
  const [prefsLoaded, setPrefsLoaded] = useState(!hasBackend());

  // Fetch prefs from server on mount.
  useEffect(() => {
    if (!hasBackend()) return;
    let cancelled = false;
    window.api.getPrefs()
      .then(p => {
        if (cancelled) return;
        setPrefs(prev => ({ ...prev, ...(p || {}) }));
        setPrefsLoaded(true);
      })
      .catch(err => {
        if (cancelled) return;
        console.warn('Prefs GET fell back to localStorage:', err.message);
        window.__backendOK = false;
        // Local mirror is the source of truth from here on.
        setPrefs(prev => ({ ...DEFAULT_PREFS, ...(Storage.get(PREFS_KEY, {}) || {}), ...prev }));
        setPrefsLoaded(true);
      });
    return () => { cancelled = true; };
  }, []);

  // Persist prefs.
  //  - Always mirror to localStorage (cheap, instant, survives reload).
  //  - Only PUT to server after the initial GET succeeded AND backend is still up.
  //  - Debounced so dragging sliders doesn't spam the API.
  useEffect(() => {
    Storage.set(PREFS_KEY, prefs);
    if (!prefsLoaded || !hasBackend()) return;
    const t = setTimeout(() => {
      window.api.putPrefs(prefs).catch(e => {
        console.warn('Prefs PUT fell back to localStorage:', e.message);
        window.__backendOK = false;
      });
    }, 400);
    return () => clearTimeout(t);
  }, [prefs, prefsLoaded]);
  useEffect(() => {
    document.documentElement.dataset.theme = prefs.theme;
  }, [prefs.theme]);
  const setPref = useCallback((k, v) => {
    if (typeof k === 'object') setPrefs(p => ({ ...p, ...k }));
    else setPrefs(p => ({ ...p, [k]: v }));
  }, []);

  const goals = useMemo(() => {
    const base = GOAL_PROFILES[prefs.goalProfile] || GOAL_PROFILES.performance;
    return { ...base, ...(prefs.goalsCustom || {}) };
  }, [prefs.goalProfile, prefs.goalsCustom]);

  // ---------- nav ----------
  const [route, setRoute] = useState(() => {
    const h = location.hash.replace('#', '');
    return ['log', 'paste', 'dashboard', 'trends', 'history'].includes(h) ? h : 'dashboard';
  });
  useEffect(() => { location.hash = route; }, [route]);

  // ---------- toasts ----------
  const { toasts, push: toast, dismiss } = useToasts();

  // ---------- tweaks panel ----------
  const [tweaksOpen, setTweaksOpen] = useState(false);
  const [prefsOpen, setPrefsOpen] = useState(false);
  useEffect(() => {
    function onMsg(e) {
      if (!e.data) return;
      if (e.data.type === '__activate_edit_mode') setTweaksOpen(true);
      if (e.data.type === '__deactivate_edit_mode') setTweaksOpen(false);
    }
    window.addEventListener('message', onMsg);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const ctx = useMemo(() => ({
    entries, addEntries, deleteEntry, updateEntry, replaceAllEntries,
    prefs, setPref, goals,
    toast,
    route, setRoute
  }), [entries, addEntries, deleteEntry, updateEntry, replaceAllEntries, prefs, setPref, goals, toast, route]);

  return (
    <AppCtx.Provider value={ctx}>
      <div className="app">
        <Header
          onTheme={() => setPref('theme', prefs.theme === 'dark' ? 'light' : 'dark')}
          theme={prefs.theme}
          onOpenPrefs={() => setPrefsOpen(true)}
        />
        <main className="app-main">
          {route === 'log' && <LogFoodPage />}
          {route === 'paste' && <PasteDayPage />}
          {route === 'dashboard' && <DashboardPage />}
          {route === 'trends' && <TrendsPage />}
          {route === 'history' && <HistoryPage />}
        </main>
        <BottomNav route={route} setRoute={setRoute} />
        <ToastHost toasts={toasts} dismiss={dismiss} />
        {prefsOpen && (
          <PreferencesModal
            prefs={prefs}
            setPref={setPref}
            onClose={() => setPrefsOpen(false)}
          />
        )}
        {tweaksOpen && (
          <TweaksUI
            prefs={prefs}
            setPref={setPref}
            onClose={() => {
              setTweaksOpen(false);
              window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
            }}
          />
        )}
      </div>
    </AppCtx.Provider>
  );
}

/* ---------- Header ---------- */
function Header({ onTheme, theme, onOpenPrefs }) {
  return (
    <header className="app-header">
      <div className="app-header-inner">
        <div className="brand">
          <div className="brand-mark">F</div>
          <div>
            FORGE
            <small>Nutrition · MTB Performance</small>
          </div>
        </div>
        <div className="header-actions">
          <button className="icon-btn" onClick={onOpenPrefs} title="Preferences" aria-label="Preferences">
            <Icon name="cog" />
          </button>
          <button className="icon-btn" onClick={onTheme} title="Toggle theme">
            <Icon name={theme === 'dark' ? 'sun' : 'moon'} />
          </button>
        </div>
      </div>
    </header>
  );
}

/* ---------- Bottom Nav ---------- */
function BottomNav({ route, setRoute }) {
  const items = [
    { id: 'log', label: 'Log Food', icon: 'log' },
    { id: 'paste', label: 'Paste Day', icon: 'paste' },
    { id: 'dashboard', label: 'Dashboard', icon: 'dashboard' },
    { id: 'trends', label: 'Trends', icon: 'trends' },
    { id: 'history', label: 'History', icon: 'history' }
  ];
  return (
    <nav className="bottom-nav" role="tablist">
      {items.map(it => (
        <button key={it.id} className={`nav-btn ${route === it.id ? 'active' : ''}`}
                onClick={() => setRoute(it.id)} role="tab" aria-selected={route === it.id}>
          <Icon name={it.icon} />
          <span>{it.label}</span>
        </button>
      ))}
    </nav>
  );
}

/* ---------- Tweaks Panel ---------- */
function TweaksUI({ prefs, setPref, onClose }) {
  return (
    <div className="tweaks-panel" style={{
      position: 'fixed', right: 16, bottom: 16, zIndex: 50,
      width: 'min(360px, calc(100vw - 32px))',
      maxHeight: 'calc(100vh - 32px)',
      overflowY: 'auto',
      background: 'var(--bg-card)', border: '1px solid var(--line)',
      borderRadius: 18, padding: 18, boxShadow: 'var(--shadow-lg)',
      fontFamily: 'var(--sans)'
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
        <div style={{ fontFamily: 'var(--serif)', fontSize: 20, fontWeight: 500 }}>Tweaks</div>
        <button className="icon-btn" onClick={onClose} aria-label="Close">×</button>
      </div>
      <PrefsForm prefs={prefs} setPref={setPref} />
    </div>
  );
}

/* ---------- Preferences Modal ---------- */
function PreferencesModal({ prefs, setPref, onClose }) {
  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, zIndex: 60,
        background: 'rgba(0,0,0,0.55)',
        display: 'grid', placeItems: 'center',
        padding: 16
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: 'var(--bg-card)', border: '1px solid var(--line)',
          borderRadius: 18, padding: 22,
          width: 'min(440px, 100%)',
          maxHeight: 'calc(100vh - 64px)',
          overflowY: 'auto',
          boxShadow: 'var(--shadow-lg)',
          fontFamily: 'var(--sans)'
        }}
      >
        <div className="row-between mb-16">
          <div>
            <div className="eyebrow">Settings</div>
            <div style={{ fontFamily: 'var(--serif)', fontSize: 24, marginTop: 4 }}>Preferences</div>
          </div>
          <button className="icon-btn" onClick={onClose} aria-label="Close">×</button>
        </div>
        <PrefsForm prefs={prefs} setPref={setPref} />
        <div className="row" style={{ justifyContent: 'flex-end', marginTop: 16 }}>
          <button className="btn btn-primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

/* ---------- Shared Preferences Form ---------- */
function PrefsForm({ prefs, setPref }) {
  const { entries, replaceAllEntries, toast } = useContext(AppCtx);
  const goals = (GOAL_PROFILES[prefs.goalProfile] || GOAL_PROFILES.performance);
  const live = { ...goals, ...(prefs.goalsCustom || {}) };
  const fileInputRef = useRef(null);
  const [importPreview, setImportPreview] = useState(null);

  function updGoal(k, v) {
    const next = { ...(prefs.goalsCustom || {}), [k]: Number(v) };
    setPref('goalsCustom', next);
  }
  function resetGoals() { setPref('goalsCustom', null); }
  function setProfile(p) { setPref({ goalProfile: p, goalsCustom: null }); }

  function exportJSON() {
    const payload = {
      app: 'nutrition-performance',
      exportedAt: new Date().toISOString(),
      version: 1,
      entries,
      prefs
    };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `nutrition-backup_${todayISO()}.json`;
    a.click();
    URL.revokeObjectURL(url);
    toast(`Exported ${entries.length} entries`);
  }

  function handleImportFile(file) {
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const data = JSON.parse(reader.result);
        const incoming = Array.isArray(data) ? data : (Array.isArray(data.entries) ? data.entries : null);
        if (!incoming) { toast('Invalid backup file', 'error'); return; }
        setImportPreview({ count: incoming.length, entries: incoming, prefs: data.prefs || null });
      } catch (e) {
        toast('Could not parse JSON', 'error');
      }
    };
    reader.readAsText(file);
  }

  function commitImport(mode) {
    if (!importPreview) return;
    if (mode === 'replace') {
      replaceAllEntries(importPreview.entries);
      toast(`Replaced log with ${importPreview.count} entries`);
    } else {
      // merge by id; new ids generated for entries that don't have one
      const existing = new Map(entries.map(e => [e.id, e]));
      importPreview.entries.forEach(e => {
        const id = e.id || `e_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
        existing.set(id, { ...e, id });
      });
      replaceAllEntries([...existing.values()]);
      toast(`Merged ${importPreview.count} entries`);
    }
    setImportPreview(null);
  }

  return (
    <>
      <div className="field mb-16">
        <label>Theme</label>
        <div className="segmented" style={{ width: '100%' }}>
          {['dark','light'].map(t => (
            <button key={t} className={prefs.theme === t ? 'active' : ''} style={{ flex: 1 }}
                    onClick={() => setPref('theme', t)}>{t}</button>
          ))}
        </div>
      </div>

      <div className="field mb-16">
        <label>Goal profile</label>
        <div className="segmented" style={{ width: '100%' }}>
          <button className={prefs.goalProfile === 'standard' ? 'active' : ''} style={{ flex: 1 }}
                  onClick={() => setProfile('standard')}>Std 2000</button>
          <button className={prefs.goalProfile === 'performance' ? 'active' : ''} style={{ flex: 1 }}
                  onClick={() => setProfile('performance')}>Perf 3000</button>
        </div>
      </div>

      <div className="field mb-16">
        <label>Volume units</label>
        <div className="segmented" style={{ width: '100%' }}>
          {['oz','ml'].map(u => (
            <button key={u} className={prefs.unitsVol === u ? 'active' : ''} style={{ flex: 1 }}
                    onClick={() => setPref('unitsVol', u)}>{u}</button>
          ))}
        </div>
      </div>

      <div className="field mb-16">
        <label>Mass units</label>
        <div className="segmented" style={{ width: '100%' }}>
          {['g','oz'].map(u => (
            <button key={u} className={prefs.unitsMass === u ? 'active' : ''} style={{ flex: 1 }}
                    onClick={() => setPref('unitsMass', u)}>{u}</button>
          ))}
        </div>
      </div>

      <div className="field mb-16">
        <label>Show micronutrients</label>
        <div className="segmented" style={{ width: '100%' }}>
          <button className={prefs.showMicros ? 'active' : ''} style={{ flex: 1 }}
                  onClick={() => setPref('showMicros', true)}>On</button>
          <button className={!prefs.showMicros ? 'active' : ''} style={{ flex: 1 }}
                  onClick={() => setPref('showMicros', false)}>Off</button>
        </div>
      </div>

      <hr className="divider" />
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
        <div className="label">Goal Targets</div>
        <button className="btn btn-ghost btn-sm" onClick={resetGoals}>Reset</button>
      </div>
      {[
        ['calories','Calories','kcal'],
        ['protein','Protein','g'],
        ['carbs','Carbs','g'],
        ['fat','Fat','g'],
        ['fiber','Fiber','g'],
        ['water','Water','oz'],
        ['caffeine','Caffeine','mg'],
        ['sodium','Sodium','mg']
      ].map(([k,label,unit]) => (
        <div key={k} className="field" style={{ marginBottom: 10 }}>
          <label style={{ display: 'flex', justifyContent: 'space-between' }}>
            <span>{label}</span><span style={{ color: 'var(--text-dim)' }}>{unit}</span>
          </label>
          <input className="input" type="number" value={live[k]} onChange={e => updGoal(k, e.target.value)} />
        </div>
      ))}

      <hr className="divider" />
      <div className="label" style={{ marginBottom: 10 }}>Data backup</div>
      <p className="muted" style={{ fontSize: 12, marginTop: 0, marginBottom: 12 }}>
        Your data is stored in this browser. Export a JSON backup to keep a copy safe, or to move it to another device.
      </p>
      <div className="row" style={{ gap: 8, flexWrap: 'wrap' }}>
        <button className="btn" onClick={exportJSON}>
          <Icon name="download" /> Export JSON ({entries.length})
        </button>
        <button className="btn btn-ghost" onClick={() => fileInputRef.current && fileInputRef.current.click()}>
          Import JSON…
        </button>
        <input
          ref={fileInputRef} type="file" accept="application/json,.json"
          style={{ display: 'none' }}
          onChange={(e) => { handleImportFile(e.target.files[0]); e.target.value = ''; }}
        />
      </div>

      {importPreview && (
        <div style={{
          marginTop: 12, padding: 12, border: '1px solid var(--line)', borderRadius: 12,
          background: 'var(--bg-elev)'
        }}>
          <div style={{ fontSize: 13, marginBottom: 10 }}>
            Found <strong>{importPreview.count}</strong> entr{importPreview.count === 1 ? 'y' : 'ies'} in backup.
            Current log has <strong>{entries.length}</strong>.
          </div>
          <div className="row" style={{ gap: 8, flexWrap: 'wrap' }}>
            <button className="btn btn-primary btn-sm" onClick={() => commitImport('merge')}>
              Merge into log
            </button>
            <button className="btn btn-sm" onClick={() => commitImport('replace')}>
              Replace log
            </button>
            <button className="btn btn-ghost btn-sm" onClick={() => setImportPreview(null)}>
              Cancel
            </button>
          </div>
        </div>
      )}
    </>
  );
}

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