'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useEffect, useRef, useState } from 'react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { useTheme } from '../components/ThemeProvider.js';
import { fetchFormsSidebarCounts } from '../lib/forms-service.js';
import { onFormsChanged } from '../lib/forms-events.js';
import { LayoutDashboard, FileText, File, Image, Settings, ChevronDown, ClipboardList, Inbox, Search as SearchIcon, Briefcase, FolderOpen, BookOpen, HelpCircle, Newspaper, PanelTop, PanelBottom, Layers, LayoutGrid, Code2, Library, ListTree, Menu as MenuIcon, PanelLeftClose, PanelLeftOpen, } from 'lucide-react';
import { ActuateBrandLogo } from '../assets/actuate-logo.js';
import { LogoPlaceholder } from '../components/LogoPlaceholder.js';
/**
 * Compact mark used in the collapsed sidebar — just the "C" symbol from the
 * full lockup, drawn as its own simple SVG so it stays crisp at 32×32.
 */
function ActuateMark({ className }) {
    return (_jsxs("svg", { viewBox: "0 0 40 44", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, "aria-hidden": "true", children: [_jsx("polygon", { points: "20,2 6,18 12,18 20,8 28,18 34,18", fill: "#F05E65" }), _jsx("rect", { x: "11", y: "20", width: "4", height: "22", rx: "1", fill: "#F05E65" }), _jsx("rect", { x: "18", y: "20", width: "4", height: "22", rx: "1", fill: "#F05E65" }), _jsx("rect", { x: "25", y: "20", width: "4", height: "22", rx: "1", fill: "#F05E65" })] }));
}
function ActuateWordmark({ className }) {
    return _jsx(ActuateBrandLogo, { className: className });
}
const ICON_MAP = {
    file: File,
    'file-text': FileText,
    briefcase: Briefcase,
    folder: FolderOpen,
    book: BookOpen,
    help: HelpCircle,
    newspaper: Newspaper,
    navigation: ListTree,
    menu: MenuIcon,
    'list-tree': ListTree,
    PanelTop: PanelTop,
    PanelBottom: PanelBottom,
    Layers: Layers,
};
/**
 * Sidebar brand lockup.
 *
 * Resolution order follows the Appearance spec, sourcing from the live
 * appearance context (DB-backed brand, cached in localStorage) and falling
 * back to legacy `config.admin.branding`, then a neutral logo placeholder for
 * branded-but-logo-less installs, then the bundled Actuate mark/wordmark for
 * un-branded installs. The placeholder replaces the old initials badge, which
 * read as a user avatar (the real account avatar already sits top-right).
 * Dark-mode uses the dark logo when one is configured. Logos are never
 * stretched/cropped (object-contain).
 */
function BrandLogo({ config, collapsed }) {
    const { adminBrand, resolvedTheme } = useTheme();
    const branding = config?.admin?.branding;
    const configLogo = branding?.logo;
    const brandName = branding?.name;
    const initials = adminBrand.initials;
    const hasInitials = Boolean(initials) && initials !== 'A';
    // Prefer the dark logo on dark backgrounds when supplied.
    const primary = resolvedTheme === 'dark' && adminBrand.darkUrl ? adminBrand.darkUrl : adminBrand.primaryUrl;
    const mark = adminBrand.markUrl;
    const alt = brandName ?? 'Admin';
    if (collapsed) {
        if (mark)
            return _jsx("img", { src: mark, alt: alt, className: "h-8 w-8 object-contain" });
        if (primary)
            return _jsx("img", { src: primary, alt: alt, className: "h-8 w-8 object-contain" });
        if (configLogo)
            return _jsx("img", { src: configLogo, alt: alt, className: "h-8 w-8 object-contain" });
        if (hasInitials)
            return _jsx(LogoPlaceholder, { tone: "sidebar" });
        return _jsx(ActuateMark, { className: "h-8 w-8" });
    }
    if (primary) {
        return (_jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [_jsx("img", { src: primary, alt: alt, className: "h-8 w-auto shrink-0 object-contain" }), brandName && (_jsx("span", { className: "text-sidebar-foreground truncate text-sm font-medium", children: brandName }))] }));
    }
    if (configLogo) {
        return (_jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [_jsx("img", { src: configLogo, alt: alt, className: "h-8 w-auto shrink-0 object-contain" }), brandName && (_jsx("span", { className: "text-sidebar-foreground truncate text-sm font-medium", children: brandName }))] }));
    }
    if (mark) {
        return (_jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [_jsx("img", { src: mark, alt: alt, className: "h-8 w-8 shrink-0 object-contain" }), _jsx("span", { className: "text-sidebar-foreground truncate text-sm font-medium", children: brandName ?? alt })] }));
    }
    if (hasInitials) {
        return _jsx(LogoPlaceholder, { tone: "sidebar", label: brandName ?? alt });
    }
    if (brandName) {
        return (_jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [_jsx(ActuateMark, { className: "h-7 w-7 shrink-0" }), _jsx("span", { className: "text-sidebar-foreground truncate text-sm font-medium", children: brandName })] }));
    }
    return _jsx(ActuateWordmark, { className: "h-9" });
}
/**
 * Children of the Posts parent. Same shape regardless of how many
 * post-type collections are registered — Post Types are accessed via
 * the `/posts/types` cards page, not as individual sidebar entries.
 */
const POSTS_CHILDREN = [
    { path: '/posts', label: 'All Posts', icon: FileText },
    { path: '/posts/types', label: 'Post Types', icon: Layers },
];
/**
 * Children of the Templates parent. Templates is its own top-level item
 * (page building tools) — Saved Sections are reusable content blocks and
 * Widgets are embeddable components. These are deliberately NOT children
 * of Pages: Pages is content, Templates/Sections/Widgets are structure.
 */
const TEMPLATES_CHILDREN = [
    { path: '/saved-sections', label: 'Saved Sections', icon: Library },
    { path: '/widgets', label: 'Widgets', icon: LayoutGrid },
];
/**
 * Nav tree for the default (config-less) install. Dashboard sits on its
 * own at the top, then a CONTENT group holding Pages (no children) above
 * Posts (with All Posts + Post Types) and Templates (with Saved Sections
 * + Widgets), followed by Media / Forms / SEO / Script Tags, then a
 * SETTINGS group (Users / API Keys / Settings). The group ordering is
 * meaningful — the renderer emits a section header the first time it
 * sees a new group, so "CONTENT" renders once, above Pages.
 */
const defaultNavItems = [
    { path: '/', label: 'Dashboard', icon: LayoutDashboard },
    { path: '/pages', label: 'Pages', icon: File, group: 'Content' },
    { path: '/posts', label: 'Posts', icon: FileText, group: 'Content', children: POSTS_CHILDREN },
    {
        path: '/page-templates',
        label: 'Templates',
        icon: Layers,
        group: 'Content',
        children: TEMPLATES_CHILDREN,
    },
    { path: '/media', label: 'Media', icon: Image, group: 'Content' },
    { path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' },
    { path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' },
    { path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' },
    { path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' },
];
export function Sidebar({ collapsed, onToggleCollapse, currentPath, onNavigate, config, }) {
    const formsSidebar = useFormsSidebar();
    const navItems = applyFormsNav(buildNavItems(config), formsSidebar);
    const { density } = useTheme();
    const compact = density === 'compact';
    return (_jsxs("aside", { className: `bg-sidebar border-sidebar-border flex h-full flex-col border-r transition-all duration-200 ${collapsed ? 'w-20' : 'w-64'}`, children: [_jsx("div", { className: `border-sidebar-border flex h-14 shrink-0 items-center border-b px-4 ${collapsed ? 'justify-center' : ''}`, children: _jsx(BrandLogo, { config: config, collapsed: collapsed }) }), _jsx("nav", { className: `flex-1 overflow-y-auto ${compact ? 'space-y-0.5 p-2' : 'space-y-1 p-3'}`, "aria-label": "Primary", children: renderNavTree(navItems, { collapsed, currentPath, onNavigate, compact }) }), _jsx("div", { className: "border-sidebar-border hidden shrink-0 border-t p-3 md:block", children: _jsx("button", { onClick: onToggleCollapse, className: `text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${collapsed ? 'justify-center' : ''}`, "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', title: collapsed ? 'Expand sidebar' : 'Collapse sidebar', children: collapsed ? (_jsx(PanelLeftOpen, { className: "h-4 w-4 shrink-0", "aria-hidden": true })) : (_jsxs(_Fragment, { children: [_jsx(PanelLeftClose, { className: "h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("span", { className: "font-medium", children: "Collapse sidebar" })] })) }) })] }));
}
/**
 * Live unread counts for the Forms submenu: a total surfaced on the Forms nav
 * item plus per-form counts that drive the "All Entries" + per-form children.
 * Refreshes on mount and whenever a forms/entries mutation fires the shared
 * event (mark read, archive, notify toggle, …). Best-effort: a failed fetch
 * keeps the last known value rather than surfacing an error in nav.
 */
function useFormsSidebar() {
    const [state, setState] = useState({ totalUnread: 0, forms: [] });
    useEffect(() => {
        let active = true;
        const load = () => {
            fetchFormsSidebarCounts()
                .then((counts) => {
                if (!active)
                    return;
                setState({
                    totalUnread: counts.reduce((sum, c) => sum + (c.unread ?? 0), 0),
                    forms: counts,
                });
            })
                .catch(() => {
                /* keep the last known value; nav must never break on a fetch error */
            });
        };
        load();
        const off = onFormsChanged(load);
        return () => {
            active = false;
            off();
        };
    }, []);
    return state;
}
/**
 * Return a copy of the nav tree with the Forms item expanded into a submenu:
 * a total-unread badge on the parent, an "All Entries" child, and one child
 * per active form carrying its own unread badge.
 */
function applyFormsNav(items, state) {
    return items.map((item) => {
        if (item.path !== '/forms')
            return item;
        const children = [
            { path: '/forms/entries', label: 'All Entries', icon: Inbox },
            ...state.forms.map((f) => ({
                path: `/forms/${f.formId}/submissions`,
                label: f.name,
                icon: ClipboardList,
                badge: f.unread,
            })),
        ];
        return {
            ...item,
            badge: state.totalUnread > 0 ? state.totalUnread : undefined,
            children,
        };
    });
}
function isPathActive(currentPath, itemPath) {
    // Ignore query string / hash so state-bearing routes (e.g. `/seo?tab=audit`)
    // still highlight their nav item.
    currentPath = currentPath.split('?')[0].split('#')[0];
    if (itemPath === '/')
        return currentPath === '/';
    // Special case: /posts is a prefix of /posts/types — without this
    // guard, "All Posts" (/posts) would stay highlighted while the user
    // is on /posts/types or /posts/types/new. Treat /posts as exact-only
    // when a sibling /posts/* route exists.
    if (itemPath === '/posts') {
        return currentPath === '/posts' || currentPath === '/posts/new';
    }
    // Use boundary match so /pages doesn't match /pages-something. Exact, or
    // followed by a `/` separator.
    return currentPath === itemPath || currentPath.startsWith(itemPath + '/');
}
function renderNavTree(items, ctx) {
    const nodes = [];
    let lastGroup = undefined;
    for (const item of items) {
        if (item.group && item.group !== lastGroup) {
            nodes.push(ctx.collapsed ? (_jsx("div", { className: "flex justify-center pt-3 pb-1", children: _jsx("span", { className: "border-sidebar-foreground/20 w-4 border-t", "aria-hidden": true }) }, `g-${item.group}`)) : (_jsx("div", { className: "px-3 pt-4 pb-1", children: _jsx("span", { className: "text-sidebar-foreground/55 text-[10px] font-semibold tracking-wider uppercase", children: item.group }) }, `g-${item.group}`)));
            lastGroup = item.group;
        }
        else if (!item.group) {
            lastGroup = undefined;
        }
        const hasChildren = !!item.children && item.children.length > 0;
        // Collapsed parents reveal their children in a hover/focus flyout instead
        // of inline (there's no room for indented rows in the 80px rail).
        if (hasChildren && ctx.collapsed) {
            nodes.push(_jsx(CollapsedFlyout, { item: item, ctx: ctx }, `flyout:${item.path}`));
            continue;
        }
        // Expanded parents with children render as a collapsible section
        // (chevron toggle to contract/expand the submenu).
        if (hasChildren) {
            nodes.push(_jsx(NavSection, { item: item, ctx: ctx }, `section:${item.path}`));
            continue;
        }
        // Keys are namespaced because a parent and its first child can share
        // the same path (e.g. the Posts parent and its "All Posts" child are
        // both `/posts`). Using the raw path as the key collided and made
        // React warn about — and risk dropping — duplicate siblings.
        nodes.push(_jsx(NavLink, { item: item, ctx: ctx }, `item:${item.path}`));
    }
    return nodes;
}
/**
 * Expanded parent with a collapsible submenu. The label/icon navigates to the
 * parent destination; a trailing chevron toggles the child list open/closed.
 * Defaults to open, and auto-opens when the parent or one of its children is
 * the active route so the current page is never hidden behind a collapsed
 * section.
 */
function NavSection({ item, ctx }) {
    const Icon = item.icon;
    const isActive = isPathActive(ctx.currentPath, item.path);
    const childActive = item.children.some((c) => isPathActive(ctx.currentPath, c.path));
    const [open, setOpen] = useState(true);
    const childListId = `nav-section-${item.path.replace(/[^a-z0-9]+/gi, '-')}`;
    // Keep the active section visible: if the user is on this section's route
    // (parent or child) it stays expanded regardless of the manual toggle.
    const expanded = open || isActive || childActive;
    return (_jsxs("div", { children: [_jsxs("div", { className: `flex items-center rounded-lg transition-colors ${isActive
                    ? 'bg-sidebar-accent text-sidebar-primary'
                    : 'text-sidebar-foreground hover:bg-sidebar-accent'}`, children: [_jsxs("button", { onClick: () => ctx.onNavigate(item.path), className: "flex min-w-0 flex-1 items-center gap-3 rounded-lg py-2.5 pr-1 pl-3 text-left", "aria-current": isActive ? 'page' : undefined, children: [_jsx(Icon, { className: "h-5 w-5 shrink-0", "aria-hidden": true }), _jsx("span", { className: "truncate text-sm font-medium", children: item.label })] }), !!item.badge && item.badge > 0 && (_jsx("span", { className: "bg-destructive text-destructive-foreground mr-1 inline-flex min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums", "aria-label": `${item.badge} unread`, children: item.badge > 99 ? '99+' : item.badge })), _jsx("button", { onClick: () => setOpen((v) => !v), className: "text-sidebar-foreground/55 hover:text-sidebar-foreground mr-1 flex shrink-0 items-center rounded-md p-1.5 transition-colors", "aria-label": `${expanded ? 'Collapse' : 'Expand'} ${item.label}`, "aria-expanded": expanded, "aria-controls": childListId, children: _jsx(ChevronDown, { className: `h-4 w-4 transition-transform duration-200 ${expanded ? '' : '-rotate-90'}`, "aria-hidden": true }) })] }), expanded && (_jsx("div", { id: childListId, className: "relative", children: item.children.map((child, i) => (_jsx(NavLink, { item: child, ctx: ctx, nested: true, isLast: i === item.children.length - 1 }, `child:${item.path}:${child.path}`))) }))] }));
}
function NavLink({ item, ctx, nested = false, isLast = false, }) {
    const Icon = item.icon;
    const isActive = isPathActive(ctx.currentPath, item.path);
    const badge = item.badge && item.badge > 0 ? item.badge : 0;
    const button = (_jsxs("button", { onClick: () => ctx.onNavigate(item.path), className: `flex w-full items-center gap-3 rounded-lg text-left transition-colors ${nested
            ? ctx.compact
                ? 'py-1 pr-3 pl-9'
                : 'py-1.5 pr-3 pl-9'
            : ctx.compact
                ? 'px-3 py-1.5'
                : 'px-3 py-2.5'} ${isActive
            ? 'bg-sidebar-accent text-sidebar-primary'
            : 'text-sidebar-foreground hover:bg-sidebar-accent'} ${ctx.collapsed ? 'justify-center px-3' : ''}`, title: ctx.collapsed ? item.label : '', "aria-current": isActive ? 'page' : undefined, children: [_jsxs("span", { className: "relative flex shrink-0 items-center", children: [!nested && _jsx(Icon, { className: "h-5 w-5 shrink-0", "aria-hidden": true }), nested && !ctx.collapsed && (_jsx(Icon, { className: "text-sidebar-foreground/60 h-4 w-4 shrink-0", "aria-hidden": true })), ctx.collapsed && badge > 0 && (_jsx("span", { className: "bg-destructive absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full", "aria-hidden": true }))] }), !ctx.collapsed && (_jsx("span", { className: `truncate font-medium ${nested ? 'text-[13px]' : 'text-sm'}`, children: item.label })), !ctx.collapsed && badge > 0 && (_jsx("span", { className: "bg-destructive text-destructive-foreground ml-auto inline-flex min-w-5 items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums", "aria-label": `${badge} unread`, children: badge > 99 ? '99+' : badge }))] }));
    if (!nested || ctx.collapsed)
        return button;
    // Curved-elbow tree connector (expanded only). The elbow draws the vertical
    // run from the row top to its vertical center, curves at the bottom-left
    // corner, then runs horizontally into the row. Non-last rows continue the
    // vertical line to the row bottom so it meets the next sibling's elbow.
    // The guide is centered (~21px) under the parent item's 20px icon.
    return (_jsxs("div", { className: "relative", children: [_jsx("span", { "aria-hidden": true, className: "border-sidebar-border absolute top-0 left-[21px] h-1/2 w-[15px] rounded-bl-lg border-b border-l" }), !isLast && (_jsx("span", { "aria-hidden": true, className: "border-sidebar-border absolute top-1/2 bottom-0 left-[21px] border-l" })), button] }));
}
/**
 * Collapsed-rail parent: shows the icon button and, on hover or keyboard
 * focus, a flyout listing the parent's children. Built on Radix
 * DropdownMenu so the panel is portaled (escaping the nav's `overflow`
 * clip), positioned, dismissable, and keyboard-navigable. Hover-opening is
 * controlled manually; we suppress Radix's auto-focus when opened by pointer
 * so hovering doesn't steal focus, while keyboard opening still moves focus
 * into the menu.
 */
function CollapsedFlyout({ item, ctx }) {
    const Icon = item.icon;
    const isActive = isPathActive(ctx.currentPath, item.path);
    const { resolvedTheme } = useTheme();
    const [open, setOpen] = useState(false);
    const openedByPointer = useRef(false);
    const closeTimer = useRef(null);
    const cancelClose = () => {
        if (closeTimer.current) {
            clearTimeout(closeTimer.current);
            closeTimer.current = null;
        }
    };
    const scheduleClose = () => {
        cancelClose();
        closeTimer.current = setTimeout(() => setOpen(false), 120);
    };
    return (_jsxs(DropdownMenu.Root, { open: open, onOpenChange: (next) => {
            if (!next)
                openedByPointer.current = false;
            setOpen(next);
        }, modal: false, children: [_jsx("div", { onMouseEnter: () => {
                    openedByPointer.current = true;
                    cancelClose();
                    setOpen(true);
                }, onMouseLeave: scheduleClose, children: _jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx("button", { onClick: () => ctx.onNavigate(item.path), className: `flex w-full items-center justify-center rounded-lg px-3 py-2.5 transition-colors ${isActive
                            ? 'bg-sidebar-accent text-sidebar-primary'
                            : 'text-sidebar-foreground hover:bg-sidebar-accent'}`, "aria-label": item.badge && item.badge > 0 ? `${item.label} (${item.badge} unread)` : item.label, title: item.label, "aria-current": isActive ? 'page' : undefined, children: _jsxs("span", { className: "relative flex shrink-0 items-center", children: [_jsx(Icon, { className: "h-5 w-5 shrink-0", "aria-hidden": true }), !!item.badge && item.badge > 0 && (_jsx("span", { className: "bg-destructive absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full", "aria-hidden": true }))] }) }) }) }), _jsx(DropdownMenu.Portal, { children: _jsxs(DropdownMenu.Content, { side: "right", align: "start", sideOffset: 10, onMouseEnter: cancelClose, onMouseLeave: scheduleClose, onCloseAutoFocus: (e) => {
                        // After a pointer-driven open, don't yank focus back to the rail
                        // trigger (it would leave a stray focus ring once the cursor moves
                        // away). Keyboard-driven opens keep the default focus-return.
                        if (openedByPointer.current)
                            e.preventDefault();
                    }, 
                    // Radix portals to <body>, outside the `.actuate-admin` scope where
                    // the theme tokens (`--popover`, `--radius`, …) live — without the
                    // scope class the panel renders transparent with square corners.
                    // Re-assert the scope (and current dark state) on the panel itself so
                    // it inherits the tokens regardless of portal position.
                    className: `actuate-admin ${resolvedTheme === 'dark' ? 'dark' : ''} bg-popover text-popover-foreground border-border motion-safe:animate-in motion-safe:fade-in z-50 min-w-44 overflow-hidden rounded-lg border p-1 shadow-lg`, children: [_jsx("div", { className: "text-muted-foreground border-border mb-1 border-b px-3 py-2 text-xs font-medium", children: item.label }), item.children.map((child) => {
                            const ChildIcon = child.icon;
                            const childActive = isPathActive(ctx.currentPath, child.path);
                            return (_jsxs(DropdownMenu.Item, { onSelect: () => ctx.onNavigate(child.path), className: `data-highlighted:bg-accent hover:bg-accent flex cursor-pointer items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none select-none ${childActive ? 'text-primary' : 'text-foreground'}`, "aria-current": childActive ? 'page' : undefined, children: [_jsx(ChildIcon, { className: "text-muted-foreground h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("span", { className: "truncate", children: child.label }), !!child.badge && child.badge > 0 && (_jsx("span", { className: "bg-destructive text-destructive-foreground ml-auto inline-flex min-w-5 items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums", "aria-label": `${child.badge} unread`, children: child.badge > 99 ? '99+' : child.badge }))] }, `flyout-item:${item.path}:${child.path}`));
                        })] }) })] }));
}
// ─── Config-driven nav builder ──────────────────────────────────────────────
function resolveIcon(collection) {
    const mapped = collection.admin?.icon ? ICON_MAP[collection.admin.icon] : undefined;
    if (mapped)
        return mapped;
    return collection.type === 'page' ? File : FileText;
}
/**
 * Build the sidebar nav tree from the CMS config. If no collections are
 * configured, returns the curated `defaultNavItems` tree. When
 * collections ARE configured, page-type collections inherit the
 * Pages / Saved Sections / Templates child block, and every static
 * destination (Media, Forms, SEO, Script Tags, Users, API Keys,
 * Settings) is anchored under the right Content / Settings group.
 */
export function buildNavItems(config) {
    if (!config?.collections)
        return defaultNavItems;
    const raw = config.collections;
    const collectionsList = Array.isArray(raw) ? raw : Object.values(raw);
    const visible = collectionsList.filter((c) => !c.admin?.hidden);
    // Treat `admin.group: 'Posts'` (case-insensitive) as a synonym for
    // `type: 'post'` so consumers can tag arbitrary collections into
    // the Posts area without changing the discriminator they already
    // use elsewhere (sitemaps, schema.org, etc.).
    const isPostsGroup = (c) => typeof c.admin?.group === 'string' && c.admin.group.toLowerCase() === 'posts';
    const pages = visible.filter((c) => c.type === 'page' && !c.admin?.group);
    const posts = visible.filter((c) => c.type === 'post' || isPostsGroup(c));
    const otherUngrouped = visible.filter((c) => c.type !== 'page' && c.type !== 'post' && !c.admin?.group);
    // Anything explicitly grouped that isn't a posts collection.
    const grouped = visible.filter((c) => c.admin?.group && !isPostsGroup(c) && c.type !== 'post');
    const items = [{ path: '/', label: 'Dashboard', icon: LayoutDashboard }];
    // Ungrouped collections that are neither page- nor post-type stay at
    // the top level, directly under Dashboard, before the CONTENT group.
    for (const collection of otherUngrouped) {
        items.push({
            label: collection.labels?.plural ?? collection.slug,
            path: `/${collection.slug}`,
            icon: resolveIcon(collection),
        });
    }
    // CONTENT group. Everything below shares `group: 'Content'`, so the
    // renderer emits the "CONTENT" header once — above Pages. Order:
    // Pages → Posts → static content destinations (Media / Forms / SEO /
    // Script Tags).
    // Pages-style collections render as plain Content entries (no nested
    // children). Saved Sections / Widgets now live under the dedicated
    // Templates parent below, not under Pages.
    pages.forEach((collection) => {
        items.push({
            label: collection.labels?.plural ?? collection.slug,
            path: `/${collection.slug}`,
            icon: resolveIcon(collection),
            group: 'Content',
        });
    });
    // Posts area — collapses every post-type collection under a single
    // "Posts" parent. The aggregated table at /posts and the type cards
    // at /posts/types are the entry points; individual post-type
    // collections are not enumerated in the sidebar to avoid the
    // duplicate-nav problem when a project has many types.
    if (posts.length > 0) {
        items.push({
            path: '/posts',
            label: 'Posts',
            icon: FileText,
            group: 'Content',
            children: POSTS_CHILDREN,
        });
    }
    // Templates — its own parent holding Saved Sections + Widgets (page
    // building structure, distinct from Pages content).
    items.push({
        path: '/page-templates',
        label: 'Templates',
        icon: Layers,
        group: 'Content',
        children: TEMPLATES_CHILDREN,
    });
    // Standard Content destinations (always present — they map to CMS
    // features, not collections).
    items.push({ path: '/media', label: 'Media', icon: Image, group: 'Content' }, { path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' }, { path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' }, { path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' });
    // Config-supplied custom groups land after the standard Content block
    // and before Settings. Each renders under its own header.
    const groupBuckets = new Map();
    for (const col of grouped) {
        const group = col.admin.group;
        const bucket = groupBuckets.get(group) ?? [];
        bucket.push({
            label: col.labels?.plural ?? col.slug,
            path: `/${col.slug}`,
            icon: resolveIcon(col),
            group,
        });
        groupBuckets.set(group, bucket);
    }
    for (const bucket of groupBuckets.values())
        items.push(...bucket);
    // Settings destinations — always last. Users + API Keys now live inside
    // the Settings view as tabs (see Settings.tsx), so only Settings is anchored
    // here.
    items.push({ path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' });
    return items;
}
//# sourceMappingURL=Sidebar.js.map