// Minimal CSV parser supporting quoted fields with embedded commas and newlines.
function parseCSV(text) {
  const rows = []; let cur = []; let buf = ""; let inQ = false;
  for (let i = 0; i < text.length; i++) {
    const c = text[i];
    if (inQ) {
      if (c === '"' && text[i + 1] === '"') { buf += '"'; i++; }
      else if (c === '"') inQ = false;
      else buf += c;
    } else {
      if (c === '"') inQ = true;
      else if (c === ",") { cur.push(buf); buf = ""; }
      else if (c === "\n" || c === "\r") {
        if (c === "\r" && text[i + 1] === "\n") i++;
        cur.push(buf); rows.push(cur); cur = []; buf = "";
      } else buf += c;
    }
  }
  if (buf.length || cur.length) { cur.push(buf); rows.push(cur); }
  return rows.filter((r) => r.some((v) => v.trim().length));
}

// Admin — dashboard, products, pre-bookings, loyalty, users (all Firestore-backed)
function AdminScreen() {
  const { activeRole, setActiveRole, role, user } = usePOS();
  const [tab, setTab] = React.useState("dash");
  const tabs = [
    { id: "dash",     label: "Dashboard" },
    { id: "reports",  label: "Reports" },
    { id: "products", label: "Products" },
    { id: "inventory", label: "Inventory" },
    { id: "loyalty",  label: "Loyalty" },
    { id: "prebook",  label: "Pre-bookings" },
    { id: "users",    label: "Users" },
    { id: "printer",  label: "Printer" },
    { id: "qris",     label: "QRIS Log" },
    { id: "maintenance", label: "Maintenance" },
    { id: "settings", label: "Settings" },
  ];
  // Role switcher options — what this user is allowed to view (defined in app.jsx).
  const allowedRoles = (window.RAIL_FOR_ROLE || {})[role] || [];
  const switchOptions = (window.ROLES || []).filter((r) => allowedRoles.includes(r.id));
  return (
    <div className="admin admin-stacked">
      <nav className="admin-topbar">
        <div className="admin-brand mono">ADMIN</div>
        {switchOptions.length > 1 && (
          <select
            className="admin-role-switch mono"
            value={activeRole}
            onChange={(e) => setActiveRole(e.target.value)}
            title="Switch to another role view"
          >
            {switchOptions.map((r) => (
              <option key={r.id} value={r.id}>{r.label}</option>
            ))}
          </select>
        )}
        <select className="admin-topbar-select mono" value={tab} onChange={(e) => setTab(e.target.value)}>
          {tabs.map((t) => <option key={t.id} value={t.id}>{t.label}</option>)}
        </select>
        <div className="admin-topbar-tabs">
          {tabs.map((t) => (
            <button key={t.id} className={"admin-tab" + (tab === t.id ? " active" : "")} onClick={() => setTab(t.id)}>
              {t.label}
            </button>
          ))}
        </div>
        <div className="admin-topbar-right">
          {window.PrintStatus && <window.PrintStatus />}
          <button className="admin-signout mono" onClick={() => firebase.auth().signOut()} title="Sign out">{(user.email || "").slice(0, 2).toUpperCase()} · Sign out</button>
        </div>
      </nav>
      <main className="admin-main">
        {tab === "dash"     && <AdminDash />}
        {tab === "reports"  && <AdminReports />}
        {tab === "products" && <AdminProducts />}
        {tab === "inventory" && <AdminInventory />}
        {tab === "loyalty"  && <AdminLoyalty />}
        {tab === "prebook"  && <AdminPrebook />}
        {tab === "users"    && <AdminUsers />}
        {tab === "printer"  && <AdminPrinter />}
        {tab === "qris"     && <AdminQrisLog />}
        {tab === "maintenance" && <AdminMaintenance />}
        {tab === "settings" && <AdminSettings />}
      </main>
    </div>
  );
}

function AdminDash() {
  const { orders: allOrders } = usePOS();
  // Only count today's calendar day (configured timezone, local 00:00–24:00).
  // The shared `orders` list also carries cross-day open bills, which must
  // not inflate today's revenue/margin figures.
  const today = dayKey();
  const orders = allOrders.filter((o) => o.dateKey === today);
  const open = orders.filter((o) => o.status !== "closed").length;
  const revenue = orders.reduce((s, o) => s + (o.total || 0) - (o.refundedAmount || 0), 0);
  const totalOrders = orders.length;
  const itemsSold = orders.reduce((s, o) => s + o.items.reduce((a, i) => a + i.qty, 0), 0);
  const avg = totalOrders ? Math.round(revenue / totalOrders) : 0;

  // Margin = sum of (price + modPrice - cogs) * qty across all order items
  const margin = orders.reduce((s, o) =>
    s + o.items.reduce((a, i) =>
      a + ((i.price + (i.modPrice || 0) - (i.cogs || 0)) * i.qty), 0), 0);
  const marginPct = revenue > 0 ? Math.round(margin / revenue * 100) : 0;

  // Top sellers + per-product margin
  const counts = {}, marginByName = {};
  orders.forEach((o) => o.items.forEach((i) => {
    counts[i.name] = (counts[i.name] || 0) + i.qty;
    marginByName[i.name] = (marginByName[i.name] || 0) + (i.price + (i.modPrice || 0) - (i.cogs || 0)) * i.qty;
  }));
  const top = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 6);
  const max = top[0]?.[1] || 1;

  // Hourly buckets
  const hourly = Array.from({ length: 18 }, (_, h) => ({ h: String(h).padStart(2, "0"), orders: 0, revenue: 0 }));
  orders.forEach((o) => {
    const ts = o.createdAt?.toDate ? o.createdAt.toDate() : new Date();
    const h = ts.getHours();
    if (hourly[h]) { hourly[h].orders++; hourly[h].revenue += (o.total || 0) - (o.refundedAmount || 0); }
  });
  const hourlyShown = hourly.filter((h) => h.orders).length ? hourly.slice(6, 22) : hourly.slice(6, 18);

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">DASHBOARD · {fmtDay()}</div>
          <h2>Today's overview</h2>
        </div>
        <div className="muted mono">Live · {fmtClock()}</div>
      </header>

      <div className="kpis">
        <KPI label="Revenue today" value={money(revenue)} delta={`${itemsSold} items sold`} />
        <KPI label="Orders" value={totalOrders} delta={`avg ${money(avg)}`} />
        <KPI label="Margin (gross)" value={money(margin)} delta={`${marginPct}% of revenue`} />
        <KPI label="Active now" value={open} delta="tickets in flight" accent />
      </div>

      <div className="dash-grid">
        <section className="panel-card">
          <h3>Hourly revenue</h3>
          {totalOrders === 0
            ? <div className="muted mono" style={{ padding: 20 }}>No orders yet today.</div>
            : <BarChart data={hourlyShown} />}
        </section>
        <section className="panel-card">
          <h3>Top sellers</h3>
          {top.length === 0
            ? <div className="muted mono" style={{ padding: 20 }}>—</div>
            : <ul className="top-list">
                {top.map(([name, qty], i) => (
                  <li key={name}>
                    <Mono className="rank">{String(i + 1).padStart(2, "0")}</Mono>
                    <span className="top-name">{name}</span>
                    <div className="top-bar"><div style={{ width: (qty / max * 100) + "%" }} /></div>
                    <Mono>{qty}</Mono>
                    <Mono className="muted" style={{ marginLeft: 12, minWidth: 80, textAlign: "right" }}>{moneyK(marginByName[name] || 0)}</Mono>
                  </li>
                ))}
              </ul>}
        </section>
      </div>
    </div>
  );
}

function KPI({ label, value, delta, accent }) {
  return (
    <div className={"kpi-card" + (accent ? " accent" : "")}>
      <div className="kpi-label mono">{label.toUpperCase()}</div>
      <Mono className="kpi-value">{value}</Mono>
      <div className="kpi-delta mono">{delta}</div>
    </div>
  );
}

function BarChart({ data }) {
  const max = Math.max(1, ...data.map((d) => d.revenue));
  return (
    <div className="bar-chart">
      {data.map((d) => (
        <div className="bar-col" key={d.h}>
          <div className="bar-wrap">
            <div className="bar" style={{ height: (d.revenue / max * 100) + "%" }} />
            <span className="bar-tip mono">{moneyK(d.revenue)}</span>
          </div>
          <Mono className="bar-label">{d.h}</Mono>
        </div>
      ))}
    </div>
  );
}

// Inline stock editor — click the number, type, Enter or blur to save.
// Goes through adjustStock (mode "count": the typed number is the new
// total, not a delta) so this quick edit is race-safe against a concurrent
// sale and logs a stock_movements entry like every other stock change,
// instead of the old direct-overwrite `.update({stock})` which could
// silently clobber a sale's decrement landing at the same moment.
function StockCell({ product, targetType = "product" }) {
  const target = product;
  const { adjustStock } = usePOS();
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState("");
  const [state, setState] = React.useState(""); // "saving" | "saved" | "error" | ""
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    if (editing && inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
  }, [editing]);

  const start = () => {
    setDraft(target.stock >= 999 ? "" : String(target.stock));
    setEditing(true);
  };
  const cancel = () => { setEditing(false); setDraft(""); };
  const save = async () => {
    const raw = draft.trim();
    const next = raw === "" ? 999 : Math.max(0, Math.floor(Number(raw)));
    setEditing(false);
    const prev = Number(target.stock) || 0;
    if (Number.isNaN(next) || next === prev) return;
    setState("saving");
    try {
      await adjustStock({
        targetType, targetId: target.id,
        mode: "count", value: next, reason: "adjustment",
        note: "Quick edit (" + (targetType === "ingredient" ? "Inventory" : "Products") + " table)",
      });
      setState("saved");
      setTimeout(() => setState((s) => s === "saved" ? "" : s), 1200);
    } catch (e) {
      setState("error");
      setTimeout(() => setState((s) => s === "error" ? "" : s), 2000);
    }
  };
  const onKey = (e) => {
    if (e.key === "Enter") save();
    else if (e.key === "Escape") cancel();
  };

  // Fallback: without an explicit threshold, default to 5.
  const threshold = Number(target.stockThreshold) || 5;
  const isLow = (target.stock || 0) <= threshold;

  if (editing) {
    return (
      <td>
        <input
          ref={inputRef}
          type="number"
          inputMode="numeric"
          className="stock-input mono"
          style={{ width: 70, padding: "4px 6px", textAlign: "right", border: "1px solid var(--accent-deep)", borderRadius: 4, font: "inherit" }}
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onBlur={save}
          onKeyDown={onKey}
          placeholder="∞"
        />
      </td>
    );
  }
  return (
    <td onClick={start} title="Click to edit stock" style={{ cursor: "pointer" }}>
      <Mono className={isLow || target.stock <= 20 ? "stock-low" : ""}>
        {target.stock >= 999 ? "∞" : target.stock}
        {isLow && " ⚠️"}
      </Mono>
      {state === "saving" && <span className="muted mono" style={{ fontSize: 10, marginLeft: 6 }}>…</span>}
      {state === "saved" && <span className="mono" style={{ fontSize: 10, marginLeft: 6, color: "var(--ok)" }}>✓</span>}
      {state === "error" && <span className="mono" style={{ fontSize: 10, marginLeft: 6, color: "var(--alert)" }}>!</span>}
    </td>
  );
}

// Inline editor for the low-stock threshold (0 = disabled).
function ThresholdCell({ product, targetType = "product" }) {
  const target = product;
  const { db } = usePOS();
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState("");
  const [state, setState] = React.useState("");
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    if (editing && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); }
  }, [editing]);

  const coll = targetType === "ingredient" ? "ingredients" : "products";
  const current = Number(target.stockThreshold) || 0;
  const start = () => { setDraft(current ? String(current) : ""); setEditing(true); };
  const cancel = () => { setEditing(false); setDraft(""); };
  const save = async () => {
    const raw = draft.trim();
    const next = raw === "" ? 0 : Math.max(0, Math.floor(Number(raw)));
    setEditing(false);
    if (Number.isNaN(next) || next === current) return;
    setState("saving");
    try {
      await db.collection(coll).doc(target.id).update({ stockThreshold: next });
      setState("saved");
      setTimeout(() => setState((s) => s === "saved" ? "" : s), 1200);
    } catch (e) {
      setState("error");
      setTimeout(() => setState((s) => s === "error" ? "" : s), 2000);
    }
  };
  const onKey = (e) => { if (e.key === "Enter") save(); else if (e.key === "Escape") cancel(); };

  if (editing) {
    return (
      <td>
        <input
          ref={inputRef} type="number" inputMode="numeric" className="mono"
          style={{ width: 60, padding: "4px 6px", textAlign: "right", border: "1px solid var(--accent-deep)", borderRadius: 4, font: "inherit" }}
          value={draft} onChange={(e) => setDraft(e.target.value)}
          onBlur={save} onKeyDown={onKey} placeholder="5"
        />
      </td>
    );
  }
  return (
    <td onClick={start} title="Low-stock threshold (blank = 5 default)" style={{ cursor: "pointer" }}>
      <Mono className="muted">{current > 0 ? `≤${current}` : "≤5"}</Mono>
      {state === "saving" && <span className="muted mono" style={{ fontSize: 10, marginLeft: 6 }}>…</span>}
      {state === "saved" && <span className="mono" style={{ fontSize: 10, marginLeft: 6, color: "var(--ok)" }}>✓</span>}
      {state === "error" && <span className="mono" style={{ fontSize: 10, marginLeft: 6, color: "var(--alert)" }}>!</span>}
    </td>
  );
}

function AdminProducts() {
  const { products, db, showToast } = usePOS();
  const [editing, setEditing] = React.useState(null);
  const [selected, setSelected] = React.useState({});
  const [query, setQuery] = React.useState("");
  const [catFilter, setCatFilter] = React.useState("");
  const [stationFilter, setStationFilter] = React.useState("");
  const [activeFilter, setActiveFilter] = React.useState(""); // "", "active", "inactive"
  const [sort, setSort] = React.useState({ key: "name", dir: "asc" });
  const fileRef = React.useRef(null);

  const categories = React.useMemo(
    () => Array.from(new Set(products.map((p) => p.category).filter(Boolean))).sort(),
    [products]
  );

  const visibleProducts = React.useMemo(() => {
    const q = query.trim().toLowerCase();
    let list = products.filter((p) => {
      if (catFilter && p.category !== catFilter) return false;
      if (stationFilter && p.station !== stationFilter) return false;
      if (activeFilter === "active" && !p.active) return false;
      if (activeFilter === "inactive" && p.active) return false;
      if (q) {
        const hay = `${p.name || ""} ${p.sku || ""} ${p.category || ""} ${p.desc || ""}`.toLowerCase();
        if (!hay.includes(q)) return false;
      }
      return true;
    });
    const dir = sort.dir === "asc" ? 1 : -1;
    const getVal = (p) => {
      switch (sort.key) {
        case "sku":      return (p.sku || "").toLowerCase();
        case "name":     return (p.name || "").toLowerCase();
        case "category": return (p.category || "").toLowerCase();
        case "station":  return (p.station || "").toLowerCase();
        case "price":    return Number(p.price) || 0;
        case "cogs":     return Number(p.cogs) || 0;
        case "margin":   return (Number(p.price) || 0) - (Number(p.cogs) || 0);
        case "stock":    return Number(p.stock) || 0;
        case "active":   return p.active ? 1 : 0;
        default:         return 0;
      }
    };
    return list.slice().sort((a, b) => {
      const va = getVal(a), vb = getVal(b);
      if (va < vb) return -1 * dir;
      if (va > vb) return  1 * dir;
      return 0;
    });
  }, [products, query, catFilter, stationFilter, activeFilter, sort]);

  const toggleSort = (key) => setSort((s) =>
    s.key === key ? { key, dir: s.dir === "asc" ? "desc" : "asc" } : { key, dir: "asc" });
  const sortIndicator = (key) => sort.key === key ? (sort.dir === "asc" ? " ▲" : " ▼") : "";
  const SortTh = ({ k, children, style }) => (
    <th style={{ ...(style || {}), cursor: "pointer", userSelect: "none" }} onClick={() => toggleSort(k)}>
      {children}<span className="mono muted" style={{ fontSize: 10 }}>{sortIndicator(k)}</span>
    </th>
  );

  const selectedIds = Object.keys(selected).filter((k) => selected[k]);
  const allSelected = visibleProducts.length > 0
    && visibleProducts.every((p) => selected[p.id]);

  const toggleAll = () => {
    if (allSelected) {
      const next = { ...selected };
      visibleProducts.forEach((p) => { delete next[p.id]; });
      setSelected(next);
    } else {
      const next = { ...selected };
      visibleProducts.forEach((p) => { next[p.id] = true; });
      setSelected(next);
    }
  };
  const toggleOne = (id) => setSelected((s) => ({ ...s, [id]: !s[id] }));

  const bulkSetActive = async (active) => {
    if (!selectedIds.length) return;
    const batch = db.batch();
    selectedIds.forEach((id) => batch.update(db.collection("products").doc(id), { active }));
    await batch.commit();
    showToast(`${selectedIds.length} product${selectedIds.length === 1 ? "" : "s"} ${active ? "activated" : "deactivated"}`);
    setSelected({});
  };
  const bulkDelete = async () => {
    if (!selectedIds.length) return;
    if (!confirm(`Delete ${selectedIds.length} product${selectedIds.length === 1 ? "" : "s"}? This cannot be undone.`)) return;
    while (selectedIds.length) {
      const chunk = selectedIds.splice(0, 400);
      const batch = db.batch();
      chunk.forEach((id) => batch.delete(db.collection("products").doc(id)));
      await batch.commit();
    }
    showToast("Deleted");
    setSelected({});
  };

  const seed = async () => {
    if (!confirm(`Add ${window.SEED_PRODUCTS.length} demo products?`)) return;
    const batch = db.batch();
    window.SEED_PRODUCTS.forEach((p) => batch.set(db.collection("products").doc(), p));
    await batch.commit();
    showToast("Demo menu added");
  };

  const remove = async (id) => {
    if (!confirm("Delete product?")) return;
    await db.collection("products").doc(id).delete();
  };

  const importCSV = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const text = await file.text();
    const rows = parseCSV(text);
    if (!rows.length) return alert("CSV is empty");
    const header = rows[0].map((h) => h.trim().toLowerCase());
    const required = ["name", "category", "station", "price"];
    const missing = required.filter((r) => !header.includes(r));
    if (missing.length) return alert("CSV is missing required columns: " + missing.join(", "));
    const items = [];
    for (let r = 1; r < rows.length; r++) {
      const row = rows[r]; if (!row.length || !row[0]) continue;
      const obj = {};
      header.forEach((h, i) => { obj[h] = (row[i] || "").trim(); });
      let modifiers = [];
      if (obj.modifiers) {
        try { modifiers = JSON.parse(obj.modifiers); } catch (_) { modifiers = []; }
      }
      items.push({
        sku:      obj.sku || "",
        name:     obj.name,
        category: obj.category,
        station:  obj.station || "bar",
        price:    Number(obj.price) || 0,
        cogs:     Number(obj.cogs) || 0,
        stock:    obj.stock === "" || obj.stock == null ? 999 : Number(obj.stock),
        bulkQty:   Number(obj.bulkqty) || 0,
        bulkType:  obj.bulktype === "price" ? "price" : "percent",
        bulkValue: Number(obj.bulkvalue) || 0,
        desc:     obj.desc || "",
        swatch:   obj.swatch || "#a98558",
        active:   obj.active == null || obj.active === "" ? true : /^(true|1|yes|y)$/i.test(obj.active),
        modifiers,
      });
    }
    if (!items.length) return alert("No valid rows found");
    if (!confirm(`Import ${items.length} products?`)) { e.target.value = ""; return; }
    // Firestore batch max is 500 ops; we're well under, but split anyway for safety.
    while (items.length) {
      const chunk = items.splice(0, 400);
      const batch = db.batch();
      chunk.forEach((p) => batch.set(db.collection("products").doc(), p));
      await batch.commit();
    }
    showToast("Imported products");
    e.target.value = "";
  };

  const downloadTemplate = () => {
    const modSample = JSON.stringify([{ group: "Serve", type: "single", options: [{ id: "hot", label: "Hot", delta: 0 }, { id: "ice", label: "Ice", delta: 0 }] }]).replace(/"/g, '""');
    const csv = `sku,name,category,station,price,cogs,stock,desc,swatch,active,modifiers
P001,Americano,Espresso,bar,22000,10000,999,House blend,#3a2a1a,true,"${modSample}"
P002,Latte,Milk,bar,34000,15000,999,Espresso silky milk,#c8a585,true,
`;
    const blob = new Blob([csv], { type: "text/csv" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob); a.download = "products-template.csv"; a.click();
    URL.revokeObjectURL(a.href);
  };

  const exportSelectedCSV = () => {
    const idSet = new Set(selectedIds);
    const list = products.filter((p) => idSet.has(p.id));
    if (!list.length) return;
    const csvEscape = (v) => {
      const s = String(v == null ? "" : v);
      return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
    };
    const header = ["sku","name","category","station","price","cogs","stock","stockThreshold","bulkQty","bulkType","bulkValue","desc","swatch","active","modifiers"];
    const lines = [header.join(",")];
    list.forEach((p) => {
      lines.push([
        csvEscape(p.sku || ""),
        csvEscape(p.name || ""),
        csvEscape(p.category || ""),
        csvEscape(p.station || "bar"),
        Number(p.price) || 0,
        Number(p.cogs) || 0,
        Number(p.stock) || 0,
        Number(p.stockThreshold) || 0,
        Number(p.bulkQty) || 0,
        p.bulkType === "price" ? "price" : "percent",
        Number(p.bulkValue) || 0,
        csvEscape(p.desc || ""),
        csvEscape(p.swatch || ""),
        p.active ? "true" : "false",
        csvEscape(p.modifiers && p.modifiers.length ? JSON.stringify(p.modifiers) : ""),
      ].join(","));
    });
    const blob = new Blob([lines.join("\n")], { type: "text/csv;charset=utf-8" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = `products-${new Date().toISOString().slice(0, 10)}.csv`;
    a.click();
    URL.revokeObjectURL(a.href);
  };

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">CATALOG</div>
          <h2>{products.length} products</h2>
        </div>
        <div className="row" style={{ gap: 8 }}>
          {products.length === 0 && <button className="btn-ghost" onClick={seed}>Install demo menu</button>}
          <button className="btn-ghost" onClick={downloadTemplate}>CSV template</button>
          <button className="btn-ghost" onClick={() => fileRef.current?.click()}>Import CSV</button>
          <input ref={fileRef} type="file" accept=".csv,text/csv" onChange={importCSV} style={{ display: "none" }} />
          <button className="btn-primary" onClick={() => setEditing({})}>+ Add product</button>
        </div>
      </header>
      <div className="row" style={{ gap: 8, flexWrap: "wrap", margin: "10px 0", alignItems: "flex-end" }}>
        <label className="field" style={{ flex: "1 1 240px" }}>
          <span className="field-label mono">Search</span>
          <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Name, SKU, category…" />
        </label>
        <label className="field" style={{ flex: "0 0 160px" }}>
          <span className="field-label mono">Category</span>
          <select value={catFilter} onChange={(e) => setCatFilter(e.target.value)}>
            <option value="">All</option>
            {categories.map((c) => <option key={c} value={c}>{c}</option>)}
          </select>
        </label>
        <label className="field" style={{ flex: "0 0 140px" }}>
          <span className="field-label mono">Station</span>
          <select value={stationFilter} onChange={(e) => setStationFilter(e.target.value)}>
            <option value="">All</option>
            <option value="bar">bar</option>
            <option value="kitchen">kitchen</option>
          </select>
        </label>
        <label className="field" style={{ flex: "0 0 140px" }}>
          <span className="field-label mono">Status</span>
          <select value={activeFilter} onChange={(e) => setActiveFilter(e.target.value)}>
            <option value="">All</option>
            <option value="active">Active</option>
            <option value="inactive">Inactive</option>
          </select>
        </label>
        {(query || catFilter || stationFilter || activeFilter) && (
          <button className="btn-ghost-sm" onClick={() => { setQuery(""); setCatFilter(""); setStationFilter(""); setActiveFilter(""); }}>
            Clear filters
          </button>
        )}
        <div className="muted mono" style={{ fontSize: 12, marginLeft: "auto" }}>
          {visibleProducts.length} of {products.length}
        </div>
      </div>
      {selectedIds.length > 0 && (
        <div className="bulk-bar">
          <span className="mono">{selectedIds.length} selected</span>
          <button className="btn-ghost-sm" onClick={exportSelectedCSV}>Export CSV</button>
          <button className="btn-ghost-sm" onClick={() => bulkSetActive(true)}>Activate</button>
          <button className="btn-ghost-sm" onClick={() => bulkSetActive(false)}>Deactivate</button>
          <button className="btn-ghost-sm danger" onClick={bulkDelete}>Delete</button>
          <button className="btn-ghost-sm" onClick={() => setSelected({})}>Clear</button>
        </div>
      )}
      <div className="panel-card no-pad">
        <table className="data-table">
          <thead>
            <tr>
              <th style={{ width: 32 }}><input type="checkbox" checked={allSelected} onChange={toggleAll} /></th>
              <th></th>
              <SortTh k="sku">SKU</SortTh>
              <SortTh k="name">Name</SortTh>
              <SortTh k="category">Category</SortTh>
              <SortTh k="station">Station</SortTh>
              <SortTh k="price">Price</SortTh>
              <SortTh k="cogs">COGS</SortTh>
              <SortTh k="margin">Margin</SortTh>
              <SortTh k="stock">Stock</SortTh>
              <th>Alert ≤</th>
              <th>Modifiers</th>
              <SortTh k="active">Active</SortTh>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {visibleProducts.length === 0 && (
              <tr><td colSpan="14" className="muted mono" style={{ textAlign: "center", padding: 24 }}>
                {products.length === 0 ? "No products yet." : "No products match the filters."}
              </td></tr>
            )}
            {visibleProducts.map((p) => {
              const m = (p.price || 0) - (p.cogs || 0);
              const mp = p.price > 0 ? Math.round(m / p.price * 100) : 0;
              return (
                <tr key={p.id} className={selected[p.id] ? "row-selected" : ""}>
                  <td><input type="checkbox" checked={!!selected[p.id]} onChange={() => toggleOne(p.id)} /></td>
                  <td><ProductSwatch color={p.swatch || "#a98558"} label={p.name} size={36} /></td>
                  <td><Mono>{p.sku || "—"}</Mono></td>
                  <td>
                    <div>
                      {p.name}
                      {Array.isArray(p.recipe) && p.recipe.length > 0 && (
                        <span className="muted mono" style={{ fontSize: 10, marginLeft: 6 }} title="Stock derived from its recipe/combo components">
                          {p.recipe.some((r) => (r.targetType || "ingredient") === "product") ? "COMBO" : "RECIPE"}
                        </span>
                      )}
                    </div>
                    <div className="muted mono">{p.desc}</div>
                  </td>
                  <td><Mono>{p.category}</Mono></td>
                  <td><span className={"chip-station chip-" + p.station}>{p.station}</span></td>
                  <td><Mono>{money(p.price)}</Mono></td>
                  <td><Mono className="muted">{money(p.cogs || 0)}</Mono></td>
                  <td><Mono className={mp < 30 ? "stock-low" : ""}>{money(m)} <span style={{ opacity: 0.6 }}>· {mp}%</span></Mono></td>
                  <StockCell product={p} />
                  <ThresholdCell product={p} />
                  <td><Mono className="muted" style={{ fontSize: 11 }}>{
                    (p.modifiers && p.modifiers.length)
                      ? p.modifiers.map((g) => `${g.group}(${g.options.length})`).join(" · ")
                      : `[${p.category} default]`
                  }</Mono></td>
                  <td><Mono>{p.active ? "✓" : "—"}</Mono></td>
                  <td className="row-actions">
                    <button className="btn-ghost-sm" onClick={() => setEditing(p)}>Edit</button>
                    <button className="btn-ghost-sm" onClick={() => remove(p.id)}>Delete</button>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      {editing && <ProductEditor product={editing} onClose={() => setEditing(null)} />}
    </div>
  );
}

function ProductEditor({ product, onClose }) {
  const { db, products, ingredients } = usePOS();
  const knownCats = Array.from(new Set([
    ...window.CATEGORIES.filter((c) => c !== "All"),
    ...products.map((x) => x.category).filter(Boolean),
  ])).sort();
  const [p, setP] = React.useState({
    sku: "", name: "", category: "Espresso", station: "bar", price: 0, cogs: 0, stock: 999,
    swatch: "#a98558", desc: "", active: true, modifiers: [], recipe: [], ...product,
  });

  // Recipe/combo editing helpers — optional per-product component list.
  // Each line targets either an `ingredient` (raw material) or a `product`
  // (this becomes a combo/package bundling other sellable items). When
  // non-empty, this product's availability/stock is derived from component
  // levels instead of the plain Stock field (see cashier.jsx recipeAvail /
  // store.jsx decrementStock). No nested combos: a product that already
  // has its own recipe/combo is excluded from the "Product" picker below,
  // so a combo can only be built from base (non-combo) products — keeps
  // stock resolution a single level deep, no recursion needed anywhere.
  const comboCandidates = products.filter((x) => x.id !== product.id && !(Array.isArray(x.recipe) && x.recipe.length));
  const addRecipeLine = () => setP({ ...p, recipe: [...(p.recipe || []), { targetType: "ingredient", targetId: "", targetName: "", qty: 0 }] });
  const setRecipeLine = (ri, patch) => setP({ ...p, recipe: p.recipe.map((r, i) => i === ri ? { ...r, ...patch } : r) });
  const removeRecipeLine = (ri) => setP({ ...p, recipe: p.recipe.filter((_, i) => i !== ri) });
  // r.ingredientId/ingredientName are read as a fallback for recipe lines
  // saved before combo support existed — those were always ingredient-type.
  const hasRecipe = (p.recipe || []).some((r) => r.targetId || r.ingredientId);
  const recipeAvailableNow = hasRecipe
    ? Math.min(...p.recipe.filter((r) => r.targetId || r.ingredientId).map((r) => {
        const qty = Number(r.qty) || 0;
        if (qty <= 0) return Infinity; // bad data — non-blocking
        const targetType = r.targetType || "ingredient";
        const targetId = r.targetId || r.ingredientId;
        const source = targetType === "product" ? products : ingredients;
        const comp = source.find((x) => x.id === targetId);
        const stock = Number(comp?.stock) || 0;
        return stock >= 999 ? Infinity : Math.floor(stock / qty);
      }))
    : null;
  const recipeCost = (p.recipe || []).reduce((s, r) => {
    const targetId = r.targetId || r.ingredientId;
    if (!targetId) return s;
    if ((r.targetType || "ingredient") === "product") {
      const comp = products.find((x) => x.id === targetId);
      return s + (Number(r.qty) || 0) * (Number(comp?.cogs) || 0);
    }
    const ing = ingredients.find((i) => i.id === targetId);
    return s + (Number(r.qty) || 0) * (Number(ing?.avgCost) || 0);
  }, 0);
  // Modifier editing helpers
  const slug = (s) => (s || "").toLowerCase().replace(/[^a-z0-9]+/g, "").slice(0, 12) || ("o" + Math.random().toString(36).slice(2, 5));
  const updateGroups = (groups) => setP({ ...p, modifiers: groups });
  const addGroup = () => updateGroups([...(p.modifiers || []), { group: "Type", type: "single", options: [{ id: "opt1", label: "", delta: 0 }] }]);
  const setGroup = (gi, patch) => updateGroups(p.modifiers.map((g, i) => i === gi ? { ...g, ...patch } : g));
  const removeGroup = (gi) => updateGroups(p.modifiers.filter((_, i) => i !== gi));
  const addOpt = (gi) => setGroup(gi, { options: [...p.modifiers[gi].options, { id: "opt" + (p.modifiers[gi].options.length + 1), label: "", delta: 0 }] });
  const setOpt = (gi, oi, patch) => setGroup(gi, { options: p.modifiers[gi].options.map((o, i) => i === oi ? { ...o, ...patch } : o) });
  const removeOpt = (gi, oi) => setGroup(gi, { options: p.modifiers[gi].options.filter((_, i) => i !== oi) });
  const applyTemplate = (name) => {
    const T = {
      "hot-ice": [{ group: "Serve", type: "single", options: [{ id: "hot", label: "Hot", delta: 0 }, { id: "ice", label: "Ice", delta: 0 }] }],
      "size":    [{ group: "Size",  type: "single", options: [{ id: "r", label: "Regular", delta: 0 }, { id: "l", label: "Large", delta: 6000 }] }],
      "milk":    [{ group: "Milk",  type: "single", options: [{ id: "full", label: "Whole", delta: 0 }, { id: "oat", label: "Oat", delta: 7000 }, { id: "almond", label: "Almond", delta: 7000 }] }],
      "category": window.MODIFIERS[p.category] ? JSON.parse(JSON.stringify(window.MODIFIERS[p.category])) : [],
    };
    updateGroups([...(p.modifiers || []), ...T[name]]);
  };

  const save = async () => {
    // Clean modifiers — drop empty groups/options and auto-generate ids from labels
    const modifiers = (p.modifiers || [])
      .map((g) => ({
        group: (g.group || "").trim() || "Option",
        type: g.type === "multi" ? "multi" : "single",
        options: (g.options || [])
          .filter((o) => (o.label || "").trim().length)
          .map((o) => ({ id: o.id || slug(o.label), label: o.label.trim(), delta: Number(o.delta) || 0 })),
      }))
      .filter((g) => g.options.length);

    // Recipe/combo items ignore the plain Stock field entirely (availability
    // comes from component levels) — store stock:999 ("unlimited") so this
    // product is correctly excluded from the plain-stock valuation report,
    // and store cogs as the computed recipe cost so it displays consistently
    // elsewhere (e.g. the products table's margin column).
    const recipe = (p.recipe || [])
      .filter((r) => (r.targetId || r.ingredientId) && (Number(r.qty) || 0) > 0)
      .map((r) => ({
        targetType: r.targetType || "ingredient",
        targetId: r.targetId || r.ingredientId,
        targetName: r.targetName || r.ingredientName || "",
        qty: Number(r.qty) || 0,
      }));

    const payload = {
      sku: p.sku || "", name: p.name, category: p.category, station: p.station,
      price: Number(p.price),
      cogs: recipe.length ? recipeCost : (Number(p.cogs) || 0),
      stock: recipe.length ? 999 : Number(p.stock),
      stockThreshold: Number(p.stockThreshold) || 0,
      bulkQty: Number(p.bulkQty) || 0,
      bulkType: p.bulkType === "price" ? "price" : "percent",
      bulkValue: Number(p.bulkValue) || 0,
      swatch: p.swatch, desc: p.desc || "", active: !!p.active, modifiers,
      recipe,
    };
    if (product.id) await db.collection("products").doc(product.id).update(payload);
    else await db.collection("products").add(payload);
    // Fire low-stock notification if this edit brought stock across the
    // threshold or down to zero. Only when editing an existing product —
    // brand-new products starting at 0 don't count as an alert.
    if (product.id) {
      const prev = Number(product.stock) || 0;
      const next = Number(p.stock) || 0;
      const threshold = Number(p.stockThreshold) || 5;
      const crossedThreshold = next <= threshold && prev > threshold;
      const hitZero = next <= 0 && prev > 0;
      if (crossedThreshold || hitZero) {
        fetch("/api/notify-low-stock", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            name: p.name,
            sku: p.sku || "",
            stock: next,
            threshold,
          }),
        }).catch(() => {});
      }
    }
    onClose();
  };
  return (
    <div className="sheet-wrap" onMouseDown={onClose}>
      <div className="sheet" onMouseDown={(e) => e.stopPropagation()}>
        <header className="sheet-head">
          <div><div className="eyebrow mono">PRODUCT</div><h3>{product.id ? "Edit" : "Add"} product</h3></div>
          <button className="icon-x" onClick={onClose}>×</button>
        </header>
        <div className="sheet-body">
          <div className="row" style={{ gap: 8 }}>
            <label className="field" style={{ flex: "0 0 140px" }}><span className="field-label mono">SKU</span>
              <input value={p.sku || ""} onChange={(e) => setP({ ...p, sku: e.target.value })} placeholder="P001" />
            </label>
            <label className="field" style={{ flex: 1 }}><span className="field-label mono">Name</span>
              <input value={p.name} onChange={(e) => setP({ ...p, name: e.target.value })} />
            </label>
          </div>
          <label className="field"><span className="field-label mono">Description</span>
            <input value={p.desc} onChange={(e) => setP({ ...p, desc: e.target.value })} />
          </label>
          <div className="row" style={{ gap: 8 }}>
            <label className="field"><span className="field-label mono">Category</span>
              <input list="cat-options" value={p.category} onChange={(e) => setP({ ...p, category: e.target.value })} placeholder="Coffee / Food / Tea …" />
              <datalist id="cat-options">{knownCats.map((c) => <option key={c} value={c} />)}</datalist>
            </label>
            <label className="field"><span className="field-label mono">Station</span>
              <select value={p.station} onChange={(e) => setP({ ...p, station: e.target.value })}>
                <option value="bar">bar</option>
                <option value="kitchen">kitchen</option>
              </select>
            </label>
          </div>
          <div className="row" style={{ gap: 8 }}>
            <label className="field"><span className="field-label mono">Price (Rp)</span>
              <input type="number" value={p.price} onChange={(e) => setP({ ...p, price: e.target.value })} />
            </label>
            {hasRecipe ? (
              <label className="field"><span className="field-label mono">COGS (from recipe/combo)</span>
                <input type="text" disabled value={money(recipeCost)} style={{ opacity: 0.6 }} />
              </label>
            ) : (
              <label className="field"><span className="field-label mono">COGS (Rp)</span>
                <input type="number" value={p.cogs || 0} onChange={(e) => setP({ ...p, cogs: e.target.value })} />
              </label>
            )}
            {hasRecipe ? (
              <label className="field"><span className="field-label mono">Stock (from recipe/combo)</span>
                <input type="text" disabled value={recipeAvailableNow === Infinity ? "∞" : recipeAvailableNow}
                  style={{ opacity: 0.6 }} title="Computed from each component's stock ÷ qty needed" />
              </label>
            ) : (
              <>
                <label className="field"><span className="field-label mono">Stock</span>
                  <input type="number" value={p.stock} onChange={(e) => setP({ ...p, stock: e.target.value })} />
                </label>
                <label className="field"><span className="field-label mono">Alert at ≤</span>
                  <input type="number" min="0" placeholder="blank = 5 (default)" value={p.stockThreshold || ""}
                    onChange={(e) => setP({ ...p, stockThreshold: e.target.value })}
                    title="Notify admin when stock drops to or below this number" />
                </label>
              </>
            )}
            <label className="field"><span className="field-label mono">Swatch</span>
              <input type="color" value={p.swatch} onChange={(e) => setP({ ...p, swatch: e.target.value })} />
            </label>
          </div>
          <div className="muted mono" style={{ fontSize: 12, marginTop: 8 }}>
            Margin: {money(Math.max(0, Number(p.price || 0) - (hasRecipe ? recipeCost : Number(p.cogs || 0))))} {p.price > 0 && `· ${Math.round((p.price - (hasRecipe ? recipeCost : (p.cogs || 0))) / p.price * 100)}%`}
          </div>

          <div className="mod-editor" style={{ marginTop: 18, paddingTop: 14, borderTop: "1px solid var(--line)" }}>
            <div className="row" style={{ justifyContent: "space-between", marginBottom: 8 }}>
              <div>
                <div className="eyebrow mono">RECIPE / COMBO</div>
                <div className="muted mono" style={{ fontSize: 11 }}>
                  Optional. Add ingredients and/or other products as
                  components — selling this item deducts stock from every
                  component instead of the plain Stock field above. Mix both
                  to build a recipe (raw materials), a combo/package
                  (bundled products), or both. A product that's already a
                  combo can't be nested inside another one. Manage
                  ingredients under Admin → Inventory.
                </div>
              </div>
              <button type="button" className="btn-ghost-sm" onClick={addRecipeLine}>+ Component</button>
            </div>
            {(p.recipe || []).length === 0 && (
              <div className="muted mono" style={{ fontSize: 12, padding: "10px 0" }}>No recipe/combo — this item tracks its own Stock field above.</div>
            )}
            {(p.recipe || []).map((r, ri) => {
              const targetType = r.targetType || "ingredient";
              const targetId = r.targetId || r.ingredientId;
              const source = targetType === "product" ? comboCandidates : ingredients;
              const comp = source.find((x) => x.id === targetId);
              return (
                <div key={ri} className="row" style={{ gap: 6, marginTop: 6, alignItems: "center" }}>
                  <select style={{ flex: "0 0 112px" }} value={targetType}
                    onChange={(e) => setRecipeLine(ri, { targetType: e.target.value, targetId: "", targetName: "" })}>
                    <option value="ingredient">Ingredient</option>
                    <option value="product">Product</option>
                  </select>
                  <select style={{ flex: 1 }} value={targetId || ""}
                    onChange={(e) => setRecipeLine(ri, { targetId: e.target.value, targetName: (source.find((x) => x.id === e.target.value) || {}).name || "" })}>
                    <option value="">Select {targetType}…</option>
                    {source.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
                  </select>
                  <input type="number" min="0" style={{ flex: "0 0 100px" }} value={r.qty}
                    onChange={(e) => setRecipeLine(ri, { qty: e.target.value })} placeholder="Qty" />
                  <span className="mono muted" style={{ flex: "0 0 34px" }}>{targetType === "ingredient" ? (comp?.unit || "") : "pcs"}</span>
                  <button type="button" className="btn-ghost-sm" onClick={() => removeRecipeLine(ri)}>×</button>
                </div>
              );
            })}
            {hasRecipe && (
              <div className="muted mono" style={{ fontSize: 12, marginTop: 10 }}>
                Available now: {recipeAvailableNow === Infinity ? "∞" : recipeAvailableNow} · Est. cost/unit: {money(recipeCost)}
              </div>
            )}
          </div>
          <div className="row" style={{ gap: 8, marginTop: 14, paddingTop: 14, borderTop: "1px solid var(--line)", alignItems: "flex-end", flexWrap: "wrap" }}>
            <div style={{ flex: "1 1 100%" }}>
              <div className="eyebrow mono">BULK / GROSIR DISCOUNT</div>
              <div className="muted mono" style={{ fontSize: 11 }}>
                When the cashier sells this many or more in one line, apply the deal. Leave qty empty or 0 to disable.
              </div>
            </div>
            <label className="field" style={{ flex: "0 0 130px" }}><span className="field-label mono">Min qty</span>
              <input type="number" min="0" placeholder="0 = off" value={p.bulkQty || ""}
                onChange={(e) => setP({ ...p, bulkQty: e.target.value })} />
            </label>
            <label className="field" style={{ flex: "0 0 160px" }}><span className="field-label mono">Deal type</span>
              <select value={p.bulkType || "percent"} onChange={(e) => setP({ ...p, bulkType: e.target.value })}>
                <option value="percent">% off</option>
                <option value="price">Fixed unit price</option>
              </select>
            </label>
            <label className="field" style={{ flex: "1 1 160px" }}>
              <span className="field-label mono">{(p.bulkType || "percent") === "price" ? "Unit price (Rp)" : "Percent off (%)"}</span>
              <input type="number" min="0" value={p.bulkValue || ""}
                onChange={(e) => setP({ ...p, bulkValue: e.target.value })}
                placeholder={(p.bulkType || "percent") === "price" ? "10000" : "10"} />
            </label>
          </div>
          <div className="mod-editor" style={{ marginTop: 18, paddingTop: 14, borderTop: "1px solid var(--line)" }}>
            <div className="row" style={{ justifyContent: "space-between", marginBottom: 8 }}>
              <div>
                <div className="eyebrow mono">MODIFIER GROUPS</div>
                <div className="muted mono" style={{ fontSize: 11 }}>
                  Shown to the cashier when this product is selected. Leave empty to use the {p.category} category defaults.
                </div>
              </div>
              <div className="row" style={{ gap: 6 }}>
                <button type="button" className="btn-ghost-sm" onClick={() => applyTemplate("hot-ice")}>+ Hot/Ice</button>
                <button type="button" className="btn-ghost-sm" onClick={() => applyTemplate("size")}>+ Size</button>
                <button type="button" className="btn-ghost-sm" onClick={() => applyTemplate("milk")}>+ Milk</button>
                <button type="button" className="btn-ghost-sm" onClick={addGroup}>+ Empty group</button>
              </div>
            </div>

            {(p.modifiers || []).length === 0 && (
              <div className="muted mono" style={{ fontSize: 12, padding: "10px 0" }}>No custom modifiers. Cashier will see the {p.category} defaults.</div>
            )}

            {(p.modifiers || []).map((g, gi) => (
              <div key={gi} className="mod-group-edit">
                <div className="row" style={{ gap: 8, alignItems: "flex-end" }}>
                  <label className="field" style={{ flex: 1 }}>
                    <span className="field-label mono">Group name</span>
                    <input value={g.group} onChange={(e) => setGroup(gi, { group: e.target.value })} placeholder="Type / Size / Milk" />
                  </label>
                  <label className="field" style={{ flex: "0 0 140px" }}>
                    <span className="field-label mono">Choose</span>
                    <select value={g.type} onChange={(e) => setGroup(gi, { type: e.target.value })}>
                      <option value="single">Pick one</option>
                      <option value="multi">Pick multiple</option>
                    </select>
                  </label>
                  <button type="button" className="btn-ghost-sm danger" onClick={() => removeGroup(gi)}>Remove group</button>
                </div>
                <div className="mod-opts-edit">
                  {g.options.map((o, oi) => (
                    <div key={oi} className="row" style={{ gap: 6, marginTop: 6 }}>
                      <input style={{ flex: 1 }} value={o.label} onChange={(e) => setOpt(gi, oi, { label: e.target.value })} placeholder="e.g. Hot, Ice, Large" />
                      <input type="number" style={{ flex: "0 0 120px" }} value={o.delta} onChange={(e) => setOpt(gi, oi, { delta: e.target.value })} placeholder="Δ price" />
                      <button type="button" className="btn-ghost-sm" onClick={() => removeOpt(gi, oi)}>×</button>
                    </div>
                  ))}
                  <button type="button" className="btn-ghost-sm" style={{ marginTop: 6 }} onClick={() => addOpt(gi)}>+ Option</button>
                </div>
              </div>
            ))}
          </div>

          <label className="row" style={{ gap: 8, marginTop: 14 }}>
            <input type="checkbox" checked={p.active} onChange={(e) => setP({ ...p, active: e.target.checked })} />
            <span className="mono">Active</span>
          </label>
        </div>
        <footer className="sheet-foot">
          <button className="btn-ghost" onClick={onClose}>Cancel</button>
          <button className="btn-primary" onClick={save}>Save</button>
        </footer>
      </div>
    </div>
  );
}

// Inventory — ingredients (raw-material stock), suppliers, receiving
// (purchases with weighted-average cost), and manual stock adjustments.
// Mirrors AdminReports' own-rail-inside-a-tab layout. All writes go through
// store.jsx's adjustStock/receiveStock so every change is race-safe and
// logged to stock_movements (see Reports → Stock movements for the log).
function AdminInventory() {
  const [section, setSection] = React.useState("ingredients");
  const SECTIONS = [
    { id: "ingredients", label: "Ingredients" },
    { id: "suppliers",   label: "Suppliers" },
    { id: "receiving",   label: "Receiving" },
    { id: "adjustments", label: "Adjustments" },
  ];
  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">INVENTORY</div>
          <h2>{SECTIONS.find((s) => s.id === section).label}</h2>
        </div>
      </header>
      <div className="reports-layout">
        <aside className="reports-rail">
          {SECTIONS.map((s) => (
            <button key={s.id} className={"reports-tab" + (section === s.id ? " active" : "")}
              onClick={() => setSection(s.id)}>{s.label}</button>
          ))}
        </aside>
        <main className="reports-main">
          {section === "ingredients" && <IngredientsPanel />}
          {section === "suppliers"   && <SuppliersPanel />}
          {section === "receiving"   && <ReceivingPanel />}
          {section === "adjustments" && <AdjustmentsPanel />}
        </main>
      </div>
    </div>
  );
}

function IngredientsPanel() {
  const { db, showToast } = usePOS();
  const [ingredients, setIngredients] = React.useState([]);
  const [name, setName] = React.useState("");
  const [unit, setUnit] = React.useState("g");
  const [initialStock, setInitialStock] = React.useState("");

  React.useEffect(() => db.collection("ingredients").onSnapshot((s) =>
    setIngredients(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  const create = async (e) => {
    e.preventDefault();
    if (!name.trim()) return;
    await db.collection("ingredients").add({
      name: name.trim(), unit, stock: Number(initialStock) || 0,
      stockThreshold: 0, avgCost: 0, active: true,
    });
    setName(""); setInitialStock("");
    showToast("Ingredient added");
  };

  const toggleActive = (i) => db.collection("ingredients").doc(i.id).update({ active: !i.active });
  const remove = (i) => { if (confirm(`Delete "${i.name}"? This does not affect past sales history.`)) db.collection("ingredients").doc(i.id).delete(); };

  return (
    <div>
      <div className="panel-card">
        <h3>Add ingredient</h3>
        <form className="row" style={{ gap: 8, flexWrap: "wrap", alignItems: "flex-end" }} onSubmit={create}>
          <label className="field" style={{ flex: "1 1 220px" }}><span className="field-label mono">Name</span>
            <input required value={name} onChange={(e) => setName(e.target.value)} placeholder="Whole milk" />
          </label>
          <label className="field" style={{ flex: "0 0 120px" }}><span className="field-label mono">Unit</span>
            <select value={unit} onChange={(e) => setUnit(e.target.value)}>
              {["g", "kg", "ml", "L", "pcs"].map((u) => <option key={u}>{u}</option>)}
            </select>
          </label>
          <label className="field" style={{ flex: "0 0 140px" }}><span className="field-label mono">Initial stock</span>
            <input type="number" min="0" value={initialStock} onChange={(e) => setInitialStock(e.target.value)} placeholder="0" />
          </label>
          <button className="btn-primary" type="submit">Add</button>
        </form>
      </div>
      <div className="panel-card no-pad" style={{ marginTop: 16 }}>
        <table className="data-table">
          <thead><tr><th>Name</th><th>Unit</th><th>Stock</th><th>Alert at ≤</th><th>Avg cost</th><th></th></tr></thead>
          <tbody>
            {ingredients.map((i) => (
              <tr key={i.id} style={i.active === false ? { opacity: 0.5 } : undefined}>
                <td>{i.name}</td>
                <td><Mono className="muted">{i.unit}</Mono></td>
                <StockCell product={i} targetType="ingredient" />
                <ThresholdCell product={i} targetType="ingredient" />
                <td><Mono className="muted">{money(Number(i.avgCost) || 0)}</Mono></td>
                <td className="row-actions">
                  <button className="btn-ghost-sm" onClick={() => toggleActive(i)}>{i.active === false ? "Activate" : "Deactivate"}</button>
                  <button className="btn-ghost-sm danger" onClick={() => remove(i)}>Delete</button>
                </td>
              </tr>
            ))}
            {ingredients.length === 0 && (
              <tr><td colSpan={6} className="muted mono" style={{ textAlign: "center", padding: 24 }}>No ingredients yet.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function SuppliersPanel() {
  const { db, showToast } = usePOS();
  const [suppliers, setSuppliers] = React.useState([]);
  const [name, setName] = React.useState("");
  const [contact, setContact] = React.useState("");
  const [phone, setPhone] = React.useState("");

  React.useEffect(() => db.collection("suppliers").onSnapshot((s) =>
    setSuppliers(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  const create = async (e) => {
    e.preventDefault();
    if (!name.trim()) return;
    await db.collection("suppliers").add({ name: name.trim(), contact: contact.trim(), phone: phone.trim(), notes: "", active: true });
    setName(""); setContact(""); setPhone("");
    showToast("Supplier added");
  };
  const remove = (s) => { if (confirm(`Delete supplier "${s.name}"?`)) db.collection("suppliers").doc(s.id).delete(); };

  return (
    <div>
      <div className="panel-card">
        <h3>Add supplier</h3>
        <form className="row" style={{ gap: 8, flexWrap: "wrap", alignItems: "flex-end" }} onSubmit={create}>
          <label className="field" style={{ flex: "1 1 200px" }}><span className="field-label mono">Name</span>
            <input required value={name} onChange={(e) => setName(e.target.value)} />
          </label>
          <label className="field" style={{ flex: "1 1 160px" }}><span className="field-label mono">Contact person</span>
            <input value={contact} onChange={(e) => setContact(e.target.value)} />
          </label>
          <label className="field" style={{ flex: "0 0 160px" }}><span className="field-label mono">Phone</span>
            <input value={phone} onChange={(e) => setPhone(e.target.value)} />
          </label>
          <button className="btn-primary" type="submit">Add</button>
        </form>
      </div>
      <div className="panel-card no-pad" style={{ marginTop: 16 }}>
        <table className="data-table">
          <thead><tr><th>Name</th><th>Contact</th><th>Phone</th><th></th></tr></thead>
          <tbody>
            {suppliers.map((s) => (
              <tr key={s.id}>
                <td>{s.name}</td>
                <td><Mono className="muted">{s.contact || "—"}</Mono></td>
                <td><Mono className="muted">{s.phone || "—"}</Mono></td>
                <td className="row-actions"><button className="btn-ghost-sm danger" onClick={() => remove(s)}>Delete</button></td>
              </tr>
            ))}
            {suppliers.length === 0 && (
              <tr><td colSpan={4} className="muted mono" style={{ textAlign: "center", padding: 24 }}>No suppliers yet.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function ReceivingPanel() {
  const { db, products, ingredients, receiveStock, showToast } = usePOS();
  const [suppliers, setSuppliers] = React.useState([]);
  const [supplierId, setSupplierId] = React.useState("");
  const [lines, setLines] = React.useState([{ targetType: "ingredient", targetId: "", qty: "", unitCost: "" }]);
  const [note, setNote] = React.useState("");
  const [recent, setRecent] = React.useState([]);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => db.collection("suppliers").onSnapshot((s) =>
    setSuppliers(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);
  React.useEffect(() => db.collection("receivings").orderBy("createdAt", "desc").limit(20).onSnapshot((s) =>
    setRecent(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  const targetsFor = (type) => type === "ingredient" ? ingredients : products;
  const addLine = () => setLines([...lines, { targetType: "ingredient", targetId: "", qty: "", unitCost: "" }]);
  const setLine = (i, patch) => setLines(lines.map((l, idx) => idx === i ? { ...l, ...patch } : l));
  const removeLine = (i) => setLines(lines.filter((_, idx) => idx !== i));

  const submit = async (e) => {
    e.preventDefault();
    const valid = lines.filter((l) => l.targetId && Number(l.qty) > 0);
    if (!valid.length) { showToast("Add at least one valid line"); return; }
    setBusy(true);
    try {
      const supplier = suppliers.find((s) => s.id === supplierId);
      await receiveStock({
        supplierId: supplierId || null, supplierName: supplier?.name || "",
        lines: valid.map((l) => ({
          targetType: l.targetType, targetId: l.targetId,
          targetName: (targetsFor(l.targetType).find((t) => t.id === l.targetId) || {}).name || "",
          qty: Number(l.qty), unitCost: Number(l.unitCost) || 0,
        })),
        note,
      });
      setLines([{ targetType: "ingredient", targetId: "", qty: "", unitCost: "" }]);
      setNote(""); setSupplierId("");
    } catch (err) {
      showToast("Receiving failed: " + (err?.message || "unknown"));
    }
    setBusy(false);
  };

  return (
    <div>
      <div className="panel-card">
        <h3>Receive stock</h3>
        <form onSubmit={submit}>
          <div className="row" style={{ gap: 8, flexWrap: "wrap", alignItems: "flex-end" }}>
            <label className="field" style={{ flex: "1 1 220px" }}><span className="field-label mono">Supplier</span>
              <select value={supplierId} onChange={(e) => setSupplierId(e.target.value)}>
                <option value="">— none —</option>
                {suppliers.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
              </select>
            </label>
            <label className="field" style={{ flex: "1 1 220px" }}><span className="field-label mono">Note</span>
              <input value={note} onChange={(e) => setNote(e.target.value)} placeholder="Invoice #, etc." />
            </label>
          </div>

          <div style={{ marginTop: 12 }}>
            {lines.map((l, i) => {
              const list = targetsFor(l.targetType);
              const t = list.find((x) => x.id === l.targetId);
              return (
                <div key={i} className="row" style={{ gap: 6, marginTop: 6, alignItems: "center" }}>
                  <select style={{ flex: "0 0 110px" }} value={l.targetType}
                    onChange={(e) => setLine(i, { targetType: e.target.value, targetId: "" })}>
                    <option value="ingredient">Ingredient</option>
                    <option value="product">Product</option>
                  </select>
                  <select style={{ flex: 1 }} value={l.targetId} onChange={(e) => setLine(i, { targetId: e.target.value })}>
                    <option value="">Select…</option>
                    {list.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
                  </select>
                  <input type="number" min="0" style={{ flex: "0 0 100px" }} value={l.qty}
                    onChange={(e) => setLine(i, { qty: e.target.value })} placeholder={t?.unit || "Qty"} />
                  <input type="number" min="0" style={{ flex: "0 0 130px" }} value={l.unitCost}
                    onChange={(e) => setLine(i, { unitCost: e.target.value })} placeholder="Unit cost (Rp)" />
                  <span className="mono muted" style={{ flex: "0 0 90px" }}>
                    {l.qty && l.unitCost ? money(Number(l.qty) * Number(l.unitCost)) : ""}
                  </span>
                  <button type="button" className="btn-ghost-sm" onClick={() => removeLine(i)}>×</button>
                </div>
              );
            })}
            <button type="button" className="btn-ghost-sm" style={{ marginTop: 8 }} onClick={addLine}>+ Line</button>
          </div>

          <div className="row" style={{ justifyContent: "flex-end", marginTop: 14 }}>
            <button className="btn-primary" type="submit" disabled={busy}>{busy ? "Saving…" : "Record receiving"}</button>
          </div>
        </form>
      </div>

      <div className="panel-card no-pad" style={{ marginTop: 16 }}>
        <table className="data-table">
          <thead><tr><th>When</th><th>Supplier</th><th>Lines</th><th>Total</th></tr></thead>
          <tbody>
            {recent.map((r) => (
              <tr key={r.id}>
                <td><Mono>{r.createdAt?.toDate ? r.createdAt.toDate().toLocaleString("en-GB") : "—"}</Mono></td>
                <td>{r.supplierName || "—"}</td>
                <td><Mono className="muted">{(r.items || []).map((it) => `${it.targetName} ×${it.qty}`).join(", ")}</Mono></td>
                <td><Mono>{money(r.total || 0)}</Mono></td>
              </tr>
            ))}
            {recent.length === 0 && (
              <tr><td colSpan={4} className="muted mono" style={{ textAlign: "center", padding: 24 }}>No receivings yet.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// The sanctioned way to change stock outside a sale/receiving: waste,
// breakage, supplier returns, or a physical recount. Always goes through
// adjustStock (store.jsx), which logs a stock_movements entry — describe
// *why* in the Note field rather than picking a reason code, so the Stock
// movements report stays a single "adjustment" bucket instead of splintering
// into near-duplicate reason labels.
function AdjustmentsPanel() {
  const { products, ingredients, adjustStock, showToast } = usePOS();
  const [targetType, setTargetType] = React.useState("product");
  const [targetId, setTargetId] = React.useState("");
  const [mode, setMode] = React.useState("count"); // "count" | "delta"
  const [value, setValue] = React.useState("");
  const [note, setNote] = React.useState("");
  const [busy, setBusy] = React.useState(false);

  const list = targetType === "ingredient" ? ingredients : products;
  const target = list.find((t) => t.id === targetId);

  const submit = async (e) => {
    e.preventDefault();
    if (!targetId || value === "") { showToast("Pick an item and enter a value"); return; }
    setBusy(true);
    try {
      await adjustStock({ targetType, targetId, mode, value: Number(value), reason: "adjustment", note });
      setValue(""); setNote("");
    } catch (err) {
      showToast("Adjustment failed: " + (err?.message || "unknown"));
    }
    setBusy(false);
  };

  return (
    <div className="panel-card" style={{ maxWidth: 560 }}>
      <h3>Adjust stock</h3>
      <p className="muted mono" style={{ fontSize: 12 }}>
        For waste, breakage, supplier returns, or a physical recount. Every
        adjustment is logged — see Reports → Stock movements for the full history.
      </p>
      <form onSubmit={submit}>
        <div className="row" style={{ gap: 8, marginTop: 10 }}>
          <label className="field" style={{ flex: "0 0 140px" }}><span className="field-label mono">Item type</span>
            <select value={targetType} onChange={(e) => { setTargetType(e.target.value); setTargetId(""); }}>
              <option value="product">Product</option>
              <option value="ingredient">Ingredient</option>
            </select>
          </label>
          <label className="field" style={{ flex: 1 }}><span className="field-label mono">Item</span>
            <select value={targetId} onChange={(e) => setTargetId(e.target.value)}>
              <option value="">Select…</option>
              {list.map((t) => <option key={t.id} value={t.id}>{t.name}{targetType === "ingredient" ? ` (${t.unit})` : ""}</option>)}
            </select>
          </label>
        </div>
        {target && (
          <div className="muted mono" style={{ fontSize: 12, marginTop: 6 }}>
            Current stock: {target.stock >= 999 ? "∞" : target.stock}
          </div>
        )}
        <div className="row" style={{ gap: 8, marginTop: 10 }}>
          <label className="field" style={{ flex: "0 0 190px" }}><span className="field-label mono">Mode</span>
            <select value={mode} onChange={(e) => setMode(e.target.value)}>
              <option value="count">Set counted total</option>
              <option value="delta">Add / subtract</option>
            </select>
          </label>
          <label className="field" style={{ flex: 1 }}><span className="field-label mono">{mode === "count" ? "Counted total" : "Change (+/-)"}</span>
            <input type="number" value={value} onChange={(e) => setValue(e.target.value)} placeholder={mode === "count" ? "e.g. 42" : "e.g. -3"} />
          </label>
        </div>
        <label className="field" style={{ marginTop: 10 }}><span className="field-label mono">Note</span>
          <input value={note} onChange={(e) => setNote(e.target.value)} placeholder="e.g. waste — dropped tray, expired, cycle count" />
        </label>
        <div className="row" style={{ justifyContent: "flex-end", marginTop: 14 }}>
          <button className="btn-primary" type="submit" disabled={busy || !targetId}>{busy ? "Saving…" : "Apply adjustment"}</button>
        </div>
      </form>
    </div>
  );
}

function AdminLoyalty() {
  const { customers } = usePOS();
  const tierOf = (spent) => spent > 2000000 ? "Golden" : spent > 500000 ? "Regular" : "New";
  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">LOYALTY</div>
          <h2>Regulars · {customers.length}</h2>
        </div>
        <div className="muted mono">Earn 1 point per Rp 10.000 spent</div>
      </header>
      {customers.length === 0
        ? <div className="panel-card muted mono" style={{ padding: 24 }}>No customer data yet. Customers appear after their first named order.</div>
        : <div className="loyalty-grid">
            {customers.map((c) => {
              const tier = tierOf(c.totalSpent || 0);
              return (
                <article className="loyalty-card" key={c.id}>
                  <header>
                    <div className="loyalty-avatar mono">{(c.name || "??").split(" ").map((w) => w[0]).slice(0, 2).join("")}</div>
                    <div>
                      <div className="loyalty-name">{c.name}</div>
                      <div className="muted mono">id · {c.id}</div>
                    </div>
                    <span className={"tier tier-" + tier.toLowerCase()}>{tier}</span>
                  </header>
                  <div className="loyalty-stats">
                    <div><span className="muted mono">VISITS</span><Mono>{c.visits || 0}</Mono></div>
                    <div><span className="muted mono">SPENT</span><Mono>{money(c.totalSpent || 0)}</Mono></div>
                    <div><span className="muted mono">POINTS</span><Mono>{c.points || 0}</Mono></div>
                  </div>
                  <footer>
                    <div className="muted mono">Last visit · {c.lastVisit?.toDate ? c.lastVisit.toDate().toLocaleString("en-GB") : "—"}</div>
                  </footer>
                </article>
              );
            })}
          </div>}
    </div>
  );
}

function AdminPrebook() {
  const { prebookings, verifyPrebooking, db, showToast, user } = usePOS();
  const [detail, setDetail] = React.useState(null);
  const [notifyOpen, setNotifyOpen] = React.useState(false);
  const cancel = async (id) => { if (confirm("Cancel booking?")) await db.collection("prebookings").doc(id).update({ status: "cancelled" }); };

  // Push a booking straight to the kitchen — creates an unpaid order from the
  // pre-booking items, mirroring what the cashier does on check-in.
  const sendToKitchen = async (pb) => {
    if (!confirm(`Send ${pb.customerName}'s booking to the kitchen now?`)) return;
    const shortId = makeShortId();
    const hasKitchen = (pb.items || []).some((i) => i.station === "kitchen");
    const hasBar = (pb.items || []).some((i) => i.station === "bar");
    await db.collection("orders").add({
      shortId,
      table: pb.table || "PRE",
      customer: pb.customerName,
      room: pb.room || null, partySize: pb.partySize || 1,
      items: (pb.items || []).map((i) => ({
        id: i.id || "", sku: i.sku || "", name: i.name, price: i.price, qty: i.qty,
        station: i.station || "kitchen", mods: i.mods || [], modPrice: i.modPrice || 0,
        cogs: i.cogs || 0, notes: i.notes || "",
      })),
      subtotal: pb.subtotal || 0,
      depositApplied: pb.depositPaid || 0,
      uniqueCode: 0,
      total: Math.max(0, (pb.subtotal || 0) - (pb.depositPaid || 0)),
      tip: 0, payments: [], paymentMethod: null,
      status: "open", paid: false,
      barStatus: hasBar ? "new" : null,
      kitchenStatus: hasKitchen ? "new" : null,
      prebookingId: pb.id,
      cashierUid: user?.uid || null, cashierEmail: user?.email || null,
      dateKey: dayKey(),
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
    });
    await db.collection("prebookings").doc(pb.id).update({ status: "checked_in" });
    showToast("Sent to kitchen");
    setDetail(null);
  };

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">PRE-BOOKINGS</div>
          <h2>{prebookings.length} on the books</h2>
        </div>
        <button className="btn-ghost" onClick={() => setNotifyOpen(true)}>Email notifications</button>
      </header>
      <div className="panel-card no-pad">
        <table className="data-table">
          <thead><tr><th>Date</th><th>Room</th><th>Customer</th><th>Phone</th><th>Items</th><th>Total</th><th>Deposit</th><th>Status</th><th></th></tr></thead>
          <tbody>
            {prebookings.map((p) => (
              <tr key={p.id}>
                <td><Mono>{p.visitDate}{p.visitTime ? " · " + p.visitTime : ""}</Mono></td>
                <td className="mono muted">
                  {p.room || "—"}
                  {p.partySize ? ` · ${p.partySize}p` : ""}
                </td>
                <td>{p.customerName}</td>
                <td className="mono muted">{p.customerPhone}</td>
                <td><Mono>{(p.items || []).reduce((a, i) => a + i.qty, 0)}</Mono></td>
                <td><Mono>{money(p.subtotal)}</Mono></td>
                <td><Mono>{money(p.depositPaid || 0)}/{money(p.depositRequired || 0)}</Mono></td>
                <td><StatusPill status={p.status}>{p.status}</StatusPill></td>
                <td className="row-actions">
                  <button className="btn-ghost-sm" onClick={() => setDetail(p.id)}>Details</button>
                  {p.status === "paid_deposit" && <button className="btn-ghost-sm" onClick={() => verifyPrebooking(p.id)}>Verify</button>}
                  <button className="btn-ghost-sm" onClick={() => cancel(p.id)}>Cancel</button>
                </td>
              </tr>
            ))}
            {prebookings.length === 0 && <tr><td colSpan="9" className="muted mono" style={{ textAlign: "center", padding: 24 }}>No pre-bookings yet.</td></tr>}
          </tbody>
        </table>
      </div>

      {detail && (
        <PrebookDetailSheet
          pbId={detail}
          onClose={() => setDetail(null)}
          onSendToKitchen={(pb) => sendToKitchen(pb)}
        />
      )}
      {notifyOpen && <NotificationSettingsSheet onClose={() => setNotifyOpen(false)} />}
    </div>
  );
}

function PrebookDetailSheet({ pbId, onClose, onSendToKitchen }) {
  const { prebookings, db, showToast } = usePOS();
  const [settings] = usePrinterSettings();
  const pb = prebookings.find((p) => p.id === pbId);
  if (!pb) return null;
  const items = pb.items || [];
  const qty = items.reduce((a, i) => a + i.qty, 0);

  // Remove an item line from the booking and refresh subtotal so the balance
  // due on the visit day reflects the cancellation.
  const cancelItem = async (idx) => {
    const it = items[idx];
    if (!confirm(`Remove ${it.qty}× ${it.name} from this booking?`)) return;
    const next = items.filter((_, i) => i !== idx);
    const subtotal = next.reduce((s, x) => s + ((x.price || 0) + (x.modPrice || 0)) * x.qty, 0);
    await db.collection("prebookings").doc(pb.id).update({
      items: next,
      subtotal,
    });
    showToast(next.length ? "Item removed" : "All items removed");
  };

  const waOpen = () => {
    const phone = (pb.customerPhone || "").replace(/\D/g, "");
    if (!phone) return alert("No phone number on file");
    const itemLines = items.map((it) => {
      const mods = it.mods && it.mods.length ? ` (${it.mods.join(", ")})` : "";
      return `- ${it.qty}× ${it.name}${mods}`;
    }).join("\n");
    const balance = Math.max(0, (pb.subtotal || 0) - (pb.depositPaid || 0));
    const lines = [
      `Halo ${pb.customerName}, pesanan pre-book Anda sudah kami verifikasi ✓`,
      "",
      "Detail pesanan:",
      itemLines,
      "",
      `Kedatangan: ${pb.visitDate}${pb.visitTime ? " · " + pb.visitTime : ""}`,
      `Total: Rp ${(pb.subtotal || 0).toLocaleString("id-ID")}`,
      `Deposit dibayar: Rp ${(pb.depositPaid || 0).toLocaleString("id-ID")}`,
      `Sisa bayar di tempat: Rp ${balance.toLocaleString("id-ID")}`,
      "",
      `Sampai jumpa di ${window.STORE_NAME}!`,
    ];
    const msg = encodeURIComponent(lines.join("\n"));
    const num = phone.startsWith("0") ? "62" + phone.slice(1) : phone;
    window.open(`https://wa.me/${num}?text=${msg}`, "_blank");
  };
  const print = () => {
    const root = document.createElement("div");
    root.className = "printable";
    root.innerHTML = `
      <div class="pr-brand pr-center">${escHtml(window.STORE_NAME)}</div>
      ${window.STORE_TAGLINE ? `<div class="pr-sub pr-center"><em>* ${escHtml(window.STORE_TAGLINE)} *</em></div>` : ""}
      <div class="pr-sub pr-center">PRE-BOOKING · ${escHtml(pb.visitDate)}${pb.visitTime ? " · " + escHtml(pb.visitTime) : ""}</div>
      <div class="pr-rule"></div>
      <div class="pr-row"><span>Customer</span><span>${escHtml(pb.customerName)}</span></div>
      <div class="pr-row"><span>WhatsApp</span><span>${escHtml(pb.customerPhone || "—")}</span></div>
      ${pb.room ? `<div class="pr-row"><span>Room</span><span>${escHtml(pb.room)}</span></div>` : ""}
      ${pb.partySize ? `<div class="pr-row"><span>People</span><span>${escHtml(pb.partySize)}</span></div>` : ""}
      <div class="pr-rule"></div>
      <table class="pr-items"><tbody>
        ${items.map((it) => `
          <tr><td>${escHtml(it.qty)}× ${escHtml(it.name)}${it.sku ? " (" + escHtml(it.sku) + ")" : ""}</td><td class="r">Rp ${(((it.price || 0) + (it.modPrice || 0)) * it.qty).toLocaleString("id-ID")}</td></tr>
          ${it.mods && it.mods.length ? `<tr><td class="pr-mod" colSpan="2">${it.mods.map(escHtml).join(" · ")}</td></tr>` : ""}
          ${it.notes ? `<tr><td class="pr-note" colSpan="2">* ${escHtml(it.notes)}</td></tr>` : ""}
        `).join("")}
      </tbody></table>
      <div class="pr-rule"></div>
      <div class="pr-row"><span>Subtotal</span><span>Rp ${(pb.subtotal || 0).toLocaleString("id-ID")}</span></div>
      <div class="pr-row"><span>Deposit paid</span><span>Rp ${(pb.depositPaid || 0).toLocaleString("id-ID")}</span></div>
      <div class="pr-row pr-total"><span>BALANCE DUE</span><span>Rp ${Math.max(0, (pb.subtotal || 0) - (pb.depositPaid || 0)).toLocaleString("id-ID")}</span></div>
      <div class="pr-rule"></div>
      <div class="pr-center" style="font-size:10px">Printed ${new Date().toLocaleString("en-GB", { timeZone: "Asia/Jakarta" })} WIB</div>
    `;
    document.body.appendChild(root);
    printPaper(settings.paperWidth);
    setTimeout(() => root.remove(), 800);
  };

  return (
    <div className="sheet-wrap" onMouseDown={onClose}>
      <div className="sheet sheet-wide" onMouseDown={(e) => e.stopPropagation()}>
        <header className="sheet-head">
          <div>
            <div className="eyebrow mono">PRE-BOOKING</div>
            <h3>{pb.customerName}</h3>
          </div>
          <button className="icon-x" onClick={onClose}>×</button>
        </header>
        <div className="sheet-body">
          <div className="pb-detail-grid">
            <div><span className="muted mono">VISIT DATE</span><div><Mono>{pb.visitDate}{pb.visitTime ? " · " + pb.visitTime : ""}</Mono></div></div>
            <div><span className="muted mono">ROOM</span><div><Mono>{pb.room || "—"}</Mono></div></div>
            <div><span className="muted mono">PEOPLE</span><div><Mono>{pb.partySize || "—"}</Mono></div></div>
            <div><span className="muted mono">WHATSAPP</span><div><Mono>{pb.customerPhone || "—"}</Mono></div></div>
            <div><span className="muted mono">TABLE</span><div><Mono>{pb.table || "—"}</Mono></div></div>
            <div><span className="muted mono">STATUS</span><div><StatusPill status={pb.status}>{pb.status}</StatusPill></div></div>
            <div><span className="muted mono">CREATED</span><div><Mono>{pb.createdAt?.toDate ? pb.createdAt.toDate().toLocaleString("en-GB", { timeZone: "Asia/Jakarta" }) + " WIB" : "—"}</Mono></div></div>
          </div>

          <div className="panel-card no-pad" style={{ marginTop: 18 }}>
            <table className="data-table">
              <thead><tr><th>Qty</th><th>Item</th><th>Modifiers</th><th>Notes</th><th style={{ textAlign: "right" }}>Line total</th><th></th></tr></thead>
              <tbody>
                {items.map((it, i) => (
                  <tr key={i}>
                    <td><Mono>{it.qty}×</Mono></td>
                    <td>{it.name}{it.sku ? <span className="muted mono"> ({it.sku})</span> : null}</td>
                    <td className="mono muted">{(it.mods || []).join(" · ") || "—"}</td>
                    <td className="muted">{it.notes || "—"}</td>
                    <td style={{ textAlign: "right" }}><Mono>{money(((it.price || 0) + (it.modPrice || 0)) * it.qty)}</Mono></td>
                    <td><button className="btn-ghost-sm danger" onClick={() => cancelItem(i)}>Cancel</button></td>
                  </tr>
                ))}
                {items.length === 0 && (
                  <tr><td colSpan="6" className="muted mono" style={{ textAlign: "center", padding: 16 }}>All items cancelled.</td></tr>
                )}
              </tbody>
            </table>
          </div>

          <div className="panel-card" style={{ marginTop: 14 }}>
            <div className="reco">
              <div className="reco-row"><span>Items</span><Mono>{qty}</Mono></div>
              <div className="reco-row"><span>Subtotal</span><Mono>{money(pb.subtotal || 0)}</Mono></div>
              <div className="reco-row"><span>Deposit required</span><Mono>{money(pb.depositRequired || 0)}</Mono></div>
              <div className="reco-row"><span>Deposit paid</span><Mono>{money(pb.depositPaid || 0)}</Mono></div>
              <div className="reco-row reco-total"><span>Balance due on arrival</span><Mono>{money(Math.max(0, (pb.subtotal || 0) - (pb.depositPaid || 0)))}</Mono></div>
            </div>
          </div>
        </div>
        <footer className="sheet-foot">
          <button className="btn-ghost" onClick={onClose}>Close</button>
          <button className="btn-ghost" onClick={waOpen}>WhatsApp customer</button>
          <button className="btn-ghost" onClick={print}>Print</button>
          <button className="btn-primary" onClick={() => onSendToKitchen(pb)} disabled={items.length === 0}>Send to kitchen now</button>
        </footer>
      </div>
    </div>
  );
}

function NotificationSettingsSheet({ onClose }) {
  return (
    <div className="sheet-wrap" onMouseDown={onClose}>
      <div className="sheet" onMouseDown={(e) => e.stopPropagation()}>
        <header className="sheet-head">
          <div><div className="eyebrow mono">SETTINGS</div><h3>Pre-booking email notifications</h3></div>
          <button className="icon-x" onClick={onClose}>×</button>
        </header>
        <div className="sheet-body">
          <p className="muted">
            Email notifications for new pre-bookings aren't available yet.
            Low-stock alerts, QRIS auto-confirm notices and the monthly
            maintenance checklist can be sent to Telegram or email — set the
            recipients in Settings → Notifications.
          </p>
        </div>
        <footer className="sheet-foot">
          <button className="btn-primary" onClick={onClose}>OK</button>
        </footer>
      </div>
    </div>
  );
}

function AdminUsers() {
  const { db } = usePOS();
  const [users, setUsers] = React.useState([]);
  const [email, setEmail] = React.useState("");
  const [pw, setPw] = React.useState("");
  const [role, setRole] = React.useState("cashier");
  const [msg, setMsg] = React.useState("");

  React.useEffect(() => db.collection("users").onSnapshot((s) =>
    setUsers(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  const create = async (e) => {
    e.preventDefault(); setMsg("");
    try {
      const secondary = firebase.initializeApp(window.FIREBASE_CONFIG, "secondary-" + Date.now());
      const cred = await secondary.auth().createUserWithEmailAndPassword(email, pw);
      await db.collection("users").doc(cred.user.uid).set({ email, role });
      await secondary.auth().signOut(); await secondary.delete();
      setEmail(""); setPw(""); setRole("cashier");
      setMsg("Created " + email);
    } catch (ex) { setMsg(ex.message); }
  };

  const setUserRole = (uid, r) => db.collection("users").doc(uid).set({ role: r }, { merge: true });
  const remove = (uid) => { if (confirm("Remove role doc? (Auth user remains — delete in Firebase console)")) db.collection("users").doc(uid).delete(); };

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">USERS · ROLES</div>
          <h2>{users.length} staff</h2>
        </div>
      </header>
      <div className="panel-card">
        <h3>Create employee</h3>
        <form className="row" style={{ gap: 8, flexWrap: "wrap", alignItems: "flex-end" }} onSubmit={create}>
          <label className="field" style={{ flex: "1 1 220px" }}><span className="field-label mono">Email</span>
            <input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} />
          </label>
          <label className="field" style={{ flex: "1 1 160px" }}><span className="field-label mono">Initial password</span>
            <input type="password" required value={pw} onChange={(e) => setPw(e.target.value)} />
          </label>
          <label className="field" style={{ flex: "0 0 160px" }}><span className="field-label mono">Role</span>
            <select value={role} onChange={(e) => setRole(e.target.value)}>
              {["cashier", "kitchen", "waitress", "display", "admin"].map((r) => <option key={r}>{r}</option>)}
            </select>
          </label>
          <button className="btn-primary" type="submit">Create</button>
        </form>
        {msg && <div className="muted mono" style={{ marginTop: 8 }}>{msg}</div>}
      </div>
      <div className="panel-card no-pad" style={{ marginTop: 16 }}>
        <table className="data-table">
          <thead><tr><th>Email</th><th>Role</th><th></th></tr></thead>
          <tbody>
            {users.map((u) => (
              <tr key={u.id}>
                <td>{u.email || u.id}</td>
                <td>
                  <select value={u.role || ""} onChange={(e) => setUserRole(u.id, e.target.value)}>
                    {["cashier", "kitchen", "waitress", "display", "admin"].map((r) => <option key={r}>{r}</option>)}
                  </select>
                </td>
                <td className="row-actions"><button className="btn-ghost-sm" onClick={() => remove(u.id)}>Remove</button></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function AdminPrinter() {
  const [s, update] = usePrinterSettings();

  const sampleOrder = {
    id: "test", shortId: "TEST", table: "5", customer: "Test Receipt",
    paymentMethod: "qris", total: 134000, subtotal: 134000, depositApplied: 0, tip: 0,
    items: [
      { qty: 1, name: "Latte",            sku: "P005", price: 34000, modPrice: 13000, mods: ["Large", "Oat"], notes: "" },
      { qty: 2, name: "Butter Croissant", sku: "P017", price: 28000, modPrice: 0,     mods: ["Warmed"],        notes: "" },
    ],
  };
  const testReceipt = () => {
    const root = document.createElement("div");
    root.className = "printable";
    root.innerHTML = `
      <div class="pr-brand pr-center">${escHtml(window.STORE_NAME)}</div>
      ${window.STORE_TAGLINE ? `<div class="pr-sub pr-center"><em>* ${escHtml(window.STORE_TAGLINE)} *</em></div>` : ""}
      <div class="pr-sub pr-center">Test print · ${new Date().toLocaleString("en-GB", { timeZone: "Asia/Jakarta" })} WIB</div>
      <div class="pr-rule"></div>
      <div class="pr-row"><span>Order TEST</span><span>Table 5</span></div>
      <div class="pr-rule"></div>
      <table class="pr-items"><tbody>
        ${sampleOrder.items.map((it) => `
          <tr><td>${it.qty}× ${it.name}</td><td class="r">${money((it.price + it.modPrice) * it.qty)}</td></tr>
          ${it.mods.length ? `<tr><td class="pr-mod" colSpan="2">${it.mods.join(" · ")}</td></tr>` : ""}
        `).join("")}
      </tbody></table>
      <div class="pr-rule"></div>
      <div class="pr-row pr-total"><span>TOTAL</span><span>${money(sampleOrder.total)}</span></div>
    `;
    document.body.appendChild(root);
    printPaper(s.paperWidth);
    setTimeout(() => root.remove(), 800);
  };

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">PRINTER</div>
          <h2>Local thermal printer</h2>
        </div>
        <button className="btn-ghost" onClick={testReceipt}>Print test receipt</button>
      </header>

      <div className="panel-card">
        <h3>Settings</h3>
        <p className="muted mono" style={{ fontSize: 12 }}>
          The browser's print dialog is used — the receipt will go to whichever printer you choose there.
          Install your thermal printer with the manufacturer's driver (Windows / macOS / Linux / Android),
          set the paper width below, then print once and tick "remember settings" in the print dialog.
        </p>

        <label className="field" style={{ maxWidth: 220, marginTop: 14 }}>
          <span className="field-label mono">Paper width</span>
          <select value={s.paperWidth} onChange={(e) => update({ paperWidth: e.target.value })}>
            <option value="80">80 mm</option>
            <option value="58">58 mm</option>
          </select>
        </label>

        <label className="row" style={{ gap: 8, marginTop: 14 }}>
          <input type="checkbox" checked={!!s.autoPrintReceipt}
            onChange={(e) => update({ autoPrintReceipt: e.target.checked })} />
          <span className="mono">Auto-print receipt after checkout</span>
        </label>

        <label className="row" style={{ gap: 8, marginTop: 8 }}>
          <input type="checkbox" checked={!!s.autoPrintKitchen}
            onChange={(e) => update({ autoPrintKitchen: e.target.checked })} />
          <span className="mono">Show "Print" button on kitchen tickets</span>
        </label>

        <div className="muted mono" style={{ fontSize: 12, marginTop: 16 }}>
          Settings are stored per device — each terminal (cashier station, kitchen
          screen) can have its own paper width and auto-print preference.
        </div>
      </div>

      <BluetoothPrinterPanel />
    </div>
  );
}

// Bluetooth-printer fallback config — only useful when the page is loaded
// inside the Android wrapper APK (which injects window.BTPrinter). The web
// app falls back to a paired Bluetooth printer when the network proxy is
// unreachable (e.g. the Cloudflare tunnel is down).
function BluetoothPrinterPanel() {
  const available = !!(window.PRINTER && window.PRINTER.btAvailable && window.PRINTER.btAvailable());
  const [paired, setPaired] = React.useState([]);
  const [map, setMapState] = React.useState(() => window.PRINTER?.btMap?.() || {});
  const [force, setForceState] = React.useState(() => !!window.PRINTER?.btForce?.());

  const refresh = () => setPaired(window.PRINTER?.btListPaired?.() || []);
  React.useEffect(() => { if (available) refresh(); }, [available]);

  const setStation = (station, address) => {
    const next = { ...map, [station]: address };
    setMapState(next);
    window.PRINTER.btSetMap(next);
  };
  const setForce = (on) => {
    setForceState(on);
    window.PRINTER.btSetForce(on);
  };

  if (!available) {
    return (
      <div className="panel-card" style={{ marginTop: 16 }}>
        <h3>Bluetooth printer (APK fallback)</h3>
        <p className="muted mono" style={{ fontSize: 12 }}>
          Open this page inside the POS Android app to enable Bluetooth printing.
          The app sends ESC/POS jobs to a paired thermal printer when the network
          proxy can't be reached. Pair your printer in Android Settings first.
        </p>
      </div>
    );
  }

  const STATIONS = [
    { k: "cashier",  label: "Receipts (cashier)" },
    { k: "kitchen",  label: "Kitchen tickets" },
    { k: "bar",      label: "Bar tickets" },
    { k: "waitress", label: "Floor tickets" },
    { k: "shift",    label: "Shift summary" },
  ];

  return (
    <div className="panel-card" style={{ marginTop: 16 }}>
      <div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
        <h3 style={{ margin: 0 }}>Bluetooth printer (APK fallback)</h3>
        <button className="btn-ghost-sm" onClick={refresh}>Refresh paired list</button>
      </div>
      <p className="muted mono" style={{ fontSize: 12, marginTop: 8 }}>
        The app prints via network first and falls back to Bluetooth automatically.
        Tick "Force Bluetooth" to skip the network entirely (e.g. when you know
        the tunnel is down).
      </p>

      {paired.length === 0 && (
        <div className="muted mono" style={{ marginTop: 12 }}>
          No paired devices found. Pair your thermal printer in Android Settings → Bluetooth, then tap Refresh.
        </div>
      )}

      {paired.length > 0 && STATIONS.map((s) => (
        <div key={s.k} className="row" style={{ gap: 10, alignItems: "center", marginTop: 10 }}>
          <span className="mono" style={{ flex: "0 0 200px" }}>{s.label}</span>
          <select value={map[s.k] || ""} onChange={(e) => setStation(s.k, e.target.value)} style={{ flex: 1 }}>
            <option value="">— none —</option>
            {paired.map((d) => (
              <option key={d.address} value={d.address}>{d.name || "(unnamed)"} · {d.address}</option>
            ))}
          </select>
          {map[s.k] && (
            <button className="btn-ghost-sm" onClick={() => {
              const r = window.PRINTER.btTestPrint(map[s.k]);
              alert(r === "ok" ? "Test print sent" : "Failed: " + r);
            }}>Test</button>
          )}
        </div>
      ))}

      <label className="row" style={{ gap: 8, marginTop: 16 }}>
        <input type="checkbox" checked={force} onChange={(e) => setForce(e.target.checked)} />
        <span className="mono">Force Bluetooth (skip network proxy)</span>
      </label>
    </div>
  );
}

// Defaults — must match the values in styles.css :root.
// Material 3 "Baseline" default palette (Google's own default scheme, seed
// #6750A4) — applied 2026-08 as the new default look, replacing the old
// warm cream/terracotta brand palette.
const THEME_DEFAULTS = {
  paper:        "#FFFBFE",
  "paper-soft": "#F7F2FA",
  ink:          "#1C1B1F",
  "ink-soft":   "#49454F",
  accent:       "#6750A4",
  "accent-deep": "#4F378B",
  white:        "#FFFFFF",
  alert:        "#B3261E",
  ok:           "#146C2E",
};
const THEME_PRESETS = {
  "Default": THEME_DEFAULTS,
  "Cool Slate": {
    paper: "#eef1f4", "paper-soft": "#e1e7ed", ink: "#1f2a3a", "ink-soft": "#3a4a5e",
    accent: "#7ba4c4", "accent-deep": "#3d6f96", white: "#fafcfe", alert: "#c44a4a", ok: "#3a8a5a",
  },
  "Forest": {
    paper: "#ebeee4", "paper-soft": "#dde2d2", ink: "#2a3520", "ink-soft": "#4a5a3a",
    accent: "#94a878", "accent-deep": "#5a7a3d", white: "#f6f8ee", alert: "#b8553a", ok: "#3a7a45",
  },
  "Midnight": {
    paper: "#1a1d22", "paper-soft": "#252830", ink: "#f0e8d8", "ink-soft": "#c8bca0",
    accent: "#d4a868", "accent-deep": "#b8854c", white: "#0e1014", alert: "#e08068", ok: "#80b070",
  },
};

// QRIS auto-confirm audit log — one row per checkout that had a QRIS split.
// Reads pending_payments live: 'waiting' entries are checkouts currently
// open and watching for a matching notification, 'matched' means the
// android-qris-listener app reported the amount and the backend found it
// (see /api/qris-notify in server.js), 'confirmed' means the cashier's
// browser tab then actually wrote the paid order. Entries a cashier
// cancels while still 'waiting' are deleted by the client, not logged here.
// Maintenance — recurring monthly tasks (clean grease trap, polish floor,
// service the AC, etc). Admin defines the task list; each calendar month a
// Telegram reminder goes out (server.js: sendMaintenanceReminder, on a
// configurable day — see the "Telegram reminder" card below) with one
// button per task. A task can be marked done either by tapping its button
// in Telegram or by checking it off here — both write to the same
// maintenance_completions collection, so either view always reflects the
// other.
function AdminMaintenance() {
  const [section, setSection] = React.useState("checklist");
  const SECTIONS = [
    { id: "checklist", label: "This month" },
    { id: "tasks",     label: "Tasks" },
    { id: "history",   label: "History" },
  ];
  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">MAINTENANCE</div>
          <h2>{SECTIONS.find((s) => s.id === section).label}</h2>
        </div>
      </header>
      <div className="reports-layout">
        <aside className="reports-rail">
          {SECTIONS.map((s) => (
            <button key={s.id} className={"reports-tab" + (section === s.id ? " active" : "")}
              onClick={() => setSection(s.id)}>{s.label}</button>
          ))}
        </aside>
        <main className="reports-main">
          {section === "checklist" && <MaintenanceChecklist />}
          {section === "tasks"     && <MaintenanceTasksPanel />}
          {section === "history"   && <MaintenanceHistory />}
        </main>
      </div>
    </div>
  );
}

function MaintenanceChecklist() {
  const { db, user, showToast } = usePOS();
  const [tasks, setTasks] = React.useState([]);
  const [completions, setCompletions] = React.useState({}); // taskId -> completion doc
  const [reminderDay, setReminderDay] = React.useState(1);
  const [loaded, setLoaded] = React.useState(false);

  const monthKey = dayKey().slice(0, 7); // "YYYY-MM-DD" -> "YYYY-MM"

  React.useEffect(() => db.collection("maintenance_tasks").where("active", "==", true).onSnapshot((s) =>
    setTasks(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  React.useEffect(() => db.collection("maintenance_completions")
    .where("monthKey", "==", monthKey)
    .onSnapshot((s) => {
      const m = {};
      s.docs.forEach((d) => { m[d.data().taskId] = { id: d.id, ...d.data() }; });
      setCompletions(m);
    }), [monthKey]);

  React.useEffect(() => {
    db.collection("settings").doc("maintenance").get().then((d) => {
      if (d.exists && d.data().reminderDay) setReminderDay(d.data().reminderDay);
      setLoaded(true);
    });
  }, []);

  const saveReminderDay = async (d) => {
    setReminderDay(d);
    await db.collection("settings").doc("maintenance").set({ reminderDay: d }, { merge: true });
    showToast("Reminder day saved");
  };

  // Toggle done/not-done for this task this month — same completion-doc
  // shape (deterministic id `${taskId}_${monthKey}`) the Telegram webhook
  // writes, so either path stays in sync with the other.
  const toggle = async (task) => {
    const id = `${task.id}_${monthKey}`;
    if (completions[task.id]) {
      await db.collection("maintenance_completions").doc(id).delete();
    } else {
      await db.collection("maintenance_completions").doc(id).set({
        taskId: task.id, taskName: task.name, monthKey, done: true,
        completedBy: user?.email || "admin", source: "admin",
        completedAt: firebase.firestore.FieldValue.serverTimestamp(),
      });
    }
  };

  const doneCount = tasks.filter((t) => completions[t.id]).length;

  return (
    <div>
      <div className="kpis kpis-5">
        <div className="kpi-card accent">
          <div className="kpi-label mono">THIS MONTH</div>
          <Mono className="kpi-value">{monthKey}</Mono>
          <div className="kpi-delta mono">{doneCount} / {tasks.length} done</div>
        </div>
      </div>
      <div className="panel-card no-pad" style={{ marginTop: 12 }}>
        <table className="data-table">
          <thead><tr><th></th><th>Task</th><th>Notes</th><th>Completed</th></tr></thead>
          <tbody>
            {tasks.map((t) => {
              const c = completions[t.id];
              return (
                <tr key={t.id}>
                  <td><input type="checkbox" checked={!!c} onChange={() => toggle(t)} /></td>
                  <td>{t.name}</td>
                  <td><Mono className="muted">{t.description || "—"}</Mono></td>
                  <td><Mono className="muted">
                    {c ? `${c.completedBy || "—"}${c.source === "telegram" ? " · Telegram" : ""}` : "—"}
                  </Mono></td>
                </tr>
              );
            })}
            {tasks.length === 0 && (
              <tr><td colSpan={4} className="muted mono" style={{ textAlign: "center", padding: 24 }}>No active tasks — add some under the Tasks tab.</td></tr>
            )}
          </tbody>
        </table>
      </div>
      <div className="panel-card" style={{ marginTop: 16, maxWidth: 420 }}>
        <h3>Telegram reminder</h3>
        <p className="muted mono" style={{ fontSize: 12 }}>
          Sends this checklist to the configured Telegram chat once a month,
          with a button per task to mark it done right from Telegram.
          Requires TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID to be set on the
          server (same ones low-stock alerts already use).
        </p>
        <label className="field" style={{ marginTop: 10, maxWidth: 220 }}><span className="field-label mono">Send on day of month</span>
          <input type="number" min="1" max="28" value={reminderDay} disabled={!loaded}
            onChange={(e) => saveReminderDay(Math.min(28, Math.max(1, Number(e.target.value) || 1)))} />
        </label>
      </div>
    </div>
  );
}

function MaintenanceTasksPanel() {
  const { db, showToast } = usePOS();
  const [tasks, setTasks] = React.useState([]);
  const [name, setName] = React.useState("");
  const [description, setDescription] = React.useState("");

  React.useEffect(() => db.collection("maintenance_tasks").onSnapshot((s) =>
    setTasks(s.docs.map((d) => ({ id: d.id, ...d.data() })))), []);

  const create = async (e) => {
    e.preventDefault();
    if (!name.trim()) return;
    await db.collection("maintenance_tasks").add({
      name: name.trim(), description: description.trim(), active: true,
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
    });
    setName(""); setDescription("");
    showToast("Task added");
  };
  const toggleActive = (t) => db.collection("maintenance_tasks").doc(t.id).update({ active: !t.active });
  const remove = (t) => { if (confirm(`Delete task "${t.name}"? Past history stays, but it won't appear in future reminders.`)) db.collection("maintenance_tasks").doc(t.id).delete(); };

  return (
    <div>
      <div className="panel-card">
        <h3>Add task</h3>
        <form className="row" style={{ gap: 8, flexWrap: "wrap", alignItems: "flex-end" }} onSubmit={create}>
          <label className="field" style={{ flex: "1 1 220px" }}><span className="field-label mono">Name</span>
            <input required value={name} onChange={(e) => setName(e.target.value)} placeholder="Cuci grease trap" />
          </label>
          <label className="field" style={{ flex: "1 1 260px" }}><span className="field-label mono">Notes (optional)</span>
            <input value={description} onChange={(e) => setDescription(e.target.value)} placeholder="e.g. who's responsible, how to do it" />
          </label>
          <button className="btn-primary" type="submit">Add</button>
        </form>
      </div>
      <div className="panel-card no-pad" style={{ marginTop: 16 }}>
        <table className="data-table">
          <thead><tr><th>Task</th><th>Notes</th><th></th></tr></thead>
          <tbody>
            {tasks.map((t) => (
              <tr key={t.id} style={t.active === false ? { opacity: 0.5 } : undefined}>
                <td>{t.name}</td>
                <td><Mono className="muted">{t.description || "—"}</Mono></td>
                <td className="row-actions">
                  <button className="btn-ghost-sm" onClick={() => toggleActive(t)}>{t.active === false ? "Activate" : "Deactivate"}</button>
                  <button className="btn-ghost-sm danger" onClick={() => remove(t)}>Delete</button>
                </td>
              </tr>
            ))}
            {tasks.length === 0 && (
              <tr><td colSpan={3} className="muted mono" style={{ textAlign: "center", padding: 24 }}>No maintenance tasks yet.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function MaintenanceHistory() {
  const { db } = usePOS();
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    db.collection("maintenance_completions").orderBy("monthKey", "desc").limit(500).get().then((s) => {
      setRows(s.docs.map((d) => {
        const c = d.data();
        return {
          id: d.id, month: c.monthKey || "—", task: c.taskName || c.taskId || "—",
          by: c.completedBy || "—", source: c.source || "—",
          when: c.completedAt?.toDate ? c.completedAt.toDate() : null,
        };
      }));
      setLoading(false);
    });
  }, []);

  if (loading) return <div className="muted mono" style={{ padding: 24 }}>Loading…</div>;

  return <DataReport
    columns={[
      { label: "Month",         key: "month" },
      { label: "Task",          key: "task" },
      { label: "Completed by",  key: "by" },
      { label: "Source",        key: "source" },
      { label: "When",          key: "when", render: (r) => r.when ? r.when.toLocaleString("en-GB") : "—" },
    ]}
    rows={rows}
    defaultSort={{ key: "when", dir: "desc" }}
  />;
}

function AdminQrisLog() {
  const { db } = usePOS();
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    const toDate = (ts) => (ts && ts.toDate ? ts.toDate() : null);
    return db.collection("pending_payments")
      .orderBy("createdAt", "desc")
      .limit(200)
      .onSnapshot((snap) => {
        setRows(snap.docs.map((d) => {
          const data = d.data();
          return {
            id: d.id, ...data,
            createdAt: toDate(data.createdAt),
            matchedAt: toDate(data.matchedAt),
            confirmedAt: toDate(data.confirmedAt),
          };
        }));
        setLoading(false);
      });
  }, []);

  const fmtTs = (v) => v ? v.toLocaleString("en-GB", {
    timeZone: window.APP_TZ || "Asia/Jakarta",
    day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", second: "2-digit",
  }) : "—";

  const counts = rows.reduce((acc, r) => { acc[r.status] = (acc[r.status] || 0) + 1; return acc; }, {});

  const columns = [
    { key: "createdAt", label: "Created", format: fmtTs },
    { key: "amount", label: "Amount", numeric: true, format: (v) => money(v || 0) },
    { key: "status", label: "Status", cell: (r) => <StatusPill status={r.status}>{r.status}</StatusPill> },
    { key: "cashierEmail", label: "Cashier" },
    { key: "uniqueCode", label: "Code", numeric: true, format: (v) => "+" + money(v || 0) },
    { key: "matchedAt", label: "Matched", format: fmtTs },
    { key: "confirmedAt", label: "Confirmed", format: fmtTs },
    { key: "rawText", label: "Notification text", render: (r) => (r.rawText || "").length > 80 ? r.rawText.slice(0, 80) + "…" : (r.rawText || "—") },
  ];

  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">QRIS AUTO-CONFIRM</div>
          <h2>Payment listener log</h2>
        </div>
      </header>

      <p className="muted mono" style={{ fontSize: 12, maxWidth: 720 }}>
        Every QRIS checkout creates a "waiting" entry here while the sheet is open.
        The android-qris-listener app (installed on the phone that receives the
        Livin Mandiri / bank notification) reports a matching amount, which flips
        it to "matched" — the cashier's open browser tab then confirms the order
        and flips it to "confirmed". Checkouts the cashier cancels while still
        waiting are deleted, so they won't show up here.
      </p>

      <div className="row" style={{ gap: 12, margin: "8px 0 14px", flexWrap: "wrap" }}>
        {["waiting", "matched", "confirmed"].map((s) => (
          <div key={s} className="panel-card" style={{ padding: "8px 14px" }}>
            <span className="mono muted" style={{ fontSize: 11 }}>{s.toUpperCase()}</span>{" "}
            <strong>{counts[s] || 0}</strong>
          </div>
        ))}
      </div>

      {loading ? (
        <div className="muted mono">Loading…</div>
      ) : rows.length === 0 ? (
        <div className="muted mono">No QRIS auto-confirm activity yet.</div>
      ) : (
        <DataReport columns={columns} rows={rows} defaultSort={{ key: "createdAt", dir: "desc" }} />
      )}
    </div>
  );
}

function AdminSettings() {
  return (
    <div className="admin-pane">
      <header className="pane-head">
        <div>
          <div className="eyebrow mono">SETTINGS</div>
          <h2>POS configuration</h2>
        </div>
      </header>
      <div className="dash-grid" style={{ gridTemplateColumns: "1fr 1fr" }}>
        <ThemeSettings />
        <QrisSettings />
        <GeneralSettings />
        <StoreProfileSettings />
        <NotificationSettings />
        <ExternalApiSettings />
        <ExternalApiSettings
          docId="qrisListener"
          title="QRIS listener key"
          description={"For the Android QRIS listener app: paste this key into the app so it can auto-confirm QRIS payments for this store via /api/qris-notify. Generating a new key immediately invalidates the old one."} />
      </div>
    </div>
  );
}

// API key for a third-party integration (e.g. an AI assistant) to read
// reports and manage product stock via /api/external/* (server.js).
// Deliberately stored in its own admin-only Firestore collection, NOT
// /settings — that collection is publicly readable (customer display/
// prebook need it for theme/QRIS), which would leak the key to anyone.
// Self-service regeneration: no env var, no redeploy needed.
//
// Multi-tenant: keys are "<tenantId>.<64 hex>" so the server can tell which
// store a key belongs to with a single doc read (tenants/{id}/integrations/
// {docId}) — no global key index needed. Reused for the QRIS listener key.
function ExternalApiSettings({ docId = "externalApi", title = "External API access", description }) {
  const { db, showToast } = usePOS();
  const [keyDoc, setKeyDoc] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);
  const [revealed, setRevealed] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const docRef = db.collection("integrations").doc(docId);

  React.useEffect(() => {
    docRef.get().then((d) => {
      setKeyDoc(d.exists ? d.data() : null);
      setLoaded(true);
    });
  }, []);

  const generate = async () => {
    if (keyDoc && !confirm(
      "Generate a new key? The old key stops working immediately — " +
      "update it wherever it's currently in use (e.g. your AI assistant) or it will lose access."
    )) return;
    setBusy(true);
    try {
      const bytes = new Uint8Array(32);
      if (window.crypto?.getRandomValues) window.crypto.getRandomValues(bytes);
      else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
      const key = db.tenantId + "." + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
      await docRef.set({
        key,
        createdAt: keyDoc?.createdAt || firebase.firestore.FieldValue.serverTimestamp(),
        regeneratedAt: firebase.firestore.FieldValue.serverTimestamp(),
      });
      const fresh = await docRef.get();
      setKeyDoc(fresh.data());
      setRevealed(true);
      showToast("New API key generated");
    } catch (e) { alert("Failed: " + e.message); }
    finally { setBusy(false); }
  };

  const copy = async () => {
    if (!keyDoc?.key) return;
    try {
      await navigator.clipboard.writeText(keyDoc.key);
      showToast("Copied to clipboard");
    } catch (e) {
      showToast("Copy failed — select the field and copy manually");
    }
  };

  return (
    <section className="panel-card">
      <h3>{title}</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        {description || (
          <React.Fragment>
            For a third-party integration (e.g. an AI assistant) to read sales
            reports and manage product stock via
            <code> /api/external/*</code>. Read + write, scoped to reports and
            products only — no order details or customer data. Generating a new
            key immediately invalidates the old one everywhere it's in use.
          </React.Fragment>
        )}
      </p>

      {!loaded ? (
        <div className="muted mono">Loading…</div>
      ) : !keyDoc ? (
        <button className="btn-primary" onClick={generate} disabled={busy}>
          {busy ? "Generating…" : "Generate API key"}
        </button>
      ) : (
        <React.Fragment>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">API key</span>
            <div className="row" style={{ gap: 8 }}>
              <input
                readOnly
                type={revealed ? "text" : "password"}
                value={keyDoc.key}
                style={{ fontFamily: "monospace", flex: 1 }}
                onFocus={(e) => e.target.select()}
              />
              <button className="btn-ghost-sm" onClick={() => setRevealed((r) => !r)}>
                {revealed ? "Hide" : "Show"}
              </button>
              <button className="btn-ghost-sm" onClick={copy}>Copy</button>
            </div>
          </label>
          <div className="muted mono" style={{ fontSize: 11, marginBottom: 12 }}>
            {keyDoc.regeneratedAt?.toDate
              ? "Last regenerated " + keyDoc.regeneratedAt.toDate().toLocaleString("en-GB", { timeZone: "Asia/Jakarta" }) + " WIB"
              : ""}
          </div>
          <button className="btn-ghost" onClick={generate} disabled={busy}>
            {busy ? "Generating…" : "Regenerate key"}
          </button>
        </React.Fragment>
      )}
    </section>
  );
}

// Where this store's alerts go (low stock, QRIS auto-confirm audit line,
// monthly maintenance checklist). The platform supplies the Telegram bot and
// email sender (server env vars); each store only says where to deliver.
// Stored under admin-only /integrations, not public-readable /settings.
function NotificationSettings() {
  const { db, showToast } = usePOS();
  const docRef = db.collection("integrations").doc("notifications");
  const [chatId, setChatId] = React.useState("");
  const [emails, setEmails] = React.useState("");
  const [loaded, setLoaded] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    docRef.get().then((d) => {
      const v = d.exists ? d.data() : {};
      setChatId(v.telegramChatId || "");
      setEmails((v.emails || []).join(", "));
      setLoaded(true);
    });
  }, []);

  const save = async () => {
    setBusy(true);
    try {
      await docRef.set({
        telegramChatId: chatId.trim(),
        emails: emails.split(",").map((s) => s.trim()).filter(Boolean),
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      showToast("Notification settings saved");
    } catch (e) { alert("Failed: " + e.message); }
    finally { setBusy(false); }
  };

  return (
    <section className="panel-card">
      <h3>Notifications</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        Low-stock alerts, QRIS auto-confirm notices and the monthly
        maintenance checklist are sent here. Leave empty to turn them off.
      </p>
      {!loaded ? <div className="muted mono">Loading…</div> : (
        <React.Fragment>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">Telegram chat ID</span>
            <input value={chatId} onChange={(e) => setChatId(e.target.value)} placeholder="e.g. -1001234567890" />
          </label>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">Email recipients (comma-separated)</span>
            <input value={emails} onChange={(e) => setEmails(e.target.value)} placeholder="owner@example.com" />
          </label>
          <button className="btn-primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save"}</button>
        </React.Fragment>
      )}
    </section>
  );
}

// Per-store identity on printed/displayed slips + pre-booking rooms. Lives in
// the public settings/general doc (the receipt and /prebook read it without
// auth), so nothing private goes here. The store NAME itself comes from the
// platform's tenant profile (window.STORE_NAME) and isn't editable here.
//   tagline        one line under the name, e.g. "Jl. Darmo 12 - Surabaya"
//   receiptFooter  lines printed at the end of every receipt
//   rooms          [{label, max}] offered on /prebook (empty = no picker)
function StoreProfileSettings() {
  const { db, showToast } = usePOS();
  const ref = db.collection("settings").doc("general");
  const [tagline, setTagline] = React.useState("");
  const [footer, setFooter] = React.useState("");
  const [rooms, setRooms] = React.useState("");
  const [loaded, setLoaded] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    ref.get().then((d) => {
      const g = d.exists ? d.data() : {};
      setTagline(g.tagline || "");
      setFooter(Array.isArray(g.receiptFooter) ? g.receiptFooter.join("\n") : "Terima kasih");
      setRooms((g.rooms || []).map((r) => r.max ? `${r.label} | ${r.max}` : r.label).join("\n"));
      setLoaded(true);
    });
  }, []);

  const save = async () => {
    setBusy(true);
    try {
      const parsedRooms = rooms.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => {
        const [label, max] = l.split("|").map((x) => x.trim());
        const n = Number(max);
        return Number.isFinite(n) && n > 0 ? { label: label.slice(0, 40), max: n } : { label: label.slice(0, 40) };
      }).filter((r) => r.label).slice(0, 12);
      const footerLines = footer.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, 6).map((l) => l.slice(0, 48));
      await ref.set({
        tagline: tagline.trim().slice(0, 60),
        receiptFooter: footerLines,
        rooms: parsedRooms,
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      window.STORE_TAGLINE = tagline.trim();
      window.STORE_FOOTER = footerLines;
      showToast("Store profile saved");
    } catch (e) { alert("Save failed: " + e.message); }
    finally { setBusy(false); }
  };

  return (
    <section className="panel-card">
      <h3>Store profile · {window.STORE_NAME}</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        Shown on receipts and the pre-booking page. To change the store name
        itself, contact support.
      </p>
      {!loaded ? <div className="muted mono">Loading…</div> : (
        <React.Fragment>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">Tagline / address (under the name)</span>
            <input value={tagline} onChange={(e) => setTagline(e.target.value)} placeholder="Jl. Darmo 12 — Surabaya" />
          </label>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">Receipt footer (one line each, max 6)</span>
            <textarea rows={3} value={footer} onChange={(e) => setFooter(e.target.value)} placeholder={"Terima kasih\nIG: @tokokamu"} />
          </label>
          <label className="field" style={{ marginBottom: 12 }}>
            <span className="field-label mono">Pre-booking rooms (one per line: name | max people)</span>
            <textarea rows={3} value={rooms} onChange={(e) => setRooms(e.target.value)} placeholder={"Indoor | 30\nOutdoor | 45"} />
          </label>
          <button className="btn-primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save"}</button>
        </React.Fragment>
      )}
    </section>
  );
}

// Common Indonesian timezones + UTC. Value is an IANA zone (what Intl needs);
// label shows the GMT offset for staff who think in WIB/WITA/WIT.
const TZ_OPTIONS = [
  { value: "Asia/Jakarta", label: "GMT+7 · WIB (Jakarta)" },
  { value: "Asia/Makassar", label: "GMT+8 · WITA (Makassar)" },
  { value: "Asia/Jayapura", label: "GMT+9 · WIT (Jayapura)" },
  { value: "Asia/Singapore", label: "GMT+8 · Singapore" },
  { value: "UTC", label: "GMT+0 · UTC" },
];

function GeneralSettings() {
  const { db, showToast } = usePOS();
  const [tz, setTz] = React.useState(DEFAULT_TZ);
  const [prebookEnabled, setPrebookEnabled] = React.useState(true);
  const [loaded, setLoaded] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [prebookBusy, setPrebookBusy] = React.useState(false);

  React.useEffect(() => {
    db.collection("settings").doc("general").get().then((d) => {
      if (d.exists && d.data().timezone) setTz(d.data().timezone);
      if (d.exists) setPrebookEnabled(d.data().prebookEnabled !== false);
      setLoaded(true);
    });
  }, []);

  const save = async () => {
    setBusy(true);
    try {
      await db.collection("settings").doc("general").set({
        timezone: tz,
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      window.APP_TZ = tz;
      showToast("Timezone saved");
    } catch (e) { alert("Save failed: " + e.message); }
    finally { setBusy(false); }
  };

  // Toggles instantly (no separate Save button) — this gates the public
  // /prebook page (prebook-app.jsx) and is also enforced server-side in
  // firestore.rules, so toggling off reliably stops new bookings even if
  // someone bypasses the UI.
  const togglePrebook = async () => {
    const next = !prebookEnabled;
    setPrebookBusy(true);
    try {
      await db.collection("settings").doc("general").set({
        prebookEnabled: next,
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      setPrebookEnabled(next);
      showToast(next ? "Pre-booking reopened" : "Pre-booking closed");
    } catch (e) { alert("Save failed: " + e.message); }
    finally { setPrebookBusy(false); }
  };

  return (
    <section className="panel-card">
      <h3>General · timezone</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        Sets the calendar day for reports and "today's" revenue (local 00:00–24:00),
        plus clock/date display. Default GMT+7 (WIB). Reload other devices after changing.
      </p>
      <label className="field" style={{ marginBottom: 12 }}>
        <span className="field-label mono">Timezone</span>
        <select value={tz} onChange={(e) => setTz(e.target.value)} disabled={!loaded}>
          {TZ_OPTIONS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
          {!TZ_OPTIONS.some((o) => o.value === tz) && <option value={tz}>{tz}</option>}
        </select>
      </label>
      <div className="row" style={{ gap: 8, marginTop: 8, alignItems: "center" }}>
        <span className="mono muted" style={{ fontSize: 12 }}>Now: {fmtClock()} · {fmtDay()}</span>
        <button className="btn-primary" style={{ marginLeft: "auto" }} onClick={save} disabled={busy || !loaded}>
          {busy ? "Saving…" : "Save timezone"}
        </button>
      </div>

      <div style={{ marginTop: 18, paddingTop: 14, borderTop: "1px solid var(--line)" }}>
        <label className="row" style={{ gap: 10, alignItems: "center", cursor: loaded ? "pointer" : "default" }}>
          <input type="checkbox" checked={prebookEnabled} disabled={!loaded || prebookBusy} onChange={togglePrebook} />
          <span className="mono">Accept new pre-bookings</span>
          {prebookBusy && <span className="muted mono" style={{ fontSize: 10 }}>…</span>}
        </label>
        <p className="muted mono" style={{ fontSize: 12, marginTop: 6 }}>
          Turn off to close the public /prebook page to new bookings (shows a
          "closed" message instead of the booking form) without touching any
          code. Existing/pending pre-bookings and the Admin → Pre-bookings
          tab are unaffected — this only blocks new submissions.
        </p>
      </div>
    </section>
  );
}

function ThemeSettings() {
  const { db, showToast } = usePOS();
  const [colors, setColors] = React.useState(THEME_DEFAULTS);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    db.collection("settings").doc("theme").get().then((d) => {
      if (d.exists && d.data().colors) {
        setColors({ ...THEME_DEFAULTS, ...d.data().colors });
      }
    });
  }, []);

  const apply = (next) => {
    setColors(next);
    Object.entries(next).forEach(([k, v]) => {
      document.documentElement.style.setProperty(`--${k}`, v);
    });
  };
  const setColor = (k, v) => apply({ ...colors, [k]: v });

  const save = async () => {
    setBusy(true);
    try {
      await db.collection("settings").doc("theme").set({ colors }, { merge: true });
      showToast("Theme saved");
    } catch (e) { alert("Save failed: " + e.message); }
    finally { setBusy(false); }
  };
  const reset = () => apply(THEME_DEFAULTS);
  const applyPreset = (name) => {
    const p = THEME_PRESETS[name]; if (p) apply(p);
  };

  return (
    <section className="panel-card">
      <h3>Theme · colors</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        Changes preview immediately. Save to apply across all devices.
      </p>

      <label className="field" style={{ marginBottom: 14 }}>
        <span className="field-label mono">Preset</span>
        <select onChange={(e) => applyPreset(e.target.value)} defaultValue="">
          <option value="" disabled>Pick a preset…</option>
          {Object.keys(THEME_PRESETS).map((n) => <option key={n} value={n}>{n}</option>)}
        </select>
      </label>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
        {Object.keys(THEME_DEFAULTS).map((k) => (
          <label key={k} className="row" style={{ gap: 8, alignItems: "center" }}>
            <input
              type="color"
              value={colors[k]}
              onChange={(e) => setColor(k, e.target.value)}
              style={{ width: 36, height: 36, padding: 0, border: "1px solid var(--line)", borderRadius: 6, cursor: "pointer" }}
            />
            <span className="mono" style={{ fontSize: 12, flex: 1 }}>{k}</span>
            <input
              type="text"
              value={colors[k]}
              onChange={(e) => setColor(k, e.target.value)}
              style={{ width: 90, fontSize: 11, fontFamily: "var(--mono)", padding: "4px 6px", border: "1px solid var(--line)", borderRadius: 4 }}
            />
          </label>
        ))}
      </div>

      <div className="row" style={{ gap: 8, marginTop: 16, justifyContent: "flex-end" }}>
        <button className="btn-ghost" onClick={reset}>Reset to default</button>
        <button className="btn-primary" onClick={save} disabled={busy}>
          {busy ? "Saving…" : "Save theme"}
        </button>
      </div>
    </section>
  );
}

function QrisSettings() {
  const { db, showToast } = usePOS();
  const [payload, setPayload] = React.useState("");
  const [loaded, setLoaded] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    db.collection("settings").doc("qris").get().then((d) => {
      if (d.exists) setPayload(d.data().staticPayload || "");
      setLoaded(true);
    });
  }, []);

  const trimmed = payload.trim();
  const isValid = window.QRIS ? window.QRIS.isValid(trimmed) : false;

  const save = async () => {
    if (!trimmed) return;
    if (!isValid && !confirm("This payload's CRC doesn't validate. Save anyway?")) return;
    setBusy(true);
    try {
      await db.collection("settings").doc("qris").set({
        staticPayload: trimmed,
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      // Update in-memory config so the next checkout uses it without reload.
      if (window.QRIS_CONFIG) window.QRIS_CONFIG.staticPayload = trimmed;
      showToast("QRIS string saved");
    } catch (e) { alert("Save failed: " + e.message); }
    finally { setBusy(false); }
  };

  return (
    <section className="panel-card">
      <h3>QRIS · static payload</h3>
      <p className="muted mono" style={{ fontSize: 12, marginBottom: 12 }}>
        Pasted from your merchant QR (scan the static QR with any reader to copy the text).
        The POS adds the transaction amount and recomputes the CRC for every payment.
      </p>
      <textarea
        value={payload}
        onChange={(e) => setPayload(e.target.value)}
        rows={6}
        spellCheck={false}
        placeholder={loaded ? "00020101021126…6304XXXX" : "Loading…"}
        style={{
          width: "100%", padding: 10, fontFamily: "var(--mono)", fontSize: 11,
          border: "1px solid var(--line)", borderRadius: 6, resize: "vertical",
          background: "var(--paper)", color: "var(--ink)", boxSizing: "border-box",
        }}
      />
      <div className="row" style={{ gap: 8, marginTop: 10, alignItems: "center" }}>
        <span className="mono" style={{ fontSize: 12, color: trimmed
          ? (isValid ? "var(--ok)" : "var(--alert)") : "var(--ink-mute)" }}>
          {!trimmed ? "—" : isValid ? "✓ CRC valid" : "⚠ CRC mismatch"}
        </span>
        <span className="mono muted" style={{ fontSize: 11, marginLeft: "auto" }}>
          {trimmed.length} chars
        </span>
        <button className="btn-primary" onClick={save} disabled={busy || !trimmed}>
          {busy ? "Saving…" : "Save QRIS"}
        </button>
      </div>
    </section>
  );
}

Object.assign(window, { AdminScreen, AdminSettings, THEME_DEFAULTS });

