// ledger-app.jsx — root app + state wiring + top bar + tweaks

const { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "oklch(0.62 0.13 38)",
  "density": "comfortable"
}/*EDITMODE-END*/;

const ACCENT_PALETTES = {
  'oklch(0.62 0.13 38)':  { accent2: 'oklch(0.55 0.14 38)',  soft: 'oklch(0.945 0.035 38)', ink: 'oklch(0.42 0.13 38)' },
  'oklch(0.55 0.07 240)': { accent2: 'oklch(0.48 0.08 240)', soft: 'oklch(0.94 0.02 240)',  ink: 'oklch(0.38 0.08 240)' },
  'oklch(0.55 0.08 145)': { accent2: 'oklch(0.48 0.09 145)', soft: 'oklch(0.94 0.03 145)',  ink: 'oklch(0.38 0.09 145)' },
  'oklch(0.50 0.12 330)': { accent2: 'oklch(0.44 0.13 330)', soft: 'oklch(0.94 0.03 330)',  ink: 'oklch(0.36 0.12 330)' },
};

function TopBar({ search, setSearch, onAdd, onExport }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '12px 24px', borderBottom: '1px solid var(--line)',
      background: 'var(--bg)',
      flexShrink: 0,
    }}>
      <div className="field" style={{ flex: 1, maxWidth: 420, background: 'var(--surface)' }}>
        <Icon name="search" size={14} style={{ color: 'var(--ink-3)', marginRight: 8 }} />
        <input value={search} onChange={e => setSearch(e.target.value)}
          placeholder="Search transactions, tags, notes…" style={{ flex: 1 }} />
        <span className="kbd">/</span>
      </div>
      <div style={{ flex: 1 }} />
      <button className="btn" onClick={onExport}><Icon name="export" size={13} /> Export</button>
      <button className="btn accent" onClick={onAdd}>
        <Icon name="plus" size={13} /> New
      </button>
    </div>
  );
}

function App() {
  // session
  const [user, setUser] = useStateA(() => STORE.load('session', null));
  const [view, setView] = useStateA('transactions');
  const [month, setMonth] = useStateA('2026-05');
  const [categoryFilter, setCategoryFilter] = useStateA(null);
  const [search, setSearch] = useStateA('');
  const [storageMode, setStorageMode] = useStateA('local'); // 'local' | 'cloud'
  const [cloudSync, setCloudSync] = useStateA('idle');      // 'idle' | 'loading' | 'saving' | 'error'

  // data (lazy init from localStorage)
  const [txs, setTxs] = useStateA(() => STORE.load('txs', SAMPLE_TX));
  const [budgets, setBudgets] = useStateA(() => STORE.load('budgets', BUDGETS));
  const [categories, setCategories] = useStateA(() => STORE.load('categories', CATEGORIES));
  const [recurring, setRecurring] = useStateA(() => STORE.load('recurring', RECURRING));

  // editors
  const [editingTx, setEditingTx] = useStateA(null);
  const [editingBudget, setEditingBudget] = useStateA(null);
  const [editingCategory, setEditingCategory] = useStateA(null);
  const [editingRecurring, setEditingRecurring] = useStateA(null);
  const [accountOpen, setAccountOpen] = useStateA(false);
  const [confirm, setConfirm] = useStateA(null);
  const [toast, setToast] = useStateA(null);

  // tweaks
  const [tweaks, setTweak] = window.useTweaks ? window.useTweaks(TWEAK_DEFAULTS) : [TWEAK_DEFAULTS, () => {}];

  const cloudReady = useRefA(false);

  // ── detect cloud mode once on mount ──
  useEffectA(() => {
    (async () => {
      if (!window.CLOUD) return;
      const mode = await window.CLOUD.detect();
      setStorageMode(mode);
      // if signed in via cloud, hydrate from server
      if (mode === 'cloud' && window.CLOUD.token && user) {
        setCloudSync('loading');
        try {
          const remote = await window.CLOUD.data.load();
          if (remote) {
            setTxs(remote.txs || []);
            setBudgets(remote.budgets || []);
            setCategories(remote.categories && remote.categories.length ? remote.categories : CATEGORIES);
            setRecurring(remote.recurring || []);
          }
          setCloudSync('idle');
        } catch (e) {
          console.warn('[cloud] load failed', e);
          setCloudSync('error');
        }
      }
      cloudReady.current = true;
    })();
  }, []);

  // ── persist on change (always write to localStorage; mirror to cloud) ──
  useEffectA(() => { STORE.save('txs', txs); pushToCloud(); }, [txs]);
  useEffectA(() => { STORE.save('budgets', budgets); pushToCloud(); }, [budgets]);
  useEffectA(() => { STORE.save('categories', categories); pushToCloud(); }, [categories]);
  useEffectA(() => { STORE.save('recurring', recurring); pushToCloud(); }, [recurring]);

  function pushToCloud() {
    if (!cloudReady.current || storageMode !== 'cloud' || !user) return;
    setCloudSync('saving');
    window.CLOUD.data.save({ txs, budgets, categories, recurring });
    // de-jitter the indicator
    setTimeout(() => setCloudSync('idle'), 1200);
  }

  // flush on unload so the last batch doesn't sit in the debounce timer
  useEffectA(() => {
    const flush = () => { if (window.CLOUD?.mode === 'cloud') window.CLOUD.data.flush(); };
    window.addEventListener('beforeunload', flush);
    return () => window.removeEventListener('beforeunload', flush);
  }, []);

  // ── keep CAT_MAP fresh ──
  useEffectA(() => {
    window.CATEGORIES = categories;
    window.CAT_MAP = Object.fromEntries(categories.map(c => [c.id, c]));
  }, [categories]);

  // ── expose tx counts for the category-delete warning ──
  useEffectA(() => {
    const m = {};
    txs.forEach(t => { m[t.category] = (m[t.category] || 0) + 1; });
    window.__txsCountByCat = m;
  }, [txs]);

  // ── apply tweaks ──
  useEffectA(() => {
    const p = ACCENT_PALETTES[tweaks.accent] || Object.values(ACCENT_PALETTES)[0];
    const root = document.documentElement;
    root.style.setProperty('--accent', tweaks.accent);
    root.style.setProperty('--accent-2', p.accent2);
    root.style.setProperty('--accent-soft', p.soft);
    root.style.setProperty('--accent-ink', p.ink);
    const rowH = tweaks.density === 'compact' ? '34px' : tweaks.density === 'cosy' ? '46px' : '40px';
    root.style.setProperty('--row-h', rowH);
  }, [tweaks.accent, tweaks.density]);

  // ── helpers ──
  const flash = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2200); };

  const handleSignIn = async (u) => {
    setUser(u);
    STORE.save('session', u);
    // After successful cloud sign-in, hydrate the ledger from the server
    if (window.CLOUD?.mode === 'cloud') {
      setCloudSync('loading');
      try {
        const remote = await window.CLOUD.data.load();
        if (remote) {
          setTxs(remote.txs || []);
          setBudgets(remote.budgets || []);
          setCategories(remote.categories && remote.categories.length ? remote.categories : CATEGORIES);
          setRecurring(remote.recurring || []);
        }
        cloudReady.current = true;
        setCloudSync('idle');
      } catch (e) {
        setCloudSync('error');
        cloudReady.current = true;
      }
    } else {
      cloudReady.current = true;
    }
  };
  const handleSignOut = async () => {
    await AUTH.signOut();
    setUser(null);
  };

  const openNewTx = () => setEditingTx({
    id: null, date: new Date().toISOString().slice(0, 10), type: 'out', amount: 0,
    category: categories.find(c => c.kind === 'out')?.id || 'other', desc: '', tags: [],
  });
  const openFuel = () => {
    const lastFuel = txs.filter(t => t.category === 'fuel' && t.fuel?.odo)
      .sort((a, b) => b.date.localeCompare(a.date))[0];
    setEditingTx({
      id: null, date: new Date().toISOString().slice(0, 10), type: 'out', amount: 0,
      category: 'fuel', desc: '', tags: ['car'],
      fuel: { litres: 0, odo: lastFuel?.fuel?.odo || 0 },
    });
  };

  const quickAdd = (tx) => {
    setTxs(arr => [tx, ...arr]);
    flash(`Added ${tx.type === 'in' ? '+' : '−'}${fmt(tx.amount)} to ${window.CAT_MAP[tx.category]?.name}`);
  };

  const saveTx = (draft) => {
    setTxs(arr => {
      const exists = arr.find(t => t.id === draft.id);
      if (exists) return arr.map(t => t.id === draft.id ? draft : t);
      return [{ ...draft, id: 'tx' + Date.now() }, ...arr];
    });
    flash(draft.id ? 'Saved' : 'Added');
    setEditingTx(null);
  };
  const deleteTx = (id) => {
    setTxs(arr => arr.filter(t => t.id !== id));
    setEditingTx(null);
    flash('Deleted');
  };

  const saveBudget = (b) => {
    setBudgets(arr => {
      const idx = arr.findIndex(x => x.category === b.category);
      if (idx >= 0) { const copy = [...arr]; copy[idx] = b; return copy; }
      return [...arr, b];
    });
    setEditingBudget(null);
    flash('Budget saved');
  };
  const deleteBudget = (cat) => {
    setBudgets(arr => arr.filter(b => b.category !== cat));
    setEditingBudget(null);
    flash('Budget removed');
  };

  const saveCategory = (c) => {
    setCategories(arr => {
      const idx = arr.findIndex(x => x.id === c.id);
      if (idx >= 0) { const copy = [...arr]; copy[idx] = c; return copy; }
      return [...arr, c];
    });
    setEditingCategory(null);
    flash('Category saved');
  };
  const deleteCategory = (id) => {
    if (id === 'other') { flash('Cannot delete "Other"'); return; }
    // move transactions/budgets/recurring referencing this cat to "other"
    setTxs(arr => arr.map(t => t.category === id ? { ...t, category: 'other' } : t));
    setBudgets(arr => arr.filter(b => b.category !== id));
    setRecurring(arr => arr.map(r => r.category === id ? { ...r, category: 'other' } : r));
    setCategories(arr => arr.filter(c => c.id !== id));
    setEditingCategory(null);
    flash('Category removed');
  };

  const saveRecurring = (r) => {
    setRecurring(arr => {
      const idx = arr.findIndex(x => x.id === r.id);
      if (idx >= 0) { const copy = [...arr]; copy[idx] = r; return copy; }
      return [...arr, r];
    });
    setEditingRecurring(null);
    flash('Recurring saved');
  };
  const deleteRecurring = (id) => {
    setRecurring(arr => arr.filter(r => r.id !== id));
    setEditingRecurring(null);
    flash('Recurring removed');
  };

  const handleExport = () => {
    exportCSV(txs, `ledger-${new Date().toISOString().slice(0, 10)}.csv`);
    flash(`Exported ${txs.length} transactions`);
  };

  const handleClearData = () => {
    setConfirm({
      title: 'Start fresh?',
      body: 'This removes all transactions, budgets, custom categories, and recurring entries — leaving the default categories so you can start adding your own data. This cannot be undone.',
      confirmLabel: 'Start fresh',
      danger: true,
      onConfirm: () => {
        setTxs([]);
        setBudgets([]);
        setCategories(CATEGORIES);  // keep default categories so user can categorize new entries
        setRecurring([]);
        setCategoryFilter(null);
        setSearch('');
        setConfirm(null);
        flash('Cleared — ready for your own records');
      },
    });
  };

  const handleResetDemo = () => {
    setConfirm({
      title: 'Reset to demo data?',
      body: 'This replaces all your data with the original demo transactions, budgets, categories, and recurring entries.',
      confirmLabel: 'Reset to demo',
      onConfirm: () => {
        setTxs(SAMPLE_TX);
        setBudgets(BUDGETS);
        setCategories(CATEGORIES);
        setRecurring(RECURRING);
        setConfirm(null);
        flash('Demo data restored');
      },
    });
  };

  if (!user) return <AuthScreen onSignIn={handleSignIn} />;

  return (
    <div style={{ height: '100%', display: 'flex', background: 'var(--bg)' }}>
      <Sidebar
        activeView={view} setView={setView}
        month={month} setMonth={setMonth}
        categoryFilter={categoryFilter}
        setCategoryFilter={(c) => { setCategoryFilter(c); setView('transactions'); }}
        txs={txs} categories={categories} recurringCount={recurring.length}
        user={user}
        storageMode={storageMode}
        cloudSync={cloudSync}
        onSignOut={handleSignOut}
        onClearData={handleClearData}
        onResetDemo={handleResetDemo}
        onAccount={() => setAccountOpen(true)}
        onExport={handleExport}
        onAddCategory={() => setEditingCategory({ id: null, name: '', kind: 'out', icon: '🏷', color: 'oklch(0.62 0.13 38)' })}
        onEditCategory={(c) => setEditingCategory(c)}
      />
      <main style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        <TopBar search={search} setSearch={setSearch} onAdd={openNewTx} onExport={handleExport} />
        <div style={{ flex: 1, minHeight: 0 }}>
          {view === 'transactions' && (
            <TransactionsView
              txs={txs} month={month}
              categoryFilter={categoryFilter} setCategoryFilter={setCategoryFilter}
              search={search}
              onOpenTx={setEditingTx}
              onAddTx={openNewTx}
              onQuickAdd={quickAdd}
            />
          )}
          {view === 'stats'     && <StatsView txs={txs} month={month} />}
          {view === 'budgets'   && <BudgetsView
            txs={txs} month={month} budgets={budgets}
            onAddBudget={() => setEditingBudget({ category: '', amount: 200 })}
            onEditBudget={(b) => setEditingBudget(b)}
            onDeleteBudget={deleteBudget}
          />}
          {view === 'recurring' && <RecurringView
            recurring={recurring}
            onAdd={() => setEditingRecurring({ id: null, desc: '', category: 'rent', amount: 0, cadence: 'monthly', nextDate: new Date().toISOString().slice(0,10) })}
            onEdit={(r) => setEditingRecurring(r)}
          />}
          {view === 'fuel'      && <FuelView txs={txs} onAddFuel={openFuel} />}
        </div>
      </main>

      {editingTx && (
        <EditDrawer
          tx={editingTx} categories={categories}
          onClose={() => setEditingTx(null)}
          onSave={saveTx}
          onDelete={deleteTx}
        />
      )}
      {editingBudget && (
        <BudgetModal
          budget={editingBudget}
          budgets={budgets}
          onClose={() => setEditingBudget(null)}
          onSave={saveBudget}
          onDelete={deleteBudget}
        />
      )}
      {editingCategory && (
        <CategoryModal
          category={editingCategory}
          categories={categories}
          onClose={() => setEditingCategory(null)}
          onSave={saveCategory}
          onDelete={deleteCategory}
        />
      )}
      {editingRecurring && (
        <RecurringModal
          item={editingRecurring}
          onClose={() => setEditingRecurring(null)}
          onSave={saveRecurring}
          onDelete={deleteRecurring}
        />
      )}
      {confirm && (
        <ConfirmDialog
          title={confirm.title} body={confirm.body}
          confirmLabel={confirm.confirmLabel} danger={confirm.danger}
          onConfirm={confirm.onConfirm}
          onClose={() => setConfirm(null)}
        />
      )}

      {accountOpen && (
        <AccountModal
          user={user}
          onClose={() => setAccountOpen(false)}
          onUsernameChange={(u) => { setUser(u); STORE.save('session', u); }}
        />
      )}

      {toast && <Toast msg={toast} />}

      <LedgerTweaks tweaks={tweaks} setTweak={setTweak} />
    </div>
  );
}

function Toast({ msg }) {
  return (
    <div style={{
      position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
      background: 'var(--ink)', color: 'var(--bg)',
      padding: '10px 18px', borderRadius: 999,
      fontSize: 13, fontWeight: 500,
      boxShadow: 'var(--shadow-lg)', zIndex: 60,
      display: 'flex', alignItems: 'center', gap: 8,
      animation: 'fadeUp .2s',
    }}>
      <Icon name="check" size={14} />
      {msg}
    </div>
  );
}

function LedgerTweaks({ tweaks, setTweak }) {
  if (!window.TweaksPanel) return null;
  const { TweaksPanel, TweakSection, TweakRadio, TweakColor } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Theme" />
      <TweakColor
        label="Accent"
        value={tweaks.accent}
        onChange={v => setTweak('accent', v)}
        options={Object.keys(ACCENT_PALETTES)}
      />
      <TweakSection label="Layout" />
      <TweakRadio
        label="Density"
        value={tweaks.density}
        onChange={v => setTweak('density', v)}
        options={['compact', 'comfortable', 'cosy']}
      />
    </TweaksPanel>
  );
}

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