// app.jsx — root component, sidebar nav, tweaks

const { useState, useEffect } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "warm",
  "fontPair": "serif-grotesk",
  "density": "comfortable"
}/*EDITMODE-END*/;

function Sidebar({ view, setView, watchCount, open, onClose }) {
  const items = [
    { id: 'dashboard', label: 'Dashboard', icon: ICONS.dash },
    { id: 'catalogue', label: 'Catalogue', icon: ICONS.cat },
    { id: 'offers', label: 'Offers', icon: ICONS.offer },
    { id: 'watchlist', label: 'Watchlist', icon: ICONS.watch, badge: watchCount },
    { id: 'settings',  label: 'Settings',  icon: ICONS.settings },
  ];
  const isOn = (id) => view === id
    || (id === 'catalogue' && view === 'model')
    || (id === 'offers' && (view === 'offer-create' || view === 'offer-history' || view === 'offer-detail'));
  function pick(id) { setView({ view: id }); if (onClose) onClose(); }
  return (
    <React.Fragment>
      {open && <div className="sidebar-scrim" onClick={onClose}></div>}
      <aside className={`sidebar ${open ? 'is-open' : ''}`}>
        <div className="sidebar-top">
          <Logo />
          <nav className="nav">
            {items.map(it => (
              <button
                key={it.id}
                className={`nav-item ${isOn(it.id) ? 'on' : ''}`}
                onClick={() => pick(it.id)}
              >
                <Icon d={it.icon} size={16} />
                <span>{it.label}</span>
                {it.badge ? <span className="nav-badge">{it.badge}</span> : null}
              </button>
            ))}
          </nav>
        </div>
        <div className="sidebar-bot">
          <div className="acct-mini">
            <div className="avatar sm">RA</div>
            <div>
              <div className="acct-mini-name">Robin Ahlers</div>
              <div className="muted xs">Pro · Research</div>
            </div>
          </div>
          <div className="footnote">
            <span>v0.4 · sample data</span>
          </div>
        </div>
      </aside>
    </React.Fragment>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRouteRaw] = useState({ view: 'dashboard' });
  const [navHistory, setNavHistory] = useState([]); // stack of previous routes
  const [navOpen, setNavOpen] = useState(false);

  function setRoute(next) {
    setRouteRaw(prev => {
      const resolved = typeof next === 'function' ? next(prev) : next;
      // Don't push duplicates of the same view (e.g. param-only updates within same screen)
      if (prev && resolved && prev.view !== resolved.view) {
        setNavHistory(h => [...h, prev]);
      } else if (prev && resolved && prev.view === resolved.view && JSON.stringify(prev) !== JSON.stringify(resolved)) {
        // Same view, different params (e.g. modelId change) — still record so back works
        setNavHistory(h => [...h, prev]);
      }
      return resolved;
    });
  }
  function goBack() {
    setNavHistory(h => {
      if (h.length === 0) return h;
      const prev = h[h.length - 1];
      setRouteRaw(prev);
      return h.slice(0, -1);
    });
  }
  // Seed the watchlist with the first 3 priced models the backend returned —
  // resilient to whichever categories happen to have data right now.
  const [watchlist, setWatchlist] = useState(
    MODELS.slice(0, 3).map(m => m.defaultVariantId).filter(Boolean)
  );
  const [offers, setOffers] = useState(() => loadOffers());
  const [drafts, setDraftsState] = useState(() => {
    // Prune any drafts that have no items — leftover empty drafts shouldn't persist
    const loaded = loadDrafts();
    const cleaned = loaded.filter(d => (d.items || []).length > 0);
    if (cleaned.length !== loaded.length) saveDrafts(cleaned);
    return cleaned;
  });

  function persistDrafts(updater) {
    setDraftsState(prev => {
      const next = typeof updater === 'function' ? updater(prev) : updater;
      saveDrafts(next);
      return next;
    });
  }
  function createDraft() {
    const id = 'DR-' + Date.now().toString(36).toUpperCase();
    // Sequential number across all offers + drafts ever created
    const used = new Set([
      ...offers.map(o => o.seq).filter(Boolean),
      ...drafts.map(d => d.seq).filter(Boolean),
    ]);
    let seq = 1;
    while (used.has(seq)) seq += 1;
    const draft = { id, seq, name: `Offer #${seq}`, items: [], pristine: true, updatedAt: new Date().toISOString() };
    persistDrafts(prev => [...prev, draft]);
    return id;
  }
  function updateDraft(id, patch) {
    persistDrafts(prev => prev.map(d => {
      if (d.id !== id) return d;
      const merged = { ...d, ...patch, updatedAt: new Date().toISOString() };
      // If user actually edited (added items, renamed past default), draft is no longer pristine
      const defaultName = `Offer #${d.seq}`;
      const hasItems = (merged.items || []).length > 0;
      const renamed = merged.name && merged.name !== defaultName && merged.name !== '';
      if (hasItems || renamed) merged.pristine = false;
      return merged;
    }));
  }
  function discardDraft(id) {
    persistDrafts(prev => prev.filter(d => d.id !== id));
  }
  function finalizeDraft(draftId, name, notes) {
    const d = drafts.find(x => x.id === draftId);
    if (!d) return;
    const offer = {
      id: 'OFF-' + Date.now().toString(36).toUpperCase(),
      seq: d.seq,
      name: name || d.name || `Offer #${d.seq}`,
      notes: notes || d.notes || '',
      createdAt: new Date().toISOString(),
      items: d.items,
    };
    setOffers(prev => { const next = [...prev, offer]; saveOffers(next); return next; });
    mirrorOffer(offer);
    discardDraft(draftId);
  }
  function addOffer(o) {
    setOffers(prev => { const next = [...prev, o]; saveOffers(next); return next; });
    mirrorOffer(o);
  }
  function removeOffer(id) {
    setOffers(prev => { const next = prev.filter(x => x.id !== id); saveOffers(next); return next; });
  }
  function updateOffer(id, patch) {
    setOffers(prev => {
      const next = prev.map(x => x.id === id ? { ...x, ...patch } : x);
      saveOffers(next);
      const updated = next.find(x => x.id === id);
      if (updated) mirrorOffer(updated);
      return next;
    });
  }
  // Fire-and-forget mirror to /api/v2/offers. localStorage stays the source
  // of truth client-side; the backend persists by client_id so cross-device
  // recovery is possible later without changing this flow.
  function mirrorOffer(o) {
    fetch('/api/v2/offers', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify(o),
    }).catch(() => { /* offline-friendly */ });
  }
  function loadIntoDraft(items, after, sourceName) {
    // Create a new draft preloaded with the given items (used by "Edit" on saved offers)
    const id = 'DR-' + Date.now().toString(36).toUpperCase();
    const used = new Set([
      ...offers.map(o => o.seq).filter(Boolean),
      ...drafts.map(d => d.seq).filter(Boolean),
    ]);
    let seq = 1;
    while (used.has(seq)) seq += 1;
    const draft = { id, seq, name: sourceName || `Offer #${seq}`, items: items.map(x => ({ ...x })), pristine: false, updatedAt: new Date().toISOString() };
    persistDrafts(prev => [...prev, draft]);
    if (after) after(id);
  }
  function addToOffer(modelOrVariantId, opts = {}) {
    // Add to most-recent draft, or create a new one
    const v = VARIANT_BY_ID[modelOrVariantId];
    const m = MODELS_BY_ID[modelOrVariantId];
    const modelId = m ? m.id : (v ? v.modelId : null);
    if (!modelId) return;
    const lid = 'L' + Date.now().toString(36) + Math.random().toString(36).slice(2,5);
    const newLine = (opts.lock && v)
      ? { lid, modelId: v.modelId, storage: v.storage, color: v.color, condition: v.condition, qty: 1 }
      : { lid, modelId, storage: null, color: null, condition: null, qty: 1 };
    persistDrafts(prev => {
      if (prev.length === 0) {
        const id = 'DR-' + Date.now().toString(36).toUpperCase();
        const used = new Set([...offers.map(o => o.seq).filter(Boolean)]);
        let seq = 1;
        while (used.has(seq)) seq += 1;
        return [{ id, seq, name: `Offer #${seq}`, items: [newLine], updatedAt: new Date().toISOString() }];
      }
      // append to last (most recent) draft
      const last = prev[prev.length - 1];
      return prev.map(d => d === last ? { ...d, items: [...d.items, newLine], updatedAt: new Date().toISOString() } : d);
    });
  }

  function toggleWatch(id) {
    setWatchlist(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  }

  // apply tweaks → root attributes (CSS handles the rest)
  useEffect(() => {
    const root = document.documentElement;
    root.setAttribute('data-palette', t.palette);
    root.setAttribute('data-fonts', t.fontPair);
    root.setAttribute('data-density', t.density);
  }, [t.palette, t.fontPair, t.density]);

  const draftCount = drafts.reduce((s, d) => s + d.items.length, 0);
  const currentDraft = drafts.find(d => d.id === route.draftId);

  let body;
  if (route.view === 'dashboard') body = <Dashboard go={setRoute} route={route} watchlist={watchlist} toggleWatch={toggleWatch} />;
  else if (route.view === 'catalogue') body = <Catalogue go={setRoute} addToOffer={addToOffer} draftCount={draftCount} />;
  else if (route.view === 'model') body = <ModelPage id={route.modelId} go={setRoute} watchlist={watchlist} toggleWatch={toggleWatch} addToOffer={addToOffer} />;
  else if (route.view === 'watchlist') body = <Watchlist go={setRoute} watchlist={watchlist} toggleWatch={toggleWatch} />;
  else if (route.view === 'offer-create') {
    // Auto-create a draft if no id supplied or id is stale
    if (!currentDraft) {
      const id = createDraft();
      setTimeout(() => setRoute({ view: 'offer-create', draftId: id }), 0);
      body = <div className="screen"><div className="empty-inline">Creating draft…</div></div>;
    } else {
      body = <OfferCreator go={setRoute} draft={currentDraft} updateDraft={(patch) => updateDraft(currentDraft.id, patch)} finalizeDraft={finalizeDraft} discardDraft={discardDraft} offerCount={offers.length} />;
    }
  }
  else if (route.view === 'offer-history') body = <OfferHistory go={setRoute} offers={offers} removeOffer={removeOffer} loadIntoDraft={loadIntoDraft} />;
  else if (route.view === 'offer-detail') body = <OfferEditor go={setRoute} offers={offers} offerId={route.offerId} updateOffer={updateOffer} removeOffer={removeOffer} loadIntoDraft={loadIntoDraft} />;
  else if (route.view === 'offers') body = <OffersIndex go={setRoute} offers={offers} drafts={drafts} createDraft={createDraft} discardDraft={discardDraft} />;
  else if (route.view === 'settings') body = <Settings />;

  return (
    <div className="app" data-screen-label={`${route.view}${route.id ? ' · ' + route.id : ''}`}>
      <Sidebar view={route.view} setView={setRoute} watchCount={watchlist.length} open={navOpen} onClose={() => setNavOpen(false)} />
      <main className="main">
        <header className="mobile-bar">
          <button className="hamburger" onClick={() => setNavOpen(true)} aria-label="Open menu">
            <span></span><span></span><span></span>
          </button>
          <Logo />
        </header>
        {body}
        {navHistory.length > 0 && (
          <button className="back-fab" onClick={goBack} aria-label="Go back">
            <Icon d="M15 6l-6 6 6 6" size={20} />
            <span>Back</span>
          </button>
        )}
      </main>

      <TweaksPanel>
        <TweakSection label="Palette" />
        <TweakRadio
          label="Palette"
          value={t.palette}
          options={['warm', 'cool', 'mono']}
          onChange={(v) => setTweak('palette', v)}
        />
        <TweakSection label="Typography" />
        <TweakSelect
          label="Font pairing"
          value={t.fontPair}
          options={[
            { value: 'serif-grotesk', label: 'Serif × Grotesk' },
            { value: 'grotesk-mono',  label: 'Grotesk × Mono' },
            { value: 'serif-serif',   label: 'Serif × Serif' },
            { value: 'mono-mono',     label: 'All Mono' },
          ]}
          onChange={(v) => setTweak('fontPair', v)}
        />
        <TweakSection label="Density" />
        <TweakRadio
          label="Spacing"
          value={t.density}
          options={['compact', 'comfortable']}
          onChange={(v) => setTweak('density', v)}
        />
      </TweaksPanel>
    </div>
  );
}

function BootError({ error }) {
  return (
    <div style={{ padding: '24px', maxWidth: '720px', margin: '40px auto', fontFamily: 'system-ui, sans-serif' }}>
      <h1 style={{ fontSize: '28px', marginBottom: '12px' }}>Dashboard failed to load</h1>
      <p style={{ color: '#666' }}>{error && error.message ? error.message : String(error)}</p>
    </div>
  );
}

(async function boot() {
  const root = ReactDOM.createRoot(document.getElementById('root'));
  try {
    // 1. Auth gate. No session -> bounce to login.
    const me = await window.fetchCurrentUser();
    if (!me) {
      window.location.href = '/login.html';
      return;
    }
    window.CURRENT_USER = me;
    // 2. Pull dashboard data.
    await window.loadDashboardData();
    // 3. Mount.
    root.render(<App />);
  } catch (err) {
    console.error('Boot failed:', err);
    root.render(<BootError error={err} />);
  }
})();
