export type SelectionEntry = { id: string path: string requirementLabel: string resolvedName: string quantity?: number slotId?: string _definition?: Record } export type StructureSelectionResult = { pieces: SelectionEntry[] products: SelectionEntry[] components: SelectionEntry[] } type CatalogMap = Map type LabelResolvers = { resolvePieceLabel: (definition: Record) => string resolveProductLabel: (definition: Record) => string resolveSubcomponentLabel: (definition: Record) => string } const isNonEmptyString = (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0 export function collectStructureSelections( root: any, catalogs: { pieceCatalogMap: CatalogMap productCatalogMap: CatalogMap componentCatalogMap: CatalogMap }, resolvers: LabelResolvers, ): StructureSelectionResult { const piecesSelected: SelectionEntry[] = [] const productsSelected: SelectionEntry[] = [] const componentsSelected: SelectionEntry[] = [] if (!root || typeof root !== 'object') { return { pieces: piecesSelected, products: productsSelected, components: componentsSelected } } const visitNode = (node: any, fallbackPath = 'racine') => { if (!node || typeof node !== 'object') { return } const nodePath = isNonEmptyString(node.path) ? node.path : fallbackPath const nodePieces = Array.isArray(node.pieces) ? node.pieces : [] nodePieces.forEach((entry: any, index: number) => { const selectedId = entry?.selectedPieceId if (!isNonEmptyString(selectedId)) { return } const definition = entry?.definition ?? entry const catalogPiece = catalogs.pieceCatalogMap.get(selectedId) piecesSelected.push({ id: selectedId, path: isNonEmptyString(entry?.path) ? entry.path : `${nodePath}:piece-${index + 1}`, requirementLabel: resolvers.resolvePieceLabel(definition), resolvedName: catalogPiece?.name || selectedId, quantity: typeof definition?.quantity === 'number' ? definition.quantity : undefined, slotId: isNonEmptyString(entry?.slotId) ? entry.slotId : undefined, _definition: definition, }) }) const nodeProducts = Array.isArray(node.products) ? node.products : [] nodeProducts.forEach((entry: any, index: number) => { const selectedId = entry?.selectedProductId if (!isNonEmptyString(selectedId)) { return } const definition = entry?.definition ?? entry const catalogProduct = catalogs.productCatalogMap.get(selectedId) productsSelected.push({ id: selectedId, path: isNonEmptyString(entry?.path) ? entry.path : `${nodePath}:product-${index + 1}`, requirementLabel: resolvers.resolveProductLabel(definition), resolvedName: catalogProduct?.name || selectedId, }) }) const nodeChildren = Array.isArray(node.subcomponents) ? node.subcomponents : Array.isArray(node.subComponents) ? node.subComponents : [] nodeChildren.forEach((child: any, index: number) => { const selectedId = child?.selectedComponentId if (isNonEmptyString(selectedId)) { const definition = child?.definition ?? child const catalogComponent = catalogs.componentCatalogMap.get(selectedId) componentsSelected.push({ id: selectedId, path: isNonEmptyString(child?.path) ? child.path : `${nodePath}:subcomponent-${index + 1}`, requirementLabel: resolvers.resolveSubcomponentLabel(definition), resolvedName: catalogComponent?.name || selectedId, }) } visitNode(child, isNonEmptyString(child?.path) ? child.path : `${nodePath}:subcomponent-${index + 1}`) }) } visitNode(root, isNonEmptyString(root?.path) ? root.path : 'racine') return { pieces: piecesSelected, products: productsSelected, components: componentsSelected } }