'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useState } from 'react';
import { AlertTriangle, Blocks, Loader2, Lock } from 'lucide-react';
import { toast } from 'sonner';
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
import { ErrorBoundary } from '../../components/ErrorBoundary.js';
import { addSection, createSection, duplicateSection, emptyPage, fetchPageForEditor, missingPageFields, publishPage, removeSection, reorderSections, savePageDraft, toggleSectionVisibility, updateSectionContent, updateSectionMeta, updateSectionSettings, validatePage, } from '../../lib/page-editor-service.js';
import { buildPublicPreviewUrl, createPreviewToken, resolveSiteUrl, } from '../../lib/preview-link.js';
import { DEFAULT_VIEWPORT } from './viewports.js';
import { EditorTopBar } from './EditorTopBar.js';
import { SectionsPanel } from './SectionsPanel.js';
import { EditorCanvas } from './EditorCanvas.js';
import { SectionInspector } from './SectionInspector.js';
import { AddSectionModal } from './AddSectionModal.js';
import { PageSettingsModal } from './PageSettingsModal.js';
import { ValidationSummary } from './ValidationSummary.js';
function deriveCan(session) {
    const role = session?.user?.role;
    // No session info (e.g. local dev) → allow; otherwise gate by role.
    if (!role)
        return { canEdit: true, canPublish: true };
    const editors = ['ADMIN', 'EDITOR', 'AUTHOR'];
    const publishers = ['ADMIN', 'EDITOR'];
    return { canEdit: editors.includes(role), canPublish: publishers.includes(role) };
}
export function PageSectionEditor({ documentId, config, session, onNavigate, onOpenPreview, }) {
    const [page, setPage] = useState(null);
    const [loading, setLoading] = useState(true);
    const [loadError, setLoadError] = useState(null);
    const [selectedId, setSelectedId] = useState(null);
    const [viewport, setViewport] = useState(DEFAULT_VIEWPORT);
    const [dirty, setDirty] = useState(false);
    const [saveState, setSaveState] = useState('idle');
    const [publishing, setPublishing] = useState(false);
    const [addOpen, setAddOpen] = useState(false);
    const [settingsOpen, setSettingsOpen] = useState(false);
    const [deleteTargetId, setDeleteTargetId] = useState(null);
    const [confirmPublish, setConfirmPublish] = useState(false);
    const [leaveTarget, setLeaveTarget] = useState(null);
    const [validation, setValidation] = useState(null);
    const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session]);
    // A misconfigured `pages` collection (missing the fields the editor
    // writes) would silently drop content on save — detect it up front.
    // Memoized to a string/null so the load effect can depend on it without
    // re-running when `config`'s object identity changes between renders.
    const schemaError = useMemo(() => {
        const missing = missingPageFields(config);
        if (missing.length === 0)
            return null;
        const plural = missing.length > 1;
        return (`The "pages" collection is missing required field${plural ? 's' : ''}: ` +
            `${missing.join(', ')}. Declare ${plural ? 'them' : 'it'} in your CMS config ` +
            `so the page editor can save without losing content.`);
    }, [config]);
    // ─── Load ──────────────────────────────────────────────────────────
    useEffect(() => {
        let cancelled = false;
        setLoading(true);
        setLoadError(null);
        // Fail loud on a misconfigured `pages` collection rather than letting
        // the user build a page whose content would be silently dropped on save.
        if (schemaError) {
            setLoadError(schemaError);
            setLoading(false);
            return;
        }
        if (!documentId) {
            setPage(emptyPage());
            setLoading(false);
            return;
        }
        fetchPageForEditor(documentId)
            .then((p) => {
            if (cancelled)
                return;
            setPage(p);
            // Default to a clean canvas (no selection / no inspector) to match
            // the editor's default composition; the inspector opens only once
            // the user picks a section to edit.
            setSelectedId(null);
        })
            .catch((err) => {
            if (!cancelled)
                setLoadError(err instanceof Error ? err.message : 'Failed to load page');
        })
            .finally(() => {
            if (!cancelled)
                setLoading(false);
        });
        return () => {
            cancelled = true;
        };
    }, [documentId, schemaError]);
    // ─── Unsaved-changes guard (browser unload) ──────────────────────────
    useEffect(() => {
        if (!dirty)
            return;
        function onBeforeUnload(e) {
            e.preventDefault();
            e.returnValue = '';
        }
        window.addEventListener('beforeunload', onBeforeUnload);
        return () => window.removeEventListener('beforeunload', onBeforeUnload);
    }, [dirty]);
    // ─── Section mutations ───────────────────────────────────────────────
    const mutateSections = useCallback((fn) => {
        setPage((prev) => (prev ? { ...prev, sections: fn(prev.sections) } : prev));
        setDirty(true);
        setSaveState('idle');
    }, []);
    const handleAdd = useCallback((type) => {
        const section = createSection(type);
        mutateSections((s) => addSection(s, section));
        setSelectedId(section.id);
        setAddOpen(false);
    }, [mutateSections]);
    const handleReorder = useCallback((orderedIds) => mutateSections((s) => reorderSections(s, orderedIds)), [mutateSections]);
    const handleToggleVisibility = useCallback((id) => mutateSections((s) => toggleSectionVisibility(s, id)), [mutateSections]);
    const handleDuplicate = useCallback((id) => mutateSections((s) => duplicateSection(s, id)), [mutateSections]);
    const confirmDelete = useCallback(() => {
        if (!deleteTargetId)
            return;
        mutateSections((s) => removeSection(s, deleteTargetId));
        setSelectedId((cur) => (cur === deleteTargetId ? null : cur));
        setDeleteTargetId(null);
    }, [deleteTargetId, mutateSections]);
    const handleContentChange = useCallback((patch) => {
        if (!selectedId)
            return;
        mutateSections((s) => updateSectionContent(s, selectedId, patch));
    }, [selectedId, mutateSections]);
    const handleSettingsChange = useCallback((patch) => {
        if (!selectedId)
            return;
        mutateSections((s) => updateSectionSettings(s, selectedId, patch));
    }, [selectedId, mutateSections]);
    const handleMetaChange = useCallback((patch) => {
        if (!selectedId)
            return;
        mutateSections((s) => updateSectionMeta(s, selectedId, patch));
    }, [selectedId, mutateSections]);
    // ─── Page-level settings ─────────────────────────────────────────────
    const handlePageSettings = useCallback((patch) => {
        setPage((prev) => (prev ? { ...prev, ...patch } : prev));
        setDirty(true);
        setSaveState('idle');
    }, []);
    // ─── Save / publish ──────────────────────────────────────────────────
    const handleSaveDraft = useCallback(async () => {
        if (!page)
            return false;
        const result = validatePage(page, false);
        if (result.errors.length > 0) {
            setValidation(result);
            toast.error(result.errors[0]);
            setSettingsOpen(true);
            return false;
        }
        setSaveState('saving');
        try {
            const saved = await savePageDraft(page);
            setPage(saved);
            setDirty(false);
            setSaveState('saved');
            setValidation(null);
            toast.success('Draft saved');
            if (!documentId && saved.id)
                onNavigate(`/pages/${saved.id}/edit`);
            return true;
        }
        catch (err) {
            setSaveState('error');
            toast.error(err instanceof Error ? err.message : 'Failed to save draft');
            return false;
        }
    }, [page, documentId, onNavigate]);
    const requestPublish = useCallback(() => {
        if (!page)
            return;
        const result = validatePage(page, true);
        setValidation(result.errors.length || result.warnings.length ? result : null);
        if (result.errors.length > 0) {
            toast.error('Resolve the listed issues before publishing');
            return;
        }
        setConfirmPublish(true);
    }, [page]);
    const doPublish = useCallback(async () => {
        if (!page)
            return;
        setPublishing(true);
        try {
            const published = await publishPage(page);
            setPage(published);
            setDirty(false);
            setSaveState('saved');
            setValidation(null);
            toast.success('Page published');
            if (!documentId && published.id)
                onNavigate(`/pages/${published.id}/edit`);
        }
        catch (err) {
            toast.error(err instanceof Error ? err.message : 'Failed to publish page');
        }
        finally {
            setPublishing(false);
        }
    }, [page, documentId, onNavigate]);
    // ─── Navigation with unsaved guard ───────────────────────────────────
    const guardedNavigate = useCallback((path) => {
        if (dirty)
            setLeaveTarget(path);
        else
            onNavigate(path);
    }, [dirty, onNavigate]);
    const handlePreview = useCallback(() => {
        if (!page?.id) {
            toast.error('Save the page before previewing');
            return;
        }
        const path = `/pages/${page.id}/preview`;
        // New-tab preview: save first when there are unsaved edits so the preview
        // (which reads the persisted document) reflects what's on screen.
        if (onOpenPreview) {
            onOpenPreview(path, dirty ? handleSaveDraft : undefined);
        }
        else {
            guardedNavigate(path);
        }
    }, [page, dirty, onOpenPreview, handleSaveDraft, guardedNavigate]);
    // "View on site": render the draft through the consumer's real public route
    // via a signed preview token. The blank tab is opened synchronously inside
    // the click (popup-safe), then pointed at the public URL once any unsaved
    // draft is persisted and the token is minted.
    const handleViewOnSite = useCallback(() => {
        if (!page?.id || !page.slug) {
            toast.error('Save the page before viewing it on the site');
            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;
        }
        void (async () => {
            try {
                if (dirty) {
                    const ok = await handleSaveDraft();
                    if (!ok) {
                        tab.close();
                        return;
                    }
                }
                const { token } = await createPreviewToken('pages', page.id);
                const target = buildPublicPreviewUrl({
                    siteUrl: resolveSiteUrl(config?.seo?.siteUrl),
                    urlPrefix: '',
                    slug: page.slug,
                    token,
                });
                try {
                    tab.opener = null;
                }
                catch {
                    /* same-origin guard */
                }
                tab.location.replace(target);
            }
            catch (err) {
                tab.close();
                toast.error(err instanceof Error ? err.message : 'Failed to open site preview');
            }
        })();
    }, [page, dirty, config, handleSaveDraft]);
    // ─── Render states ───────────────────────────────────────────────────
    if (loading) {
        return (_jsxs("div", { className: "bg-background flex h-full items-center justify-center", children: [_jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin", "aria-hidden": true }), _jsx("span", { className: "sr-only", children: "Loading page editor\u2026" })] }));
    }
    if (loadError || !page) {
        return (_jsx("div", { className: "bg-background flex h-full items-center justify-center p-6", children: _jsxs("div", { className: "border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4", children: [_jsx(AlertTriangle, { className: "text-destructive shrink-0", size: 20, "aria-hidden": true }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: "Failed to load page" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: loadError ?? 'Page not found.' }), _jsx("button", { type: "button", onClick: () => onNavigate('/pages'), className: "text-primary mt-2 text-sm font-medium hover:underline", children: "Back to Pages" })] })] }) }));
    }
    const selected = page.sections.find((s) => s.id === selectedId) ?? null;
    // ADR 0002: a Page-Builder-owned page (it has a `_layout`) compiles its
    // sections from the builder layout on every save, so editing them here would
    // be overwritten. Open this editor read-only and route the user to the
    // builder. The server independently refuses such edits with 409.
    const readOnly = page.builderOwned;
    const effectiveCanEdit = canEdit && !readOnly;
    // Sections whose type has no registered definition (seed/code-owned, e.g.
    // `opal-*`). They are preserved verbatim on save but can't be edited here.
    const unmanagedTypes = Array.from(new Set(page.sections.filter((s) => s.unmanaged).map((s) => s.sectionType)));
    return (_jsxs(ErrorBoundary, { children: [_jsxs("div", { className: "bg-background flex h-full flex-col overflow-hidden", children: [_jsx(EditorTopBar, { title: page.title, status: page.status, dirty: dirty, saveState: saveState, publishing: publishing, canEdit: effectiveCanEdit, canPublish: canPublish && effectiveCanEdit, viewport: viewport, onViewport: setViewport, onBack: () => guardedNavigate('/pages'), onPreview: handlePreview, onViewOnSite: page.id ? handleViewOnSite : undefined, onOpenSettings: () => setSettingsOpen(true), onSaveDraft: handleSaveDraft, onPublish: requestPublish }), readOnly && (_jsxs("div", { role: "status", className: "border-border bg-accent/40 flex items-center gap-3 border-b px-4 py-2.5", children: [_jsx(Blocks, { className: "text-muted-foreground shrink-0", size: 16, "aria-hidden": true }), _jsxs("p", { className: "text-muted-foreground flex-1 text-sm", children: ["This page is built in the", ' ', _jsx("span", { className: "text-foreground font-medium", children: "Page Builder" }), ". Its sections are generated from the builder layout and are read-only here."] }), page.id && (_jsx("button", { type: "button", onClick: () => guardedNavigate(`/page-builder/${page.id}`), className: "bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors", children: "Open in Page Builder" }))] })), unmanagedTypes.length > 0 && (_jsxs("div", { role: "status", className: "border-border bg-muted/50 flex items-start gap-3 border-b px-4 py-2.5", children: [_jsx(Lock, { className: "text-muted-foreground mt-0.5 shrink-0", size: 16, "aria-hidden": true }), _jsxs("p", { className: "text-muted-foreground flex-1 text-sm", children: ["This page has", ' ', _jsxs("span", { className: "text-foreground font-medium", children: [unmanagedTypes.length, " section type", unmanagedTypes.length > 1 ? 's' : ''] }), ' ', "managed outside the editor (", unmanagedTypes.join(', '), "). They are preserved on save but edited in your seed script or code, not here."] })] })), validation && (_jsx(ValidationSummary, { errors: validation.errors, warnings: validation.warnings, onDismiss: () => setValidation(null) })), _jsxs("div", { className: "flex flex-1 overflow-hidden", children: [_jsx(SectionsPanel, { sections: page.sections, selectedId: selectedId, canEdit: effectiveCanEdit, onSelect: setSelectedId, onReorder: handleReorder, onToggleVisibility: handleToggleVisibility, onDuplicate: handleDuplicate, onRequestDelete: (id) => setDeleteTargetId(id), onAdd: () => setAddOpen(true) }), _jsx("div", { className: "flex-1 overflow-hidden", children: _jsx(EditorCanvas, { sections: page.sections, selectedId: selectedId, viewport: viewport, onSelect: setSelectedId, onAddSection: () => setAddOpen(true) }) }), selected && (_jsx(SectionInspector, { section: selected, canEdit: effectiveCanEdit, onClose: () => setSelectedId(null), onContentChange: handleContentChange, onSettingsChange: handleSettingsChange, onMetaChange: handleMetaChange, onToggleVisibility: () => handleToggleVisibility(selected.id) }))] })] }), _jsx(AddSectionModal, { open: addOpen, onClose: () => setAddOpen(false), onAdd: handleAdd }), _jsx(PageSettingsModal, { open: settingsOpen, page: page, canEdit: effectiveCanEdit, onClose: () => setSettingsOpen(false), onSave: handlePageSettings }), _jsx(ConfirmDialog, { open: deleteTargetId !== null, onClose: () => setDeleteTargetId(null), onConfirm: confirmDelete, title: "Delete section?", description: "This removes the section from the page. You can undo by leaving without saving.", confirmLabel: "Delete", destructive: true }), _jsx(ConfirmDialog, { open: confirmPublish, onClose: () => setConfirmPublish(false), onConfirm: doPublish, title: "Publish this page?", description: "The current content will go live and replace the published version.", confirmLabel: "Publish" }), _jsx(ConfirmDialog, { open: leaveTarget !== null, onClose: () => setLeaveTarget(null), onConfirm: () => {
                    const target = leaveTarget;
                    setLeaveTarget(null);
                    setDirty(false);
                    if (target)
                        onNavigate(target);
                }, title: "Discard unsaved changes?", description: "You have unsaved changes that will be lost if you leave now.", confirmLabel: "Leave", cancelLabel: "Stay", destructive: true })] }));
}
//# sourceMappingURL=PageSectionEditor.js.map