import { useEffect, useMemo, useRef, useState } from "react"

const SIDEBAR_KEY = "crema.shell.sidebar"
const NAV_GROUPS_KEY = "crema.shell.nav-groups"
import { NavLink, useLocation, useNavigate } from "react-router"
import {
  Bell,
  LayoutDashboard,
  Boxes,
  Activity,
  Building2,
  Sparkles,
  Bot,
  BookOpen,
  Settings,
  Search,
  PanelLeftClose,
  PanelLeftOpen,
  User as UserIcon,
  LogOut,
  HelpCircle,
  Menu,
  Play,
  HardDrive,
  Users as UsersIcon,
  KeyRound,
  Webhook as WebhookIcon,
  CalendarClock,
  Gauge,
  UserCheck,
  Network,
  Building,
  ShieldCheck,
  Megaphone,
  AlertOctagon,
  SearchCode,
  ChevronDown,
  Database,
  Plug,
  MessageSquare,
  Eye,
  LayoutGrid,
  CreditCard,
  // CREMA:NAV-ICONS
} from "lucide-react"

import {
  useBrand,
  useUser,
  type Brand,
  type User,
} from "~/lib/identity"

import {
  Appbar,
  AppbarActions,
  AppbarSpacer,
  AppbarTitle,
} from "~/components/layout/appbar"
import { ThemeToggle } from "~/components/layout/theme-toggle"
import { BackgroundPicker } from "~/components/layout/background-picker"
import { FontSizePicker } from "~/components/layout/font-size-picker"
import { SurfacePicker } from "~/components/layout/surface-picker"
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "~/components/ui/popover"
import { profileInitials, useProfile } from "~/lib/profile"
import { signOut, useSession } from "~/lib/session"
import { capabilityForPath, useCapabilities } from "~/lib/capabilities"
import {
  addNotification,
  dismiss,
  dismissAll,
  markAllRead,
  markRead,
  seedIfEmpty,
  unreadCount,
  useNotifications,
} from "~/lib/notifications"
import { Button } from "~/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu"
import { Input } from "~/components/ui/input"
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "~/components/ui/sheet"
import { ScriptsDialog, useScriptsHotkey } from "~/components/scripts-dialog"
import { RouteGuard } from "~/components/route-guard"

type NavItem = {
  to: string
  icon: React.ComponentType<{ className?: string }>
  label: string
  end?: boolean
}

type NavGroup = {
  key: string
  label: string
  icon: React.ComponentType<{ className?: string }>
  items: NavItem[]
}

// Pinned items render flat at the top of the rail, above any groups.
const pinnedTop: NavItem[] = [
  { to: "/", icon: LayoutDashboard, label: "Overview", end: true },
]

// Pinned items render flat at the bottom of the rail, below all groups.
const pinnedBottom: NavItem[] = [
  { to: "/settings", icon: Settings, label: "Settings" },
]

const navGroups: NavGroup[] = [
  {
    key: "tenancy",
    label: "Tenancy",
    icon: Building2,
    items: [
      { to: "/tenants", icon: Building2, label: "Tenants" },
      { to: "/memberships", icon: UserCheck, label: "Memberships" },
      { to: "/organizations", icon: Building, label: "Organizations" },
      { to: "/users", icon: UsersIcon, label: "Users" },
      { to: "/sso", icon: ShieldCheck, label: "SSO" },
    ],
  },
  {
    key: "billing",
    label: "Billing",
    icon: CreditCard,
    items: [
      { to: "/apps", icon: LayoutGrid, label: "Apps" },
      { to: "/plan", icon: CreditCard, label: "Plan" },
      { to: "/entitlements", icon: Gauge, label: "Entitlements" },
    ],
  },
  {
    key: "data",
    label: "Data",
    icon: Database,
    items: [
      { to: "/storage", icon: HardDrive, label: "Storage" },
      { to: "/buckets", icon: Boxes, label: "Buckets" },
      { to: "/secrets", icon: KeyRound, label: "Secrets" },
      { to: "/integrations", icon: Plug, label: "Integrations" },
    ],
  },
  {
    key: "integrations",
    label: "Integrations",
    icon: Plug,
    items: [
      { to: "/webhooks", icon: WebhookIcon, label: "Webhooks" },
      { to: "/scheduled-tasks", icon: CalendarClock, label: "Scheduled" },
      { to: "/networking", icon: Network, label: "Networking" },
    ],
  },
  {
    key: "comms",
    label: "Communications",
    icon: MessageSquare,
    items: [
      { to: "/announcements", icon: Megaphone, label: "Announcements" },
      { to: "/status-page", icon: AlertOctagon, label: "Status page" },
    ],
  },
  {
    key: "observability",
    label: "Observability",
    icon: Eye,
    items: [
      { to: "/monitoring", icon: Gauge, label: "Monitoring" },
      { to: "/activity", icon: Activity, label: "Audit log" },
    ],
  },
  {
    key: "ai",
    label: "AI & Search",
    icon: Sparkles,
    items: [
      { to: "/ai", icon: Bot, label: "AI" },
      { to: "/search", icon: SearchCode, label: "Search" },
    ],
  },
]

// Items appended by `crema add <lib>` land here. Rendered ungrouped at
// the bottom of the groups, above the pinned footer.
const extraNavItems: NavItem[] = [
  // CREMA:NAV-ITEMS
]

function readNavGroupState(): Record<string, boolean> {
  if (typeof window === "undefined") return {}
  try {
    const raw = localStorage.getItem(NAV_GROUPS_KEY)
    return raw ? (JSON.parse(raw) as Record<string, boolean>) : {}
  } catch {
    return {}
  }
}

type AppShellProps = {
  children: React.ReactNode
  brand?: Brand
  user?: User
  /**
   * Optional theme name. When set, the shell wraps itself in
   * `data-theme={theme}` so a route can opt into an alternate theme without
   * the caller having to add an extra wrapping div.
   */
  theme?: string
}

export function AppShell({
  children,
  brand: brandOverride,
  user: userOverride,
  theme,
}: AppShellProps) {
  const defaultBrand = useBrand()
  const defaultUser = useUser()
  const profile = useProfile()
  const session = useSession()
  const caps = useCapabilities()
  const navigate = useNavigate()
  const brand = brandOverride ?? defaultBrand
  // Prefer the live session for identity, fall back to the stub user.
  const user = userOverride ?? {
    name: session?.name || defaultUser.name,
    email: session?.email || defaultUser.email,
    initials: profileInitials(session?.name || defaultUser.name),
  }

  // Protected shell: bounce to /login when there's no session.
  useEffect(() => {
    if (typeof window === "undefined") return
    if (!session) {
      const next = encodeURIComponent(
        window.location.pathname + window.location.search,
      )
      navigate(`/login?next=${next}`, { replace: true })
    }
  }, [session, navigate])
  // All hooks must run unconditionally — keep them above the session
  // short-circuit so a sign-out doesn't reduce the hook count and trip
  // React's "rendered fewer hooks than expected" check.
  const [expanded, setExpanded] = useState<boolean>(() => {
    if (typeof window === "undefined") return false
    return localStorage.getItem(SIDEBAR_KEY) === "1"
  })
  useEffect(() => {
    localStorage.setItem(SIDEBAR_KEY, expanded ? "1" : "0")
  }, [expanded])
  const [mobileOpen, setMobileOpen] = useState(false)
  const [scriptsOpen, setScriptsOpen] = useState(false)
  useScriptsHotkey(() => setScriptsOpen(true))

  const location = useLocation()

  // Filter the nav by what the active session can actually reach. A
  // capability map exists for every protected route — items without one
  // (or whose capability isn't held) are dropped here, so the sidebar
  // doesn't advertise routes the user will only hit a 403 from.
  const allowed = (item: NavItem): boolean => {
    const cap = capabilityForPath(item.to)
    if (!cap) return true // unknown routes default to visible
    return caps.has(cap)
  }
  const visiblePinnedTop = useMemo(
    () => pinnedTop.filter(allowed),
    [caps],
  )
  const visiblePinnedBottom = useMemo(
    () => pinnedBottom.filter(allowed),
    [caps],
  )
  const visibleNavGroups: NavGroup[] = useMemo(
    () =>
      navGroups
        .map((g) => ({ ...g, items: g.items.filter(allowed) }))
        .filter((g) => g.items.length > 0),
    [caps],
  )
  const visibleExtraItems = useMemo(
    () => extraNavItems.filter(allowed),
    [caps],
  )
  const visibleAllNavItems: NavItem[] = useMemo(
    () => [
      ...visiblePinnedTop,
      ...visibleNavGroups.flatMap((g) => g.items),
      ...visibleExtraItems,
      ...visiblePinnedBottom,
    ],
    [visiblePinnedTop, visibleNavGroups, visibleExtraItems, visiblePinnedBottom],
  )

  const activeGroupKey = useMemo(
    () =>
      visibleNavGroups.find((g) =>
        g.items.some((it) => location.pathname.startsWith(it.to)),
      )?.key ?? null,
    [location.pathname, visibleNavGroups],
  )

  const [openGroups, setOpenGroups] = useState<Record<string, boolean>>(() =>
    readNavGroupState(),
  )

  // Auto-open the group that owns the current route on first mount or
  // navigation, but never auto-close — the user's explicit toggles win.
  useEffect(() => {
    if (!activeGroupKey) return
    setOpenGroups((prev) =>
      prev[activeGroupKey] ? prev : { ...prev, [activeGroupKey]: true },
    )
  }, [activeGroupKey])

  useEffect(() => {
    if (typeof window === "undefined") return
    localStorage.setItem(NAV_GROUPS_KEY, JSON.stringify(openGroups))
  }, [openGroups])

  const toggleGroup = (key: string) =>
    setOpenGroups((prev) => ({ ...prev, [key]: !prev[key] }))

  if (!session) return null
  const BrandIcon = brand.icon

  return (
    <div
      data-theme={theme}
      className="relative isolate flex min-h-svh"
    >
      <a
        href="#main-content"
        className="sr-only focus:not-sr-only focus:fixed focus:left-2 focus:top-2 focus:z-50 focus:rounded-md focus:bg-primary focus:px-3 focus:py-2 focus:text-primary-foreground focus:shadow-lg"
      >
        Skip to main content
      </a>
      <aside
        data-slot="sidebar"
        data-expanded={expanded ? "true" : "false"}
        className={[
          "sticky top-0 z-30 hidden h-svh shrink-0 flex-col border-r bg-sidebar transition-[width] duration-base ease-standard md:flex",
          expanded ? "w-60" : "w-16",
        ].join(" ")}
      >
        <div
          className={[
            "flex h-14 items-center gap-2 border-b px-3",
            expanded ? "justify-between" : "justify-center",
          ].join(" ")}
        >
          {expanded ? (
            <div className="flex items-center gap-2">
              <div className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
                <BrandIcon className="size-4" />
              </div>
              <span className="font-heading font-semibold tracking-tight">
                {brand.name}
              </span>
            </div>
          ) : (
            <div className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
              <BrandIcon className="size-4" />
            </div>
          )}
        </div>

        <nav className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto p-2">
          {expanded ? (
            <>
              {visiblePinnedTop.map((item) => (
                <NavRow key={item.label} item={item} expanded />
              ))}

              {visibleNavGroups.map((group) => {
                const isOpen = !!openGroups[group.key]
                const GroupIcon = group.icon
                return (
                  <div key={group.key} className="mt-3 flex flex-col gap-0.5">
                    <button
                      type="button"
                      data-action={`nav-group-${group.key}`}
                      onClick={() => toggleGroup(group.key)}
                      aria-expanded={isOpen}
                      className="flex items-center gap-2 rounded-md px-3 py-1.5 text-left text-caption font-semibold uppercase tracking-wider text-muted-foreground/70 transition-colors duration-fast ease-standard hover:text-foreground"
                    >
                      <GroupIcon className="size-3.5 shrink-0" />
                      <span className="flex-1 truncate">{group.label}</span>
                      <ChevronDown
                        className={[
                          "size-3.5 shrink-0 transition-transform duration-fast ease-standard",
                          isOpen ? "" : "-rotate-90",
                        ].join(" ")}
                      />
                    </button>
                    {isOpen ? (
                      <div className="flex flex-col gap-0.5">
                        {group.items.map((item) => (
                          <NavRow key={item.label} item={item} expanded inGroup />
                        ))}
                      </div>
                    ) : null}
                  </div>
                )
              })}

              {visibleExtraItems.length > 0 ? (
                <div className="mt-1.5 flex flex-col gap-0.5">
                  {visibleExtraItems.map((item) => (
                    <NavRow key={item.label} item={item} expanded />
                  ))}
                </div>
              ) : null}

              <div className="mt-auto flex flex-col gap-0.5 pt-2">
                {visiblePinnedBottom.map((item) => (
                  <NavRow key={item.label} item={item} expanded />
                ))}
              </div>
            </>
          ) : (
            // Icon-only rail: flat list, no group headers.
            <>
              {visibleAllNavItems.map((item) => (
                <NavRow key={item.label} item={item} expanded={false} />
              ))}
            </>
          )}
        </nav>

        <div className="shrink-0 border-t p-2">
          <Button
            data-action="sidebar-toggle"
            variant="ghost"
            size={expanded ? "sm" : "icon-sm"}
            onClick={() => setExpanded((v) => !v)}
            aria-label={expanded ? "Collapse sidebar" : "Expand sidebar"}
            className={expanded ? "w-full justify-start" : "w-full"}
          >
            {expanded ? (
              <>
                <PanelLeftClose className="size-4" />
                <span>Collapse</span>
              </>
            ) : (
              <PanelLeftOpen className="size-4" />
            )}
          </Button>
        </div>
      </aside>

      <main className="flex min-w-0 flex-1 flex-col">
        {/* Mobile-only menu trigger, floating top-left of main */}
        <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
          <SheetTrigger
            data-action="mobile-nav-toggle"
            aria-label="Open navigation"
            className="fixed left-3 top-3 z-30 inline-flex size-9 items-center justify-center rounded-full border bg-card/70 text-muted-foreground shadow-sm backdrop-blur-md transition-colors hover:bg-accent hover:text-accent-foreground md:hidden"
          >
            <Menu className="size-5" />
          </SheetTrigger>
          <SheetContent
            side="left"
            className="flex h-svh w-72 flex-col p-0"
          >
            <SheetHeader className="shrink-0 border-b">
              <SheetTitle className="flex items-center gap-2">
                <div className="flex size-7 items-center justify-center rounded-lg bg-primary text-primary-foreground">
                  <BrandIcon className="size-4" />
                </div>
                {brand.name}
              </SheetTitle>
            </SheetHeader>
            <nav className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto p-2">
              {visiblePinnedTop.map((item) => (
                <NavRow
                  key={item.label}
                  item={item}
                  expanded
                  mobile
                  onNavigate={() => setMobileOpen(false)}
                />
              ))}

              {visibleNavGroups.map((group) => {
                const isOpen = !!openGroups[group.key]
                const GroupIcon = group.icon
                return (
                  <div key={group.key} className="mt-3 flex flex-col gap-0.5">
                    <button
                      type="button"
                      data-action={`nav-mobile-group-${group.key}`}
                      onClick={() => toggleGroup(group.key)}
                      aria-expanded={isOpen}
                      className="flex items-center gap-2 rounded-md px-3 py-1.5 text-left text-caption font-semibold uppercase tracking-wider text-muted-foreground/70 transition-colors duration-fast ease-standard hover:text-foreground"
                    >
                      <GroupIcon className="size-3.5 shrink-0" />
                      <span className="flex-1 truncate">{group.label}</span>
                      <ChevronDown
                        className={[
                          "size-3.5 shrink-0 transition-transform duration-fast ease-standard",
                          isOpen ? "" : "-rotate-90",
                        ].join(" ")}
                      />
                    </button>
                    {isOpen ? (
                      <div className="flex flex-col gap-0.5">
                        {group.items.map((item) => (
                          <NavRow
                            key={item.label}
                            item={item}
                            expanded
                            mobile
                            inGroup
                            onNavigate={() => setMobileOpen(false)}
                          />
                        ))}
                      </div>
                    ) : null}
                  </div>
                )
              })}

              {visibleExtraItems.length > 0 ? (
                <div className="mt-1.5 flex flex-col gap-0.5">
                  {visibleExtraItems.map((item) => (
                    <NavRow
                      key={item.label}
                      item={item}
                      expanded
                      mobile
                      onNavigate={() => setMobileOpen(false)}
                    />
                  ))}
                </div>
              ) : null}

              <div className="mt-auto flex flex-col gap-0.5 pt-2">
                {visiblePinnedBottom.map((item) => (
                  <NavRow
                    key={item.label}
                    item={item}
                    expanded
                    mobile
                    onNavigate={() => setMobileOpen(false)}
                  />
                ))}
              </div>
            </nav>
          </SheetContent>
        </Sheet>

        {/* Floating glass pill, top-right, replacing the appbar action group */}
        <div
          data-slot="floating-actions"
          className="fixed right-3 top-3 z-30 flex items-center gap-0.5 rounded-full border bg-card/70 px-1.5 py-1 shadow-sm backdrop-blur-md"
        >
          <Button
            data-action="appbar-scripts"
            variant="ghost"
            size="icon-sm"
            aria-label="Run script (Cmd+Shift+P)"
            title="Run script (Cmd+Shift+P)"
            onClick={() => setScriptsOpen(true)}
          >
            <Play />
          </Button>
          <FontSizePicker />
          <SurfacePicker />
          <BackgroundPicker />
          <ThemeToggle />
          <NotificationsBell />
          <DropdownMenu>
            <DropdownMenuTrigger
              data-action="appbar-avatar"
              aria-label="Account menu"
              className="ml-1 rounded-full outline-none ring-offset-2 ring-offset-background transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring"
            >
              <Avatar className="size-7 cursor-pointer">
                {profile.avatarUrl ? (
                  <AvatarImage
                    key={profile.avatarUrl}
                    src={profile.avatarUrl}
                    alt={user.name}
                  />
                ) : null}
                <AvatarFallback>{user.initials}</AvatarFallback>
              </Avatar>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end" sideOffset={8} className="w-56">
              <DropdownMenuGroup>
                <DropdownMenuLabel className="flex flex-col gap-0.5">
                  <span className="font-medium">{user.name}</span>
                  <span className="text-xs font-normal text-muted-foreground">
                    {user.email}
                  </span>
                </DropdownMenuLabel>
              </DropdownMenuGroup>
              <DropdownMenuSeparator />
              <DropdownMenuItem
                data-action="avatar-profile"
                onClick={() => navigate("/profile")}
              >
                <UserIcon /> Profile
              </DropdownMenuItem>
              <DropdownMenuItem
                data-action="avatar-settings"
                onClick={() => navigate("/settings")}
              >
                <Settings /> Settings
              </DropdownMenuItem>
              <DropdownMenuItem data-action="avatar-help">
                <HelpCircle /> Help
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem
                data-action="avatar-signout"
                variant="destructive"
                onClick={() => {
                  signOut()
                  navigate("/login", { replace: true })
                }}
              >
                <LogOut /> Sign out
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </div>

        <div
          id="main-content"
          tabIndex={-1}
          className="flex flex-1 flex-col focus:outline-none"
        >
          {/* Centered content column. Caps line lengths and frames pages
              on wide displays so the canvas reads as composed instead of
              one floating card in a sea of black. The floating actions
              pill is fixed to the viewport edge and lives outside this
              column, so it stays clear regardless of cap width. */}
          <div className="mx-auto flex w-full max-w-[1180px] flex-1 flex-col gap-6 p-6 [&>*:first-child]:lg:pr-72">
            <RouteGuard>{children}</RouteGuard>
          </div>
        </div>
      </main>

      <ScriptsDialog open={scriptsOpen} onOpenChange={setScriptsOpen} />
      <NotificationDispatcher />
    </div>
  )
}

function NavRow({
  item,
  expanded,
  mobile = false,
  inGroup = false,
  onNavigate,
}: {
  item: NavItem
  expanded: boolean
  mobile?: boolean
  /** True when rendered inside a collapsible group — hides the per-item
   * icon and indents the label so it aligns under the group header. */
  inGroup?: boolean
  onNavigate?: () => void
}) {
  const Icon = item.icon
  const prefix = mobile ? "nav-mobile-" : "nav-"
  // Icons are hidden inside groups in the expanded rail. The collapsed
  // icon-only rail (expanded=false) always shows icons regardless.
  const showIcon = !inGroup || !expanded
  return (
    <NavLink
      to={item.to}
      end={item.end}
      title={expanded ? undefined : item.label}
      onClick={onNavigate}
      data-action={`${prefix}${item.label.toLowerCase()}`}
      className={({ isActive }) =>
        [
          "relative flex items-center gap-3 rounded-lg py-2 text-sm font-medium transition-colors duration-fast ease-standard",
          // 2px left accent rail on active. Absolute-positioned so the rail
          // anchors to the rail's edge regardless of per-item left padding,
          // and a fixed 14px height keeps it from filling tall rows.
          "before:absolute before:left-0 before:top-1/2 before:h-3.5 before:w-[2px] before:-translate-y-1/2 before:rounded-r-full before:bg-primary before:opacity-0 before:transition-opacity before:duration-fast",
          expanded
            ? inGroup
              ? // Indent the label by chevron(12) + gap(8) = 20px so it
                // visually aligns under the group header label.
                "justify-start pl-[1.625rem] pr-3"
              : "justify-start px-3"
            : "justify-center px-3",
          isActive
            ? "bg-primary/[0.08] text-primary before:opacity-100"
            : "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
        ].join(" ")
      }
    >
      {showIcon ? <Icon className="size-5 shrink-0" /> : null}
      {expanded ? <span className="truncate">{item.label}</span> : null}
    </NavLink>
  )
}

function NotificationDispatcher() {
  // Hidden bridge so the action bus can create real notifications:
  //   fill notify-title "Hello"
  //   fill notify-body "Body text"
  //   fill notify-kind "info"        # info|success|warning|error
  //   fill notify-href "/library"    # optional
  //   click notify-create
  const titleRef = useRef<HTMLInputElement>(null)
  const bodyRef = useRef<HTMLInputElement>(null)
  const kindRef = useRef<HTMLInputElement>(null)
  const hrefRef = useRef<HTMLInputElement>(null)
  const submit = () => {
    const title = titleRef.current?.value.trim() ?? ""
    if (!title) return
    const body = bodyRef.current?.value.trim() || undefined
    const rawKind = (kindRef.current?.value || "info").trim().toLowerCase()
    const kind = (
      ["info", "success", "warning", "error"].includes(rawKind)
        ? rawKind
        : "info"
    ) as "info" | "success" | "warning" | "error"
    const href = hrefRef.current?.value.trim() || undefined
    addNotification({ title, body, kind, href })
    if (titleRef.current) titleRef.current.value = ""
    if (bodyRef.current) bodyRef.current.value = ""
    if (kindRef.current) kindRef.current.value = ""
    if (hrefRef.current) hrefRef.current.value = ""
  }
  return (
    <div
      aria-hidden
      className="pointer-events-none fixed left-0 top-0 size-px overflow-hidden opacity-0"
    >
      <input
        ref={titleRef}
        data-action="notif-title"
        placeholder="title"
        aria-label="Notification title"
      />
      <input
        ref={bodyRef}
        data-action="notif-body"
        placeholder="body"
        aria-label="Notification body"
      />
      <input
        ref={kindRef}
        data-action="notif-kind"
        placeholder="info|success|warning|error"
        aria-label="Notification kind"
      />
      <input
        ref={hrefRef}
        data-action="notif-href"
        placeholder="/path (optional)"
        aria-label="Notification href"
      />
      <button
        type="button"
        data-action="notif-create"
        aria-label="Create notification"
        onClick={submit}
        className="pointer-events-auto"
      >
        create
      </button>
    </div>
  )
}

function NotificationsBell() {
  const items = useNotifications()
  const unread = unreadCount(items)
  const navigate = useNavigate()

  useEffect(() => {
    seedIfEmpty()
  }, [])

  return (
    <Popover>
      <PopoverTrigger
        render={
          <Button
            data-action="appbar-notifications"
            variant="ghost"
            size="icon-sm"
            aria-label="Notifications"
          >
            <span className="relative inline-flex">
              <Bell />
              {unread > 0 && (
                <span className="absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[9px] font-semibold text-primary-foreground">
                  {unread > 9 ? "9+" : unread}
                </span>
              )}
            </span>
          </Button>
        }
      />
      <PopoverContent align="end" className="w-80 p-0">
        <div className="flex items-center justify-between border-b px-3 py-2">
          <span className="text-sm font-semibold">Notifications</span>
          <div className="flex items-center gap-1">
            <Button
              data-action="notif-mark-all-read"
              variant="ghost"
              size="sm"
              onClick={() => markAllRead()}
              disabled={unread === 0}
            >
              Mark all read
            </Button>
            <Button
              data-action="notif-clear"
              variant="ghost"
              size="sm"
              onClick={() => dismissAll()}
              disabled={items.length === 0}
            >
              Clear
            </Button>
          </div>
        </div>
        <ul className="max-h-80 overflow-y-auto">
          {items.length === 0 ? (
            <li className="px-3 py-6 text-center text-sm text-muted-foreground">
              No notifications.
            </li>
          ) : (
            items.map((n) => (
              <li
                key={n.id}
                className={
                  "group flex items-start gap-2 border-b px-3 py-2 text-sm transition-colors hover:bg-accent/40 " +
                  (!n.readAt ? "bg-primary/5" : "")
                }
              >
                <span
                  className={
                    "mt-1 size-2 shrink-0 rounded-full " +
                    (n.kind === "success"
                      ? "bg-emerald-500"
                      : n.kind === "warning"
                        ? "bg-amber-500"
                        : n.kind === "error"
                          ? "bg-rose-500"
                          : "bg-primary")
                  }
                  aria-hidden
                />
                <button
                  type="button"
                  data-action={`notif-open-${n.id}`}
                  onClick={() => {
                    markRead(n.id)
                    if (n.href) navigate(n.href)
                  }}
                  className="flex flex-1 flex-col items-start text-left"
                >
                  <span className="font-medium">{n.title}</span>
                  {n.body && (
                    <span className="text-xs text-muted-foreground">
                      {n.body}
                    </span>
                  )}
                  <span className="text-[10px] text-muted-foreground/70">
                    {new Date(n.createdAt).toLocaleString()}
                  </span>
                </button>
                <button
                  type="button"
                  data-action={`notif-dismiss-${n.id}`}
                  onClick={() => dismiss(n.id)}
                  aria-label="Dismiss"
                  className="rounded p-1 text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100"
                >
                  <span aria-hidden>×</span>
                </button>
              </li>
            ))
          )}
        </ul>
      </PopoverContent>
    </Popover>
  )
}
