import { describe, expect, it } from 'vitest';
import { buildPageTree, checkReparent, collectDescendantIds, defaultExpandedIds, flattenPageTree, } from '../../lib/pages-tree.js';
/**
 * Unit coverage for the Pages tree hierarchy resolution. Pure functions —
 * locked independent of the REST layer and the view. The contract:
 * explicit `parentPageId` wins, path-prefix derivation fills the gaps,
 * and no input (orphans, cycles, duplicates) can lose or duplicate a page.
 */
function page(partial) {
    return {
        id: partial.id,
        title: partial.title,
        path: partial.path,
        slug: partial.slug ?? partial.path.split('/').filter(Boolean).pop() ?? '',
        parentPageId: partial.parentPageId ?? null,
        tags: [],
        primaryTagId: null,
        authorId: 'u1',
        authorName: 'Maria K.',
        authorInitials: 'MK',
        status: partial.status ?? 'PUBLISHED',
        publishDate: null,
        scheduledDate: null,
        lastModified: '2026-05-01',
        seoScore: null,
        seoTitle: null,
        seoDescription: null,
        templateId: partial.templateId ?? null,
        isHomepage: partial.path === '/',
        isSystemPage: false,
        createdAt: '2026-01-01',
        updatedAt: '2026-05-01',
    };
}
function titles(nodes) {
    return nodes.map((n) => n.page.title);
}
function countNodes(nodes) {
    return nodes.reduce((n, node) => n + 1 + countNodes(node.children), 0);
}
describe('buildPageTree', () => {
    it('nests children under explicit parentPageId parents', () => {
        const roots = buildPageTree([
            page({ id: 'about', title: 'About Us', path: '/about' }),
            page({ id: 'team', title: 'Our Team', path: '/team-page', parentPageId: 'about' }),
        ]);
        expect(titles(roots)).toEqual(['About Us']);
        expect(titles(roots[0].children)).toEqual(['Our Team']);
    });
    it('derives parents from path prefixes when parentPageId is null', () => {
        const roots = buildPageTree([
            page({ id: 'home', title: 'Home', path: '/' }),
            page({ id: 'about', title: 'About Us', path: '/about' }),
            page({ id: 'team', title: 'Our Team', path: '/about/team' }),
            page({ id: 'story', title: 'Our Story', path: '/about/story' }),
            page({ id: 'contact', title: 'Contact', path: '/contact' }),
        ]);
        expect(titles(roots)).toEqual(['About Us', 'Contact', 'Home']);
        const about = roots[0];
        expect(titles(about.children)).toEqual(['Our Story', 'Our Team']);
    });
    it('skips missing intermediate path levels to the longest existing prefix', () => {
        const roots = buildPageTree([
            page({ id: 'svc', title: 'Services', path: '/services' }),
            // No "/services/web" page exists — attaches to "/services".
            page({ id: 'design', title: 'Web Design', path: '/services/web/design' }),
        ]);
        expect(titles(roots)).toEqual(['Services']);
        expect(titles(roots[0].children)).toEqual(['Web Design']);
    });
    it('never derives the homepage ("/") as a path parent', () => {
        const roots = buildPageTree([
            page({ id: 'home', title: 'Home', path: '/' }),
            page({ id: 'blog', title: 'Blog', path: '/blog' }),
        ]);
        expect(titles(roots)).toEqual(['Blog', 'Home']);
    });
    it('prefers explicit parentPageId over the path prefix', () => {
        const roots = buildPageTree([
            page({ id: 'about', title: 'About', path: '/about' }),
            page({ id: 'lp', title: 'Landing', path: '/lp' }),
            // Path says "/about/…" but the explicit link says "lp" — link wins.
            page({ id: 'x', title: 'Campaign', path: '/about/campaign', parentPageId: 'lp' }),
        ]);
        const lp = roots.find((n) => n.page.id === 'lp');
        expect(titles(lp.children)).toEqual(['Campaign']);
        expect(roots.find((n) => n.page.id === 'about').children).toHaveLength(0);
    });
    it('treats orphans (parent not in the set) as roots', () => {
        const roots = buildPageTree([
            page({ id: 'a', title: 'A', path: '/a', parentPageId: 'missing' }),
            page({ id: 'b', title: 'B', path: '/b' }),
        ]);
        expect(titles(roots)).toEqual(['A', 'B']);
    });
    it('breaks parentPageId cycles to the root without losing pages', () => {
        const roots = buildPageTree([
            page({ id: 'a', title: 'A', path: '/x-a', parentPageId: 'b' }),
            page({ id: 'b', title: 'B', path: '/x-b', parentPageId: 'a' }),
            page({ id: 'c', title: 'C', path: '/x-c' }),
        ]);
        expect(countNodes(roots)).toBe(3);
        // One of the cycle members must have been rooted.
        const rootIds = roots.map((n) => n.page.id);
        expect(rootIds).toContain('c');
        expect(rootIds.some((id) => id === 'a' || id === 'b')).toBe(true);
    });
    it('ignores a self-referencing parentPageId', () => {
        const roots = buildPageTree([page({ id: 'a', title: 'A', path: '/a', parentPageId: 'a' })]);
        expect(titles(roots)).toEqual(['A']);
    });
    it('orders siblings by title then path, at every depth', () => {
        const roots = buildPageTree([
            page({ id: 'svc', title: 'Services', path: '/services' }),
            page({ id: 's2', title: 'Web Design', path: '/services/web-design' }),
            page({ id: 's1', title: 'CMS Solutions', path: '/services/cms' }),
            page({ id: 's3', title: 'SEO Services', path: '/services/seo' }),
            page({ id: 'blog', title: 'Blog', path: '/blog' }),
        ]);
        expect(titles(roots)).toEqual(['Blog', 'Services']);
        expect(titles(roots.find((n) => n.page.id === 'svc').children)).toEqual([
            'CMS Solutions',
            'SEO Services',
            'Web Design',
        ]);
    });
    it('keeps every page exactly once on messy input', () => {
        const pages = [
            page({ id: 'home', title: 'Home', path: '/' }),
            page({ id: 'dup1', title: 'Dup A', path: '/dup' }),
            page({ id: 'dup2', title: 'Dup B', path: '/dup' }),
            page({ id: 'kid', title: 'Kid', path: '/dup/kid' }),
            page({ id: 'cyc1', title: 'Cyc 1', path: '/c1', parentPageId: 'cyc2' }),
            page({ id: 'cyc2', title: 'Cyc 2', path: '/c2', parentPageId: 'cyc1' }),
            page({ id: 'orph', title: 'Orphan', path: '/orphan', parentPageId: 'gone' }),
        ];
        const roots = buildPageTree(pages);
        expect(countNodes(roots)).toBe(pages.length);
        // "/dup/kid" attaches to the first page seen with path "/dup".
        expect(roots.some((n) => n.page.id === 'dup1' && n.children.length === 1)).toBe(true);
    });
});
describe('flattenPageTree', () => {
    const roots = buildPageTree([
        page({ id: 'about', title: 'About Us', path: '/about' }),
        page({ id: 'team', title: 'Our Team', path: '/about/team' }),
        page({ id: 'blog', title: 'Blog', path: '/blog' }),
    ]);
    it('renders children only when the parent is expanded', () => {
        const collapsed = flattenPageTree(roots, new Set());
        expect(collapsed.map((r) => r.page.id)).toEqual(['about', 'blog']);
        const open = flattenPageTree(roots, new Set(['about']));
        expect(open.map((r) => r.page.id)).toEqual(['about', 'team', 'blog']);
    });
    it('reports depth and hasChildren per row', () => {
        const rows = flattenPageTree(roots, new Set(['about']));
        expect(rows[0]).toMatchObject({ depth: 0, hasChildren: true });
        expect(rows[1]).toMatchObject({ depth: 1, hasChildren: false });
        expect(rows[2]).toMatchObject({ depth: 0, hasChildren: false });
    });
});
describe('defaultExpandedIds', () => {
    it('expands only root nodes that have children', () => {
        const roots = buildPageTree([
            page({ id: 'about', title: 'About Us', path: '/about' }),
            page({ id: 'team', title: 'Our Team', path: '/about/team' }),
            page({ id: 'deep', title: 'Deep', path: '/about/team/deep' }),
            page({ id: 'blog', title: 'Blog', path: '/blog' }),
        ]);
        expect([...defaultExpandedIds(roots)]).toEqual(['about']);
    });
});
describe('collectDescendantIds', () => {
    it('returns every id in the subtree, excluding the page itself', () => {
        const roots = buildPageTree([
            page({ id: 'about', title: 'About Us', path: '/about' }),
            page({ id: 'team', title: 'Our Team', path: '/about/team' }),
            page({ id: 'deep', title: 'Deep', path: '/about/team/deep' }),
            page({ id: 'blog', title: 'Blog', path: '/blog' }),
        ]);
        expect([...collectDescendantIds(roots, 'about')].sort()).toEqual(['deep', 'team']);
        expect([...collectDescendantIds(roots, 'blog')]).toEqual([]);
        expect([...collectDescendantIds(roots, 'missing')]).toEqual([]);
    });
});
describe('checkReparent', () => {
    const roots = () => buildPageTree([
        page({ id: 'about', title: 'About Us', path: '/about' }),
        page({ id: 'team', title: 'Our Team', path: '/about/team' }),
        page({ id: 'deep', title: 'Deep', path: '/about/team/deep' }),
        page({ id: 'blog', title: 'Blog', path: '/blog' }),
    ]);
    it('allows moving a page under an unrelated page', () => {
        expect(checkReparent(roots(), 'blog', 'team')).toEqual({ ok: true });
    });
    it('allows moving a subtree up to an ancestor that is not its parent', () => {
        // deep currently sits under team; moving it directly under about is legal.
        expect(checkReparent(roots(), 'deep', 'about')).toEqual({ ok: true });
    });
    it('rejects self-drops', () => {
        expect(checkReparent(roots(), 'about', 'about').ok).toBe(false);
    });
    it('rejects drops into the page own subtree (cycle)', () => {
        expect(checkReparent(roots(), 'about', 'team').ok).toBe(false);
        expect(checkReparent(roots(), 'about', 'deep').ok).toBe(false);
    });
    it('rejects no-op moves onto the current parent', () => {
        expect(checkReparent(roots(), 'team', 'about').ok).toBe(false);
        expect(checkReparent(roots(), 'deep', 'team').ok).toBe(false);
    });
});
//# sourceMappingURL=pages-tree.test.js.map