'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useMemo, useEffect, useRef, useCallback } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { toast } from 'sonner';
import { Layout } from './layout/Layout.js';
import { adminPortalContainer } from './lib/portal-container.js';
import { ADMIN_ROOT_ATTR, getAdminRootElement } from './lib/admin-root-element.js';
import { useAdminRouter, parseRouteQuery } from './router/index.js';
import { cmsApi, ensureCsrfToken } from './lib/api.js';
import { setDocumentFavicon, BRAND_UPDATED_EVENT } from './lib/favicon.js';
import { registerSectionTypes, resetCustomSectionTypes, resolveCustomSectionTypes, } from '@actuate-media/cms-core/page-sections';
import { Dashboard } from './views/Dashboard.js';
import { CollectionList } from './views/CollectionList.js';
import { DocumentEdit } from './views/DocumentEdit.js';
import { MediaBrowser } from './views/MediaBrowser.js';
import { Settings } from './views/Settings.js';
import { Profile } from './views/Profile.js';
import { Forms } from './views/Forms.js';
import { FormEditor } from './views/FormEditor.js';
import { FormSubmissions } from './views/FormSubmissions.js';
import { FormEntries } from './views/FormEntries.js';
import { SEO } from './views/SEO.js';
import { ScriptTags } from './views/ScriptTags.js';
import { SetupWizard } from './views/SetupWizard.js';
import { Login } from './views/Login.js';
import { HiddenLoginNotFound } from './views/HiddenLoginNotFound.js';
import { ISOLATION_STYLE, useRobotsNoIndex } from './lib/root-chrome.js';
import { ForgotPassword } from './views/ForgotPassword.js';
import { ResetPassword } from './views/ResetPassword.js';
import { ErrorBoundary } from './components/ErrorBoundary.js';
import { initAdminSentry } from './lib/sentry.js';
import { ThemeProvider } from './components/ThemeProvider.js';
import { LocaleProvider } from './components/LocaleProvider.js';
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts.js';
import { useAppearanceBootstrap } from './hooks/useAppearanceBootstrap.js';
import { useIdleLogout } from './hooks/useIdleLogout.js';
import { PageBuilder } from './views/page-builder/PageBuilder.js';
import { PageSectionEditor } from './views/page-editor/PageSectionEditor.js';
import { PagePreview } from './views/page-editor/PagePreview.js';
import { AdminSectionRenderersProvider, } from './views/page-editor/sections/index.js';
import { SavedSections } from './views/page-builder/SavedSections.js';
import { PageTemplates } from './views/page-builder/PageTemplates.js';
import { Widgets } from './views/page-builder/Widgets.js';
import { PagesListView } from './views/Pages/PagesListView.js';
import { PageTagManager } from './views/Pages/PageTagManager.js';
import { PostsListView } from './views/Posts/PostsListView.js';
import { PostTypesView } from './views/Posts/PostTypesView.js';
import { PostTypeEditor } from './views/Posts/PostTypeEditor.js';
import { NewPostPicker } from './views/Posts/NewPostPicker.js';
import { NewPostTypeInstructions } from './views/Posts/NewPostTypeInstructions.js';
import { PostSectionEditor } from './views/post-editor/PostSectionEditor.js';
import { PostTemplateEditor } from './views/post-editor/PostTemplateEditor.js';
import { PostPreview } from './views/post-editor/PostPreview.js';
function AdminShell({ config, session, basePath = '/admin', initialPath = '/', loginPath, setupRequired, onSetupComplete, onLogin, onVerifyTotp, onLogout, captchaConfig, }) {
    const { currentPath, navigate, matchRoute, buildHref } = useAdminRouter(basePath, initialPath);
    const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false);
    // Sign out: revoke the server-side session (and clear the cookie) first, then
    // let the consumer drop its local session state. We clear local state even if
    // the network call fails so a user is never stuck "logged in" in the UI.
    const handleLogout = useCallback(async () => {
        try {
            await cmsApi('/auth/logout', { method: 'POST' });
        }
        catch {
            // Best-effort: proceed to clear client state regardless.
        }
        navigate(initialPath);
        await onLogout?.();
    }, [navigate, initialPath, onLogout]);
    // Open a preview route in a new browser tab so it behaves like a normal,
    // shareable page (its own URL, no admin chrome — see the standalone branch
    // below). The blank tab is opened synchronously inside the click gesture so
    // popup blockers permit it; `prepare` (e.g. saving an unsaved draft) runs
    // before we point the tab at the URL, guaranteeing the preview reflects the
    // latest saved content. If `prepare` rejects, the tab is closed.
    const openPreview = useCallback((path, prepare) => {
        if (typeof window === 'undefined')
            return;
        const tab = window.open('about:blank', '_blank');
        if (!tab) {
            toast.error('Enable pop-ups for this site to open the preview in a new tab.');
            return;
        }
        const go = (ok) => {
            if (ok === false) {
                tab?.close();
                return;
            }
            if (tab && !tab.closed) {
                // Sever the opener link for safety, then load the real URL.
                try {
                    tab.opener = null;
                }
                catch {
                    /* cross-origin guard — never throws for same-origin admin */
                }
                tab.location.replace(buildHref(path));
            }
        };
        Promise.resolve(prepare?.())
            .then(go)
            .catch(() => tab?.close());
    }, [buildHref]);
    // Idle auto-logout (Settings → Security). The policy is read from the
    // non-admin-gated /auth/session-policy endpoint so every member's client
    // enforces it, not just admins. When enabled, `timeoutHours` of inactivity
    // signs the user out through the normal logout flow (server revocation +
    // cookie clear + local state reset).
    const [sessionPolicy, setSessionPolicy] = useState(null);
    useEffect(() => {
        if (!session) {
            setSessionPolicy(null);
            return;
        }
        let cancelled = false;
        void cmsApi('/auth/session-policy').then((res) => {
            if (!cancelled && res.data && typeof res.data.timeoutHours === 'number') {
                setSessionPolicy(res.data);
            }
        });
        return () => {
            cancelled = true;
        };
    }, [session]);
    const { stayActive } = useIdleLogout({
        enabled: Boolean(session && sessionPolicy?.idleLogoutEnabled),
        timeoutMs: (sessionPolicy?.timeoutHours ?? 0) * 3_600_000,
        onIdle: handleLogout,
        // One-minute heads-up so the sign-out is never a surprise. Any activity
        // (or the toast action) resets the clock and dismisses the warning.
        warningMs: 60_000,
        onWarn: () => {
            toast.warning('You will be signed out in about a minute due to inactivity.', {
                id: 'idle-logout-warning',
                duration: 60_000,
                action: {
                    label: 'Stay signed in',
                    onClick: () => stayActive(),
                },
            });
        },
        onWarnCancelled: () => {
            toast.dismiss('idle-logout-warning');
        },
    });
    useBranding(config);
    useAppearanceBootstrap(session);
    useAdminPageTitle(config, currentPath);
    // When the session was minted via a custom Server Action (the Next.js App
    // Router pattern) the admin won't have hit /auth/login directly and thus
    // won't have an actuate_csrf cookie yet — every subsequent mutation 403s.
    // Proactively bootstrap one as soon as we mount with a session. cmsApi
    // also bootstraps lazily inside non-GET requests as a belt-and-braces.
    useEffect(() => {
        if (session) {
            void ensureCsrfToken();
        }
    }, [session]);
    // Make the Page/Post editors aware of custom section types (Add Section
    // gallery + field-driven inspector) without a separately bundled registry.
    //
    // 1. Register config-declared types synchronously so the editor has them on
    //    first paint and as the offline fallback when the API can't be reached.
    // 2. Then load the authoritative list from `/page-sections/types`, which the
    //    server builds by merging built-ins + every config-declared custom type
    //    it knows about. This is the single source of truth, so admin and server
    //    can never drift. Built-ins are skipped on registration (compiled in),
    //    so this only (re)adds custom types.
    useEffect(() => {
        resetCustomSectionTypes();
        registerSectionTypes(resolveCustomSectionTypes(config?.sections));
        let cancelled = false;
        void cmsApi('/page-sections/types').then((res) => {
            if (cancelled || !Array.isArray(res.data))
                return;
            resetCustomSectionTypes();
            registerSectionTypes(res.data);
        });
        return () => {
            cancelled = true;
        };
    }, [config]);
    const shortcuts = useMemo(() => ({
        'mod+k': () => {
            const searchInput = document.querySelector('input[placeholder*="Search"]');
            searchInput?.focus();
        },
        'mod+s': () => {
            const saveBtn = document.querySelector('[data-shortcut="save"]');
            saveBtn?.click();
        },
        escape: () => {
            const closeBtn = document.querySelector('[data-shortcut="close"]');
            closeBtn?.click();
        },
        'mod+/': () => {
            setShortcutHelpOpen((prev) => !prev);
        },
    }), []);
    useKeyboardShortcuts(shortcuts);
    // IMPORTANT: this useMemo MUST stay above the `setupRequired` / `!session`
    // early returns. React's Rules of Hooks require the same number of hooks
    // on every render — putting it below the early returns means we call N
    // hooks pre-login and N+1 post-login, which crashes AdminShell with
    // "Rendered more hooks than during the previous render." (caught by the
    // e2e auth.spec.ts → login redirects to dashboard test).
    const collectionMap = useMemo(() => {
        const map = new Map();
        if (config?.collections) {
            for (const c of Object.values(config.collections)) {
                if (c?.slug)
                    map.set(c.slug, c);
            }
        }
        if (map.size === 0) {
            map.set('pages', { slug: 'pages', type: 'page' });
            map.set('posts', { slug: 'posts' });
        }
        return map;
    }, [config]);
    // Split-login mode: the auth screens (login, wizard, forgot/reset) live on
    // their own mount (`LoginRoot` at `loginPath`), so signed-out traffic to the
    // admin mount gets a 404 lookalike. No redirect — that would leak the real
    // login location to anyone probing the well-known `/admin` path.
    const hideAuthScreens = Boolean(loginPath && loginPath !== basePath);
    if (setupRequired && onSetupComplete) {
        if (hideAuthScreens) {
            return _jsx(HiddenLoginNotFound, {});
        }
        return (_jsx(SetupWizard, { onComplete: onSetupComplete, siteName: config?.admin?.branding?.name ?? 'Actuate CMS' }));
    }
    if (!session && !setupRequired) {
        if (hideAuthScreens) {
            return _jsx(HiddenLoginNotFound, {});
        }
        if (matchRoute('/forgot-password')) {
            return _jsx(ForgotPassword, { onNavigate: navigate });
        }
        const resetMatch = matchRoute('/reset-password');
        if (resetMatch) {
            // currentPath carries the query string (router syncs it from the URL on
            // mount/popstate), so the emailed `?token=` deep link survives reloads.
            return (_jsx(ResetPassword, { onNavigate: navigate, token: parseRouteQuery(currentPath).get('token') }));
        }
        if (onLogin) {
            // Thread `config.admin.branding` through so the login screen matches the
            // sidebar (single source of truth for the logo + brand name).
            const branding = config?.admin?.branding;
            return (_jsx(Login, { onLogin: onLogin, onVerifyTotp: onVerifyTotp, onNavigate: navigate, captchaConfig: captchaConfig, branding: branding }));
        }
        return (_jsx("div", { className: "bg-background flex min-h-screen items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("h1", { className: "text-foreground mb-2 text-xl font-medium", children: "Unauthorized" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Please log in to access the admin panel." })] }) }));
    }
    function renderView() {
        if (matchRoute('/')) {
            return _jsx(Dashboard, { config: config, session: session, onNavigate: navigate });
        }
        // Reserved admin routes — must match BEFORE iterating collection slugs
        // so they cannot be shadowed by a consumer-defined collection (e.g.
        // a collection slugged `media`, `users`, or `settings`).
        if (matchRoute('/media')) {
            return _jsx(MediaBrowser, { onNavigate: navigate, currentPath: currentPath });
        }
        // ─── Posts area ────────────────────────────────────────────────
        // These take priority over the per-collection `/<slug>` matcher
        // below so a consumer-defined `posts` collection can't accidentally
        // shadow the aggregate Posts view.
        if (matchRoute('/posts/types/new')) {
            return _jsx(NewPostTypeInstructions, { onNavigate: navigate });
        }
        // Per-type template editor — must match before `/posts/types/:slug/edit`
        // is irrelevant (distinct last segment) but stays grouped with the type
        // routes so it can't be shadowed by the generic `/posts/:type/*` block.
        const postTypeTemplate = matchRoute('/posts/types/:slug/template');
        if (postTypeTemplate?.slug) {
            return (_jsx(PostTemplateEditor, { postType: postTypeTemplate.slug, config: config, session: session, onNavigate: navigate }));
        }
        const postTypeEdit = matchRoute('/posts/types/:slug/edit');
        if (postTypeEdit?.slug) {
            return _jsx(PostTypeEditor, { slug: postTypeEdit.slug, onNavigate: navigate });
        }
        if (matchRoute('/posts/types')) {
            return _jsx(PostTypesView, { onNavigate: navigate });
        }
        if (matchRoute('/posts/new')) {
            return _jsx(NewPostPicker, { onNavigate: navigate });
        }
        if (matchRoute('/posts')) {
            return _jsx(PostsListView, { onNavigate: navigate });
        }
        // Section-based Post editor. These `/posts/:type/*` patterns are tested
        // AFTER every `/posts/types/*` route above so `:type` cannot swallow the
        // reserved "types" segment.
        const postNew = matchRoute('/posts/:type/new');
        if (postNew?.type) {
            return (_jsx(PostSectionEditor, { postType: postNew.type, config: config, session: session, onNavigate: navigate, onOpenPreview: openPreview }));
        }
        const postPreview = matchRoute('/posts/:type/:id/preview');
        if (postPreview?.type && postPreview?.id) {
            return (_jsx(PostPreview, { postType: postPreview.type, documentId: postPreview.id, session: session, onNavigate: navigate }));
        }
        const postEdit = matchRoute('/posts/:type/:id/edit');
        if (postEdit?.type && postEdit?.id) {
            return (_jsx(PostSectionEditor, { postType: postEdit.type, documentId: postEdit.id, config: config, session: session, onNavigate: navigate, onOpenPreview: openPreview }));
        }
        // ─── Pages area ────────────────────────────────────────────────
        // Ordering matters: tag routes and /pages/new must be tested before
        // the `/pages/:id` matcher, otherwise `:id` swallows "tags"/"new".
        if (matchRoute('/pages/tags/new')) {
            return _jsx(PageTagManager, { onNavigate: navigate, startCreating: true });
        }
        const pageTagEdit = matchRoute('/pages/tags/:id/edit');
        if (pageTagEdit?.id) {
            // The manager is a single CRUD surface; deep-linking to a tag just
            // opens the manager (inline edit lives per-row).
            return _jsx(PageTagManager, { onNavigate: navigate });
        }
        if (matchRoute('/pages/tags')) {
            return _jsx(PageTagManager, { onNavigate: navigate });
        }
        if (matchRoute('/pages/new')) {
            // "Add sub-page" in the Pages tree view arrives with the parent id
            // in the query string (`/pages/new?parent=<id>`); matchRoute ignores
            // the query so it's parsed here and preselected in the editor.
            const newPageQuery = parseRouteQuery(currentPath);
            return (_jsx(PageSectionEditor, { initialParentId: newPageQuery.get('parent'), config: config, session: session, onNavigate: navigate, onOpenPreview: openPreview }));
        }
        const pagePreview = matchRoute('/pages/:id/preview');
        if (pagePreview?.id) {
            return _jsx(PagePreview, { documentId: pagePreview.id, onNavigate: navigate });
        }
        const pageEdit = matchRoute('/pages/:id/edit');
        if (pageEdit?.id) {
            return (_jsx(PageSectionEditor, { documentId: pageEdit.id, config: config, session: session, onNavigate: navigate, onOpenPreview: openPreview }));
        }
        const pageEditBare = matchRoute('/pages/:id');
        if (pageEditBare?.id) {
            return (_jsx(PageSectionEditor, { documentId: pageEditBare.id, config: config, session: session, onNavigate: navigate, onOpenPreview: openPreview }));
        }
        if (matchRoute('/pages')) {
            return _jsx(PagesListView, { onNavigate: navigate });
        }
        if (matchRoute('/forms/new')) {
            return _jsx(FormEditor, { onNavigate: navigate });
        }
        if (matchRoute('/forms/entries')) {
            return _jsx(FormEntries, { onNavigate: navigate });
        }
        const formEdit = matchRoute('/forms/:id/edit');
        if (formEdit?.id) {
            return _jsx(FormEditor, { formId: formEdit.id, onNavigate: navigate });
        }
        const formSubmissions = matchRoute('/forms/:id/submissions');
        if (formSubmissions?.id) {
            return _jsx(FormSubmissions, { formId: formSubmissions.id, onNavigate: navigate });
        }
        if (matchRoute('/forms')) {
            return _jsx(Forms, { onNavigate: navigate });
        }
        // Legacy deep paths map onto the new five-tab IA.
        if (matchRoute('/seo/redirects')) {
            return _jsx(SEO, { onNavigate: navigate, initialTab: "redirects" });
        }
        if (matchRoute('/seo/content') || matchRoute('/seo/canonicals')) {
            return _jsx(SEO, { onNavigate: navigate, initialTab: "content" });
        }
        if (matchRoute('/seo/technical') || matchRoute('/seo/links')) {
            return _jsx(SEO, { onNavigate: navigate, initialTab: "technical" });
        }
        if (matchRoute('/seo/audit')) {
            return _jsx(SEO, { onNavigate: navigate, initialTab: "audit" });
        }
        if (matchRoute('/seo')) {
            return _jsx(SEO, { onNavigate: navigate, initialTab: "overview" });
        }
        if (matchRoute('/script-tags/new')) {
            return _jsx(ScriptTags, { onNavigate: navigate, initialNew: true });
        }
        const scriptTagEdit = matchRoute('/script-tags/:id');
        if (scriptTagEdit?.id) {
            return _jsx(ScriptTags, { onNavigate: navigate, initialSelectedId: scriptTagEdit.id });
        }
        if (matchRoute('/script-tags')) {
            return _jsx(ScriptTags, { onNavigate: navigate });
        }
        if (matchRoute('/saved-sections')) {
            return _jsx(SavedSections, { onNavigate: navigate, config: config });
        }
        if (matchRoute('/page-templates')) {
            return _jsx(PageTemplates, { onNavigate: navigate });
        }
        if (matchRoute('/widgets')) {
            return _jsx(Widgets, { onNavigate: navigate });
        }
        // Users + API Keys moved into the Settings view as tabs. The legacy
        // top-level routes still resolve — they just land on the matching tab.
        if (matchRoute('/users')) {
            return (_jsx(Settings, { onNavigate: navigate, config: config, session: session, initialTab: "users", currentPath: currentPath }));
        }
        if (matchRoute('/api-keys')) {
            return (_jsx(Settings, { onNavigate: navigate, config: config, session: session, initialTab: "api-keys", currentPath: currentPath }));
        }
        if (matchRoute('/settings')) {
            return (_jsx(Settings, { onNavigate: navigate, config: config, session: session, currentPath: currentPath }));
        }
        if (matchRoute('/profile')) {
            return _jsx(Profile, { onNavigate: navigate, session: session });
        }
        // Page Builder routes — explicit /page-builder/* takes precedence over
        // the collection-derived routing below, but we ALSO route a collection
        // declared as `type: 'page'` through the Page Builder by default.
        const pageBuilderNew = matchRoute('/page-builder/new');
        if (pageBuilderNew) {
            // "Use template" on the Templates grid seeds the new page from a
            // saved template (`/page-builder/new?template=<id>`).
            const builderQuery = parseRouteQuery(currentPath);
            return (_jsx(PageBuilder, { templateId: builderQuery.get('template') ?? undefined, collectionSlug: "pages", config: config, onNavigate: navigate }));
        }
        const pageBuilderEdit = matchRoute('/page-builder/:id');
        if (pageBuilderEdit?.id) {
            return (_jsx(PageBuilder, { documentId: pageBuilderEdit.id, collectionSlug: "pages", config: config, onNavigate: navigate }));
        }
        const RESERVED_SLUGS = new Set([
            'media',
            'forms',
            'seo',
            'script-tags',
            'saved-sections',
            'page-templates',
            'widgets',
            'users',
            'settings',
            'profile',
            'page-builder',
            'collections',
            // `pages` is reserved so the Pages list/editor/tag routes above
            // own the `/pages/*` namespace and can't be shadowed by a
            // consumer-defined `pages` collection. The per-collection list is
            // still reachable via `/collections/pages`.
            'pages',
            // `posts` is reserved so the aggregate Posts area is reachable
            // even if a consumer defines a collection literally called
            // `posts`. The per-type collection list is still available via
            // `/collections/posts`.
            'posts',
        ]);
        for (const [slug, def] of collectionMap) {
            if (RESERVED_SLUGS.has(slug))
                continue;
            const isPageType = def.type === 'page';
            const newMatch = matchRoute(`/${slug}/new`);
            if (newMatch) {
                return isPageType ? (_jsx(PageBuilder, { collectionSlug: slug, config: config, onNavigate: navigate })) : (_jsx(DocumentEdit, { collectionSlug: slug, config: config }));
            }
            const editMatch = matchRoute(`/${slug}/:id`);
            if (editMatch?.id) {
                return isPageType ? (_jsx(PageBuilder, { documentId: editMatch.id, collectionSlug: slug, config: config, onNavigate: navigate })) : (_jsx(DocumentEdit, { collectionSlug: slug, documentId: editMatch.id, config: config }));
            }
            if (matchRoute(`/${slug}`)) {
                return _jsx(CollectionList, { collectionSlug: slug, config: config, onNavigate: navigate });
            }
        }
        const newCollMatch = matchRoute('/collections/:slug/new');
        if (newCollMatch?.slug) {
            const def = collectionMap.get(newCollMatch.slug);
            return def?.type === 'page' ? (_jsx(PageBuilder, { collectionSlug: newCollMatch.slug, config: config, onNavigate: navigate })) : (_jsx(DocumentEdit, { collectionSlug: newCollMatch.slug, config: config }));
        }
        const editCollMatch = matchRoute('/collections/:slug/:id');
        if (editCollMatch?.slug && editCollMatch.id) {
            const def = collectionMap.get(editCollMatch.slug);
            return def?.type === 'page' ? (_jsx(PageBuilder, { documentId: editCollMatch.id, collectionSlug: editCollMatch.slug, config: config, onNavigate: navigate })) : (_jsx(DocumentEdit, { collectionSlug: editCollMatch.slug, documentId: editCollMatch.id, config: config }));
        }
        const collectionMatch = matchRoute('/collections/:slug');
        if (collectionMatch?.slug) {
            return (_jsx(CollectionList, { collectionSlug: collectionMatch.slug, config: config, onNavigate: navigate }));
        }
        return (_jsxs("div", { className: "flex min-h-[400px] flex-col items-center justify-center p-6 text-center", children: [_jsx("h1", { className: "text-foreground mb-2 text-4xl font-medium", children: "404" }), _jsx("p", { className: "text-muted-foreground mb-4", children: "The page you are looking for does not exist." }), _jsx("button", { onClick: () => navigate('/'), className: "text-primary-foreground bg-primary rounded-md px-4 py-2 text-sm font-medium hover:opacity-90", children: "Back to Dashboard" })] }));
    }
    // Preview routes render as standalone pages (no sidebar/header) so a
    // new-tab preview looks like the real published page rather than an
    // embedded admin view. Everything else gets the full admin chrome.
    // Query string is stripped — currentPath carries it after reloads.
    if (currentPath.split('?')[0].endsWith('/preview')) {
        return (_jsx("div", { className: "bg-background h-full min-h-screen", children: _jsx(ErrorBoundary, { children: renderView() }) }));
    }
    return (_jsxs(Layout, { config: config, session: session, currentPath: currentPath, onNavigate: navigate, onLogout: handleLogout, children: [_jsx(ErrorBoundary, { children: renderView() }), _jsx(ShortcutHelp, { open: shortcutHelpOpen, onClose: () => setShortcutHelpOpen(false) })] }));
}
function ShortcutHelp({ open, onClose }) {
    return (_jsx(Dialog.Root, { open: open, onOpenChange: (next) => !next && onClose(), children: _jsxs(Dialog.Portal, { container: adminPortalContainer(), children: [_jsx(Dialog.Overlay, { className: "fixed inset-0 z-100 bg-black/50" }), _jsxs(Dialog.Content, { "aria-describedby": undefined, className: "bg-card text-card-foreground border-border fixed top-1/2 left-1/2 z-100 mx-4 w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 rounded-xl border p-6 shadow-2xl focus:outline-none", children: [_jsx(Dialog.Title, { className: "mb-4 text-lg font-medium", children: "Keyboard Shortcuts" }), _jsx("div", { className: "space-y-3 text-sm", children: [
                                ['⌘ K', 'Open search'],
                                ['⌘ S', 'Save document'],
                                ['⌘ /', 'Toggle this help'],
                                ['Esc', 'Close panel / modal'],
                            ].map(([key, desc]) => (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: desc }), _jsx("kbd", { className: "bg-muted rounded px-2 py-1 font-mono text-xs", children: key })] }, key))) }), _jsx(Dialog.Close, { asChild: true, children: _jsx("button", { type: "button", className: "bg-primary text-primary-foreground focus-visible:ring-ring mt-4 w-full rounded-lg py-2 text-sm hover:opacity-90 focus-visible:ring-2 focus-visible:outline-none", children: "Close" }) })] })] }) }));
}
function useBranding(config) {
    const originalFaviconRef = useRef(null);
    const originalTitleRef = useRef(null);
    const injectedLinkRef = useRef(null);
    useEffect(() => {
        const branding = config?.admin?.branding;
        // Capture the originals up-front so cleanup can restore them even after the
        // DB favicon (below) overrides whatever config set.
        originalFaviconRef.current =
            document.querySelector('link[rel="icon"]')?.href ?? null;
        originalTitleRef.current = document.title;
        const applyFavicon = (href, type) => {
            const { link, created } = setDocumentFavicon(href, type);
            if (created && !injectedLinkRef.current)
                injectedLinkRef.current = link;
        };
        // --- Static config favicon (instant, no network) ---
        // Acts as a fast default; the DB favicon below overrides it when one is set
        // in Settings → Appearance.
        if (branding?.favicon)
            applyFavicon(branding.favicon);
        // --- Document title ---
        if (branding?.title) {
            document.title = branding.title;
        }
        else if (branding?.name) {
            document.title = `${branding.name} Admin`;
        }
        // --- Primary color ---
        const adminEl = getAdminRootElement();
        if (branding?.primaryColor && adminEl) {
            adminEl.style.setProperty('--primary', branding.primaryColor);
            adminEl.style.setProperty('--sidebar-primary', branding.primaryColor);
        }
        // --- DB favicon (Settings → Appearance) ---
        // The uploaded favicon is the source of truth for the browser tab. Fetch it
        // on mount and re-apply whenever the brand is saved, so the tab icon tracks
        // the upload without a full reload. The admin request carries the session
        // cookie, so the public/brand read bypasses any shared cache (see
        // cache/http.ts) and is always fresh.
        let cancelled = false;
        const applyDbFavicon = async () => {
            try {
                const res = await cmsApi('/public/brand');
                const favicon = res.data?.favicon;
                if (!cancelled && favicon?.icon)
                    applyFavicon(favicon.icon, favicon.type ?? undefined);
            }
            catch {
                /* brand API unavailable — keep the config/default favicon */
            }
        };
        void applyDbFavicon();
        const onBrandUpdated = () => void applyDbFavicon();
        window.addEventListener(BRAND_UPDATED_EVENT, onBrandUpdated);
        return () => {
            cancelled = true;
            window.removeEventListener(BRAND_UPDATED_EVENT, onBrandUpdated);
            // Restore favicon
            const link = document.querySelector('link[rel="icon"]');
            if (link && originalFaviconRef.current)
                link.href = originalFaviconRef.current;
            if (injectedLinkRef.current) {
                injectedLinkRef.current.remove();
                injectedLinkRef.current = null;
            }
            // Restore title
            if (originalTitleRef.current !== null) {
                document.title = originalTitleRef.current;
            }
            // Remove primary color override
            if (branding?.primaryColor && adminEl) {
                adminEl.style.removeProperty('--primary');
                adminEl.style.removeProperty('--sidebar-primary');
            }
        };
    }, [config]);
}
function useAdminPageTitle(config, currentPath) {
    useEffect(() => {
        const branding = config?.admin?.branding;
        const baseName = branding?.title ?? (branding?.name ? `${branding.name} Admin` : null);
        if (!baseName)
            return;
        // Strip the query so `/settings?tab=users` titles as "Settings".
        const pathname = currentPath.split('?')[0].split('#')[0];
        const segment = pathname === '/' ? 'Dashboard' : pathname.split('/').filter(Boolean)[0];
        const pageName = segment ? segment.charAt(0).toUpperCase() + segment.slice(1) : 'Dashboard';
        document.title = `${pageName} — ${baseName}`;
    }, [config, currentPath]);
}
// Stable identity for the default (no custom renderers) case so the provider
// value doesn't change on every render.
const EMPTY_RENDERERS = {};
export function AdminRoot(props) {
    const defaultDarkMode = props.config?.admin?.branding?.darkMode;
    useRobotsNoIndex();
    useEffect(() => {
        initAdminSentry(props.sentryDsn);
    }, [props.sentryDsn]);
    return (_jsx("div", { style: ISOLATION_STYLE, className: "actuate-admin", [ADMIN_ROOT_ATTR]: '', children: _jsx(ThemeProvider, { defaultDarkMode: defaultDarkMode, children: _jsx(LocaleProvider, { config: props.config, children: _jsx(AdminSectionRenderersProvider, { value: props.sectionRenderers ?? EMPTY_RENDERERS, children: _jsx(AdminShell, { ...props }) }) }) }) }));
}
//# sourceMappingURL=AdminRoot.js.map