"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import QRCode from "qrcode";
import { io } from "socket.io-client";
import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, startAuthentication, startRegistration } from "@simplewebauthn/browser";
import {
  Activity,
  ArrowDownUp,
  BarChart3,
  Boxes,
  ClipboardList,
  Factory,
  LogOut,
  Mail,
  Menu,
  MoveRight,
  PackageMinus,
  PackagePlus,
  Plus,
  QrCode,
  RefreshCw,
  RotateCcw,
  Save,
  ScanLine,
  Trash2,
  UserPlus,
  Users,
  Truck,
  Wifi,
  WifiOff
} from "lucide-react";
import { API_URL, api, clearSessionToken, idempotencyKey, saveSessionToken, type BomComponentRow, type InventoryRow, type Location, type Part, type TransactionRow } from "@/lib/api";
import { clearQueue, queueOperation, readQueue, type QueuedOperation } from "@/lib/offline";

type Tab = "inventory" | "stock" | "move" | "build" | "admin";
type PartTypeFilter = "all" | "full" | "partial" | "base";
type BaseUsageFilter = "all" | "used" | "unused";
type AdminTab = "users" | "log" | "reports";
type BuildTab = "define" | "assemble";
type StockTab = "plus" | "minus";
type InventorySortKey = "part" | "location" | "quantity" | "cost" | "value";
type SortDirection = "asc" | "desc";
type AuthMode = "login" | "bootstrap" | "invite" | "forgot" | "reset";
type User = { id: string; email: string };
type AuthResponse = { user: User; token?: string };
type ManagedUser = { id: string; email: string; createdAt: string };
type ManagedPasskey = { id: string; createdAt: string; lastUsedAt: string | null; deviceType: string | null; backedUp: boolean };
type ManagedInvite = {
  id: string;
  email: string;
  acceptedAt: string | null;
  expiresAt: string;
  createdAt: string;
  createdByEmail: string | null;
};
type BomDraftLine = { id: string; componentPartId: string; quantityRequired: string };

const tabs: { id: Tab; label: string; icon: React.ComponentType<{ size?: number }> }[] = [
  { id: "inventory", label: "Inventory", icon: Boxes },
  { id: "stock", label: "Stock", icon: ArrowDownUp },
  { id: "move", label: "Move", icon: MoveRight },
  { id: "build", label: "Build", icon: Factory },
  { id: "admin", label: "Admin", icon: ClipboardList }
];

const quantityFormatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 4 });
const moneyFormatter = new Intl.NumberFormat(undefined, { style: "currency", currency: "USD" });

function formatQuantity(value: number) {
  return quantityFormatter.format(value);
}

function formatMoney(value: number) {
  return moneyFormatter.format(value);
}

function MoneyValue({ value, neutral = false }: { value: number; neutral?: boolean }) {
  const tone = neutral ? "text-slate-900" : value < 0 ? "money-negative" : value > 0 ? "money-positive" : "text-slate-900";
  return <span className={tone}>{formatMoney(value)}</span>;
}

export default function AppPage() {
  const [user, setUser] = useState<User | null>(null);
  const [authMode, setAuthMode] = useState<AuthMode>("login");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [inviteToken, setInviteToken] = useState("");
  const [resetToken, setResetToken] = useState("");
  const [authError, setAuthError] = useState("");
  const [authNotice, setAuthNotice] = useState("");
  const [authLoading, setAuthLoading] = useState(false);
  const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
  const [passkeyAutofillSupported, setPasskeyAutofillSupported] = useState(false);
  const [bootstrapAvailable, setBootstrapAvailable] = useState<boolean | null>(null);
  const [tab, setTab] = useState<Tab>("inventory");
  const [parts, setParts] = useState<Part[]>([]);
  const [locations, setLocations] = useState<Location[]>([]);
  const [inventory, setInventory] = useState<InventoryRow[]>([]);
  const [bomComponentIds, setBomComponentIds] = useState<string[]>([]);
  const [transactions, setTransactions] = useState<TransactionRow[]>([]);
  const [queue, setQueue] = useState<QueuedOperation[]>([]);
  const [online, setOnline] = useState(true);
  const [message, setMessage] = useState("");
  const [selectedPartId, setSelectedPartId] = useState("");
  const [qrDataUrl, setQrDataUrl] = useState("");
  const [liveConnected, setLiveConnected] = useState(false);
  const [lastAutoRefreshAt, setLastAutoRefreshAt] = useState<Date | null>(null);
  const [usersRefreshSignal, setUsersRefreshSignal] = useState(0);
  const refreshingRef = useRef(false);
  const passkeyAutofillStartedRef = useRef(false);
  const authEmailRef = useRef<HTMLInputElement | null>(null);

  useEffect(() => {
    setOnline(navigator.onLine);
    setPasskeysSupported(browserSupportsWebAuthn());
    browserSupportsWebAuthnAutofill().then(setPasskeyAutofillSupported).catch(() => setPasskeyAutofillSupported(false));
    setQueue(readQueue());
    const params = new URLSearchParams(window.location.search);
    const invite = params.get("invite");
    const reset = params.get("reset");
    if (reset) {
      setResetToken(reset);
      setAuthMode("reset");
    } else if (invite) {
      setInviteToken(invite);
      setAuthMode("invite");
    }
    const onOnline = () => setOnline(true);
    const onOffline = () => setOnline(false);
    const onQueue = () => setQueue(readQueue());
    window.addEventListener("online", onOnline);
    window.addEventListener("offline", onOffline);
    window.addEventListener("offline-queue-changed", onQueue);
    navigator.serviceWorker?.register("/sw.js").catch(() => undefined);
    api<{ available: boolean }>("/auth/bootstrap-status")
      .then((data) => {
        setBootstrapAvailable(data.available);
        if (data.available && !invite && !reset) setAuthMode("bootstrap");
        if (!data.available && authMode === "bootstrap") setAuthMode("login");
      })
      .catch(() => setBootstrapAvailable(false));
    api<{ user: User }>("/auth/me").then((data) => setUser(data.user)).catch(() => undefined);
    return () => {
      window.removeEventListener("online", onOnline);
      window.removeEventListener("offline", onOffline);
      window.removeEventListener("offline-queue-changed", onQueue);
    };
  }, []);

  const refreshAll = useCallback(async () => {
    const [partData, locationData, inventoryData, txData, bomData] = await Promise.all([
      api<{ parts: Part[] }>("/parts"),
      api<{ locations: Location[] }>("/locations"),
      api<{ inventory: InventoryRow[] }>("/inventory"),
      api<{ transactions: TransactionRow[] }>("/transactions"),
      api<{ componentPartIds: BomComponentRow[] }>("/bom-components")
    ]);
    setParts(partData.parts);
    setLocations(locationData.locations);
    setInventory(inventoryData.inventory);
    setTransactions(txData.transactions);
    setBomComponentIds(bomData.componentPartIds.map((row) => row.componentPartId));
    setSelectedPartId((current) => current || partData.parts[0]?.id || "");
  }, []);

  useEffect(() => {
    if (!user) return;
    refreshAll();
    const socket = io(API_URL, { withCredentials: true });
    const refreshFromServerEvent = async () => {
      if (refreshingRef.current) return;
      refreshingRef.current = true;
      try {
        await refreshAll();
        setLastAutoRefreshAt(new Date());
      } finally {
        refreshingRef.current = false;
      }
    };
    socket.on("connect", () => {
      setLiveConnected(true);
      refreshFromServerEvent();
    });
    socket.on("disconnect", () => setLiveConnected(false));
    socket.on("inventory.updated", refreshFromServerEvent);
    socket.on("catalog.updated", refreshFromServerEvent);
    socket.on("users.updated", () => {
      setUsersRefreshSignal((value) => value + 1);
      refreshFromServerEvent();
    });
    return () => {
      socket.disconnect();
      setLiveConnected(false);
    };
  }, [user, refreshAll]);

  useEffect(() => {
    if (!message) return;
    const timer = window.setTimeout(() => setMessage(""), 4000);
    return () => window.clearTimeout(timer);
  }, [message]);

  useEffect(() => {
    const part = parts.find((item) => item.id === selectedPartId);
    if (!part?.sku) {
      setQrDataUrl("");
      return;
    }
    QRCode.toDataURL(part.sku, { margin: 1, width: 220 }).then(setQrDataUrl).catch(() => setQrDataUrl(""));
  }, [selectedPartId, parts]);

  async function submitAuth(event: React.FormEvent) {
    event.preventDefault();
    setAuthError("");
    setAuthNotice("");
    setAuthLoading(true);
    try {
      if (authMode === "forgot") {
        if (!email.trim()) throw new Error("Email is required.");
        const data = await api<{ ok: boolean; emailSent: boolean }>("/auth/password-reset/request", {
          method: "POST",
          body: JSON.stringify({ email })
        });
        setPassword("");
        setConfirmPassword("");
        setAuthNotice(
          data.emailSent
            ? "If that email matches an account, a password reset link is on the way."
            : "If that email matches an account, SMTP is not configured yet, so no reset email can be sent."
        );
        return;
      }

      if (authMode === "reset") {
        if (!resetToken.trim()) throw new Error("Reset link or code is required.");
        if (!password) throw new Error("Password is required.");
        if (password.length < 8) throw new Error("Password must be at least 8 characters.");
        if (password !== confirmPassword) throw new Error("Passwords do not match.");
        const data = await api<AuthResponse>("/auth/password-reset/confirm", {
          method: "POST",
          body: JSON.stringify({ token: parseToken(resetToken, "reset"), password })
        });
        saveSessionToken(data.token);
        setUser(data.user);
        return;
      }

      if (!email.trim() && authMode !== "invite") {
        throw new Error("Email is required.");
      }
      if (!inviteToken.trim() && authMode === "invite") {
        throw new Error("Invite link or code is required.");
      }
      if (!password) {
        throw new Error("Password is required.");
      }
      if (password.length < 8) {
        throw new Error("Password must be at least 8 characters.");
      }
      if (authMode === "invite" && password !== confirmPassword) {
        throw new Error("Passwords do not match.");
      }
      if (authMode === "bootstrap" && bootstrapAvailable === false) {
        throw new Error("Bootstrap has already been completed. Please log in.");
      }
      const path = authMode === "bootstrap" ? "/auth/bootstrap" : authMode === "invite" ? "/invites/accept" : "/auth/login";
      const payload = authMode === "invite" ? { token: parseToken(inviteToken, "invite"), password } : { email, password };
      const data = await api<AuthResponse>(path, { method: "POST", body: JSON.stringify(payload) });
      saveSessionToken(data.token);
      setUser(data.user);
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unable to sign in";
      setAuthError(message);
      if (authMode === "bootstrap" && message.includes("Bootstrap")) {
        setAuthMode("login");
        setBootstrapAvailable(false);
      }
    } finally {
      setAuthLoading(false);
    }
  }

  const completePasskeySignIn = useCallback(async (useBrowserAutofill = false) => {
    if (!useBrowserAutofill) {
      setAuthError("");
      setAuthNotice("");
      setAuthLoading(true);
    }
    try {
      const optionsJSON = await api<Parameters<typeof startAuthentication>[0]["optionsJSON"]>("/auth/passkeys/authentication-options", { method: "POST", body: "{}" });
      const asseResp = await startAuthentication({ optionsJSON, useBrowserAutofill });
      const data = await api<AuthResponse>("/auth/passkeys/authenticate", {
        method: "POST",
        body: JSON.stringify(asseResp)
      });
      saveSessionToken(data.token);
      setUser(data.user);
    } catch (error) {
      if (!useBrowserAutofill) {
        setAuthError(error instanceof Error ? error.message : "Passkey sign-in failed.");
      }
    } finally {
      if (!useBrowserAutofill) setAuthLoading(false);
    }
  }, []);

  useEffect(() => {
    if (user || authMode !== "login" || !passkeyAutofillSupported || passkeyAutofillStartedRef.current) return;
    passkeyAutofillStartedRef.current = true;
    authEmailRef.current?.focus();
    completePasskeySignIn(true).catch(() => undefined);
  }, [authMode, completePasskeySignIn, passkeyAutofillSupported, user]);

  async function signInWithPasskey() {
    await completePasskeySignIn(false);
  }

  function parseToken(value: string, key: "invite" | "reset") {
    try {
      const url = new URL(value);
      return url.searchParams.get(key) ?? value;
    } catch {
      return value;
    }
  }

  async function logout() {
    await api("/auth/logout", { method: "POST", body: "{}" });
    clearSessionToken();
    setUser(null);
  }

  async function refreshAllFromMenu() {
    try {
      await refreshAll();
      setMessage("Inventory data refreshed.");
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Refresh failed.");
    }
  }

  function restartApp() {
    window.location.reload();
  }

  async function syncQueue() {
    const pending = readQueue();
    if (!pending.length) return;
    const data = await api<{ results: { ok: boolean; error?: string }[] }>("/sync/operations", {
      method: "POST",
      body: JSON.stringify(pending.map((item) => ({ type: item.type, payload: item.payload })))
    });
    const syncedIds = pending.filter((_item, index) => data.results[index]?.ok).map((item) => item.id);
    clearQueue(syncedIds);
    setMessage(`${formatQuantity(syncedIds.length)} queued operation${syncedIds.length === 1 ? "" : "s"} synced.`);
    await refreshAll();
  }

  if (!user) {
    const authButtonLabel = authMode === "invite" ? "Create password" : authMode === "forgot" ? "Email reset link" : authMode === "reset" ? "Reset password" : authMode === "bootstrap" ? "Create owner account" : "Login";
    const passwordLabel = authMode === "reset" || authMode === "invite" ? "New password" : "Password";
    const passwordAutocomplete = authMode === "login" ? "current-password" : "new-password";

    return (
      <main className="min-h-screen grid place-items-center p-5">
        <form method="post" onSubmit={submitAuth} autoComplete="on" className="panel login-panel w-full max-w-md p-6 space-y-5">
          <div>
            <img className="login-logo mb-5" src="/brand/solar-slate-logo-wide.svg" alt="Solar Slate Solutions" />
            <h1 className="text-3xl font-black tracking-tight">SSS Inventory</h1>
            <p className="text-sm text-slate-600 mt-2">Invite-only inventory control for warehouses, trucks, assemblies, and field work.</p>
          </div>
          {bootstrapAvailable === false ? <div className="rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm font-bold text-slate-600">Bootstrap is complete. Log in with your account.</div> : null}
          {authMode !== "invite" && authMode !== "reset" ? (
            <label className="block text-sm font-bold">
              Email
              <input ref={authEmailRef} className="field mt-1" id="auth-email" name="username" type="email" autoComplete="username webauthn" value={email} onChange={(event) => setEmail(event.target.value)} />
            </label>
          ) : authMode === "invite" ? (
            <label className="block text-sm font-bold">
              Invite from email
              <input className="field mt-1" id="invite-token" name="invite-token" placeholder="Paste invite link from email" autoComplete="off" value={inviteToken} onChange={(event) => setInviteToken(event.target.value)} />
            </label>
          ) : (
            <label className="block text-sm font-bold">
              Reset link or code
              <input className="field mt-1" id="reset-token" name="reset-token" placeholder="Paste reset link from email" autoComplete="off" value={resetToken} onChange={(event) => setResetToken(event.target.value)} />
            </label>
          )}
          {authMode !== "forgot" ? (
            <label className="block text-sm font-bold">
              {passwordLabel}
              <input className="field mt-1" id="auth-password" type="password" autoComplete={passwordAutocomplete} value={password} onChange={(event) => setPassword(event.target.value)} />
            </label>
          ) : null}
          {authMode === "reset" || authMode === "invite" ? (
            <label className="block text-sm font-bold">
              Confirm password
              <input className="field mt-1" id="confirm-password" type="password" autoComplete="new-password" value={confirmPassword} onChange={(event) => setConfirmPassword(event.target.value)} />
            </label>
          ) : null}
          {authMode === "login" ? (
            <button className="brand-link text-sm font-bold" type="button" onClick={() => { setAuthMode("forgot"); setAuthError(""); setAuthNotice(""); setPassword(""); }}>
              Forgot password?
            </button>
          ) : authMode === "forgot" || authMode === "reset" ? (
            <button className="brand-link text-sm font-bold" type="button" onClick={() => { setAuthMode("login"); setAuthError(""); setAuthNotice(""); setPassword(""); setConfirmPassword(""); }}>
              Back to login
            </button>
          ) : null}
          {authNotice ? (
            <div className="notice-success rounded-lg border p-3 text-sm font-bold">{authNotice}</div>
          ) : null}
          {authError ? <div className="notice-error rounded-lg border p-3 text-sm font-bold">{authError}</div> : null}
          <button className="btn btn-primary w-full" type="submit" disabled={authLoading}>{authLoading ? "Working..." : authButtonLabel}</button>
          {authMode === "login" && passkeysSupported === true ? (
            <button className="btn btn-secondary passkey-button w-full" type="button" disabled={authLoading} onClick={signInWithPasskey}>
              Sign in with Face ID, Touch ID, or passkey
            </button>
          ) : null}
          {authMode === "login" && passkeyAutofillSupported ? (
            <p className="text-xs font-bold text-slate-500">Passkey sign-in is the default. Safari will show the Apple passkey prompt when a saved passkey is available.</p>
          ) : null}
          {authMode === "login" && passkeysSupported === false ? (
            <div className="notice-warning rounded-lg border p-3 text-xs font-bold">
              Face ID, Touch ID, and passkeys require HTTPS or localhost. Password login is available on this local network address.
            </div>
          ) : null}
          <p className="text-xs text-slate-500">
            {authMode === "invite"
              ? "Invites are normally opened from email. The code is the secure one-time part of that invite link."
              : authMode === "forgot"
                ? "Enter your account email. For privacy, the response will look the same whether or not an account exists."
              : authMode === "reset"
                ? "Reset links expire after 1 hour and can only be used once."
              : authMode === "bootstrap"
                ? "Create the first owner account. After this, bootstrap disappears."
              : bootstrapAvailable === false
                ? "Need another user? Sign in, open Admin, and use Invite users."
              : "Use your email and password, or your saved passkey if one is available."}
          </p>
        </form>
      </main>
    );
  }

  return (
    <div className="app-shell">
      <aside className="sidebar">
        <div className="mb-8">
          <img className="sidebar-logo" src="/brand/solar-slate-logo-wide.svg" alt="Solar Slate Solutions" />
          <div>
            <div className="font-black text-xl leading-none">SSS Inventory</div>
            <div className="text-xs text-slate-500 mt-1">{user.email}</div>
          </div>
        </div>
        <nav className="space-y-2">
          {tabs.map((item) => (
            <NavButton key={item.id} active={tab === item.id} icon={item.icon} label={item.label} onClick={() => setTab(item.id)} />
          ))}
        </nav>
        <div className="mt-8 space-y-3 text-sm">
          <StatusPill online={online} queueCount={queue.length} />
        </div>
      </aside>

      <main className="main">
        <header className="flex items-start justify-between gap-4 mb-6">
          <div className="min-w-0">
            <h1 className="text-3xl md:text-4xl font-black tracking-tight">{tabs.find((item) => item.id === tab)?.label}</h1>
            <p className="text-slate-600 mt-1">Offline-capable, no-negative stock, multi-location assembly.</p>
          </div>
          <div className="flex items-center gap-3">
            <LiveSyncStatus connected={liveConnected} lastAutoRefreshAt={lastAutoRefreshAt} />
            <ActionMenu refreshAll={refreshAllFromMenu} syncQueue={syncQueue} restartApp={restartApp} logout={logout} />
          </div>
        </header>

        {message ? <FloatingNotice message={message} /> : null}

        {tab === "inventory" ? (
          <InventoryPanel
            inventory={inventory}
            parts={parts}
            locations={locations}
            bomComponentIds={bomComponentIds}
            selectedPartId={selectedPartId}
            setSelectedPartId={setSelectedPartId}
            qrDataUrl={qrDataUrl}
          />
        ) : null}
        {tab === "stock" ? <StockPanel parts={parts} locations={locations} onDone={refreshAll} setMessage={setMessage} /> : null}
        {tab === "move" ? <MovePanel parts={parts} locations={locations} onDone={refreshAll} setMessage={setMessage} /> : null}
        {tab === "build" ? <BuildPanel parts={parts} locations={locations} onDone={refreshAll} setMessage={setMessage} /> : null}
        {tab === "admin" ? <AdminPanel currentUser={user} transactions={transactions} inventory={inventory} usersRefreshSignal={usersRefreshSignal} /> : null}
      </main>

      <nav className="bottom-nav">
        {tabs.map((item) => {
          const Icon = item.icon;
          return (
            <button key={item.id} onClick={() => setTab(item.id)} className={`py-3 text-xs font-bold grid place-items-center gap-1 ${tab === item.id ? "brand-text" : "text-slate-500"}`}>
              <Icon size={22} />
              {item.label}
            </button>
          );
        })}
      </nav>
    </div>
  );
}

function NavButton({ active, icon: Icon, label, onClick }: { active: boolean; icon: React.ComponentType<{ size?: number }>; label: string; onClick: () => void }) {
  return (
    <button onClick={onClick} className={`w-full flex items-center gap-3 rounded-lg px-3 py-3 text-sm font-bold ${active ? "nav-active" : "text-slate-700 hover:bg-white"}`}>
      <Icon size={18} />
      {label}
    </button>
  );
}

function StatusPill({ online, queueCount }: { online: boolean; queueCount: number }) {
  return (
    <div className="panel p-3 flex items-center gap-2">
      {online ? <Wifi size={17} className="success-text" /> : <WifiOff size={17} className="danger-text" />}
      <div>
        <div className="font-bold">{online ? "Online" : "Offline"}</div>
        <div className="text-xs text-slate-500">{formatQuantity(queueCount)} pending operation{queueCount === 1 ? "" : "s"}</div>
      </div>
    </div>
  );
}

function LiveSyncStatus({ connected, lastAutoRefreshAt }: { connected: boolean; lastAutoRefreshAt: Date | null }) {
  return (
    <div className="hidden sm:flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-bold text-slate-600">
      {connected ? <Wifi size={15} className="success-text" /> : <WifiOff size={15} className="warning-text" />}
      <span>{connected ? "Live sync" : "Reconnecting"}</span>
      {lastAutoRefreshAt ? <span className="text-slate-400">Updated {lastAutoRefreshAt.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })}</span> : null}
    </div>
  );
}

function FloatingNotice({ message, tone = "info" }: { message: string; tone?: "info" | "error" }) {
  return (
    <div className="fixed left-4 right-4 top-4 z-50 mx-auto max-w-2xl">
      <div className={`panel border-2 p-3 text-sm font-bold shadow-2xl ${tone === "error" ? "notice-error" : "notice-success"}`}>
        {message}
      </div>
    </div>
  );
}

function ActionMenu({
  refreshAll,
  syncQueue,
  restartApp,
  logout
}: {
  refreshAll: () => Promise<void>;
  syncQueue: () => Promise<void>;
  restartApp: () => void;
  logout: () => Promise<void>;
}) {
  const [open, setOpen] = useState(false);
  const menuRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!open) return;
    function closeOnOutsideClick(event: MouseEvent) {
      if (!menuRef.current?.contains(event.target as Node)) setOpen(false);
    }
    document.addEventListener("mousedown", closeOnOutsideClick);
    return () => document.removeEventListener("mousedown", closeOnOutsideClick);
  }, [open]);

  async function runAction(action: () => void | Promise<void>) {
    await action();
    setOpen(false);
  }

  return (
    <div className="action-menu" ref={menuRef}>
      <button className="btn btn-secondary action-menu-trigger" type="button" aria-label="Open app menu" aria-expanded={open} aria-haspopup="menu" title="Open app menu" onClick={() => setOpen((value) => !value)}>
        <Menu size={22} />
      </button>
      {open ? <div className="action-menu-panel" role="menu">
        <MenuButton icon={RefreshCw} label="Refresh" onClick={() => runAction(refreshAll)} />
        <MenuButton icon={Wifi} label="Sync queue" onClick={() => runAction(syncQueue)} />
        <MenuButton icon={RotateCcw} label="Restart" onClick={() => runAction(restartApp)} />
        <MenuButton icon={LogOut} label="Logout" onClick={() => runAction(logout)} />
      </div> : null}
    </div>
  );
}

function MenuButton({ icon: Icon, label, onClick }: { icon: React.ComponentType<{ size?: number }>; label: string; onClick: () => void | Promise<void> }) {
  return (
    <button className="w-full rounded-md px-3 py-2 text-left text-sm font-bold text-slate-700 hover:bg-slate-50 flex items-center gap-2" type="button" onClick={onClick}>
      <Icon size={16} />
      {label}
    </button>
  );
}

function emptyTypeSummary() {
  return {
    full: { qty: 0, value: 0 },
    partial: { qty: 0, value: 0 },
    base: { qty: 0, value: 0 }
  };
}

function summarizeByType(rows: InventoryRow[]) {
  const totals = emptyTypeSummary();
  for (const row of rows) {
    const quantity = Number(row.quantity);
    const value = quantity * Number(row.averageCost);
    totals[row.partType].qty += quantity;
    totals[row.partType].value += value;
  }
  return totals;
}

function summarizeByLocationAndType(rows: InventoryRow[]) {
  const map = new Map<string, { locationId: string; locationName: string; full: { qty: number; value: number }; partial: { qty: number; value: number }; base: { qty: number; value: number }; value: number }>();
  for (const row of rows) {
    const current = map.get(row.locationId) ?? { locationId: row.locationId, locationName: row.locationName, ...emptyTypeSummary(), value: 0 };
    const quantity = Number(row.quantity);
    const value = quantity * Number(row.averageCost);
    current[row.partType].qty += quantity;
    current[row.partType].value += value;
    current.value += value;
    map.set(row.locationId, current);
  }
  return Array.from(map.values()).sort((a, b) => a.locationName.localeCompare(b.locationName));
}

function partTypeLabel(type: "base" | "partial" | "full") {
  if (type === "full") return "Fully assembled";
  if (type === "partial") return "Partially assembled";
  return "Base part";
}

function inventorySortValue(row: InventoryRow, key: InventorySortKey) {
  if (key === "part") return row.partName.toLowerCase();
  if (key === "location") return row.locationName.toLowerCase();
  if (key === "quantity") return Number(row.quantity);
  if (key === "cost") return Number(row.averageCost);
  return Number(row.quantity) * Number(row.averageCost);
}

function SortableHeader({
  label,
  column,
  sortKey,
  direction,
  onSort,
  align = "left"
}: {
  label: string;
  column: InventorySortKey;
  sortKey: InventorySortKey;
  direction: SortDirection;
  onSort: (column: InventorySortKey) => void;
  align?: "left" | "right";
}) {
  const active = sortKey === column;
  return (
    <th className={`p-0 ${align === "right" ? "text-right" : "text-left"}`}>
      <button className={`w-full p-3 text-xs uppercase font-black text-slate-500 flex items-center gap-1 ${align === "right" ? "justify-end" : "justify-start"}`} type="button" onClick={() => onSort(column)}>
        {label}
        <span className={`${active ? "brand-text" : "text-slate-300"}`}>{active ? (direction === "asc" ? "▲" : "▼") : "↕"}</span>
      </button>
    </th>
  );
}

function InventoryPanel(props: {
  inventory: InventoryRow[];
  parts: Part[];
  locations: Location[];
  bomComponentIds: string[];
  selectedPartId: string;
  setSelectedPartId: (value: string) => void;
  qrDataUrl: string;
}) {
  const [locationFilter, setLocationFilter] = useState("all");
  const [typeFilter, setTypeFilter] = useState<PartTypeFilter>("all");
  const [partFilter, setPartFilter] = useState("all");
  const [baseUsageFilter, setBaseUsageFilter] = useState<BaseUsageFilter>("all");
  const [sortKey, setSortKey] = useState<InventorySortKey>("part");
  const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
  const usedComponentIds = useMemo(() => new Set(props.bomComponentIds), [props.bomComponentIds]);
  const selectableParts = useMemo(() => props.parts.filter((part) => {
    if (typeFilter !== "all" && part.type !== typeFilter) return false;
    if (part.type === "base" && baseUsageFilter === "used" && !usedComponentIds.has(part.id)) return false;
    if (part.type === "base" && baseUsageFilter === "unused" && usedComponentIds.has(part.id)) return false;
    return true;
  }), [props.parts, typeFilter, baseUsageFilter, usedComponentIds]);
  const filteredInventory = useMemo(() => props.inventory.filter((row) => {
    if (locationFilter !== "all" && row.locationId !== locationFilter) return false;
    if (typeFilter !== "all" && row.partType !== typeFilter) return false;
    if (partFilter !== "all" && row.partId !== partFilter) return false;
    if (row.partType === "base" && baseUsageFilter === "used" && !usedComponentIds.has(row.partId)) return false;
    if (row.partType === "base" && baseUsageFilter === "unused" && usedComponentIds.has(row.partId)) return false;
    return true;
  }), [props.inventory, locationFilter, typeFilter, partFilter, baseUsageFilter, usedComponentIds]);
  const sortedInventory = useMemo(() => {
    const multiplier = sortDirection === "asc" ? 1 : -1;
    return [...filteredInventory].sort((a, b) => {
      const aValue = inventorySortValue(a, sortKey);
      const bValue = inventorySortValue(b, sortKey);
      const comparison = typeof aValue === "number" && typeof bValue === "number"
        ? aValue - bValue
        : String(aValue).localeCompare(String(bValue));
      return comparison * multiplier;
    });
  }, [filteredInventory, sortKey, sortDirection]);
  const typeTotals = summarizeByType(filteredInventory);
  const locationTotals = summarizeByLocationAndType(filteredInventory);
  const totalValue = filteredInventory.reduce((sum, row) => sum + Number(row.quantity) * Number(row.averageCost), 0);
  const totalQty = filteredInventory.reduce((sum, row) => sum + Number(row.quantity), 0);
  const selectedPartHasRows = partFilter === "all" || filteredInventory.some((row) => row.partId === partFilter);

  useEffect(() => {
    if (partFilter !== "all" && !selectableParts.some((part) => part.id === partFilter)) setPartFilter("all");
  }, [partFilter, selectableParts]);

  function changeSort(nextKey: InventorySortKey) {
    if (sortKey === nextKey) {
      setSortDirection((direction) => (direction === "asc" ? "desc" : "asc"));
      return;
    }
    setSortKey(nextKey);
    setSortDirection("asc");
  }

  return (
    <section className="grid xl:grid-cols-[minmax(0,1fr)_320px] gap-5">
      <div className="space-y-5">
        <div className="panel p-4">
          <div className="grid sm:grid-cols-2 2xl:grid-cols-4 gap-3">
            <Select label="Location" value={locationFilter} onChange={setLocationFilter} options={[["all", "All locations"], ...props.locations.map((location) => [location.id, location.name] as [string, string])]} />
            <Select label="Part type" value={typeFilter} onChange={(value) => setTypeFilter(value as PartTypeFilter)} options={[["all", "All types"], ["full", "Fully assembled"], ["partial", "Partially assembled"], ["base", "Base parts"]]} />
            <Select label="Base usage" value={baseUsageFilter} onChange={(value) => setBaseUsageFilter(value as BaseUsageFilter)} options={[["all", "All base parts"], ["used", "Used in assemblies"], ["unused", "Not used in assemblies"]]} />
            <Select label="Part" value={partFilter} onChange={setPartFilter} options={[["all", "All parts"], ...selectableParts.map((part) => [part.id, `${part.name} · ${part.sku ?? "no sku"}`] as [string, string])]} />
          </div>
        </div>

        <div className="grid sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-5 gap-3">
          <ReportCard label="All matching qty" value={formatQuantity(totalQty)} />
          <ReportCard label="All matching value" value={<MoneyValue value={totalValue} />} />
          <ReportCard label="Fully assembled" value={<>{formatQuantity(typeTotals.full.qty)} · <MoneyValue value={typeTotals.full.value} /></>} />
          <ReportCard label="Partially assembled" value={<>{formatQuantity(typeTotals.partial.qty)} · <MoneyValue value={typeTotals.partial.value} /></>} />
          <ReportCard label="Base parts" value={<>{formatQuantity(typeTotals.base.qty)} · <MoneyValue value={typeTotals.base.value} /></>} />
        </div>

        <div className="panel overflow-hidden">
          <div className="p-4 border-b border-slate-200">
            <h2 className="font-black text-xl">Stock by Location</h2>
            <p className="text-sm text-slate-600 mt-1">Subtotals are grouped by fully assembled, partially assembled, and base parts.</p>
          </div>
          <div className="overflow-x-auto">
            <table className="w-full text-sm">
              <thead className="bg-slate-50 text-left text-xs uppercase text-slate-500">
                <tr>
                  <th className="p-3">Location</th>
                  <th className="p-3 text-right">Full qty</th>
                  <th className="p-3 text-right">Partial qty</th>
                  <th className="p-3 text-right">Base qty</th>
                  <th className="p-3 text-right">Value</th>
                </tr>
              </thead>
              <tbody>
                {locationTotals.map((row) => (
                  <tr key={row.locationId} className="border-t border-slate-100">
                    <td className="p-3 font-bold">{row.locationName}</td>
                    <td className="p-3 text-right">{formatQuantity(row.full.qty)}</td>
                    <td className="p-3 text-right">{formatQuantity(row.partial.qty)}</td>
                    <td className="p-3 text-right">{formatQuantity(row.base.qty)}</td>
                    <td className="p-3 text-right font-black"><MoneyValue value={row.value} /></td>
                  </tr>
                ))}
                {!locationTotals.length ? <tr><td className="p-4 text-sm text-slate-500" colSpan={5}>No balances match these filters.</td></tr> : null}
              </tbody>
            </table>
          </div>
        </div>

        <div className="panel overflow-hidden">
          <div className="p-4 border-b border-slate-200">
            <h2 className="font-black text-xl">Inventory Detail</h2>
            {!selectedPartHasRows ? <p className="text-sm text-slate-600 mt-1">That part is selectable but does not have stock at the selected location yet.</p> : null}
          </div>
          <div className="overflow-x-auto">
            <table className="w-full text-sm">
              <thead className="bg-slate-50 text-left text-xs uppercase text-slate-500">
                <tr>
                  <SortableHeader label="Part" column="part" sortKey={sortKey} direction={sortDirection} onSort={changeSort} />
                  <SortableHeader label="Location" column="location" sortKey={sortKey} direction={sortDirection} onSort={changeSort} />
                  <SortableHeader label="Qty" column="quantity" sortKey={sortKey} direction={sortDirection} onSort={changeSort} align="right" />
                  <SortableHeader label="Cost" column="cost" sortKey={sortKey} direction={sortDirection} onSort={changeSort} align="right" />
                  <SortableHeader label="Value" column="value" sortKey={sortKey} direction={sortDirection} onSort={changeSort} align="right" />
                </tr>
              </thead>
              <tbody>
                {sortedInventory.map((row) => {
                  const low = row.minQuantity && Number(row.quantity) < Number(row.minQuantity);
                  return (
                    <tr key={`${row.partId}-${row.locationId}`} className="border-t border-slate-100">
                      <td className="p-3">
                        <div className="font-bold">{row.partName}</div>
                        <div className="text-xs text-slate-500">{row.sku} · {partTypeLabel(row.partType)}</div>
                      </td>
                      <td className="p-3">{row.locationName}</td>
                      <td className={`p-3 text-right font-black ${low ? "danger-text" : "text-slate-900"}`}>{formatQuantity(Number(row.quantity))}</td>
                      <td className="p-3 text-right"><MoneyValue value={Number(row.averageCost)} neutral /></td>
                      <td className="p-3 text-right"><MoneyValue value={Number(row.quantity) * Number(row.averageCost)} /></td>
                    </tr>
                  );
                })}
                {!filteredInventory.length ? <tr><td className="p-4 text-sm text-slate-500" colSpan={5}>No inventory rows match these filters.</td></tr> : null}
              </tbody>
            </table>
          </div>
        </div>
      </div>
      <div className="panel p-4">
        <h2 className="font-black text-lg flex items-center gap-2"><QrCode size={19} /> Label</h2>
        <select className="field mt-3" value={props.selectedPartId} onChange={(event) => props.setSelectedPartId(event.target.value)}>
          {props.parts.map((part) => <option key={part.id} value={part.id}>{part.name}</option>)}
        </select>
        {props.qrDataUrl ? <img src={props.qrDataUrl} alt="Generated barcode" className="mx-auto my-5 h-48 w-48" /> : null}
        <button className="btn btn-secondary w-full" onClick={() => window.print()}>Print label sheet</button>
      </div>
    </section>
  );
}

function StockPanel({ parts, locations, onDone, setMessage }: { parts: Part[]; locations: Location[]; onDone: () => Promise<void>; setMessage: (value: string) => void }) {
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const [stockTab, setStockTab] = useState<StockTab>("plus");
  const [partId, setPartId] = useState("");
  const [locationId, setLocationId] = useState("");
  const [quantity, setQuantity] = useState("1");
  const [unitCost, setUnitCost] = useState("0");
  const [reason, setReason] = useState("Sold");
  const [manualCode, setManualCode] = useState("");
  const [scanning, setScanning] = useState(false);

  useEffect(() => {
    if (!partId && parts[0]) setPartId(parts[0].id);
    if (!locationId && locations[0]) setLocationId(locations[0].id);
  }, [parts, locations, partId, locationId]);

  function applyCode(code: string) {
    const match = parts.find((part) => part.sku?.toLowerCase() === code.trim().toLowerCase());
    if (match) {
      setPartId(match.id);
      setManualCode(code);
      setMessage(`Matched ${match.name}.`);
    } else {
      setMessage(`No part matched ${code}.`);
    }
  }

  async function startScanner() {
    if (!("BarcodeDetector" in window)) {
      setMessage("This browser does not expose BarcodeDetector. Use manual code entry for now.");
      return;
    }
    const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
    if (videoRef.current) {
      videoRef.current.srcObject = stream;
      await videoRef.current.play();
    }
    setScanning(true);
    const Detector = (window as unknown as { BarcodeDetector: new (options: { formats: string[] }) => { detect: (source: HTMLVideoElement) => Promise<{ rawValue: string }[]> } }).BarcodeDetector;
    const detector = new Detector({ formats: ["qr_code", "code_128", "ean_13", "upc_a"] });
    const timer = window.setInterval(async () => {
      if (!videoRef.current) return;
      const codes = await detector.detect(videoRef.current).catch(() => []);
      const first = codes[0]?.rawValue;
      if (first) {
        applyCode(first);
        window.clearInterval(timer);
        stream.getTracks().forEach((track) => track.stop());
        setScanning(false);
      }
    }, 650);
  }

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    const quantityValue = Number(quantity);
    const payload = stockTab === "plus"
      ? { idempotencyKey: idempotencyKey("receive"), partId, locationId, quantity: quantityValue, unitCost: Number(unitCost) }
      : { idempotencyKey: idempotencyKey("adjust"), partId, locationId, quantityDelta: -quantityValue, reason: reason.trim() || "Sold" };
    try {
      await api(stockTab === "plus" ? "/inventory/receive" : "/inventory/adjust", { method: "POST", body: JSON.stringify(payload) });
      setMessage(stockTab === "plus" ? "Stock added." : "Stock reduced.");
      await onDone();
    } catch (error) {
      if (!navigator.onLine) {
        queueOperation(stockTab === "plus" ? "receive" : "adjust", payload);
        setMessage(`Offline. Stock ${stockTab === "plus" ? "add" : "reduction"} operation queued.`);
      } else {
        setMessage(error instanceof Error ? error.message : "Stock update failed.");
      }
    }
  }

  return (
    <section className="panel p-5 max-w-2xl">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <h2 className="font-black text-xl flex items-center gap-2"><ArrowDownUp /> Stock</h2>
          <p className="text-sm text-slate-600 mt-1">Scan generated QR/Code 128 labels where supported, or type the code when a browser blocks camera scanning.</p>
        </div>
        <div className="inline-grid grid-cols-2 rounded-lg border border-slate-200 bg-white p-1">
          <button className={`px-4 py-2 text-sm font-black rounded-md flex items-center gap-2 ${stockTab === "plus" ? "nav-active" : "text-slate-600"}`} type="button" onClick={() => setStockTab("plus")}>
            <PackagePlus size={17} /> Stock +
          </button>
          <button className={`px-4 py-2 text-sm font-black rounded-md flex items-center gap-2 ${stockTab === "minus" ? "nav-active" : "text-slate-600"}`} type="button" onClick={() => setStockTab("minus")}>
            <PackageMinus size={17} /> Stock -
          </button>
        </div>
      </div>
      <form className="grid gap-4 mt-5" onSubmit={submit}>
        <div className="rounded-lg border border-slate-200 bg-slate-50 p-3">
          <video ref={videoRef} className={`w-full rounded-lg bg-black ${scanning ? "block" : "hidden"}`} muted playsInline />
          <div className="grid md:grid-cols-[1fr_auto] gap-2">
            <input className="field" placeholder="Scan or type generated code" value={manualCode} onChange={(event) => setManualCode(event.target.value)} />
            <div className="flex gap-2">
              <button className="btn btn-secondary" type="button" onClick={() => applyCode(manualCode)}><QrCode size={18} /> Match</button>
              <button className="btn btn-secondary" type="button" onClick={startScanner}><ScanLine size={18} /> Camera</button>
            </div>
          </div>
        </div>
        <Select label="Part" value={partId} onChange={setPartId} options={parts.map((part) => [part.id, `${part.name} · ${part.sku}`])} />
        <Select label="Location" value={locationId} onChange={setLocationId} options={locations.map((location) => [location.id, location.name])} />
        <div className="grid grid-cols-2 gap-3">
          <TextField label="Quantity" value={quantity} onChange={setQuantity} type="number" />
          {stockTab === "plus" ? (
            <TextField label="Unit cost" value={unitCost} onChange={setUnitCost} type="number" />
          ) : (
            <TextField label="Reason" value={reason} onChange={setReason} />
          )}
        </div>
        <button className="btn btn-primary" type="submit">
          {stockTab === "plus" ? <PackagePlus size={18} /> : <PackageMinus size={18} />}
          {stockTab === "plus" ? "Add stock" : "Reduce stock"}
        </button>
      </form>
    </section>
  );
}

function MovePanel({ parts, locations, onDone, setMessage }: { parts: Part[]; locations: Location[]; onDone: () => Promise<void>; setMessage: (value: string) => void }) {
  const [partId, setPartId] = useState("");
  const [fromLocationId, setFromLocationId] = useState("");
  const [toLocationId, setToLocationId] = useState("");
  const [quantity, setQuantity] = useState("1");

  useEffect(() => {
    if (!partId && parts[0]) setPartId(parts[0].id);
    if (!fromLocationId && locations[0]) setFromLocationId(locations[0].id);
    if (!toLocationId && locations[1]) setToLocationId(locations[1].id);
  }, [parts, locations, partId, fromLocationId, toLocationId]);

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    const payload = { idempotencyKey: idempotencyKey("transfer"), partId, fromLocationId, toLocationId, quantity: Number(quantity) };
    try {
      await api("/inventory/transfer", { method: "POST", body: JSON.stringify(payload) });
      setMessage("Transfer committed.");
      await onDone();
    } catch (error) {
      if (!navigator.onLine) {
        queueOperation("transfer", payload);
        setMessage("Offline. Transfer queued.");
      } else {
        setMessage(error instanceof Error ? error.message : "Transfer failed.");
      }
    }
  }

  return (
    <section className="panel p-5 max-w-2xl">
      <h2 className="font-black text-xl flex items-center gap-2"><Truck /> Transfer stock</h2>
      <form className="grid gap-4 mt-5" onSubmit={submit}>
        <Select label="Part" value={partId} onChange={setPartId} options={parts.map((part) => [part.id, `${part.name} · ${part.sku}`])} />
        <div className="grid md:grid-cols-2 gap-3">
          <Select label="From" value={fromLocationId} onChange={setFromLocationId} options={locations.map((location) => [location.id, location.name])} />
          <Select label="To" value={toLocationId} onChange={setToLocationId} options={locations.map((location) => [location.id, location.name])} />
        </div>
        <TextField label="Quantity" value={quantity} onChange={setQuantity} type="number" />
        <button className="btn btn-primary" type="submit"><MoveRight size={18} /> Transfer</button>
      </form>
    </section>
  );
}

function BuildPanel({ parts, locations, onDone, setMessage }: { parts: Part[]; locations: Location[]; onDone: () => Promise<void>; setMessage: (value: string) => void }) {
  const assemblies = parts.filter((part) => part.type !== "base");
  const componentOptions = parts.filter((part) => part.type !== "full");
  const [assemblyPartId, setAssemblyPartId] = useState("");
  const [outputLocationId, setOutputLocationId] = useState("");
  const [sourceLocationId, setSourceLocationId] = useState("");
  const [quantity, setQuantity] = useState("1");
  const [mode, setMode] = useState<"single_location" | "auto_pick">("auto_pick");
  const [newPartName, setNewPartName] = useState("");
  const [newPartSku, setNewPartSku] = useState("");
  const [newPartType, setNewPartType] = useState<"partial" | "full">("partial");
  const [basePartName, setBasePartName] = useState("");
  const [basePartSku, setBasePartSku] = useState("");
  const [basePartCost, setBasePartCost] = useState("0");
  const [bomLines, setBomLines] = useState<BomDraftLine[]>([]);
  const [buildTab, setBuildTab] = useState<BuildTab>("define");

  useEffect(() => {
    if (!assemblyPartId && assemblies[0]) setAssemblyPartId(assemblies[0].id);
    if (!outputLocationId && locations[0]) setOutputLocationId(locations[0].id);
    if (!sourceLocationId && locations[0]) setSourceLocationId(locations[0].id);
    if (!bomLines.length && componentOptions[0]) {
      setBomLines([{ id: crypto.randomUUID(), componentPartId: componentOptions[0].id, quantityRequired: "1" }]);
    }
  }, [assemblies, locations, componentOptions, assemblyPartId, outputLocationId, sourceLocationId, bomLines.length]);

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    const payload = {
      idempotencyKey: idempotencyKey("assemble"),
      assemblyPartId,
      outputLocationId,
      quantity: Number(quantity),
      sourcingMode: mode,
      sourceLocationId: mode === "single_location" ? sourceLocationId : undefined
    };
    try {
      await api("/assemble", { method: "POST", body: JSON.stringify(payload) });
      setMessage("Assembly committed.");
      await onDone();
    } catch (error) {
      if (!navigator.onLine) {
        queueOperation("assemble", payload);
        setMessage("Offline. Assembly queued for validation.");
      } else {
        setMessage(error instanceof Error ? error.message : "Assembly failed.");
      }
    }
  }

  function updateBomLine(id: string, patch: Partial<BomDraftLine>) {
    setBomLines((lines) => lines.map((line) => (line.id === id ? { ...line, ...patch } : line)));
  }

  function addBomLine() {
    const firstComponent = componentOptions[0];
    if (!firstComponent) {
      setMessage("Create at least one base or partial part before defining a BOM.");
      return;
    }
    setBomLines((lines) => [...lines, { id: crypto.randomUUID(), componentPartId: firstComponent.id, quantityRequired: "1" }]);
  }

  function removeBomLine(id: string) {
    setBomLines((lines) => lines.filter((line) => line.id !== id));
  }

  async function createAssemblyDefinition(event: React.FormEvent) {
    event.preventDefault();
    const lineTotals = new Map<string, number>();
    for (const line of bomLines) {
      const quantityRequired = Number(line.quantityRequired);
      if (line.componentPartId && quantityRequired > 0) {
        lineTotals.set(line.componentPartId, (lineTotals.get(line.componentPartId) ?? 0) + quantityRequired);
      }
    }
    const cleanLines = Array.from(lineTotals.entries()).map(([componentPartId, quantityRequired]) => ({ componentPartId, quantityRequired }));

    if (!newPartName.trim()) {
      setMessage("Assembly name is required.");
      return;
    }
    if (!cleanLines.length) {
      setMessage("Add at least one component with a quantity greater than zero.");
      return;
    }

    try {
      const partData = await api<{ part: Part }>("/parts", {
        method: "POST",
        body: JSON.stringify({
          name: newPartName,
          sku: newPartSku || null,
          type: newPartType,
          defaultUnitCost: 0
        })
      });

      await api("/bom", {
        method: "POST",
        body: JSON.stringify({
          assemblyPartId: partData.part.id,
          name: "Default",
          lines: cleanLines
        })
      });

      setMessage(`${partData.part.name} created with ${cleanLines.length} component${cleanLines.length === 1 ? "" : "s"}.`);
      setNewPartName("");
      setNewPartSku("");
      setNewPartType("partial");
      setBomLines(componentOptions[0] ? [{ id: crypto.randomUUID(), componentPartId: componentOptions[0].id, quantityRequired: "1" }] : []);
      setAssemblyPartId(partData.part.id);
      await onDone();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Unable to create assembly.");
    }
  }

  async function createBasePart(event: React.FormEvent) {
    event.preventDefault();
    if (!basePartName.trim()) {
      setMessage("Base part name is required.");
      return;
    }
    try {
      const partData = await api<{ part: Part }>("/parts", {
        method: "POST",
        body: JSON.stringify({
          name: basePartName,
          sku: basePartSku || null,
          type: "base",
          defaultUnitCost: Number(basePartCost || 0)
        })
      });
      setMessage(`${partData.part.name} was created as a base part.`);
      setBasePartName("");
      setBasePartSku("");
      setBasePartCost("0");
      setBomLines((lines) => lines.length ? lines : [{ id: crypto.randomUUID(), componentPartId: partData.part.id, quantityRequired: "1" }]);
      await onDone();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Unable to create base part.");
    }
  }

  return (
    <section className="space-y-5">
      <div className="panel p-2 flex gap-2">
        <button className={`btn flex-1 ${buildTab === "define" ? "btn-primary" : "btn-secondary"}`} type="button" onClick={() => setBuildTab("define")}><Save size={18} /> Define Parts</button>
        <button className={`btn flex-1 ${buildTab === "assemble" ? "btn-primary" : "btn-secondary"}`} type="button" onClick={() => setBuildTab("assemble")}><Factory size={18} /> Assemble Products</button>
      </div>

      {buildTab === "assemble" ? (
      <div className="panel p-5 max-w-3xl">
        <h2 className="font-black text-xl flex items-center gap-2"><Factory /> Assemble product</h2>
        <form className="grid gap-4 mt-5" onSubmit={submit}>
          <Select label="Assembly" value={assemblyPartId} onChange={setAssemblyPartId} options={assemblies.map((part) => [part.id, `${part.name} · ${part.type}`])} />
          <Select label="Output location" value={outputLocationId} onChange={setOutputLocationId} options={locations.map((location) => [location.id, location.name])} />
          <label className="text-sm font-bold">
            Source mode
            <select className="field mt-1" value={mode} onChange={(event) => setMode(event.target.value as "single_location" | "auto_pick")}>
              <option value="auto_pick">Auto-pick across locations</option>
              <option value="single_location">Single source location</option>
            </select>
          </label>
          {mode === "single_location" ? <Select label="Source location" value={sourceLocationId} onChange={setSourceLocationId} options={locations.map((location) => [location.id, location.name])} /> : null}
          <TextField label="Quantity to build" value={quantity} onChange={setQuantity} type="number" />
          <button className="btn btn-primary" type="submit"><Factory size={18} /> Assemble</button>
        </form>
      </div>
      ) : null}

      {buildTab === "define" ? (
      <div className="grid xl:grid-cols-[minmax(280px,380px)_1fr] gap-5">
      <div className="panel p-5">
        <h2 className="font-black text-xl flex items-center gap-2"><PackagePlus /> New Part Definition</h2>
        <p className="text-sm text-slate-600 mt-1">Create individual components before using them in partial or full assemblies.</p>
        <form className="grid gap-4 mt-5" onSubmit={createBasePart}>
          <TextField label="Part name" value={basePartName} onChange={setBasePartName} />
          <TextField label="SKU / barcode" value={basePartSku} onChange={setBasePartSku} />
          <TextField label="Default unit cost" value={basePartCost} onChange={setBasePartCost} type="number" />
          <button className="btn btn-primary" type="submit"><Plus size={18} /> Create base part</button>
        </form>
      </div>

      <div className="panel p-5">
        <h2 className="font-black text-xl flex items-center gap-2"><Save /> New Assembly Definition</h2>
        <p className="text-sm text-slate-600 mt-1">Create a partial or full assembly from base parts and partial assemblies.</p>
        <form className="grid gap-4 mt-5" onSubmit={createAssemblyDefinition}>
          <TextField label="Assembly name" value={newPartName} onChange={setNewPartName} />
          <div className="grid md:grid-cols-2 gap-3">
            <TextField label="SKU / barcode" value={newPartSku} onChange={setNewPartSku} />
            <label className="text-sm font-bold">
              Assembly type
              <select className="field mt-1" value={newPartType} onChange={(event) => setNewPartType(event.target.value as "partial" | "full")}>
                <option value="partial">Partial assembly</option>
                <option value="full">Full assembly</option>
              </select>
            </label>
          </div>

          <div className="rounded-lg border border-slate-200 bg-slate-50 p-3">
            <div className="flex items-center justify-between gap-3 mb-3">
              <div className="font-black">Components</div>
              <button className="btn btn-secondary" type="button" onClick={addBomLine}><Plus size={17} /> Add</button>
            </div>
            <div className="grid gap-3">
              {bomLines.map((line) => (
                <div key={line.id} className="grid md:grid-cols-[1fr_120px_auto] gap-2 items-end">
                  <Select
                    label="Part or partial"
                    value={line.componentPartId}
                    onChange={(value) => updateBomLine(line.id, { componentPartId: value })}
                    options={componentOptions.map((part) => [part.id, `${part.name} · ${part.sku ?? "no sku"} · ${part.type}`])}
                  />
                  <TextField label="Qty" value={line.quantityRequired} onChange={(value) => updateBomLine(line.id, { quantityRequired: value })} type="number" />
                  <button className="btn btn-secondary" type="button" onClick={() => removeBomLine(line.id)} aria-label="Remove component"><Trash2 size={17} /></button>
                </div>
              ))}
              {!bomLines.length ? <div className="text-sm text-slate-500">No components yet.</div> : null}
            </div>
          </div>

          <button className="btn btn-primary" type="submit"><Save size={18} /> Save assembly definition</button>
        </form>
      </div>
      </div>
      ) : null}
    </section>
  );
}

function AdminPanel({ currentUser, transactions, inventory, usersRefreshSignal }: { currentUser: User; transactions: TransactionRow[]; inventory: InventoryRow[]; usersRefreshSignal: number }) {
  const [adminTab, setAdminTab] = useState<AdminTab>("users");
  return (
    <section className="space-y-5">
      <div className="panel p-2 flex flex-wrap gap-2">
        <button className={`btn flex-1 ${adminTab === "users" ? "btn-primary" : "btn-secondary"}`} type="button" onClick={() => setAdminTab("users")}><Users size={18} /> Users</button>
        <button className={`btn flex-1 ${adminTab === "log" ? "btn-primary" : "btn-secondary"}`} type="button" onClick={() => setAdminTab("log")}><Activity size={18} /> Log</button>
        <button className={`btn flex-1 ${adminTab === "reports" ? "btn-primary" : "btn-secondary"}`} type="button" onClick={() => setAdminTab("reports")}><BarChart3 size={18} /> Reports</button>
      </div>
      {adminTab === "users" ? <UserManagementPanel currentUser={currentUser} usersRefreshSignal={usersRefreshSignal} /> : null}
      {adminTab === "log" ? <TransactionLogPanel transactions={transactions} /> : null}
      {adminTab === "reports" ? <ReportsPanel transactions={transactions} inventory={inventory} /> : null}
    </section>
  );
}

function ReportsPanel({ transactions, inventory }: { transactions: TransactionRow[]; inventory: InventoryRow[] }) {
  const assemblies = transactions.filter((row) => row.action === "assemble" && Number(row.quantityDelta) > 0).length;
  const componentUsage = transactions.filter((row) => row.action === "assemble" && Number(row.quantityDelta) < 0).reduce((sum, row) => sum + Math.abs(Number(row.quantityDelta)), 0);
  const valuation = inventory.reduce((sum, row) => sum + Number(row.quantity) * Number(row.averageCost), 0);
  const lowStock = inventory.filter((row) => row.minQuantity && Number(row.quantity) < Number(row.minQuantity)).length;
  const byType = summarizeByType(inventory);
  return (
    <div className="space-y-5">
      <div className="grid md:grid-cols-4 gap-3">
        <ReportCard label="Inventory value" value={<MoneyValue value={valuation} />} />
        <ReportCard label="Assemblies logged" value={formatQuantity(assemblies)} />
        <ReportCard label="Base usage qty" value={formatQuantity(componentUsage)} />
        <ReportCard label="Low-stock alerts" value={formatQuantity(lowStock)} />
      </div>
      <div className="grid md:grid-cols-3 gap-3">
        <ReportCard label="Full stock value" value={<MoneyValue value={byType.full.value} />} />
        <ReportCard label="Partial stock value" value={<MoneyValue value={byType.partial.value} />} />
        <ReportCard label="Base stock value" value={<MoneyValue value={byType.base.value} />} />
      </div>
    </div>
  );
}

function TransactionLogPanel({ transactions }: { transactions: TransactionRow[] }) {
  return (
    <div className="panel overflow-hidden">
      <div className="p-4 border-b border-slate-200 flex items-center justify-between">
        <h2 className="font-black text-xl flex items-center gap-2"><Activity /> Transaction log</h2>
        <a className="btn btn-secondary" href={`${API_URL}/transactions/export.csv`}>CSV</a>
      </div>
      <div className="divide-y divide-slate-100">
        {transactions.map((transaction) => (
          <div key={transaction.id} className="p-4 grid md:grid-cols-[170px_1fr_auto] gap-2 md:items-center">
            <div className="text-xs text-slate-500">{new Date(transaction.createdAt).toLocaleString()}</div>
            <div>
              <div className="font-bold">{transaction.action} · {transaction.partName}</div>
              <div className="text-sm text-slate-600">{transaction.locationName} · {transaction.userEmail ?? "Deleted user"}</div>
            </div>
            <div className={`font-black ${Number(transaction.quantityDelta) >= 0 ? "success-text" : "danger-text"}`}>
              {Number(transaction.quantityDelta) >= 0 ? "+" : ""}{formatQuantity(Number(transaction.quantityDelta))}
            </div>
          </div>
        ))}
        {!transactions.length ? <div className="p-4 text-sm text-slate-500">No transactions yet.</div> : null}
      </div>
    </div>
  );
}

function UserManagementPanel({ currentUser, usersRefreshSignal }: { currentUser: User; usersRefreshSignal: number }) {
  const [email, setEmail] = useState("");
  const [inviteEmail, setInviteEmail] = useState("");
  const [emailSent, setEmailSent] = useState(false);
  const [users, setUsers] = useState<ManagedUser[]>([]);
  const [invites, setInvites] = useState<ManagedInvite[]>([]);
  const [passkeys, setPasskeys] = useState<ManagedPasskey[]>([]);
  const [peopleLoading, setPeopleLoading] = useState(false);
  const [peopleNotice, setPeopleNotice] = useState("");
  const [peopleNoticeTone, setPeopleNoticeTone] = useState<"info" | "error">("info");
  const [deletingUserId, setDeletingUserId] = useState("");

  useEffect(() => {
    refreshPeople(false).catch(() => undefined);
  }, []);

  useEffect(() => {
    if (!usersRefreshSignal) return;
    refreshPeople(false).catch(() => undefined);
  }, [usersRefreshSignal]);

  useEffect(() => {
    if (!peopleNotice) return;
    const timer = window.setTimeout(() => setPeopleNotice(""), 3500);
    return () => window.clearTimeout(timer);
  }, [peopleNotice]);

  async function refreshPeople(announce = true) {
    setPeopleLoading(true);
    try {
      const [userData, inviteData, passkeyData] = await Promise.all([
        api<{ users: ManagedUser[] }>("/users"),
        api<{ invites: ManagedInvite[] }>("/invites"),
        api<{ passkeys: ManagedPasskey[] }>("/auth/passkeys")
      ]);
      setUsers(userData.users);
      setInvites(inviteData.invites);
      setPasskeys(passkeyData.passkeys);
      if (announce) {
        setPeopleNoticeTone("info");
        setPeopleNotice(`User list refreshed: ${userData.users.length} user${userData.users.length === 1 ? "" : "s"}.`);
      }
    } catch (error) {
      setPeopleNoticeTone("error");
      setPeopleNotice(error instanceof Error ? error.message : "Unable to refresh users.");
    } finally {
      setPeopleLoading(false);
    }
  }

  async function createInvite(event: React.FormEvent) {
    event.preventDefault();
    try {
      const data = await api<{ invite: { email: string }; emailSent: boolean }>("/invites", {
        method: "POST",
        body: JSON.stringify({ email })
      });
      setInviteEmail(data.invite.email);
      setEmailSent(data.emailSent);
      setEmail("");
      await refreshPeople();
      setPeopleNoticeTone(data.emailSent ? "info" : "error");
      setPeopleNotice(data.emailSent ? `Invite email sent to ${data.invite.email}.` : `Invite saved for ${data.invite.email}, but SMTP is not configured so no email was sent.`);
    } catch (error) {
      setPeopleNoticeTone("error");
      setPeopleNotice(error instanceof Error ? error.message : "Unable to create invite.");
    }
  }

  async function deleteUser(managedUser: ManagedUser) {
    if (managedUser.id === currentUser.id) {
      setPeopleNoticeTone("error");
      setPeopleNotice("You cannot delete your own account while signed in.");
      return;
    }
    const confirmed = window.confirm(`Delete ${managedUser.email}? Their transaction history will stay in the log.`);
    if (!confirmed) {
      return;
    }
    setDeletingUserId(managedUser.id);
    try {
      await api(`/users/${managedUser.id}`, { method: "DELETE", body: "{}" });
      await refreshPeople();
      setPeopleNoticeTone("info");
      setPeopleNotice(`${managedUser.email} was deleted. Transaction history was preserved.`);
    } catch (error) {
      setPeopleNoticeTone("error");
      setPeopleNotice(error instanceof Error ? error.message : "Unable to delete user.");
    } finally {
      setDeletingUserId("");
    }
  }

  async function registerPasskey() {
    if (!browserSupportsWebAuthn()) {
      setPeopleNoticeTone("error");
      setPeopleNotice("This browser does not support passkeys.");
      return;
    }
    try {
      const optionsJSON = await api<Parameters<typeof startRegistration>[0]["optionsJSON"]>("/auth/passkeys/registration-options", { method: "POST", body: "{}" });
      const attResp = await startRegistration({ optionsJSON, useAutoRegister: true });
      await api("/auth/passkeys/register", { method: "POST", body: JSON.stringify(attResp) });
      await refreshPeople();
      setPeopleNoticeTone("info");
      setPeopleNotice("Passkey registered. You can now sign in with Face ID, Touch ID, or your device passkey.");
    } catch (error) {
      setPeopleNoticeTone("error");
      setPeopleNotice(error instanceof Error ? error.message : "Passkey registration failed.");
    }
  }

  return (
    <section className="grid xl:grid-cols-[1fr_1fr] gap-5">
      {peopleNotice ? <FloatingNotice message={peopleNotice} tone={peopleNoticeTone} /> : null}
      <div className="space-y-5">
      <div className="panel p-4">
        <div className="flex items-center gap-2">
          <UserPlus size={20} />
          <h2 className="font-black text-xl">Invite users</h2>
        </div>
        <p className="mt-2 text-sm text-slate-600">Enter an email address. SSS Inventory sends a formatted invite email with a secure password setup link.</p>
        <form className="mt-4 grid md:grid-cols-[1fr_auto] gap-3" onSubmit={createInvite}>
          <input className="field" type="email" placeholder="teammate@example.com" value={email} onChange={(event) => setEmail(event.target.value)} />
          <button className="btn btn-primary" type="submit"><Mail size={18} /> Send invite</button>
        </form>
        {inviteEmail ? (
          <div className={`mt-4 rounded-lg border p-3 ${emailSent ? "notice-success" : "notice-warning"}`}>
            <div className="text-sm font-bold">{emailSent ? "Invite email sent" : "Invite email not sent"}</div>
            <div className="mt-1 text-sm">{emailSent ? `${inviteEmail} can use the link in their email to choose a password.` : "Configure SMTP before inviting users by email."}</div>
          </div>
        ) : null}
      </div>

      <div className="panel p-4">
        <div className="flex items-center gap-2">
          <UserPlus size={20} />
          <h2 className="font-black text-xl">Your passkeys</h2>
        </div>
        <p className="mt-2 text-sm text-slate-600">Register this device to sign in with Face ID, Touch ID, or a passkey.</p>
        <button className="btn btn-primary mt-4" type="button" onClick={registerPasskey}>Add passkey for this device</button>
        <div className="mt-4 space-y-2">
          {passkeys.map((passkey) => (
            <div key={passkey.id} className="rounded-lg border border-slate-200 bg-white p-3">
              <div className="font-bold">{passkey.deviceType === "multiDevice" ? "Synced passkey" : "Device passkey"}</div>
              <div className="text-xs text-slate-500">Created {new Date(passkey.createdAt).toLocaleDateString()}{passkey.lastUsedAt ? ` · Last used ${new Date(passkey.lastUsedAt).toLocaleDateString()}` : ""}</div>
            </div>
          ))}
          {!passkeys.length ? <div className="text-sm text-slate-500">No passkeys registered yet.</div> : null}
        </div>
      </div>
      </div>

      <div className="panel overflow-hidden">
        <div className="p-4 border-b border-slate-200 flex items-center justify-between">
          <h2 className="font-black text-xl">Users</h2>
          <button className="btn btn-secondary" type="button" disabled={peopleLoading} onClick={() => refreshPeople(true)}><RefreshCw size={17} /> {peopleLoading ? "Refreshing" : "Refresh"}</button>
        </div>
        <div className="divide-y divide-slate-100">
          {users.map((managedUser) => (
            <div key={managedUser.id} data-user-email={managedUser.email} className="p-4 flex items-center justify-between gap-3">
              <div>
                <div className="font-bold">{managedUser.email}</div>
                <div className="text-xs text-slate-500">Created {new Date(managedUser.createdAt).toLocaleDateString()}</div>
              </div>
              <button
                className="btn btn-secondary"
                type="button"
                disabled={managedUser.id === currentUser.id || deletingUserId === managedUser.id}
                onClick={() => deleteUser(managedUser)}
              >
                <Trash2 size={17} /> {deletingUserId === managedUser.id ? "Deleting" : "Delete"}
              </button>
            </div>
          ))}
          {!users.length ? <div className="p-4 text-sm text-slate-500">No users found.</div> : null}
        </div>
        <div className="border-t border-slate-200 p-4">
          <h3 className="font-black">Recent invites</h3>
          <div className="mt-3 space-y-2">
            {invites.map((invite) => {
              const expired = new Date(invite.expiresAt).getTime() < Date.now();
              const status = invite.acceptedAt ? "Accepted" : expired ? "Expired" : "Pending";
              return (
                <div key={invite.id} className="rounded-lg border border-slate-200 bg-white p-3">
                  <div className="flex items-center justify-between gap-3">
                    <div className="font-bold">{invite.email}</div>
                    <div className={`text-xs font-black ${status === "Pending" ? "warning-text" : status === "Accepted" ? "success-text" : "text-slate-500"}`}>{status}</div>
                  </div>
                  <div className="mt-1 text-xs text-slate-500">Expires {new Date(invite.expiresAt).toLocaleDateString()}</div>
                </div>
              );
            })}
            {!invites.length ? <div className="text-sm text-slate-500">No invites yet.</div> : null}
          </div>
        </div>
      </div>
    </section>
  );
}

function ReportCard({ label, value }: { label: string; value: React.ReactNode }) {
  return (
    <div className="panel report-card p-4 min-w-0">
      <div className="text-xs font-bold uppercase text-slate-500">{label}</div>
      <div className="report-card-value mt-1">{value}</div>
    </div>
  );
}

function Select({ label, value, onChange, options }: { label: string; value: string; onChange: (value: string) => void; options: [string, string][] }) {
  return (
    <label className="text-sm font-bold">
      {label}
      <select className="field mt-1" value={value} onChange={(event) => onChange(event.target.value)}>
        {options.map(([optionValue, optionLabel]) => <option key={optionValue} value={optionValue}>{optionLabel}</option>)}
      </select>
    </label>
  );
}

function TextField({ label, value, onChange, type = "text" }: { label: string; value: string; onChange: (value: string) => void; type?: string }) {
  return (
    <label className="text-sm font-bold">
      {label}
      <input className="field mt-1" type={type} min={type === "number" ? "0" : undefined} step={type === "number" ? "0.0001" : undefined} value={value} onChange={(event) => onChange(event.target.value)} />
    </label>
  );
}
