function normalizePath(path) {
    let p = path.trim();
    if (!p.startsWith('/'))
        p = `/${p}`;
    p = p.replace(/\/{2,}/g, '/');
    if (p.length > 1)
        p = p.replace(/\/+$/, '');
    return p;
}
/**
 * Longest existing path-prefix parent, or null. Only prefixes with at
 * least one segment count — "/" (the homepage) is never a derived parent.
 */
function derivePathParent(page, idByPath) {
    const segments = normalizePath(page.path).split('/').filter(Boolean);
    for (let i = segments.length - 1; i >= 1; i--) {
        const prefix = `/${segments.slice(0, i).join('/')}`;
        const candidate = idByPath.get(prefix);
        if (candidate !== undefined && candidate !== page.id)
            return candidate;
    }
    return null;
}
function compareSiblings(a, b) {
    return (a.page.title.localeCompare(b.page.title) ||
        a.page.path.localeCompare(b.page.path) ||
        a.page.id.localeCompare(b.page.id));
}
/** Build the parent/child hierarchy for the tree view. Returns root nodes. */
export function buildPageTree(pages) {
    const byId = new Map();
    for (const p of pages)
        byId.set(p.id, p);
    // First occurrence wins on duplicate paths, keeping resolution stable
    // with respect to input order.
    const idByPath = new Map();
    for (const p of pages) {
        const norm = normalizePath(p.path);
        if (!idByPath.has(norm))
            idByPath.set(norm, p.id);
    }
    const parentOf = new Map();
    for (const p of pages) {
        const explicit = p.parentPageId && p.parentPageId !== p.id && byId.has(p.parentPageId) ? p.parentPageId : null;
        parentOf.set(p.id, explicit ?? derivePathParent(p, idByPath));
    }
    // Cycle guard: walk each page's ancestor chain; the edge that revisits a
    // seen node is severed, rooting that page. Deterministic in input order.
    for (const p of pages) {
        const seen = new Set([p.id]);
        let cur = p.id;
        for (;;) {
            const parent = parentOf.get(cur) ?? null;
            if (parent === null)
                break;
            if (seen.has(parent)) {
                parentOf.set(cur, null);
                break;
            }
            seen.add(parent);
            cur = parent;
        }
    }
    const nodeById = new Map();
    for (const p of pages)
        nodeById.set(p.id, { page: p, children: [] });
    const roots = [];
    for (const p of pages) {
        const node = nodeById.get(p.id);
        const parentId = parentOf.get(p.id) ?? null;
        const parentNode = parentId !== null ? nodeById.get(parentId) : undefined;
        if (parentNode)
            parentNode.children.push(node);
        else
            roots.push(node);
    }
    const sortRecursive = (nodes) => {
        nodes.sort(compareSiblings);
        for (const n of nodes)
            sortRecursive(n.children);
    };
    sortRecursive(roots);
    return roots;
}
/**
 * Flatten the tree into visible rows: children render only when their
 * parent's id is in `expanded`.
 */
export function flattenPageTree(nodes, expanded) {
    const rows = [];
    const walk = (list, depth) => {
        for (const node of list) {
            const hasChildren = node.children.length > 0;
            rows.push({ page: node.page, depth, hasChildren });
            if (hasChildren && expanded.has(node.page.id))
                walk(node.children, depth + 1);
        }
    };
    walk(nodes, 0);
    return rows;
}
/** Default expand state: every root node that has children starts open. */
export function defaultExpandedIds(roots) {
    return new Set(roots.filter((n) => n.children.length > 0).map((n) => n.page.id));
}
/** Ids of every page in `id`'s subtree (excluding `id` itself). */
export function collectDescendantIds(roots, id) {
    const out = new Set();
    const collect = (node) => {
        for (const child of node.children) {
            out.add(child.page.id);
            collect(child);
        }
    };
    const find = (nodes) => {
        for (const n of nodes) {
            if (n.page.id === id)
                return n;
            const hit = find(n.children);
            if (hit)
                return hit;
        }
        return undefined;
    };
    const node = find(roots);
    if (node)
        collect(node);
    return out;
}
/**
 * Validate a drag-reparent: `sourceId` becomes a child of `targetId`
 * (or a root when `targetId` is null). Rejects self-drops, drops into the
 * page's own subtree (would create a cycle), and no-op moves.
 */
export function checkReparent(roots, sourceId, targetId) {
    if (targetId === sourceId) {
        return { ok: false, reason: 'A page cannot be its own parent' };
    }
    if (targetId !== null && collectDescendantIds(roots, sourceId).has(targetId)) {
        return { ok: false, reason: 'Cannot move a page into its own sub-pages' };
    }
    const parentOf = (nodes, parent) => {
        for (const n of nodes) {
            if (n.page.id === sourceId)
                return parent;
            const hit = parentOf(n.children, n.page.id);
            if (hit !== undefined)
                return hit;
        }
        return undefined;
    };
    if ((parentOf(roots, null) ?? null) === targetId) {
        return { ok: false, reason: 'Page is already there' };
    }
    return { ok: true };
}
//# sourceMappingURL=pages-tree.js.map