import { type DeclaredPortKind, isKindWith, KIND, plainKindOf } from '../properties/kinds'; import { itemKindOf, structItemFields } from '../properties/listItems'; import type { PropertyDef, StructFieldDef } from '../properties/PropertySchema'; import { isStruct, type PropertyValue } from '../model/Properties'; import type { DeclaredPort, GraphNode } from './Graph'; /** The port a variable or property travels on. A kind no port declares (an enum, a key) is what it travels as. */ export function portKindOf(def: PropertyDef): DeclaredPortKind { if (isKindWith('port', def.kind)) return def.kind; const plain = plainKindOf(KIND[def.kind].travels); return plain !== undefined && isKindWith('port', plain) ? plain : 'text'; } /** * The bundle shape of a variable, if it has one: a struct variable declares it, a list takes it from * the first struct item its default holds – items declare nothing, so the value is the only source. * Both are what a Pack builds and an Expand takes apart, so both can seed a node's fields. */ export function structShapeOf(def: PropertyDef): readonly StructFieldDef[] | undefined { if (def.kind === 'struct') return def.fields; if (def.kind !== 'list') return undefined; const item = def.default.find((entry) => itemKindOf(entry) === 'struct'); return item === undefined ? undefined : itemShape(item); } /** The fields a struct item carries as a declaration would name them, nested ones included. */ function itemShape(item: PropertyValue | undefined): StructFieldDef[] { return structItemFields(item).map(({ key, kind }) => kind === 'struct' ? { key, kind, fields: itemShape(isStruct(item) ? item[key] : undefined) } : { key, kind }, ); } /** * The ports `fields` would add to `node`. A key the node already declares is left alone rather than * retyped: it may carry an edge, and the manual declaration is the one the user typed. */ export function missingFieldPorts(node: GraphNode, fields: readonly StructFieldDef[]): DeclaredPort[] { const taken = new Set((node.ports ?? []).map((p) => p.key)); return fields.filter((f) => !taken.has(f.key)).map((f) => ({ key: f.key, kind: f.kind })); }