'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useState } from 'react';
import { AlertTriangle, Loader2, Lock } from 'lucide-react';
import { toast } from 'sonner';
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
import { useNavigationBlocker } from '../../router/index.js';
import { SchedulePublishDialog } from '../../components/SchedulePublishDialog.js';
import { EditorSeoAside } from '../../components/EditorSeoAside.js';
import { ErrorBoundary } from '../../components/ErrorBoundary.js';
import { VersionHistory } from '../../components/VersionHistory.js';
import { addSection, createSection, duplicateSection, emptyPostFromTemplate, fetchPostForEditor, fetchTemplateForEditor, missingPostFields, publishPost, removeSection, reorderSections, savePostDraft, toggleSectionVisibility, updateSectionContent, updateSectionMeta, updateSectionSettings, validatePost, } from '../../lib/post-editor-service.js';
import { defaultPostHeader } from '@actuate-media/cms-core/page-sections';
import { buildPublicPreviewUrl, createPreviewToken, resolveSiteUrl, } from '../../lib/preview-link.js';
import { hasCanvasRenderer, useAdminSectionRenderers, } from '../page-editor/sections/index.js';
import { DEFAULT_VIEWPORT } from '../page-editor/viewports.js';
import { EditorTopBar } from '../page-editor/EditorTopBar.js';
import { SitePreviewFrame } from '../page-editor/SitePreviewFrame.js';
import { useSitePreview } from '../page-editor/useSitePreview.js';
import { SectionsPanel } from '../page-editor/SectionsPanel.js';
import { SectionInspector } from '../page-editor/SectionInspector.js';
import { AddSectionModal } from '../page-editor/AddSectionModal.js';
import { ValidationSummary } from '../page-editor/ValidationSummary.js';
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js';
import { computeLiveSeoScore } from '../../lib/editor-seo-content.js';
import { PostEditorCanvas } from './PostEditorCanvas.js';
import { PostFieldsModal } from './PostFieldsModal.js';
function deriveCan(session) {
    const role = session?.user?.role;
    // Default closed: when we can't identify a role, hide the edit/publish
    // affordances rather than exposing them. The server independently enforces
    // access on every write, so this is a UX guard — but it should fail safe.
    if (!role)
        return { canEdit: false, canPublish: false };
    const editors = ['ADMIN', 'EDITOR', 'AUTHOR'];
    const publishers = ['ADMIN', 'EDITOR'];
    return { canEdit: editors.includes(role), canPublish: publishers.includes(role) };
}
export function PostSectionEditor({ postType, documentId, config, session, onNavigate, onOpenPreview, }) {
    const [post, setPost] = useState(null);
    const [header, setHeader] = useState(defaultPostHeader());
    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 [fieldsOpen, setFieldsOpen] = useState(false);
    const [seoOpen, setSeoOpen] = useState(false);
    const [historyOpen, setHistoryOpen] = useState(false);
    const [deleteTargetId, setDeleteTargetId] = useState(null);
    const [confirmPublish, setConfirmPublish] = useState(false);
    const [scheduleOpen, setScheduleOpen] = useState(false);
    const [leaveTarget, setLeaveTarget] = useState(null);
    const [validation, setValidation] = useState(null);
    const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session]);
    const customRenderers = useAdminSectionRenderers();
    const typeLabel = useMemo(() => {
        const col = config?.collections?.[postType];
        return col?.labels?.singular ?? postType;
    }, [config, postType]);
    // A misconfigured post-type 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 = missingPostFields(config, postType);
        if (missing.length === 0)
            return null;
        const plural = missing.length > 1;
        return (`The "${postType}" collection is missing required field${plural ? 's' : ''}: ` +
            `${missing.join(', ')}. Declare ${plural ? 'them' : 'it'} in your CMS config ` +
            `so the post editor can save without losing content.`);
    }, [config, postType]);
    const publishCheck = useMemo(() => (post ? validatePost(post, true) : { errors: [''], warnings: [] }), [post]);
    const publishReady = Boolean(post) && publishCheck.errors.length === 0;
    const liveSeoScore = useMemo(() => {
        if (!post)
            return 0;
        return computeLiveSeoScore({
            title: post.title,
            slug: post.slug,
            seoTitle: post.seoTitle,
            seoDescription: post.seoDescription,
            sections: post.sections,
            bodyHtml: post.body,
        });
    }, [post]);
    const publishDisabledReason = publishCheck.errors[0];
    // ─── Load ──────────────────────────────────────────────────────────
    useEffect(() => {
        let cancelled = false;
        setLoading(true);
        setLoadError(null);
        // Fail loud on a misconfigured post-type collection rather than letting
        // the user build a post whose content would be silently dropped on save.
        if (schemaError) {
            setLoadError(schemaError);
            setLoading(false);
            return;
        }
        // Always load the type template header (the post editor renders it but
        // never edits it — header design lives at the type level).
        fetchTemplateForEditor(postType)
            .then((t) => {
            if (!cancelled)
                setHeader(t.header);
        })
            .catch(() => {
            /* fall back to the default header */
        });
        const load = documentId
            ? fetchPostForEditor(postType, documentId)
            : emptyPostFromTemplate(postType);
        load
            .then((p) => {
            if (cancelled)
                return;
            setPost(p);
            setSelectedId(null);
            if (!documentId) {
                setDirty(true);
                // Name-it-first flow (matches the Page editor): a new post opens
                // straight into the fields modal so title/slug are set up front.
                setFieldsOpen(true);
            }
        })
            .catch((err) => {
            if (!cancelled)
                setLoadError(err instanceof Error ? err.message : 'Failed to load post');
        })
            .finally(() => {
            if (!cancelled)
                setLoading(false);
        });
        return () => {
            cancelled = true;
        };
    }, [postType, documentId, schemaError]);
    // ─── Unsaved-changes guard (client-side navigation + browser unload) ──
    useNavigationBlocker(dirty);
    const context = useMemo(() => ({
        title: post?.title,
        excerpt: post?.excerpt,
        body: post?.body,
        featuredImageUrl: post?.featuredImage,
        publishDate: post?.publishDate ?? post?.updatedAt ?? null,
        category: post?.category,
        author: { name: session?.user?.name || session?.user?.email },
    }), [post, session]);
    // ─── Section mutations ───────────────────────────────────────────────
    const mutateSections = useCallback((fn) => {
        setPost((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]);
    const handleFields = useCallback((patch) => {
        setPost((prev) => (prev ? { ...prev, ...patch } : prev));
        setDirty(true);
        setSaveState('idle');
    }, []);
    const handlePostBodyChange = useCallback((body) => {
        setPost((prev) => (prev ? { ...prev, body } : prev));
        setDirty(true);
        setSaveState('idle');
    }, []);
    // ─── Save / publish ──────────────────────────────────────────────────
    const handleSaveDraft = useCallback(async () => {
        if (!post)
            return false;
        const result = validatePost(post, false);
        if (result.errors.length > 0) {
            setValidation(result);
            toast.error(result.errors[0]);
            setFieldsOpen(true);
            return false;
        }
        setSaveState('saving');
        try {
            const saved = await savePostDraft(post);
            setPost(saved);
            setDirty(false);
            setSaveState('saved');
            setValidation(null);
            toast.success('Draft saved');
            if (!documentId && saved.id)
                onNavigate(`/posts/${postType}/${saved.id}/edit`);
            return true;
        }
        catch (err) {
            setSaveState('error');
            toast.error(err instanceof Error ? err.message : 'Failed to save draft');
            return false;
        }
    }, [post, documentId, postType, onNavigate]);
    // Inline branded preview — iframe of the public site rendering this draft.
    const sitePreview = useSitePreview({
        collection: postType,
        documentId: post?.id,
        slug: post?.slug,
        urlPrefix: config?.collections?.[postType]?.urlPrefix ?? postType,
        siteUrl: config?.seo?.siteUrl,
        dirty,
        saveState,
        saveDraft: handleSaveDraft,
        defaultActive: resolveDefaultSitePreview(config?.admin?.editor, 'posts'),
    });
    const sitePreviewAvailable = Boolean(post?.id && post?.slug);
    const requestPublish = useCallback(() => {
        if (!post)
            return;
        const result = validatePost(post, 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);
    }, [post]);
    const requestSchedule = useCallback(async () => {
        if (!post)
            return;
        if (!post.id) {
            toast.error('Save the post before scheduling');
            return;
        }
        const result = validatePost(post, true);
        setValidation(result.errors.length || result.warnings.length ? result : null);
        if (result.errors.length > 0) {
            toast.error('Resolve the listed issues before scheduling');
            return;
        }
        if (dirty) {
            const ok = await handleSaveDraft();
            if (!ok)
                return;
        }
        setScheduleOpen(true);
    }, [post, dirty, handleSaveDraft]);
    const handleScheduled = useCallback((next) => {
        setPost((prev) => prev
            ? {
                ...prev,
                status: next.status,
                scheduledAt: next.scheduledAt,
                scheduledUnpublishAt: next.scheduledUnpublishAt,
                publishDate: next.scheduledAt ?? prev.publishDate,
            }
            : prev);
        setValidation(null);
    }, []);
    const doPublish = useCallback(async () => {
        if (!post)
            return;
        setPublishing(true);
        try {
            const published = await publishPost(post);
            setPost(published);
            setDirty(false);
            setSaveState('saved');
            setValidation(null);
            toast.success('Post published');
            if (!documentId && published.id)
                onNavigate(`/posts/${postType}/${published.id}/edit`);
        }
        catch (err) {
            toast.error(err instanceof Error ? err.message : 'Failed to publish post');
        }
        finally {
            setPublishing(false);
        }
    }, [post, documentId, postType, onNavigate]);
    // After a version restore the server has already written the restored
    // data — refetch so the editor reflects it instead of the stale state.
    const handleRestored = useCallback(() => {
        if (!post?.id)
            return;
        fetchPostForEditor(postType, post.id)
            .then((p) => {
            setPost(p);
            setSelectedId(null);
            setSeoOpen(false);
            setDirty(false);
            setSaveState('idle');
            setValidation(null);
        })
            .catch(() => {
            toast.error('Version restored — refresh the page to see it.');
        });
    }, [postType, post?.id]);
    const handleSeoSaved = useCallback(() => {
        if (!post?.id)
            return;
        fetchPostForEditor(postType, post.id)
            .then((p) => setPost(p))
            .catch(() => {
            /* SEO API saved; editor meta will sync on next full load */
        });
    }, [postType, post?.id]);
    const handleToggleSeo = useCallback(() => {
        setSeoOpen((open) => {
            if (!open)
                setSelectedId(null);
            return !open;
        });
    }, []);
    const handleSelectSection = useCallback((id) => {
        setSeoOpen(false);
        setSelectedId(id);
    }, []);
    const guardedNavigate = useCallback((path) => {
        if (dirty)
            setLeaveTarget(path);
        else
            onNavigate(path);
    }, [dirty, onNavigate]);
    const handlePreview = useCallback(() => {
        if (!post?.id) {
            toast.error('Save the post before previewing');
            return;
        }
        const path = `/posts/${postType}/${post.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);
        }
    }, [post, postType, dirty, onOpenPreview, handleSaveDraft, guardedNavigate]);
    // "View on site": render the draft through the consumer's real public route
    // (`/<urlPrefix>/<slug>?preview=<token>`) via a signed preview token.
    const handleViewOnSite = useCallback(() => {
        if (!post?.id || !post.slug) {
            toast.error('Save the post 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(postType, post.id);
                const urlPrefix = config?.collections?.[postType]?.urlPrefix ?? postType;
                const target = buildPublicPreviewUrl({
                    siteUrl: resolveSiteUrl(config?.seo?.siteUrl),
                    urlPrefix,
                    slug: post.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');
            }
        })();
    }, [post, postType, 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 post editor\u2026" })] }));
    }
    if (loadError || !post) {
        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 post" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: loadError ?? 'Post not found.' }), _jsx("button", { type: "button", onClick: () => onNavigate('/posts'), className: "text-primary mt-2 text-sm font-medium hover:underline", children: "Back to Posts" })] })] }) }));
    }
    const selected = post.sections.find((s) => s.id === selectedId) ?? null;
    // 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(post.sections.filter((s) => s.unmanaged).map((s) => s.sectionType)));
    // Section types the canvas renders as placeholders — no built-in body
    // component and no consumer-registered admin renderer. When present the
    // editor surfaces "structure view" messaging and promotes "View on site"
    // as the primary design preview.
    const structuralTypes = Array.from(new Set(post.sections
        .filter((s) => !hasCanvasRenderer(s.sectionType) && !customRenderers[s.sectionType])
        .map((s) => s.sectionType)));
    return (_jsxs(ErrorBoundary, { children: [_jsxs("div", { className: "bg-background flex h-full flex-col overflow-hidden", children: [_jsx(EditorTopBar, { title: post.title || `New ${typeLabel}`, status: post.status, dirty: dirty, saveState: saveState, publishing: publishing, canEdit: canEdit, canPublish: canPublish && canEdit, publishReady: publishReady, publishDisabledReason: publishDisabledReason, viewport: viewport, backLabel: "Posts", onViewport: setViewport, onBack: () => guardedNavigate('/posts'), onPreview: handlePreview, onViewOnSite: post.id ? handleViewOnSite : undefined, sitePreview: sitePreview.active, onToggleSitePreview: post.id && post.slug ? sitePreview.toggle : undefined, onHistory: post.id ? () => setHistoryOpen(true) : undefined, onOpenSettings: () => setFieldsOpen(true), onOpenSeo: handleToggleSeo, seoOpen: seoOpen, seoScore: liveSeoScore, onSaveDraft: handleSaveDraft, onPublish: requestPublish, onSchedule: post.id ? requestSchedule : undefined, scheduleReady: Boolean(post.id), scheduledAt: post.scheduledAt, scheduledUnpublishAt: post.scheduledUnpublishAt, structuralTypes: structuralTypes }), 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 post 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: "relative flex flex-1 overflow-hidden", children: [_jsx(SectionsPanel, { sections: post.sections, selectedId: selectedId, canEdit: canEdit, onSelect: handleSelectSection, onReorder: handleReorder, onToggleVisibility: handleToggleVisibility, onDuplicate: handleDuplicate, onRequestDelete: (id) => setDeleteTargetId(id), onAdd: () => setAddOpen(true) }), _jsx("div", { className: "flex-1 overflow-hidden", children: sitePreview.active ? (_jsx(SitePreviewFrame, { src: sitePreview.url, viewport: viewport, reloadKey: sitePreview.reloadKey, title: `Site preview: ${post.title || `New ${typeLabel}`}` })) : (_jsx(PostEditorCanvas, { sections: post.sections, header: header, context: context, selectedId: selectedId, viewport: viewport, onSelect: handleSelectSection, onAddSection: () => setAddOpen(true), structuralTypes: structuralTypes, sitePreviewAvailable: sitePreviewAvailable && !sitePreview.active })) }), selected && !seoOpen && (_jsx(SectionInspector, { section: selected, canEdit: canEdit, onClose: () => setSelectedId(null), onContentChange: handleContentChange, onSettingsChange: handleSettingsChange, onMetaChange: handleMetaChange, onToggleVisibility: () => handleToggleVisibility(selected.id), postBody: post.body, onPostBodyChange: handlePostBodyChange })), seoOpen && (_jsx(EditorSeoAside, { entityType: "post", entityId: post.id, title: post.title, slug: post.slug, seoTitle: post.seoTitle, seoDescription: post.seoDescription, sections: post.sections, bodyHtml: post.body, canEdit: canEdit, onClose: () => setSeoOpen(false), onDraftChange: handleFields, onSaved: handleSeoSaved }))] })] }), _jsx(AddSectionModal, { open: addOpen, scope: "post", onClose: () => setAddOpen(false), onAdd: handleAdd }), post.id && (_jsx(VersionHistory, { collectionSlug: postType, documentId: post.id, open: historyOpen, onClose: () => setHistoryOpen(false), onRestore: handleRestored })), _jsx(PostFieldsModal, { open: fieldsOpen, post: post, canEdit: canEdit, fields: config?.collections?.[postType]?.fields, onClose: () => setFieldsOpen(false), onSave: handleFields }), _jsx(ConfirmDialog, { open: deleteTargetId !== null, onClose: () => setDeleteTargetId(null), onConfirm: confirmDelete, title: "Delete section?", description: "This removes the section from the post. You can undo by leaving without saving.", confirmLabel: "Delete", destructive: true }), _jsx(ConfirmDialog, { open: confirmPublish, onClose: () => setConfirmPublish(false), onConfirm: doPublish, title: "Publish this post?", description: "The current content will go live and replace the published version.", confirmLabel: "Publish" }), post.id && (_jsx(SchedulePublishDialog, { collectionSlug: postType, documentId: post.id, scheduledAt: post.scheduledAt, scheduledUnpublishAt: post.scheduledUnpublishAt, open: scheduleOpen, onClose: () => setScheduleOpen(false), onScheduled: handleScheduled })), _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=PostSectionEditor.js.map