import type { EntryOut } from '../../types/api'; export type EntryNode = EntryOut & { key: string; children?: EntryNode[] }; /** Дерево строк ОСВ по parent_entry_id; порядок — как пришёл (по row_number). * Защита от «грязных» данных: дубль id — второй экземпляр пропускается; самоссылка и цикл — такие строки идут в корень. */ export function buildEntryTree(input: EntryOut[]): EntryNode[] { const seen = new Set(); const entries = input.filter((entry) => !seen.has(entry.id) && seen.add(entry.id)); const byId = new Map(entries.map((entry) => [entry.id, entry])); // Строка в цикле, если, идя вверх по родителям, мы вернёмся к ней самой. const inCycle = (start: EntryOut): boolean => { let current = start.parent_entry_id ? byId.get(start.parent_entry_id) : undefined; for (let steps = 0; current && steps <= entries.length; steps++) { if (current.id === start.id) return true; current = current.parent_entry_id ? byId.get(current.parent_entry_id) : undefined; } return false; }; const nodes = new Map(); for (const entry of entries) nodes.set(entry.id, { ...entry, key: entry.id }); const roots: EntryNode[] = []; for (const entry of entries) { const node = nodes.get(entry.id) as EntryNode; const parent = entry.parent_entry_id && !inCycle(entry) ? nodes.get(entry.parent_entry_id) : undefined; if (parent) { parent.children = [...(parent.children ?? []), node]; } else { roots.push(node); } } return roots; }