"use client";

/**
 * The web app shell (PRD 1.6), built from the design system's app shell pattern
 * (`/design-system/layout-patterns`, "App shell"): Medora sidebar (light or teal, collapsible to
 * icons), top bar with breadcrumb, search (Ctrl K), current branch, notifications and user menu.
 *
 * Navigation follows the PRD's 10-menu sitemap and lists only pages that exist today, each shown
 * when the user's role has "Show in menu" on it (PRD 1.1). A menu with nothing to show is hidden.
 * The Electron desktop app keeps its own top-bar chrome until the navigation decision (OQ-05).
 *
 * It also provides the Medora theme scope, tooltips and toasts once for every page inside it.
 */

import * as React from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { ArrowLeftRightIcon, BadgePercentIcon, BookOpenIcon, ShieldPlusIcon, ChevronsUpDownIcon, ClipboardListIcon, ClipboardPlusIcon, LayoutDashboardIcon, LogOutIcon, BarcodeIcon, PackageOpenIcon, Trash2Icon, PillIcon, ReceiptIcon, SearchIcon, SettingsIcon, ShoppingCartIcon, TrendingUpIcon, TruckIcon, UserCogIcon, UsersIcon, WalletIcon, KeyRoundIcon, CalendarClockIcon, ReceiptTextIcon, TagIcon, SmartphoneIcon, FileTextIcon, HandCoinsIcon, IdCardIcon, BanknoteIcon, type LucideIcon } from "lucide-react";

import { MedoraScope } from "@/components/design-system/medora-scope";
import { useSidebarTone } from "@/components/design-system/sidebar-tone";
import { NotificationsBell, useNotices, type Notice } from "@/components/medora/notifications";
import { SidebarLogo } from "@/components/medora/sidebar-logo";
import { UserMenu } from "@/components/medora/user-menu";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Kbd } from "@/components/ui/kbd";
import { Separator } from "@/components/ui/separator";
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarTrigger } from "@/components/ui/sidebar";
import { toast, Toaster } from "@/components/ui/toast";
import { TooltipProvider } from "@/components/ui/tooltip";
import { AlertsApi, API_REACHABLE_EVENT, BranchesApi, UsersApi } from "@/lib/api";
import { OfflineBanner } from "@/components/medora/errors";
import { useMenuPermissions } from "@/lib/permissions";
import { SidebarProfileSkeleton, SidebarSkeleton } from "@/components/medora/sidebar-skeleton";

type NavItem = { title: string; href: string; icon: LucideIcon; roles?: string[] };

/**
 * The PRD's 10 menus (section 1), with today's pages under them. `roles` is only the fallback used
 * until permissions load (the old sidebar's lists).
 */
const MENUS: { label: string; items: NavItem[] }[] = [
  { label: "Dashboard", items: [{ title: "Dashboard", href: "/dashboard", icon: LayoutDashboardIcon }] },
  {
    label: "POS",
    items: [
      { title: "POS sales", href: "/pos", icon: ShoppingCartIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Open and close shift", href: "/shift", icon: KeyRoundIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Cashier balances", href: "/shifts", icon: WalletIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Preorders", href: "/preorders", icon: CalendarClockIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Credit notes", href: "/credit-notes", icon: ReceiptTextIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
    ],
  },
  {
    label: "Inventory",
    items: [
      { title: "Products and stock", href: "/inventory", icon: PillIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Purchase orders", href: "/purchase-orders", icon: TruckIcon, roles: ["Admin", "Inventory Manager"] },
      { title: "Branch transfers", href: "/branch-transfers", icon: ArrowLeftRightIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Stocktakes", href: "/stocktakes", icon: ClipboardListIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Waste", href: "/waste", icon: Trash2Icon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Internal issues", href: "/issues", icon: PackageOpenIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Barcode labels", href: "/labels", icon: BarcodeIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
      { title: "Price groups", href: "/price-groups", icon: BadgePercentIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] },
    ],
  },
  {
    label: "Customers",
    items: [
      { title: "Customers", href: "/customers", icon: UsersIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Prescriptions", href: "/prescriptions", icon: ClipboardPlusIcon, roles: ["Admin", "Cashier", "Pharmacist"] },
      { title: "Groups and schemes", href: "/customer-groups", icon: ShieldPlusIcon, roles: ["Admin"] },
    ],
  },
  { label: "Suppliers", items: [{ title: "Suppliers", href: "/suppliers", icon: TruckIcon, roles: ["Admin", "Inventory Manager"] }] },
  {
    label: "Accounts",
    items: [
      { title: "General ledger", href: "/accounts", icon: BookOpenIcon, roles: ["Admin"] },
      { title: "Expenses", href: "/expenses", icon: ReceiptIcon, roles: ["Admin"] },
      { title: "Receivables", href: "/receivables", icon: FileTextIcon, roles: ["Admin"] },
      { title: "Payables", href: "/payables", icon: HandCoinsIcon, roles: ["Admin"] },
    ],
  },
  {
    label: "HRM",
    items: [
      { title: "Employees", href: "/employees", icon: IdCardIcon, roles: ["Admin"] },
      { title: "Payroll", href: "/payroll", icon: BanknoteIcon, roles: ["Admin"] },
    ],
  },
  { label: "Reports", items: [{ title: "Reports", href: "/reports", icon: TrendingUpIcon, roles: ["Admin", "Pharmacist", "Inventory Manager"] }] },
  {
    label: "Settings",
    items: [
      { title: "General settings", href: "/settings", icon: SettingsIcon },
      { title: "Promotions", href: "/promotions", icon: TagIcon, roles: ["Admin"] },
      { title: "Users and roles", href: "/users", icon: UserCogIcon, roles: ["Admin"] },
    ],
  },
  { label: "Integrations", items: [{ title: "M-Pesa", href: "/integrations/mpesa", icon: SmartphoneIcon, roles: ["Admin"] }] },
];

type StoredUser = { id?: number; name?: string; firstName?: string | null; lastName?: string | null; phone?: string | null; username?: string; email?: string; role?: string; branchId?: number | null };

const initials = (name: string) =>
  name
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 2)
    .map((w) => w[0]!.toUpperCase())
    .join("");

const TEXT_KEY = "medora-text-size";

/** "Large text" from the user menu: scales the whole app's rem-based sizes. Remembered per browser. */
function useTextSize() {
  const [size, setSize] = React.useState<"default" | "large">("default");
  React.useEffect(() => {
    try {
      if (localStorage.getItem(TEXT_KEY) === "large") setSize("large");
    } catch {
      /* storage blocked: default size */
    }
  }, []);
  React.useEffect(() => {
    document.documentElement.style.fontSize = size === "large" ? "112.5%" : "";
    try {
      localStorage.setItem(TEXT_KEY, size);
    } catch {
      /* storage blocked: applies for this visit */
    }
  }, [size]);
  return [size, setSize] as const;
}

/**
 * The bell's alerts (ST-14, plan C): what `GET /alerts` raises for this role from the rules on Settings → Alerts. The
 * API counts at most every five minutes, so asking every minute costs little; an alert stays read until its text changes.
 */
function useLiveNotices() {
  const notices = useNotices([]);
  const { setItems } = notices;

  React.useEffect(() => {
    let live = true;
    const load = async () => {
      try {
        const { at, alerts } = await AlertsApi.mine();
        // The bell compares with `now` as an ISO minute, as the design's `noticeTime` expects
        const stamp = at.slice(0, 16);
        const next: Notice[] = alerts.map((a) => ({ id: a.id, tone: a.tone, word: a.word, text: a.text, at: stamp, href: a.href }));
        if (live) setItems((prev) => next.map((n) => ({ ...n, read: prev.find((p) => p.id === n.id && p.text === n.text)?.read })));
      } catch {
        /* offline or signed out: keep what is shown */
      }
    };
    load();
    const timer = setInterval(load, 60_000);
    return () => {
      live = false;
      clearInterval(timer);
    };
  }, [setItems]);

  return notices;
}

/** The stored 2547… phone as people write it, 0712 345 678. */
const localPhone = (p?: string | null) => (p && /^254\d{9}$/.test(p) ? `0${p.slice(3, 6)} ${p.slice(6, 9)} ${p.slice(9)}` : (p ?? ""));

/** Your own account (ST-17): first and last name, phone and password (anyone may, PRD 0.5). Password rules are the pharmacy's, checked by the API. */
function AccountDialog({ user, open, onOpenChange, onSaved }: { user: StoredUser; open: boolean; onOpenChange: (o: boolean) => void; onSaved: (u: StoredUser) => void }) {
  const split = (u: StoredUser) => {
    if (u.firstName) return { first: u.firstName, last: u.lastName ?? "" };
    const [first, ...rest] = (u.name ?? "").trim().split(/\s+/);
    return { first: first ?? "", last: rest.join(" ") };
  };
  const [first, setFirst] = React.useState(split(user).first);
  const [last, setLast] = React.useState(split(user).last);
  const [phone, setPhone] = React.useState(localPhone(user.phone));
  const [password, setPassword] = React.useState("");
  const [confirm, setConfirm] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [tried, setTried] = React.useState(false);
  React.useEffect(() => {
    if (open) {
      setFirst(split(user).first);
      setLast(split(user).last);
      setPhone(localPhone(user.phone));
      setPassword("");
      setConfirm("");
      setTried(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, user.name, user.firstName, user.lastName, user.phone]);
  const problems = {
    first: !first.trim() ? "Enter your first name." : undefined,
    phone: phone.trim() && !/^(\+?254|0)[17]\d{8}$/.test(phone.replace(/\s/g, "")) ? "Use a Kenyan mobile number, e.g. 0712 345 678." : undefined,
    password: password && password.length < 8 ? "At least 8 characters." : undefined,
    confirm: password && confirm !== password ? "The two passwords don't match." : undefined,
  };
  const invalid = Object.values(problems).some(Boolean);

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Your account</DialogTitle>
          <DialogDescription>Change your name, phone or password. Your role and branch are set by an administrator.</DialogDescription>
        </DialogHeader>
        <div className="grid gap-4">
          <div className="grid gap-4 sm:grid-cols-2">
            <Field data-invalid={tried && !!problems.first}>
              <FieldLabel htmlFor="acc-first">First name</FieldLabel>
              <Input id="acc-first" value={first} onChange={(e) => setFirst(e.target.value)} aria-invalid={tried && !!problems.first} />
              {tried && problems.first ? <FieldError>{problems.first}</FieldError> : null}
            </Field>
            <Field>
              <FieldLabel htmlFor="acc-last">Last name</FieldLabel>
              <Input id="acc-last" value={last} onChange={(e) => setLast(e.target.value)} />
            </Field>
          </div>
          <Field data-invalid={tried && !!problems.phone} className="sm:col-span-2">
            <FieldLabel htmlFor="acc-phone">Mobile number</FieldLabel>
            <Input id="acc-phone" inputMode="tel" value={phone} onChange={(e) => setPhone(e.target.value)} aria-invalid={tried && !!problems.phone} />
            {tried && problems.phone ? <FieldError>{problems.phone}</FieldError> : null}
          </Field>
        </div>
        <div className="grid gap-4">
          <Field data-invalid={tried && !!problems.password}>
            <FieldLabel htmlFor="acc-pw">New password</FieldLabel>
            <Input id="acc-pw" type="password" autoComplete="new-password" value={password} onChange={(e) => setPassword(e.target.value)} aria-invalid={tried && !!problems.password} />
            {tried && problems.password ? <FieldError>{problems.password}</FieldError> : <FieldDescription>Leave empty to keep your current password.</FieldDescription>}
          </Field>
          {password ? (
            <Field data-invalid={tried && !!problems.confirm}>
              <FieldLabel htmlFor="acc-pw2">Repeat the new password</FieldLabel>
              <Input id="acc-pw2" type="password" autoComplete="new-password" value={confirm} onChange={(e) => setConfirm(e.target.value)} aria-invalid={tried && !!problems.confirm} />
              {tried && problems.confirm ? <FieldError>{problems.confirm}</FieldError> : null}
            </Field>
          ) : null}
        </div>
        <DialogFooter>
          <Button variant="outline" disabled={busy} onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            disabled={busy}
            onClick={async () => {
              setTried(true);
              if (invalid || !user.id) return;
              setBusy(true);
              try {
                const body: Record<string, string> = { firstName: first.trim(), lastName: last.trim(), phone: phone.trim() };
                if (password) body.password = password;
                const saved = (await UsersApi.update(user.id, body)) as StoredUser;
                onSaved({ ...user, name: saved.name ?? `${first.trim()} ${last.trim()}`.trim(), firstName: saved.firstName, lastName: saved.lastName, phone: saved.phone });
                toast.add({ title: password ? "Account and password saved" : "Account saved" });
                onOpenChange(false);
              } catch (e) {
                const m = (e as { response?: { data?: { message?: string } } })?.response?.data?.message;
                toast.add({ type: "error", title: "Not saved", description: m || "Try again." });
              } finally {
                setBusy(false);
              }
            }}
          >
            Save
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

/**
 * Spec 04 §9.4: while the API can't be reached, the offline banner sits under the top bar, so lists that came back
 * empty aren't read as "nothing there". It checks again every 15 seconds; when the server answers it goes, and a
 * toast says to reload what didn't load (the page isn't reloaded for them, so nothing typed is lost).
 */
function ApiOfflineBanner() {
  const [offline, setOffline] = React.useState(false);
  const [checking, setChecking] = React.useState(false);
  React.useEffect(() => {
    const on = (e: Event) => {
      const ok = (e as CustomEvent<boolean>).detail;
      setOffline(!ok);
      if (ok) toast.add({ title: "Connected again", description: "Reload the page to load anything that didn't load." });
    };
    window.addEventListener(API_REACHABLE_EVENT, on);
    return () => window.removeEventListener(API_REACHABLE_EVENT, on);
  }, []);
  const check = React.useCallback(() => {
    setChecking(true);
    AlertsApi.mine()
      .catch(() => {})
      .finally(() => setChecking(false));
  }, []);
  React.useEffect(() => {
    if (!offline) return;
    const t = setInterval(check, 15000);
    return () => clearInterval(t);
  }, [offline, check]);
  return offline ? <OfflineBanner onRetry={check} checking={checking} /> : null;
}

export default function AppShell({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();
  const router = useRouter();
  const { tone, setTone } = useSidebarTone();
  const [textSize, setTextSize] = useTextSize();
  const { showsInMenu, settled } = useMenuPermissions();
  const notices = useLiveNotices();
  const [user, setUser] = React.useState<StoredUser>({});
  const [branch, setBranch] = React.useState("");
  const [searching, setSearching] = React.useState(false);
  const [account, setAccount] = React.useState(false);

  React.useEffect(() => {
    let u: StoredUser = {};
    try {
      u = JSON.parse(localStorage.getItem("user") || "{}");
    } catch {
      /* the layout redirects to sign-in */
    }
    setUser(u);
    // DB-12: the current branch in the top bar. Admins see every branch.
    if (u.branchId)
      BranchesApi.getAll()
        .then((bs: { id: number; name: string }[]) => setBranch(bs.find((b) => b.id === u.branchId)?.name ?? "Your branch"))
        .catch(() => setBranch("Your branch"));
    else setBranch("All branches");
  }, []);

  // Ctrl K / Cmd K opens search, as in the design
  React.useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setSearching((o) => !o);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const menus = MENUS.map((m) => ({ ...m, items: m.items.filter((it) => showsInMenu(it.href, user.role, it.roles)) })).filter((m) => m.items.length);
  const items = menus.flatMap((m) => m.items);
  const active = items.find((it) => pathname === it.href || pathname.startsWith(it.href + "/"));
  const name = user.name || user.username || "You";
  const role = user.role || "";
  const signOut = () => {
    localStorage.removeItem("token");
    localStorage.removeItem("user");
    router.push("/login");
  };
  const BADGES: Record<string, number> = {
    "/inventory": notices.items.some((n) => n.id === "low-stock") ? 1 : 0,
    "/prescriptions": notices.items.some((n) => n.id === "pending-rx") ? 1 : 0,
  };

  return (
    <div className="medora h-svh">
      <MedoraScope />
      <TooltipProvider>
        <Toaster>
          <SidebarProvider className="h-svh min-h-0">
            <Sidebar collapsible="icon" tone="auto">
              <SidebarHeader className="items-center px-3 pt-4 pb-2">
                <Link href="/dashboard" aria-label="Medora, go to the dashboard">
                  <SidebarLogo tone="auto" className="group-data-[collapsible=icon]:hidden" />
                  <SidebarLogo tone="auto" symbol className="hidden group-data-[collapsible=icon]:block" />
                </Link>
              </SidebarHeader>
              <SidebarContent className="gap-0">
                {!settled ? <SidebarSkeleton groups={MENUS.map((m) => ({ label: m.label, items: m.items.map((it) => it.title) }))} /> : null}
                {settled && menus.map((g) => (
                  <SidebarGroup key={g.label} className="py-1">
                    <SidebarGroupLabel>{g.label}</SidebarGroupLabel>
                    <SidebarGroupContent>
                      <SidebarMenu>
                        {g.items.map((it) => (
                          <SidebarMenuItem key={it.href}>
                            <SidebarMenuButton tooltip={it.title} isActive={active?.href === it.href} render={<Link href={it.href} />}>
                              <it.icon />
                              <span>{it.title}</span>
                            </SidebarMenuButton>
                            {BADGES[it.href] ? <SidebarMenuBadge>!</SidebarMenuBadge> : null}
                          </SidebarMenuItem>
                        ))}
                      </SidebarMenu>
                    </SidebarGroupContent>
                  </SidebarGroup>
                ))}
              </SidebarContent>
              <SidebarFooter className="border-t border-sidebar-border">
                <SidebarMenu>
                  <SidebarMenuItem>
                    {/* The profile is a skeleton until the signed-in user is read (loading spec 8) */}
                    {!user.name && !user.username ? (
                      <SidebarProfileSkeleton />
                    ) : (
                      <DropdownMenu>
                        <DropdownMenuTrigger render={<SidebarMenuButton size="lg" className="h-10" />}>
                          <Avatar className="size-7">
                            <AvatarFallback>{initials(name)}</AvatarFallback>
                          </Avatar>
                          <div className="grid flex-1 text-left leading-tight">
                            <span className="truncate text-sm font-medium">{name}</span>
                            <span className="truncate text-xs text-(--sidebar-muted)">{role}</span>
                          </div>
                          <ChevronsUpDownIcon className="ml-auto size-4" />
                        </DropdownMenuTrigger>
                        <DropdownMenuContent side="right" align="end" className="min-w-48">
                          {user.email ? (
                            <DropdownMenuGroup>
                              <DropdownMenuLabel>{user.email}</DropdownMenuLabel>
                            </DropdownMenuGroup>
                          ) : null}
                          <DropdownMenuSeparator />
                          <DropdownMenuItem onClick={() => setAccount(true)}>
                            <SettingsIcon /> Account settings
                          </DropdownMenuItem>
                          <DropdownMenuItem onClick={signOut}>
                            <LogOutIcon /> Sign out
                          </DropdownMenuItem>
                        </DropdownMenuContent>
                      </DropdownMenu>
                    )}
                  </SidebarMenuItem>
                </SidebarMenu>
              </SidebarFooter>
              <SidebarRail />
            </Sidebar>
            <SidebarInset className="min-w-0 overflow-hidden bg-muted/40">
              <header className="flex h-14 shrink-0 items-center gap-3 border-b border-border bg-background px-4">
                <SidebarTrigger className="-ml-1" />
                <Separator orientation="vertical" className="h-5" />
                <Breadcrumb className="hidden md:block">
                  <BreadcrumbList>
                    <BreadcrumbItem>
                      <BreadcrumbLink render={<Link href="/dashboard" />}>Medora</BreadcrumbLink>
                    </BreadcrumbItem>
                    {active ? (
                      <>
                        <BreadcrumbSeparator />
                        <BreadcrumbItem>
                          <BreadcrumbPage>{active.title}</BreadcrumbPage>
                        </BreadcrumbItem>
                      </>
                    ) : null}
                  </BreadcrumbList>
                </Breadcrumb>
                <div className="ml-auto flex items-center gap-2">
                  <Button variant="outline" size="sm" className="hidden w-48 justify-start text-muted-foreground sm:flex" onClick={() => setSearching(true)}>
                    <SearchIcon data-icon="inline-start" />
                    Search
                    <Kbd className="ml-auto">Ctrl K</Kbd>
                  </Button>
                  <span className="hidden h-8 items-center rounded-md border border-border bg-background px-3 text-sm md:inline-flex" title="Your branch">
                    {branch}
                  </span>
                  <NotificationsBell items={notices.items} now={new Date().toISOString().slice(0, 16)} onOpenNotice={(id) => {
                    notices.open(id);
                    const href = notices.items.find((n) => n.id === id)?.href;
                    if (href) router.push(href);
                  }} onReadAll={notices.readAll} settingsHref={showsInMenu("/settings", role) ? "/settings" : undefined} />
                  <UserMenu
                    name={name}
                    email={user.email ?? ""}
                    role={role}
                    branch={branch}
                    appearance={{ sidebar: tone, text: textSize }}
                    onAppearance={(a) => {
                      setTone(a.sidebar);
                      setTextSize(a.text);
                    }}
                    onProfile={() => setAccount(true)}
                    onPassword={() => setAccount(true)}
                    onSignOut={signOut}
                  />
                </div>
              </header>
              <ApiOfflineBanner />
              <main className="min-h-0 flex-1 overflow-y-auto p-4 md:p-6">{children}</main>
            </SidebarInset>
          </SidebarProvider>
          <CommandDialog open={searching} onOpenChange={setSearching}>
            <Command>
              <CommandInput placeholder="Search pages..." />
              <CommandList>
                <CommandEmpty>No results.</CommandEmpty>
                <CommandGroup heading="Pages">
                  {items.map((it) => (
                    <CommandItem
                      key={it.href}
                      onSelect={() => {
                        router.push(it.href);
                        setSearching(false);
                      }}
                    >
                      <it.icon /> {it.title}
                    </CommandItem>
                  ))}
                </CommandGroup>
              </CommandList>
            </Command>
          </CommandDialog>
          <AccountDialog
            user={user}
            open={account}
            onOpenChange={setAccount}
            onSaved={(u) => {
              setUser(u);
              try {
                localStorage.setItem("user", JSON.stringify({ ...JSON.parse(localStorage.getItem("user") || "{}"), name: u.name }));
              } catch {
                /* storage blocked: the name updates on next sign-in */
              }
            }}
          />
        </Toaster>
      </TooltipProvider>
    </div>
  );
}
