// ============================================================
// Farm Fresh — root app: register screen, dock, wiring
// ============================================================
const { useState: useStateApp, useEffect: useEffectApp, useMemo } = React;

function App() {
  const [role, setRole]       = useStateApp(null);
  const [me, setMe]           = useStateApp(null);     // { name, role, standName, ... }
  const [booting, setBooting] = useStateApp(true);     // checking for an existing login
  const [catalog, setCat]     = useStateApp([]);       // loaded from the cloud after login
  const [catReady, setCatReady] = useStateApp(false);  // catalog finished loading (guards cloud save)
  const [people, setPeople]   = useStateApp(() => FF.loadPeople());
  const [day, setDay]         = useStateApp({ date: FF.todayKey(), counter: 0, tickets: [] }); // today's sales, from cloud
  const [ticket, setTicket]   = useStateApp({ number: null, lines: [], time: null });

  // overlays / panels
  const [weightFor, setWeightFor] = useStateApp(null);   // { item, editIndex|null }
  const [showTicket, setShowTicket] = useStateApp(false);
  const [showTickets, setShowTickets] = useStateApp(false);
  const [showGate, setShowGate] = useStateApp(false);
  const [panel, setPanel] = useStateApp(null);           // null | "manager" | "dev"
  const [toast, setToast] = useStateApp(null);
  const [activeCat, setActiveCat] = useStateApp("All");
  const [confirmDlg, setConfirmDlg] = useStateApp(null);   // themed confirm dialog
  const [saving, setSaving] = useStateApp(false);          // a sale is being saved
  const savingRef = React.useRef(false);                   // sync guard against double-taps
  const [voidFor, setVoidFor] = useStateApp(null);         // { number, total } being voided

  const askConfirm = (opts) => setConfirmDlg(opts);

  // ---- load this stand's catalog from the cloud (seed starter if empty) ----
  useEffectApp(() => {
    if (!me || !me.standId) return;
    let alive = true;
    setCatReady(false);
    FF.itemsLoadOrSeed(me.standId)
      .then(items => { if (alive) { setCat(items); setCatReady(true); } })
      .catch(() => { if (alive) { setCat(FF.defaultCatalog().map(c => ({ ...c, id: FF.uuid() }))); } }); // offline fallback (display only)
    return () => { alive = false; };
  }, [me && me.standId]);

  // ---- save catalog changes back to the cloud (debounced) ----
  useEffectApp(() => {
    if (!catReady || !me || !me.standId) return;
    const h = setTimeout(() => { FF.itemsSave(me.standId, catalog).catch(() => {}); }, 700);
    return () => clearTimeout(h);
  }, [catalog, catReady]);

  // ---- people still device-local for now ----
  useEffectApp(() => { FF.savePeople(people); }, [people]);

  // ---- reload today's sales from the cloud ----
  const reloadDay = () => {
    if (!me || !me.standId) return;
    FF.ticketsLoad(me.standId, FF.todayKey()).then(setDay).catch(() => {});
  };

  // ---- load today's sales on login + live updates from other devices ----
  useEffectApp(() => {
    if (!me || !me.standId || !FF.sb) return;
    let alive = true;
    FF.ticketsLoad(me.standId, FF.todayKey()).then(d => { if (alive) setDay(d); }).catch(() => {});
    const ch = FF.sb.channel("sales-" + me.standId)
      .on("postgres_changes", { event: "*", schema: "public", table: "tickets", filter: "stand_id=eq." + me.standId },
          () => { FF.ticketsLoad(me.standId, FF.todayKey()).then(d => { if (alive) setDay(d); }).catch(() => {}); })
      .subscribe();
    return () => { alive = false; FF.sb.removeChannel(ch); };
  }, [me && me.standId]);

  // ---- resume an existing login on open ----
  useEffectApp(() => {
    let alive = true;
    FF.currentUser()
      .then(u => { if (!alive) return; if (u) { setRole(u.role); setMe(u); } setBooting(false); })
      .catch(() => { if (alive) setBooting(false); });
    return () => { alive = false; };
  }, []);

  // ---- daily rollover watcher (if app left open past midnight) ----
  useEffectApp(() => {
    const iv = setInterval(() => {
      if (day.date !== FF.todayKey()) {
        setDay({ date: FF.todayKey(), counter: 0, tickets: [] });
        setTicket({ number: null, lines: [], time: null });
        setPanel(null);
        reloadDay();
      }
    }, 30000);
    return () => clearInterval(iv);
  }, [day]);

  const total = useMemo(() => FF.ticketTotal(ticket, catalog), [ticket, catalog]);
  const itemCount = (ticket.lines || []).reduce((s, l) => {
    const it = catalog.find(c => c.id === l.itemId);
    return s + (it && it.unit === "each" ? l.qty : 1);
  }, 0);
  const displayNo = ticket.number != null ? ticket.number : (day.counter + 1);

  const flash = (msg) => { setToast(msg); setTimeout(() => setToast(null), 1900); };

  // ---- add item to ticket ----
  const tapItem = (item) => {
    if (item.unit === "lb") { setWeightFor({ item, editIndex: null }); return; }
    // each → increment existing line or add new
    setTicket(t => {
      const lines = [...t.lines];
      const idx = lines.findIndex(l => l.itemId === item.id);
      if (idx >= 0) lines[idx] = { ...lines[idx], qty: lines[idx].qty + 1 };
      else lines.push({ itemId: item.id, qty: 1 });
      return { ...t, lines };
    });
  };

  const confirmWeight = (w) => {
    const { item, editIndex } = weightFor;
    setTicket(t => {
      const lines = [...t.lines];
      if (editIndex != null) lines[editIndex] = { ...lines[editIndex], weight: w };
      else lines.push({ itemId: item.id, weight: w });
      return { ...t, lines };
    });
    setWeightFor(null);
  };

  const changeLine = (i, patch) => setTicket(t => {
    const lines = [...t.lines]; lines[i] = { ...lines[i], ...patch }; return { ...t, lines };
  });
  const removeLine = (i) => setTicket(t => {
    const lines = t.lines.filter((_, j) => j !== i); return { ...t, lines };
  });
  const editWeight = (i) => {
    const line = ticket.lines[i];
    const item = catalog.find(c => c.id === line.itemId);
    setWeightFor({ item, editIndex: i, initial: line.weight });
  };

  // ---- complete sale (server assigns the number → no collisions; locked against double-taps) ----
  const complete = async () => {
    if (!ticket.lines.length || savingRef.current) return;   // ignore extra taps while saving
    savingRef.current = true; setSaving(true);
    const amount = FF.ticketTotal(ticket, catalog);
    try {
      const number = await FF.ticketComplete(me.standId, day.date, ticket, catalog);
      flash("Sale " + FF.pad3(number) + " saved · " + FF.money(amount));
      setTicket({ number: null, lines: [], time: null });
      setShowTicket(false);
      reloadDay();
    } catch (e) {
      flash("Couldn't save — check your connection");
    } finally {
      savingRef.current = false; setSaving(false);
    }
  };

  // ---- reopen a completed sale (safeguards: confirm + keep it on the list) ----
  const reopen = (number) => {
    const t = day.tickets.find(x => x.number === number);
    if (!t) return;
    askConfirm({
      title: "Reopen Sale " + FF.pad3(number) + " · " + FF.money(FF.ticketTotal(t, catalog)) + "?",
      message: "It comes back into the register so you can change it."
        + (ticket.lines.length ? " Your current unsaved ticket will be discarded." : ""),
      confirmLabel: "Reopen",
      onConfirm: async () => {
        // mark it reopened in the cloud (it stays on the list, never vanishes)
        try { await FF.ticketSetReopened(me.standId, day.date, number, true); }
        catch (e) { flash("Couldn't reopen — check your connection"); return; }
        setTicket({ number: t.number, lines: t.lines, time: t.time, reopened: true });
        setShowTickets(false);
        flash("Reopened Sale " + FF.pad3(number) + " — Complete to save changes");
        reloadDay();
      },
    });
  };

  // ---- clear / abandon the current ticket (with confirm) ----
  const clearTicket = () => {
    if (!ticket.lines.length) return;
    const editing = ticket.number != null;
    askConfirm({
      title: editing ? "Stop editing Sale " + FF.pad3(ticket.number) + "?" : "Delete this transaction?",
      message: editing ? "It stays saved the way it was." : "The current ticket will be cleared.",
      confirmLabel: editing ? "Stop editing" : "Delete",
      tone: editing ? "green" : "terra",
      onConfirm: () => { setTicket({ number: null, lines: [], time: null }); setShowTicket(false); },
    });
  };

  // ---- put a reopened sale back (undo a mistaken reopen — no cart needed) ----
  const putBack = (number) => {
    const saved = day.tickets.find(x => x.number === number);
    const doPutBack = async () => {
      try { await FF.ticketSetReopened(me.standId, day.date, number, false); }
      catch (e) { flash("Couldn't put back — check your connection"); return; }
      if (ticket.number === number) setTicket({ number: null, lines: [], time: null });  // drop the edit if it's loaded
      setShowTicket(false);
      flash("Sale " + FF.pad3(number) + " put back");
      reloadDay();
    };
    // if it's loaded and you've changed it, confirm before tossing those edits
    const edited = saved && ticket.number === number && JSON.stringify(saved.lines) !== JSON.stringify(ticket.lines);
    if (!edited) { doPutBack(); return; }
    askConfirm({
      title: "Put Sale " + FF.pad3(number) + " back?",
      message: "It goes back the way it was. Your changes won't be saved.",
      confirmLabel: "Put back",
      onConfirm: doPutBack,
    });
  };

  // ---- void a sale (keeps an audit record: reason + who + when) ----
  const doVoid = (number, reason) => {
    setVoidFor(null);
    FF.ticketVoid(me.standId, day.date, number, reason, me && me.id, me && me.name)
      .then(() => { flash("Sale " + FF.pad3(number) + " voided"); reloadDay(); })
      .catch(() => flash("Couldn't void — check your connection"));
  };

  const dayTotal = useMemo(() => (day.tickets || []).filter(t => !t.voided).reduce((s, t) => s + FF.ticketTotal(t, catalog), 0), [day, catalog]);
  const cats = useMemo(() => FF.orderedCategories(catalog), [catalog]);
  const inBagMap = useMemo(() => {
    const m = {};
    (ticket.lines || []).forEach(l => {
      const it = catalog.find(c => c.id === l.itemId);
      if (!it) return;
      m[l.itemId] = it.unit === "each" ? l.qty : 1;
    });
    return m;
  }, [ticket, catalog]);

  // ---- gating ----
  const unlock = (u) => { setRole(u.role); setMe(u); };
  const enterPanel = (r) => { setShowGate(false); setPanel(r === "developer" ? "dev" : "manager"); };
  const signOut = () => askConfirm({
    title: "Sign out?",
    message: "You'll need your email and password to get back in.",
    confirmLabel: "Sign out",
    onConfirm: async () => {
      await FF.signOut();
      setRole(null); setMe(null); setPanel(null);
      setTicket({ number: null, lines: [], time: null });
    },
  });

  if (booting) return <div className="app"><div style={{ height:"100%", display:"flex", alignItems:"center",
    justifyContent:"center", color:"var(--ink-faint)", fontWeight:700 }}>Loading…</div></div>;
  if (!role) return <div className="app"><LoginScreen onUnlock={unlock} /></div>;
  if (panel === "manager") return <div className="app"><ManagerScreen catalog={catalog} setCatalog={setCat} onExit={() => setPanel(null)} /></div>;
  if (panel === "dev") return <div className="app"><DeveloperScreen catalog={catalog} setCatalog={setCat} people={people} setPeople={setPeople} onExit={() => setPanel(null)} me={me} onManageStands={() => setPanel("stands")} /></div>;
  if (panel === "stands") return <div className="app"><MyStandsScreen onExit={() => setPanel("dev")} /></div>;

  return (
    <div className="app">
      {/* ---- header ---- */}
      <div style={{ display:"flex", alignItems:"center", gap:10, padding:"13px 15px 11px",
        background:"var(--bg-2)", borderBottom:"1.5px solid var(--line)", flex:"none" }}>
        <span style={{ fontSize:23 }}>🧺</span>
        <div style={{ flex:1, lineHeight:1.05 }}>
          <div style={{ fontWeight:800, fontSize:18, letterSpacing:"-.01em", color:"var(--green-ink)" }}>Farm Fresh</div>
          <div className="mono" style={{ fontSize:11, color:"var(--ink-faint)", fontWeight:700 }}>
            TICKET No. {FF.pad3(displayNo)}{ticket.number != null && <span style={{ color:"var(--terra-ink)" }}> · reopened</span>}
          </div>
        </div>
        <button onClick={() => { setShowTickets(true); reloadDay(); }}
          style={{ display:"flex", alignItems:"center", gap:7,
            background:"var(--green-bg)", borderRadius:11, padding:"9px 13px" }}>
          <span style={{ fontSize:16 }}>🧾</span>
          <span style={{ fontSize:13.5, fontWeight:800, color:"var(--green-ink)" }}>Sales</span>
          {(day.tickets || []).length > 0 && (
            <span className="mono" style={{ fontSize:11.5, fontWeight:700, color:"#fff",
              background:"var(--green)", borderRadius:999, padding:"1px 7px" }}>{(day.tickets || []).length}</span>
          )}
        </button>
        <button onClick={() => setShowGate(true)} aria-label="Admin"
          style={{ width:40, height:40, borderRadius:11, background:"var(--surface)", border:"1.5px solid var(--line)",
            display:"flex", alignItems:"center", justifyContent:"center", fontSize:18, flex:"none" }}>⚙️</button>
        <button onClick={signOut} aria-label="Sign out"
          style={{ width:40, height:40, borderRadius:11, background:"var(--surface)", border:"1.5px solid var(--line)",
            display:"flex", alignItems:"center", justifyContent:"center", fontSize:17, flex:"none" }}>🚪</button>
      </div>

      {/* ---- category tabs ---- */}
      <div className="tabbar">
        <button className={"tab" + (activeCat === "All" ? " on" : "")} onClick={() => setActiveCat("All")}>
          All <span className="cnt">{catalog.length}</span>
        </button>
        {cats.map(c => (
          <button key={c} className={"tab" + (activeCat === c ? " on" : "")} onClick={() => setActiveCat(c)}>
            {c} <span className="cnt">{catalog.filter(i => (i.cat || "Other") === c).length}</span>
          </button>
        ))}
      </div>

      {/* ---- produce grid (grouped when All, filtered otherwise) ---- */}
      <div className="grid-wrap scroll">
        {(activeCat === "All" ? cats : [activeCat]).map(cat => {
          const items = catalog.filter(i => (i.cat || "Other") === cat);
          if (!items.length) return null;
          return (
            <div className="cat-block" key={cat}>
              {activeCat === "All" && (
                <div className="cat-head">
                  <span className="lbl">{cat}</span>
                  <span className="rule" />
                  <span className="n">{items.length}</span>
                </div>
              )}
              <div className="produce-grid">
                {items.map(item => (
                  <Tile key={item.id} item={item} inBag={inBagMap[item.id] || 0} onTap={tapItem} />
                ))}
              </div>
            </div>
          );
        })}
      </div>

      {/* ---- bottom dock ---- */}
      <div style={{ flex:"none", background:"var(--surface)", borderTop:"1.5px solid var(--line)",
        boxShadow:"0 -6px 20px oklch(0.4 0.02 60 /.07)", padding:"12px 15px calc(14px + env(safe-area-inset-bottom))" }}>
        <button onClick={() => ticket.lines.length && setShowTicket(true)}
          style={{ width:"100%", display:"flex", alignItems:"center", justifyContent:"space-between",
            marginBottom:11, padding:"2px 2px", opacity: ticket.lines.length ? 1 : .55 }}>
          <span style={{ display:"flex", alignItems:"center", gap:8, fontWeight:700, fontSize:14.5, color:"var(--ink-soft)" }}>
            🧾 {ticket.lines.length ? `${itemCount} item${itemCount !== 1 ? "s" : ""} · tap to review` : "Tap produce to start a ticket"}
            {ticket.lines.length > 0 && <span style={{ fontSize:13, color:"var(--ink-faint)" }}>›</span>}
          </span>
          <span className="mono" style={{ fontSize:30, fontWeight:700, color:"var(--green-ink)" }}>{FF.money(total)}</span>
        </button>
        <button className="btn btn-terra" disabled={!ticket.lines.length} onClick={() => setShowTicket(true)}
          style={{ width:"100%" }}>
          🧾&nbsp; Open Cart
        </button>
      </div>

      {/* ---- overlays ---- */}
      {weightFor && (
        <WeightModal item={weightFor.item} initial={weightFor.initial}
          onConfirm={confirmWeight} onCancel={() => setWeightFor(null)} />
      )}
      {showTicket && (
        <TicketSheet ticket={ticket} catalog={catalog} number={FF.pad3(displayNo)}
          onClose={() => setShowTicket(false)}
          onChangeLine={changeLine} onRemoveLine={removeLine} onEditWeight={(i) => { setShowTicket(false); editWeight(i); }}
          onComplete={complete} onClear={clearTicket} onPutBack={() => putBack(ticket.number)} saving={saving} />
      )}
      {showTickets && (
        <TicketsSheet day={day} catalog={catalog} onClose={() => setShowTickets(false)} onReopen={reopen} onPutBack={putBack}
          onVoid={(number, total) => setVoidFor({ number, total })} />
      )}
      {showGate && <StaffGate onPass={enterPanel} onCancel={() => setShowGate(false)} />}

      {voidFor && (
        <VoidModal number={FF.pad3(voidFor.number)} total={voidFor.total}
          onVoid={(reason) => doVoid(voidFor.number, reason)} onCancel={() => setVoidFor(null)} />
      )}

      {/* ---- themed confirm dialog ---- */}
      {confirmDlg && (
        <ConfirmModal {...confirmDlg}
          onConfirm={() => { const f = confirmDlg.onConfirm; setConfirmDlg(null); if (f) f(); }}
          onCancel={() => setConfirmDlg(null)} />
      )}

      {/* ---- toast ---- */}
      {toast && (
        <div style={{ position:"absolute", left:"50%", bottom:"calc(120px + env(safe-area-inset-bottom))",
          transform:"translateX(-50%)", zIndex:60, background:"var(--ink)", color:"var(--bg)",
          padding:"12px 20px", borderRadius:999, fontWeight:700, fontSize:14, whiteSpace:"nowrap",
          boxShadow:"var(--shadow-pop)", animation:"ff-pop .2s ease" }}>
          {toast}
        </div>
      )}
    </div>
  );
}

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