'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Columns3, Copy, List, ListTree, PenLine, Plus, Search, Tag as TagIcon, Trash2, X, } from 'lucide-react';
import { toast } from 'sonner';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { adminPortalContainer } from '../../lib/portal-container.js';
import { applyPagesQuery, bulkChangePageTags, bulkDeletePages, bulkUpdatePages, deletePage, duplicatePage, fetchPagesDataset, groupPages, movePage, } from '../../lib/pages-service.js';
import { buildPageTree, checkReparent, defaultExpandedIds, flattenPageTree, } from '../../lib/pages-tree.js';
import { PostStatusBadge, SeoScoreIndicator } from '../../components/posts/badges.js';
import { PageTagChip, PageTagsCell } from '../../components/pages/tag-badges.js';
import { AuthorAvatar } from '../../components/ui/AuthorAvatar.js';
import { Button } from '../../components/ui/Button.js';
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
import { RowActionButton, RowActions } from '../../components/ui/RowActions.js';
import { PagesTreeTable } from './PagesTreeTable.js';
const TOGGLEABLE_COLUMNS = [
    { key: 'tags', label: 'Tags' },
    { key: 'author', label: 'Author' },
    { key: 'seo', label: 'SEO score' },
];
const PAGE_SIZE = 25;
const VIEW_MODE_KEY = 'actuate.pages.viewMode';
const HIDDEN_COLUMNS_KEY = 'actuate.pages.hiddenColumns';
function readStoredViewMode() {
    if (typeof window === 'undefined')
        return 'list';
    try {
        return localStorage.getItem(VIEW_MODE_KEY) === 'tree' ? 'tree' : 'list';
    }
    catch {
        return 'list';
    }
}
function readStoredHiddenColumns() {
    if (typeof window === 'undefined')
        return new Set();
    try {
        const raw = localStorage.getItem(HIDDEN_COLUMNS_KEY);
        if (!raw)
            return new Set();
        const known = new Set(TOGGLEABLE_COLUMNS.map((c) => c.key));
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed))
            return new Set();
        return new Set(parsed.filter((v) => known.has(v)));
    }
    catch {
        return new Set();
    }
}
export function PagesListView({ onNavigate }) {
    const [search, setSearch] = useState('');
    const [debouncedSearch, setDebouncedSearch] = useState('');
    const [status, setStatus] = useState('all');
    const [groupBy, setGroupBy] = useState('none');
    const [selectedTags, setSelectedTags] = useState(new Set());
    const [sortBy, setSortBy] = useState('title');
    const [dir, setDir] = useState('asc');
    const [page, setPage] = useState(1);
    const [selected, setSelected] = useState(new Set());
    const [viewMode, setViewMode] = useState(readStoredViewMode);
    const [hiddenCols, setHiddenCols] = useState(readStoredHiddenColumns);
    // null = "not yet touched" → falls back to the default expand state
    // (all root nodes with children open) computed from the current tree.
    const [expandedOverride, setExpandedOverride] = useState(null);
    const [dataset, setDataset] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [refreshToken, setRefreshToken] = useState(0);
    const [pendingDelete, setPendingDelete] = useState(false);
    const [rowDelete, setRowDelete] = useState(null);
    const debounceTimer = useRef(null);
    useEffect(() => {
        if (debounceTimer.current)
            clearTimeout(debounceTimer.current);
        debounceTimer.current = setTimeout(() => {
            setDebouncedSearch(search.trim());
            setPage(1);
        }, 250);
        return () => {
            if (debounceTimer.current)
                clearTimeout(debounceTimer.current);
        };
    }, [search]);
    const tagIds = useMemo(() => [...selectedTags], [selectedTags]);
    // Single network read. Search / sort / group / paging are applied in
    // memory below, so changing any of those never re-fetches. The dataset
    // refreshes only on mount and after a mutation (refreshToken bump).
    useEffect(() => {
        let cancelled = false;
        setLoading(true);
        setError(null);
        fetchPagesDataset()
            .then((res) => {
            if (cancelled)
                return;
            setDataset(res);
            setLoading(false);
        })
            .catch((err) => {
            if (cancelled)
                return;
            setError(err instanceof Error ? err.message : 'Failed to load pages');
            setLoading(false);
        });
        return () => {
            cancelled = true;
        };
    }, [refreshToken]);
    // Reset paging/selection when the active filters change shape.
    useEffect(() => {
        setPage(1);
        setSelected(new Set());
    }, [debouncedSearch, status, tagIds, groupBy]);
    const allPages = useMemo(() => dataset?.pages ?? [], [dataset]);
    const tags = useMemo(() => dataset?.tags ?? [], [dataset]);
    const grandTotal = allPages.length;
    const grouped = groupBy !== 'none';
    const filtered = useMemo(() => applyPagesQuery(allPages, {
        status,
        tagIds,
        search: debouncedSearch,
        sortBy,
        sortDirection: dir,
    }), [allPages, status, tagIds, debouncedSearch, sortBy, dir]);
    const total = filtered.length;
    const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
    // Pagination only applies to the flat view; grouped views render every
    // match so the buckets stay complete.
    const pages = useMemo(() => (grouped ? filtered : filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)), [filtered, grouped, page]);
    const tagBySlug = useMemo(() => {
        const m = new Map();
        for (const t of tags)
            m.set(t.slug, t);
        return m;
    }, [tags]);
    const displayGroups = useMemo(() => groupPages(pages, groupBy, tags), [pages, groupBy, tags]);
    // ─── Tree mode ─────────────────────────────────────────────────────
    const treeMode = viewMode === 'tree';
    const treeRoots = useMemo(() => buildPageTree(allPages), [allPages]);
    const defaultExpanded = useMemo(() => defaultExpandedIds(treeRoots), [treeRoots]);
    const expanded = expandedOverride ?? defaultExpanded;
    // Per the design, an active search or status filter flattens the tree:
    // every match renders at depth 0 and expand state is ignored. (Tag
    // filters and group-by don't apply in tree mode — the hierarchy *is*
    // the grouping.)
    const treeFiltersActive = debouncedSearch !== '' || status !== 'all';
    const treeRows = useMemo(() => {
        if (treeFiltersActive) {
            return applyPagesQuery(allPages, {
                status,
                search: debouncedSearch,
                sortBy: 'title',
                sortDirection: 'asc',
            }).map((p) => ({ page: p, depth: 0, hasChildren: false }));
        }
        return flattenPageTree(treeRoots, expanded);
    }, [treeFiltersActive, allPages, status, debouncedSearch, treeRoots, expanded]);
    const toggleExpand = (id) => setExpandedOverride((prev) => {
        const next = new Set(prev ?? defaultExpanded);
        if (next.has(id))
            next.delete(id);
        else
            next.add(id);
        return next;
    });
    const changeViewMode = (mode) => {
        if (mode === viewMode)
            return;
        setViewMode(mode);
        setSelected(new Set());
        try {
            localStorage.setItem(VIEW_MODE_KEY, mode);
        }
        catch {
            /* storage unavailable (private mode) — the toggle still works for the session */
        }
    };
    const toggleColumn = (key) => {
        setHiddenCols((prev) => {
            const next = new Set(prev);
            if (next.has(key))
                next.delete(key);
            else
                next.add(key);
            try {
                localStorage.setItem(HIDDEN_COLUMNS_KEY, JSON.stringify([...next]));
            }
            catch {
                /* storage unavailable — the choice still applies for the session */
            }
            return next;
        });
        // Hiding the column you're sorted by would leave an invisible sort —
        // fall back to the default title sort instead.
        if ((key === 'author' && sortBy === 'author') || (key === 'seo' && sortBy === 'seo')) {
            setSortBy('title');
            setDir('asc');
        }
    };
    const showTags = !hiddenCols.has('tags');
    const showAuthor = !hiddenCols.has('author');
    const showSeo = !hiddenCols.has('seo');
    // checkbox + title + status + modified + actions are always visible.
    const columnCount = 5 + (showTags ? 1 : 0) + (showAuthor ? 1 : 0) + (showSeo ? 1 : 0);
    // The table is `table-fixed`, so the flexible Title column only gets
    // whatever the min-width leaves after the fixed columns (400px always
    // visible + optional ones). Keep ≥240px for the title or it collapses
    // and headers overlap on narrow viewports.
    const tableMinWidth = 400 + 240 + (showTags ? 200 : 0) + (showAuthor ? 170 : 0) + (showSeo ? 80 : 0);
    const handleSort = useCallback((field) => {
        if (sortBy === field) {
            setDir((d) => (d === 'asc' ? 'desc' : 'asc'));
        }
        else {
            setSortBy(field);
            setDir(field === 'modified' || field === 'seo' ? 'desc' : 'asc');
        }
    }, [sortBy]);
    // `aria-sort` belongs on the column header cell (`<th>`), not the button
    // inside it — assistive tech reads sort state from the header.
    const ariaSortFor = (field) => sortBy === field ? (dir === 'asc' ? 'ascending' : 'descending') : 'none';
    const filtersAreActive = debouncedSearch !== '' || status !== 'all' || selectedTags.size > 0;
    const clearFilters = () => {
        setSearch('');
        setStatus('all');
        setSelectedTags(new Set());
        setPage(1);
    };
    const toggleTag = (slug) => setSelectedTags((s) => {
        const next = new Set(s);
        if (next.has(slug))
            next.delete(slug);
        else
            next.add(slug);
        return next;
    });
    // ─── Selection ─────────────────────────────────────────────────────
    const toggleOne = (id) => setSelected((s) => {
        const next = new Set(s);
        if (next.has(id))
            next.delete(id);
        else
            next.add(id);
        return next;
    });
    // Selection operates over the rows currently visible in the active
    // view mode (paged flat list, or visible tree rows).
    const visiblePages = useMemo(() => (treeMode ? treeRows.map((r) => r.page) : pages), [treeMode, treeRows, pages]);
    const allOnPageSelected = visiblePages.length > 0 && visiblePages.every((p) => selected.has(p.id));
    const toggleAllOnPage = (checked) => setSelected((s) => {
        const next = new Set(s);
        if (checked)
            for (const p of visiblePages)
                next.add(p.id);
        else
            for (const p of visiblePages)
                next.delete(p.id);
        return next;
    });
    const selectedPages = useMemo(() => visiblePages.filter((p) => selected.has(p.id)), [visiblePages, selected]);
    const deletablePages = useMemo(() => selectedPages.filter((p) => !p.isHomepage), [selectedPages]);
    const blockedHomepage = selectedPages.length !== deletablePages.length;
    // ─── Bulk actions ──────────────────────────────────────────────────
    const afterBulk = () => {
        setSelected(new Set());
        setRefreshToken((t) => t + 1);
    };
    // Bulk service calls always resolve a { ok, failed } tally (they never
    // reject), so we promote a *total* failure to a rejection — that's what
    // makes `toast.promise`'s `error` branch reachable. The `await` is
    // wrapped so a rejection can't escape as an unhandled promise, and
    // `afterBulk` always runs to refresh the (possibly partially-changed) set.
    const allFailed = (r) => r.ok === 0 && r.failed > 0;
    const runBulkStatus = async (target) => {
        const ids = selectedPages.map((p) => p.id);
        if (ids.length === 0)
            return;
        const verb = target === 'PUBLISHED' ? 'Publishing' : 'Updating';
        const promise = bulkUpdatePages(ids, { status: target }).then((r) => {
            if (allFailed(r))
                throw new Error('Bulk update failed');
            return r;
        });
        toast.promise(promise, {
            loading: `${verb} ${ids.length} page${ids.length === 1 ? '' : 's'}…`,
            success: (r) => `${r.ok} updated${r.failed ? `, ${r.failed} failed` : ''}`,
            error: 'Bulk update failed',
        });
        try {
            await promise;
        }
        catch {
            /* surfaced via toast.promise */
        }
        afterBulk();
    };
    const runBulkTag = async (slug, mode) => {
        if (selectedPages.length === 0)
            return;
        const promise = bulkChangePageTags(selectedPages.map((p) => ({ id: p.id, tags: p.tags, primaryTagId: p.primaryTagId })), mode === 'add' ? { add: [slug] } : { remove: [slug] }).then((r) => {
            if (allFailed(r))
                throw new Error('Bulk tag change failed');
            return r;
        });
        const tagName = tagBySlug.get(slug)?.name ?? slug;
        toast.promise(promise, {
            loading: `${mode === 'add' ? 'Adding' : 'Removing'} "${tagName}"…`,
            success: (r) => `${r.ok} page${r.ok === 1 ? '' : 's'} updated${r.failed ? `, ${r.failed} failed` : ''}`,
            error: 'Bulk tag change failed',
        });
        try {
            await promise;
        }
        catch {
            /* surfaced via toast.promise */
        }
        afterBulk();
    };
    const runBulkDelete = async () => {
        const ids = deletablePages.map((p) => p.id);
        if (ids.length === 0)
            return;
        const promise = bulkDeletePages(ids).then((r) => {
            if (allFailed(r))
                throw new Error('Bulk delete failed');
            return r;
        });
        toast.promise(promise, {
            loading: `Deleting ${ids.length} page${ids.length === 1 ? '' : 's'}…`,
            success: (r) => `${r.ok} deleted${r.failed ? `, ${r.failed} failed` : ''}`,
            error: 'Bulk delete failed',
        });
        try {
            await promise;
        }
        catch {
            /* surfaced via toast.promise */
        }
        afterBulk();
    };
    // ─── Row actions ───────────────────────────────────────────────────
    const runDuplicate = async (p) => {
        const promise = duplicatePage(p.id).then((r) => {
            if (r.error)
                throw new Error(r.error);
            return r;
        });
        toast.promise(promise, {
            loading: `Duplicating "${p.title}"…`,
            success: `"${p.title}" duplicated`,
            error: (e) => e.message || 'Duplicate failed',
        });
        try {
            await promise;
            setRefreshToken((t) => t + 1);
        }
        catch {
            /* surfaced via toast.promise */
        }
    };
    /** Drag-reparent from the tree view: `sourceId` becomes a child of `targetId`. */
    const runReparent = async (sourceId, targetId) => {
        const source = allPages.find((p) => p.id === sourceId);
        const target = allPages.find((p) => p.id === targetId);
        if (!source || !target)
            return;
        const check = checkReparent(treeRoots, sourceId, targetId);
        if (!check.ok) {
            toast.error(check.reason);
            return;
        }
        const promise = movePage(sourceId, targetId).then((r) => {
            if (r.error)
                throw new Error(r.error);
        });
        toast.promise(promise, {
            loading: `Moving "${source.title}"…`,
            success: `"${source.title}" moved under "${target.title}"`,
            error: (e) => e.message || 'Move failed',
        });
        try {
            await promise;
            // Make sure the new parent is expanded so the moved row stays visible.
            setExpandedOverride((prev) => {
                const next = new Set(prev ?? defaultExpanded);
                next.add(targetId);
                return next;
            });
            setRefreshToken((t) => t + 1);
        }
        catch {
            /* surfaced via toast.promise */
        }
    };
    const runRowDelete = async (p) => {
        const promise = deletePage(p.id).then((r) => {
            if (r.error)
                throw new Error(r.error);
        });
        toast.promise(promise, {
            loading: `Deleting "${p.title}"…`,
            success: `"${p.title}" moved to trash`,
            error: (e) => e.message || 'Delete failed',
        });
        try {
            await promise;
            setSelected((s) => {
                const next = new Set(s);
                next.delete(p.id);
                return next;
            });
            setRefreshToken((t) => t + 1);
        }
        catch {
            /* surfaced via toast.promise */
        }
    };
    // ─── Render ────────────────────────────────────────────────────────
    return (_jsxs("div", { className: "flex h-full flex-col gap-4 p-4 sm:p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-[var(--txt)]", children: "Pages" }), _jsx("p", { className: "text-sm text-[var(--sub)]", children: treeMode
                                    ? `${grandTotal} page${grandTotal === 1 ? '' : 's'} total`
                                    : `${grandTotal} page${grandTotal === 1 ? '' : 's'} · organized by tags` })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", className: "shadow-sm", onClick: () => onNavigate('/pages/new'), leftIcon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: "New page" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--sub)]", "aria-hidden": true }), _jsx("input", { type: "search", value: search, onChange: (e) => setSearch(e.target.value), "aria-label": "Search pages", placeholder: "Search pages\u2026", className: "focus:border-primary focus:ring-primary/30 w-full rounded-lg border border-[var(--bdr)] bg-[var(--card)] py-2 pr-3 pl-9 text-sm text-[var(--txt)] shadow-sm placeholder:text-[var(--sub)] focus:ring-2 focus:outline-none" })] }), !treeMode && (_jsx(FilterSelect, { label: "Group by", value: groupBy, onChange: (v) => setGroupBy(v), options: [
                            { value: 'none', label: 'None' },
                            { value: 'tag', label: 'Tag' },
                            { value: 'status', label: 'Status' },
                            { value: 'author', label: 'Author' },
                        ] })), _jsx(FilterSelect, { label: "Status", value: status, onChange: (v) => {
                            setStatus(v);
                            setPage(1);
                        }, options: [
                            { value: 'all', label: 'All statuses' },
                            { value: 'PUBLISHED', label: 'Published' },
                            { value: 'DRAFT', label: 'Draft' },
                            { value: 'SCHEDULED', label: 'Scheduled' },
                        ] }), _jsx(ViewModeToggle, { mode: viewMode, onChange: changeViewMode }), !treeMode && _jsx(ColumnsMenu, { hidden: hiddenCols, onToggle: toggleColumn }), _jsx("span", { className: "ml-auto text-sm text-[var(--sub)] tabular-nums", children: treeMode
                            ? treeFiltersActive
                                ? `${treeRows.length} result${treeRows.length === 1 ? '' : 's'}`
                                : `${grandTotal} page${grandTotal === 1 ? '' : 's'}`
                            : `${total} of ${grandTotal}` })] }), !treeMode && (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium tracking-wider text-[var(--sub)] uppercase", children: "Tags" }), tags.map((t) => (_jsx(PageTagChip, { name: t.name, color: t.color, count: t.pageCount, selected: selectedTags.has(t.slug), onToggle: () => toggleTag(t.slug) }, t.slug))), selectedTags.size > 0 && (_jsxs("button", { type: "button", onClick: () => setSelectedTags(new Set()), className: "inline-flex items-center gap-1 text-xs text-[var(--sub)] hover:text-[var(--txt)]", children: [_jsx(X, { className: "h-3 w-3", "aria-hidden": true }), "Clear tags"] })), _jsxs("button", { type: "button", onClick: () => onNavigate('/pages/tags'), className: "text-primary ml-auto inline-flex items-center gap-1 text-xs font-medium hover:underline", children: [_jsx(TagIcon, { className: "h-3 w-3", "aria-hidden": true }), "Manage tags"] })] })), selected.size > 0 && (_jsx(BulkActionBar, { count: selected.size, tags: tags, blockedHomepage: blockedHomepage, onClear: () => setSelected(new Set()), onPublish: () => runBulkStatus('PUBLISHED'), onDraft: () => runBulkStatus('DRAFT'), onAddTag: (slug) => runBulkTag(slug, 'add'), onRemoveTag: (slug) => runBulkTag(slug, 'remove'), onDelete: () => setPendingDelete(true), deletableCount: deletablePages.length })), _jsx("div", { className: "overflow-hidden rounded-xl border border-[var(--bdr)] bg-[var(--card)] shadow-sm", children: treeMode ? (_jsx(PagesTreeTable, { rows: treeRows, loading: loading, error: error, flattened: treeFiltersActive, selected: selected, expanded: expanded, onToggleExpand: toggleExpand, onToggleSelect: toggleOne, onToggleAll: toggleAllOnPage, onOpen: (p) => onNavigate(`/pages/${p.id}/edit`), onAddSubPage: (p) => onNavigate(`/pages/new?parent=${encodeURIComponent(p.id)}`), onDuplicate: (p) => void runDuplicate(p), onDelete: (p) => setRowDelete(p), onReparent: (sourceId, targetId) => void runReparent(sourceId, targetId), canReparent: (sourceId, targetId) => checkReparent(treeRoots, sourceId, targetId).ok, onRetry: () => setRefreshToken((t) => t + 1), onClearFilters: clearFilters, onNewPage: () => onNavigate('/pages/new') })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full table-fixed", style: { minWidth: tableMinWidth }, role: "table", "aria-label": "Pages", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: '40px' } }), _jsx("col", {}), showTags && _jsx("col", { style: { width: '200px' } }), showAuthor && _jsx("col", { style: { width: '170px' } }), _jsx("col", { style: { width: '120px' } }), _jsx("col", { style: { width: '130px' } }), showSeo && _jsx("col", { style: { width: '80px' } }), _jsx("col", { style: { width: '110px' } })] }), _jsx("thead", { className: "bg-muted/40 border-b border-[var(--bdr)]", children: _jsxs("tr", { className: "text-xs text-[var(--sub)]", children: [_jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("input", { type: "checkbox", "aria-label": "Select all pages on this page", checked: allOnPageSelected, onChange: (e) => toggleAllOnPage(e.target.checked), className: "accent-primary rounded border-[var(--bdr)]" }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('title'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Title", field: "title", current: sortBy, dir: dir, onSort: handleSort }) }), showTags && (_jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("span", { className: "text-xs font-medium tracking-wider uppercase", children: "Tags" }) })), showAuthor && (_jsx("th", { scope: "col", "aria-sort": ariaSortFor('author'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Author", field: "author", current: sortBy, dir: dir, onSort: handleSort }) })), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('status'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Status", field: "status", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('modified'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Last Modified", field: "modified", current: sortBy, dir: dir, onSort: handleSort }) }), showSeo && (_jsx("th", { scope: "col", "aria-sort": ariaSortFor('seo'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "SEO", field: "seo", current: sortBy, dir: dir, onSort: handleSort }) })), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-right", children: _jsx("span", { className: "sr-only", children: "Actions" }) })] }) }), _jsxs("tbody", { children: [loading && pages.length === 0 && _jsx(LoadingRows, { columns: columnCount }), !loading && error && (_jsx("tr", { children: _jsxs("td", { colSpan: columnCount, className: "px-3 py-12 text-center", children: [_jsx("div", { className: "mb-2 text-sm font-medium text-[var(--txt)]", children: error }), _jsx("button", { onClick: () => setRefreshToken((t) => t + 1), className: "text-primary text-sm hover:underline", children: "Try again" })] }) })), !loading && !error && pages.length === 0 && (_jsx(EmptyRow, { columns: columnCount, filtersActive: filtersAreActive, onClear: clearFilters, onNewPage: () => onNavigate('/pages/new') })), !loading &&
                                                !error &&
                                                displayGroups.map((group) => (_jsx(GroupBlock, { showHeader: grouped, label: group.label, color: group.color, count: group.pages.length, columns: columnCount, children: group.pages.map((p) => (_jsx(PageRow, { page: p, selected: selected.has(p.id), showTags: showTags, showAuthor: showAuthor, showSeo: showSeo, resolveTag: (slug) => tagBySlug.get(slug), onSelect: () => toggleOne(p.id), onOpen: () => onNavigate(`/pages/${p.id}/edit`), onDuplicate: () => void runDuplicate(p), onDelete: () => setRowDelete(p) }, p.id))) }, group.key)))] })] }) }), !grouped && (_jsx(PaginationFooter, { page: page, totalPages: totalPages, total: total, pageSize: PAGE_SIZE, onPage: setPage }))] })) }), _jsx(ConfirmDialog, { open: rowDelete !== null, title: `Delete "${rowDelete?.title ?? ''}"?`, description: "The page moves to trash and can be restored for 30 days.", confirmLabel: "Delete", destructive: true, onClose: () => setRowDelete(null), onConfirm: () => {
                    if (rowDelete)
                        void runRowDelete(rowDelete);
                    setRowDelete(null);
                } }), _jsx(ConfirmDialog, { open: pendingDelete, title: `Delete ${deletablePages.length} page${deletablePages.length === 1 ? '' : 's'}?`, description: blockedHomepage
                    ? 'The homepage cannot be deleted and will be skipped. The remaining pages move to trash and can be restored for 30 days.'
                    : 'The selected pages move to trash and can be restored for 30 days.', confirmLabel: "Delete", destructive: true, onClose: () => setPendingDelete(false), onConfirm: () => {
                    void runBulkDelete();
                } })] }));
}
// ─── Sub-components ──────────────────────────────────────────────────
function SortHeader({ label, field, current, dir, onSort, }) {
    const active = current === field;
    return (_jsxs("button", { type: "button", onClick: () => onSort(field), className: `inline-flex items-center gap-1 text-xs font-medium tracking-wider uppercase transition-colors hover:text-[var(--txt)] ${active ? 'text-[var(--txt)]' : 'text-[var(--sub)]'}`, children: [_jsx("span", { children: label }), active &&
                (dir === 'asc' ? (_jsx(ChevronUp, { className: "h-3 w-3", "aria-hidden": true })) : (_jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": true })))] }));
}
/**
 * List / Tree segmented control (`.view-toggle` in the design). The
 * choice persists in localStorage so the preferred mode survives reloads.
 */
function ViewModeToggle({ mode, onChange, }) {
    const btn = (target, label, icon) => {
        const active = mode === target;
        return (_jsx("button", { type: "button", "aria-label": label, title: label, "aria-pressed": active, onClick: () => onChange(target), className: `flex h-[30px] w-[34px] items-center justify-center transition-colors duration-[var(--motion-fast)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none ${active
                ? 'bg-[var(--acc-l)] text-[var(--acc)]'
                : 'bg-[var(--card)] text-[var(--muted)] hover:text-[var(--acc)]'}`, children: icon }));
    };
    return (_jsxs("div", { role: "group", "aria-label": "View mode", className: "flex overflow-hidden rounded-lg border border-[var(--bdr)] shadow-sm", children: [btn('list', 'List view', _jsx(List, { size: 15, "aria-hidden": true })), _jsx("span", { "aria-hidden": true, className: "w-px bg-[var(--bdr)]" }), btn('tree', 'Tree view', _jsx(ListTree, { size: 15, "aria-hidden": true }))] }));
}
/**
 * Column visibility menu — the product translation of the design's
 * floating "Tweaks" panel. Optional columns can be hidden per user;
 * the choice persists in localStorage alongside the view mode.
 */
function ColumnsMenu({ hidden, onToggle, }) {
    return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": "Choose visible columns", className: "inline-flex items-center gap-1.5 rounded-lg border border-[var(--bdr)] bg-[var(--card)] px-2.5 py-1.5 text-sm text-[var(--txt)] shadow-sm transition-colors hover:bg-[var(--acc-l)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none", children: [_jsx(Columns3, { size: 15, "aria-hidden": true }), "Columns", _jsx(ChevronDown, { className: "h-3 w-3 text-[var(--sub)]", "aria-hidden": true })] }) }), _jsx(DropdownMenu.Portal, { container: adminPortalContainer(), children: _jsxs(DropdownMenu.Content, { align: "end", sideOffset: 4, className: "bg-popover z-50 w-44 rounded-lg border border-[var(--bdr)] p-1 shadow-md", children: [_jsx(DropdownMenu.Label, { className: "px-2 py-1.5 text-[10px] font-medium tracking-wider text-[var(--sub)] uppercase", children: "Columns" }), TOGGLEABLE_COLUMNS.map((col) => {
                            const visible = !hidden.has(col.key);
                            return (_jsxs(DropdownMenu.CheckboxItem, { checked: visible, onCheckedChange: () => onToggle(col.key), onSelect: (e) => e.preventDefault(), className: "focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-[var(--txt)] outline-none", children: [_jsx("span", { "aria-hidden": true, className: `flex h-4 w-4 items-center justify-center rounded border ${visible
                                            ? 'bg-primary border-transparent text-white'
                                            : 'border-[var(--bdr)] bg-[var(--card)]'}`, children: visible && _jsx(Check, { size: 11, strokeWidth: 3 }) }), col.label] }, col.key));
                        })] }) })] }));
}
function FilterSelect({ label, value, onChange, options, }) {
    return (_jsxs("label", { className: "inline-flex items-center gap-1.5 text-sm text-[var(--txt)]", children: [_jsxs("span", { className: "text-[var(--sub)]", children: [label, ":"] }), _jsx("select", { value: value, onChange: (e) => onChange(e.target.value), className: "focus:border-primary focus:ring-primary/30 rounded-lg border border-[var(--bdr)] bg-[var(--card)] px-2.5 py-1.5 text-sm text-[var(--txt)] shadow-sm focus:ring-2 focus:outline-none", children: options.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) })] }));
}
function GroupBlock({ showHeader, label, color, count, columns, children, }) {
    return (_jsxs(_Fragment, { children: [showHeader && (_jsx("tr", { className: "bg-muted/30 border-b border-[var(--bdr)]", children: _jsx("td", { colSpan: columns, className: "px-3 py-2", children: _jsxs("span", { className: "inline-flex items-center gap-2 text-xs font-medium tracking-wider text-[var(--txt)] uppercase", children: [color && _jsx("span", { "aria-hidden": true, className: `h-2 w-2 rounded-full ${dotFor(color)}` }), label, _jsx("span", { className: "text-[var(--sub)] tabular-nums", children: count })] }) }) })), children] }));
}
function dotFor(color) {
    const map = {
        purple: 'bg-brand',
        green: 'bg-success',
        orange: 'bg-warning',
        red: 'bg-destructive',
        blue: 'bg-info',
        pink: 'bg-chart-4',
    };
    return map[color] ?? 'bg-muted-foreground';
}
function BulkActionBar({ count, tags, blockedHomepage, deletableCount, onClear, onPublish, onDraft, onAddTag, onRemoveTag, onDelete, }) {
    return (_jsxs("div", { className: "border-primary/30 bg-primary/5 flex flex-wrap items-center gap-2 rounded-lg border px-4 py-2", children: [_jsxs("span", { className: "text-sm font-medium text-[var(--txt)]", children: [count, " selected"] }), _jsx("div", { className: "bg-border h-4 w-px", "aria-hidden": true }), _jsx("button", { type: "button", onClick: onPublish, className: "rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]", children: "Publish" }), _jsx("button", { type: "button", onClick: onDraft, className: "rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]", children: "Move to draft" }), _jsx(TagMenu, { label: "Add tag", tags: tags, onPick: onAddTag }), _jsx(TagMenu, { label: "Remove tag", tags: tags, onPick: onRemoveTag }), _jsxs("button", { type: "button", onClick: onDelete, disabled: deletableCount === 0, title: blockedHomepage ? 'The homepage is excluded from deletion' : undefined, className: "text-destructive hover:bg-destructive/10 inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40", children: [_jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true }), "Delete"] }), _jsxs("button", { type: "button", onClick: onClear, className: "ml-auto inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm text-[var(--sub)] transition-colors hover:text-[var(--txt)]", children: [_jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true }), "Clear"] })] }));
}
function TagMenu({ label, tags, onPick, }) {
    return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]", children: [label, _jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": true })] }) }), _jsx(DropdownMenu.Portal, { container: adminPortalContainer(), children: _jsx(DropdownMenu.Content, { align: "start", sideOffset: 4, className: "bg-popover z-50 max-h-64 w-44 overflow-auto rounded-lg border border-[var(--bdr)] p-1 shadow-md", children: tags.length === 0 ? (_jsx("div", { className: "px-2 py-1.5 text-xs text-[var(--sub)]", children: "No tags yet" })) : (tags.map((t) => (_jsxs(DropdownMenu.Item, { onSelect: () => onPick(t.slug), className: "focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-[var(--txt)] outline-none", children: [_jsx("span", { "aria-hidden": true, className: `h-2 w-2 rounded-full ${dotFor(t.color ?? 'gray')}` }), t.name] }, t.slug)))) }) })] }));
}
function PageRow({ page, selected, showTags, showAuthor, showSeo, resolveTag, onSelect, onOpen, onDuplicate, onDelete, }) {
    return (_jsxs("tr", { className: `group border-b border-[var(--bdr)] transition-colors last:border-b-0 hover:bg-[var(--acc-l)] ${selected ? 'bg-[var(--acc-l)]' : ''}`, children: [_jsx("td", { className: "px-3 py-3", onClick: (e) => e.stopPropagation(), children: _jsx("input", { type: "checkbox", "aria-label": `Select "${page.title}"`, checked: selected, onChange: onSelect, className: "accent-primary rounded border-[var(--bdr)]" }) }), _jsx("td", { className: "px-3 py-3", children: _jsxs("button", { type: "button", onClick: onOpen, className: "block w-full min-w-0 text-left", children: [_jsx("span", { className: "block truncate text-sm font-medium text-[var(--txt)] group-hover:text-[var(--acc)]", children: page.title }), _jsx("span", { className: "block truncate text-xs text-[var(--sub)]", children: page.path })] }) }), showTags && (_jsx("td", { className: "px-3 py-3", children: _jsx(PageTagsCell, { tagSlugs: page.tags, primaryTagId: page.primaryTagId, resolve: resolveTag }) })), showAuthor && (_jsx("td", { className: "px-3 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(AuthorAvatar, { name: page.authorName, size: 28 }), _jsx("span", { className: "truncate text-sm text-[var(--txt)]", children: page.authorName })] }) })), _jsx("td", { className: "px-3 py-3", children: _jsx(PostStatusBadge, { status: page.status }) }), _jsx("td", { className: "px-3 py-3 text-sm text-[var(--sub)] tabular-nums", children: formatDateShort(page.lastModified) }), showSeo && (_jsx("td", { className: "px-3 py-3", children: _jsx(SeoScoreIndicator, { score: page.seoScore }) })), _jsx("td", { className: "px-3 py-3", children: _jsxs(RowActions, { children: [_jsx(RowActionButton, { "aria-label": `Edit "${page.title}"`, title: "Edit", onClick: onOpen, children: _jsx(PenLine, { size: 13, "aria-hidden": true }) }), _jsx(RowActionButton, { "aria-label": `Duplicate "${page.title}"`, title: "Duplicate", onClick: onDuplicate, children: _jsx(Copy, { size: 12, "aria-hidden": true }) }), _jsx(RowActionButton, { "aria-label": `Delete "${page.title}"`, title: page.isHomepage ? 'The homepage cannot be deleted' : 'Delete', danger: true, disabled: page.isHomepage, onClick: onDelete, children: _jsx(Trash2, { size: 12, "aria-hidden": true }) })] }) })] }));
}
function LoadingRows({ columns }) {
    return (_jsx(_Fragment, { children: Array.from({ length: 8 }).map((_, i) => (_jsx("tr", { className: "border-b border-[var(--bdr)] last:border-b-0", children: Array.from({ length: columns }).map((__, j) => (_jsx("td", { className: "px-3 py-3", children: _jsx("div", { className: "bg-muted h-4 w-full animate-pulse rounded" }) }, j))) }, i))) }));
}
function EmptyRow({ columns, filtersActive, onClear, onNewPage, }) {
    return (_jsx("tr", { children: _jsxs("td", { colSpan: columns, className: "px-3 py-16 text-center", children: [_jsx("div", { className: "mb-2 text-sm font-medium text-[var(--txt)]", children: filtersActive ? 'No pages match your filters' : 'No pages yet' }), _jsx("div", { className: "mb-4 text-sm text-[var(--sub)]", children: filtersActive
                        ? 'Try clearing the search, tags, or status filter above.'
                        : 'Create your first page to get started.' }), filtersActive ? (_jsx("button", { type: "button", onClick: onClear, className: "text-primary text-sm hover:underline", children: "Clear filters" })) : (_jsx(Button, { type: "button", variant: "primary", size: "sm", className: "shadow-sm", onClick: onNewPage, leftIcon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: "New page" }))] }) }));
}
function PaginationFooter({ page, totalPages, total, pageSize, onPage, }) {
    const start = total === 0 ? 0 : (page - 1) * pageSize + 1;
    const end = Math.min(total, page * pageSize);
    return (_jsxs("div", { className: "flex items-center justify-between border-t border-[var(--bdr)] px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-[var(--sub)] tabular-nums", children: [start, "\u2013", end, " of ", total, " page", total === 1 ? '' : 's'] }), _jsxs("nav", { className: "flex items-center gap-1", "aria-label": "Pages pagination", children: [_jsx("button", { type: "button", onClick: () => onPage(Math.max(1, page - 1)), disabled: page === 1, "aria-label": "Previous page", className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)] disabled:cursor-not-allowed disabled:opacity-40", children: _jsx(ChevronLeft, { className: "h-4 w-4", "aria-hidden": true }) }), pageNumbers(page, totalPages).map((p, idx) => p === '…' ? (_jsx("span", { "aria-hidden": true, className: "inline-flex h-8 w-8 items-center justify-center text-sm text-[var(--sub)]", children: "\u2026" }, `gap-${idx}`)) : (_jsx("button", { type: "button", onClick: () => onPage(p), "aria-current": p === page ? 'page' : undefined, className: `inline-flex h-8 w-8 items-center justify-center rounded-md text-sm tabular-nums transition-colors ${p === page
                            ? 'bg-primary text-primary-foreground'
                            : 'border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] hover:bg-[var(--acc-l)]'}`, children: p }, p))), _jsx("button", { type: "button", onClick: () => onPage(Math.min(totalPages, page + 1)), disabled: page === totalPages, "aria-label": "Next page", className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)] disabled:cursor-not-allowed disabled:opacity-40", children: _jsx(ChevronRight, { className: "h-4 w-4", "aria-hidden": true }) })] })] }));
}
// ─── Helpers ─────────────────────────────────────────────────────────
function formatDateShort(input) {
    const d = new Date(input);
    if (Number.isNaN(d.getTime()))
        return '—';
    return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
}
export function pageNumbers(current, total) {
    if (total <= 7)
        return Array.from({ length: total }, (_, i) => i + 1);
    const out = [1];
    const start = Math.max(2, current - 1);
    const end = Math.min(total - 1, current + 1);
    if (start > 2)
        out.push('…');
    for (let i = start; i <= end; i++)
        out.push(i);
    if (end < total - 1)
        out.push('…');
    out.push(total);
    return out;
}
//# sourceMappingURL=PagesListView.js.map