import { CONTRACT_LABEL, FIELD_SEPARATOR, FLOW_ARROW, LAYER_LABEL, LIST_SEPARATOR, NONE_LABEL, } from "#configuration/strings/ontology.strings"; import { DERIVED_LABEL, EXPLICIT_LABEL, INCOMING_LABEL, MECHANISM_LABEL, MEMBERSHIP_INTRO, MEMBERSHIP_TITLE, MEMBER_CATEGORIES_LABEL, OUTGOING_LABEL, RESOLUTION_INTRO, RESOLUTION_TITLE, RULE_LABEL, SCOPE_A_LABEL, SCOPE_B_LABEL, TOPOLOGY_CAPTION, TOPOLOGY_INTRO, TOPOLOGY_SECTION_TITLE, VERSUS_LABEL, } from "#configuration/strings/tension.strings"; import { LAYER_ANCHOR, MEMBERSHIP_SECTION_ID, RESOLUTION_SECTION_ID, TENSION_ANCHOR, TOPOLOGY_SECTION_ID, } from "#core/ids/ontology.ids"; import type { LayerEdgeView, LayerNodeView, LayerView, TensionView } from "#types/ontology.types"; import type { Section, Subsection } from "#types/document.types"; import { chips, diagram, edgeLine, fields, glossary, labelled, linkList, linkOf, linked, node, row, } from "#domain/converters/ontology.converter"; import { LAYER_SECTION_ICON } from "#configuration/icons/ontology.icons"; const HEADER = "flowchart TB"; const LABEL_OPEN = " -- "; const LABEL_CLOSE = " --> "; const SPACE = " "; const LAYER_ANCHOR_REF = "layer:"; const topologyDiagram = function topologyDiagram(layers: LayerView): string { const nodes = layers.nodes.map((layer) => node(LAYER_ANCHOR_REF + layer.id, layer.label)); const edges = layers.topology.map((edge) => edgeLine(edge.from.ref ?? edge.from.label, LABEL_OPEN + edge.kind + LABEL_CLOSE, edge.to.ref ?? edge.to.label), ); return diagram(HEADER, [...nodes, ...edges]); }; const edgeText = function edgeText(edges: readonly LayerEdgeView[], pick: (edge: LayerEdgeView) => string): string { return edges.length === 0 ? NONE_LABEL : edges.map((edge) => edge.kind + FLOW_ARROW + pick(edge)).join(LIST_SEPARATOR); }; const layerSubsection = function layerSubsection(layer: LayerNodeView, topology: readonly LayerEdgeView[]): Subsection { const outgoing = topology.filter((edge) => edge.from.ref === LAYER_ANCHOR_REF + layer.id); const incoming = topology.filter((edge) => edge.to.ref === LAYER_ANCHOR_REF + layer.id); return { blocks: [ chips([linked(CONTRACT_LABEL, layer.contract)]), glossary([ row(MEMBER_CATEGORIES_LABEL, linkList(layer.members)), row( OUTGOING_LABEL, edgeText(outgoing, (edge) => linkOf(edge.to)), ), row( INCOMING_LABEL, edgeText(incoming, (edge) => linkOf(edge.from)), ), ]), ], id: LAYER_ANCHOR + layer.id, title: layer.label, }; }; const topologySection = function topologySection(layers: LayerView): Section { return { blocks: [{ caption: TOPOLOGY_CAPTION, kind: "mermaid", text: topologyDiagram(layers) }], icon: LAYER_SECTION_ICON, id: TOPOLOGY_SECTION_ID, intro: TOPOLOGY_INTRO, subsections: layers.nodes.map((layer) => layerSubsection(layer, layers.topology)), title: TOPOLOGY_SECTION_TITLE, }; }; const membershipSection = function membershipSection(layers: LayerView): Section { return { icon: LAYER_SECTION_ICON, id: MEMBERSHIP_SECTION_ID, intro: MEMBERSHIP_INTRO, subsections: [ { blocks: [ glossary( layers.membership.map((entry) => row( entry.category.label, fields([ linkOf(entry.category), entry.layer === null ? NONE_LABEL : linkOf(entry.layer), ]), ), ), ), ], title: MEMBERSHIP_TITLE, }, ], title: MEMBERSHIP_TITLE, }; }; const tensionTitle = function tensionTitle(tension: TensionView): string { return tension.a.label + SPACE + VERSUS_LABEL + SPACE + tension.b.label; }; const tensionSubsection = function tensionSubsection(tension: TensionView): Subsection { return { blocks: [ chips([labelled(MECHANISM_LABEL, tension.mechanism), tension.explicit ? EXPLICIT_LABEL : DERIVED_LABEL]), glossary([ row( SCOPE_A_LABEL, linkOf(tension.a) + FIELD_SEPARATOR + (labelled(LAYER_LABEL, linkOf(tension.scopeA)) ?? ""), ), row( SCOPE_B_LABEL, linkOf(tension.b) + FIELD_SEPARATOR + (labelled(LAYER_LABEL, linkOf(tension.scopeB)) ?? ""), ), row(RULE_LABEL, tension.rule), ]), ], id: TENSION_ANCHOR + tension.id, title: tensionTitle(tension), }; }; const tensionSection = function tensionSection(layers: LayerView): Section { const ordered = [...layers.resolutions].sort((left, right) => Number(right.explicit) - Number(left.explicit)); return { icon: LAYER_SECTION_ICON, id: RESOLUTION_SECTION_ID, intro: RESOLUTION_INTRO, subsections: ordered.map(tensionSubsection), title: RESOLUTION_TITLE, }; }; export const tensionSections = function tensionSections(layers: LayerView): readonly Section[] { return [topologySection(layers), membershipSection(layers), tensionSection(layers)]; };