{"version":3,"sources":["../src/utils/handle-ids.ts","../src/utils/port-position.ts","../src/utils/power-connectors.ts","../src/utils/render-ports.ts","../src/utils/document-adapter.ts","../src/utils/endpoint-metadata.ts","../src/validation/connectors.ts","../src/validation/graph.ts","../src/validation/engine.ts","../src/core/connection-validation.ts","../src/validation/rules/shared.ts","../src/validation/rules/tier-a.ts","../src/validation/rules/tier-b.ts","../src/validation/rules/index.ts"],"names":["DIRECTIONAL_HANDLE_SUFFIX","FACE_ALIAS_HANDLE_SUFFIX","normalizeDirectionalHandleId","handleId","normalizeHandleId","isPowerPortType","portType","PDU_INLET_HANDLE_ID","BOARD_INCOMER_HANDLE_ID","POWER_INLET_HANDLE_IDS","isPowerInletHandleId","HEX_PAIR","RESERVED_SEGMENT_CHAR","encodeHandleSegment","segment","decodeHandleSegment","decoded","i","percentEscapeChar","ch","hex","baseCollidesWithGrammar","base","classifyHandleBase","escapeCollidingLabel","label","buildDevicePortBase","group","encodedGroup","buildPortHandleId","portBlockName","port","decodePortHandleId","delimiter","looksLikePowerHandleId","compact","OUTLET_BASE","FIBER_TUBE_BASE","FIBER_BASE","TUBE_BASE","SIDE_PORT_BASE","SPLIT_IO_BASE","BREAKER_WAY_BASE","fiberTube","fiber","tube","sidePort","splitIo","breakerWay","alternateShapes","shape","parseHandleId","raw","directionMatch","afterDirection","faceMatch","parsed","alternates","isRecord","value","toNumber","normalizeSemanticPositionTokens","token","canonicalizeSideToken","hasCenterToken","tokens","resolveAxisOffsetPercent","primarySide","hasLeft","hasRight","hasTop","hasBottom","hasCenter","resolveSemanticEdgePortPosition","side","offsetPercent","resolveSemanticCompositePortPosition","hasFront","hasRear","xPercent","yPercent","parseSemanticPortPosition","normalizePortPositionInput","sideRaw","semanticPosition","offsetPx","widthPercent","heightPercent","POWER_CONNECTORS","PORT_TYPE_CONNECTOR","inferConnectorFromPortType","match","hasPduInlet","data","inletConnector","inletAmps","inletGender","gender","getPDUPortType","portIndex","getPduOutletConnectorType","portTypes","position","index","explicit","toOutletPhaseIndex","phases","count","DEFAULT_FRONT_GROUP_NAME","DEFAULT_REAR_GROUP_NAME","DEFAULT_POWER_GROUP_NAME","DISTRIBUTED_OFFSET_MIN_PERCENT","DISTRIBUTED_OFFSET_MAX_PERCENT","clampPercent","normalizePortAnchor","getNormalizedPortPosition","hasExplicitAbsoluteCoordinates","hasExplicitOffset","resolveRenderPortFace","resolveRenderPortGroupName","face","getEntrySortValue","entry","toDistributedOffsetPercent","buildDistributedOffsetMap","entries","distributedEntries","offsetPercentByIndex","entriesByAnchor","anchor","anchorEntries","sortedEntries","left","right","orderDifference","toPortPosition","normalizedPosition","distributedOffsetPercent","resolvedOffsetPercent","toPort","normalizePortBlockCollection","portBlocks","normalizedBlocks","block","portsByUnpositionedSide","ports","isGroupedPortBlockArray","isFlatDevicePortArray","buildPortBlocksFromPorts","devicePorts","bucketsByKey","indexedPorts","distributedOffsetsByFace","groupName","key","existingBucket","rearPorts","bucket","distributedOffsetByIndex","getResolvedPortBlocks","MAX_POWER_PORTS","handleMetadataCache","warnedDuplicateHandleIds","warnDuplicateHandleId","normalizeCompatibilityToken","normalized","uniqueTokens","values","inferPowerCompatibilityToken","normalizedValues","inferNetworkCompatibilityToken","input","candidates","resolveEndpointCompatibilityKeys","connectorToken","mediumToken","fallbackKind","getFallbackPduPortBlocks","node","powerPortCount","_","defaultConnector","getPduPortBlocks","buildHandleMetadataMap","cached","metadata","outletPosition","addBlocks","blocks","trackPosition","inferHandleKind","lowerHandle","isStandardNetworkHandle","isCustomPortName","PDU_OUTLET_HANDLE","inferPduOutletConnectorType","resolveOutletPosition","normalizedHandle","handleMetadata","inferHandleCompatibility","kind","POWER_DISTRIBUTION_NODE_TYPES","isPowerOutletPortType","resolvePowerEndpointRole","nodeType","getPowerEndpointRole","handle","getEndpointMetadata","normalizedHandleWithDirection","parsedHandle","explicitFace","connectorType","getPduOutletPosition","IEC_60320_AMPERAGE","IEC_60320_INLETS","IEC_60320_CONNECTORS","IEC_60320_MATES","getConnectorRole","type","upper","getConnectorAmperage","region","regional","rendered","isValidIecMate","inlet","connector","accepted","POWER_SOURCE_CAPABLE_NODE_TYPES","ELEMENT_SEPARATOR","escapeKeyNodeId","nodeId","unescapeKeyNodeId","escaped","powerElementKey","wayId","parsePowerElementKey","resolvePowerEndpointConnector","fallback","isPowerEdge","edge","wayIdFromHandle","readNumber","buildPowerValidationContext","nodes","edges","options","nodeById","links","incidence","addIncidence","list","link","isSourceCapable","sourceNodeIds","id","orientedEdgeIds","nodeDepth","queue","head","current","depth","currentCannotSupply","supplyNodeId","loadNodeId","supplyHandle","loadHandle","linkById","feedsByNode","downstreamByNode","linksByWay","feeds","down","wayLinks","getFeeds","getDownstreamLinks","getDownstreamNodeIds","seen","out","stack","getUpstreamNodeIds","getDownstreamNodeIdsOfLink","edgeId","ancestorCache","getAncestorsOfLink","pending","visited","currentId","getAncestors","feed","isUnreachable","elementKey","getDepth","getDeepestShared","b","setB","best","bestDepth","bestUnreachable","unreachable","getWays","ways","wayState","way","getWayLinks","wayKey","isConsumer","sumDownstreamDrawW","field","fallbackField","total","getEndpointConnector","getLinkConnectors","describeElement","candidate","readRegion","mergeOptions","rule","config","runPowerValidation","rules","warnings","evaluatedRuleIds","skippedRuleIds","issues","baseRegion","baseContext","connectorLookupsFor","seenRuleIds","usedIssueIds","ruleConfig","severity","ruleOptions","ctx","drafts","error","draft","resolvedSeverity","suffix","POWER_SOURCE_NODE_TYPES","CIRCUIT_TERMINATION_REASON","PDU_INLET_FEED_REASON","WAY_TO_WAY_REASON","OUTLET_TO_OUTLET_REASON","num","opt","THREE_PHASE_FACTOR","isThreePhaseWay","phaseFactor","wattsToAmps","watts","voltageV","factor","powerFactor","pf","DEFAULT_PHASE_VOLTAGE_V","resolveDerating","ref","boardData","codeMode","codeDefault","maxUtilization","explicitPhase","positive","explicitLine","legacy","phaseVoltageV","lineToLineVoltageV","voltageBasis","unitInterval","wayCircuitKind","wayCircuitVoltage","derating","wayVoltage","n","ratedWays","linkMinAmperage","supply","load","round","places","downstreamLinkEdgeIds","seenNodes","consumers","pwrA01UnprotectedTap","unprotected","findUnprotectedPath","exhausted","onPath","walk","edgeIds","next","found","pwrA02SharedUpstream","sources","j","a","shared","sharedNodeId","onlyIncomer","pwrA03SamePdu","byPdu","pduId","pwrA04SingleCorded","fallbackExpected","declared","expected","pwrA05ConnectorMismatch","outletProtectionRating","rating","protectedLinkEdgeIds","direct","scope","linkIds","pwrA06ConnectorUnderBreaker","amperage","pwrA07UnpoweredByBreaker","nodeState","wayByKey","state","ancestors","tripped","open","IEC_60898_RATING_LADDER","NEC_240_6_RATING_LADDER","pwrA09NonStandardRating","ladder","pwrA10PowerLoop","parent","find","root","cursor","pwrA11WayConnectorBound","limit","ownLinkEdgeIds","seenFeeds","pwrA12LengthAdvisory","maxLengthCm","length","pwrA13CircuitTermination","elementOf","ends","end","indexOfRole","role","push","wayIds","wayIndex","near","far","farNode","preInletStrip","landing","wayLabel","inletIndex","tierAPowerRules","computeWayLoad","diversityFactor","circuit","pwrB01BreakerOverSubscribed","pwrB02BreakerOverAllocated","pwrB03DeratingMode","boards","boardId","kinds","volts","reason","hundredPercentRated","nestedWayPairs","pairs","byKey","upstream","pairKey","pwrB04SelectivityScreen","mcbRatio","fuseRatio","pair","up","ratio","threshold","pwrB05UpstreamBelowDownstream","linkDownstreamWatts","pwrB08PhaseImbalance","maxImbalancePct","perPhaseW","downstreamLinks","attributed","max","min","imbalancePct","tierBPowerRules","standardPowerRules","getStandardPowerRule"],"mappings":"AAEA,IAAMA,EAAAA,CAA4B,oBAAA,CAC5BC,EAAAA,CAA2B,kBAAA,CAM1B,SAASC,CAAAA,CACdC,CAAAA,CACQ,CACR,OAAKA,EACEA,CAAAA,CAAS,OAAA,CAAQH,EAAAA,CAA2B,EAAE,EAD/B,EAExB,CAiBO,SAASI,EAAAA,CAAkBD,CAAAA,CAA6C,CAC7E,OAAOD,CAAAA,CAA6BC,CAAQ,CAAA,CAAE,OAAA,CAC5CF,EAAAA,CACA,EACF,CACF,CAKO,SAASI,CAAAA,CAAgBC,CAAAA,CAAuC,CACrE,OACE,OAAOA,CAAAA,EAAa,QAAA,EAAYA,CAAAA,CAAS,WAAA,EAAY,CAAE,QAAA,CAAS,OAAO,CAE3E,CAUO,IAAMC,EAAAA,CAAsB,QAQtBC,EAAAA,CAA0B,SAAA,CAEjCC,EAAAA,CAAyB,IAAI,IAAY,CAC7CF,EAAAA,CACAC,EACF,CAAC,EAQM,SAASE,EAAAA,CAAqBP,CAAAA,CAA8C,CACjF,OAAKA,CAAAA,CACEM,EAAAA,CAAuB,GAAA,CAAIL,EAAAA,CAAkBD,CAAQ,CAAA,CAAE,IAAA,EAAK,CAAE,WAAA,EAAa,CAAA,CAD5D,KAExB,CAkBA,IAAMQ,EAAAA,CAAW,kBAAA,CAGXC,EAAAA,CAAwB,MAAA,CAM9B,SAASC,EAAAA,CAAoBC,CAAAA,CAAyB,CACpD,OAAKF,GAAsB,IAAA,CAAKE,CAAO,CAAA,CAGhCA,CAAAA,CAAQ,QAAQ,IAAA,CAAM,KAAK,CAAA,CAAE,OAAA,CAAQ,IAAA,CAAM,KAAK,CAAA,CAF9CA,CAGX,CAOA,SAASC,CAAAA,CAAoBD,CAAAA,CAAyB,CACpD,GAAI,CAACA,CAAAA,CAAQ,QAAA,CAAS,GAAG,EACvB,OAAOA,CAAAA,CAET,IAAIE,CAAAA,CAAU,EAAA,CACd,IAAA,IAASC,CAAAA,CAAI,CAAA,CAAGA,EAAIH,CAAAA,CAAQ,MAAA,CAAQG,CAAAA,EAAK,CAAA,CAErCH,EAAQG,CAAC,CAAA,GAAM,GAAA,EACfA,CAAAA,CAAI,EAAIH,CAAAA,CAAQ,MAAA,EAChBH,EAAAA,CAAS,IAAA,CAAKG,EAAQ,KAAA,CAAMG,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAI,CAAC,CAAC,CAAA,EAEzCD,CAAAA,EAAW,MAAA,CAAO,aAAa,MAAA,CAAO,QAAA,CAASF,CAAAA,CAAQ,KAAA,CAAMG,EAAI,CAAA,CAAGA,CAAAA,CAAI,CAAC,CAAA,CAAG,EAAE,CAAC,CAAA,CAC/EA,CAAAA,EAAK,GAELD,CAAAA,EAAWF,CAAAA,CAAQG,CAAC,CAAA,CAGxB,OAAOD,CACT,CAGA,SAASE,EAAAA,CAAkBC,EAAoB,CAC7C,IAAMC,CAAAA,CAAMD,CAAAA,CAAG,UAAA,CAAW,CAAC,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,WAAA,EAAY,CACtD,OAAO,IAAIC,CAAAA,CAAI,MAAA,GAAW,CAAA,CAAI,CAAA,CAAA,EAAIA,CAAG,CAAA,CAAA,CAAKA,CAAG,CAAA,CAC/C,CAOA,SAASC,EAAAA,CAAwBC,CAAAA,CAAuB,CACtD,OACEC,EAAAA,CAAmBD,CAAI,CAAA,CAAE,IAAA,GAAS,QAClCtB,EAAAA,CAA0B,IAAA,CAAKsB,CAAI,CAAA,EACnCrB,GAAyB,IAAA,CAAKqB,CAAI,CAEtC,CASA,SAASE,EAAAA,CAAqBC,CAAAA,CAAuB,CACnD,OAAOP,GAAkBO,CAAAA,CAAM,CAAC,CAAC,CAAA,CAAIZ,GAAoBY,CAAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CACzE,CAOA,SAASC,EAAAA,CAAoBC,CAAAA,CAAeF,CAAAA,CAAuB,CACjE,IAAMG,CAAAA,CAAef,GAAoBc,CAAK,CAAA,CACxCL,CAAAA,CAAO,CAAA,EAAGM,CAAY,CAAA,CAAA,EAAIf,EAAAA,CAAoBY,CAAK,CAAC,GAC1D,OAAIA,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAKJ,GAAwBC,CAAI,CAAA,CAC3C,CAAA,EAAGM,CAAY,IAAIJ,EAAAA,CAAqBC,CAAK,CAAC,CAAA,CAAA,CAEhDH,CACT,CAqBO,SAASO,EAAAA,CACdC,CAAAA,CACAC,EACQ,CACR,OAAI,OAAOA,CAAAA,CAAK,EAAA,EAAO,QAAA,EAAYA,CAAAA,CAAK,EAAA,CAAG,OAAS,CAAA,CAC3CA,CAAAA,CAAK,EAAA,CAEV1B,CAAAA,CAAgB0B,EAAK,IAAI,CAAA,CACpBA,CAAAA,CAAK,KAAA,CAEPL,GAAoBI,CAAAA,CAAeC,CAAAA,CAAK,KAAK,CACtD,CAeO,SAASC,EAAAA,CAAmB7B,CAAAA,CAAoD,CACrF,IAAM8B,CAAAA,CAAY9B,CAAAA,CAAS,OAAA,CAAQ,GAAG,EACtC,OAAI8B,CAAAA,GAAc,EAAA,CACT,CAAE,MAAOlB,CAAAA,CAAoBZ,CAAQ,CAAA,CAAG,KAAA,CAAO,EAAG,CAAA,CAEpD,CACL,KAAA,CAAOY,EAAoBZ,CAAAA,CAAS,KAAA,CAAM,CAAA,CAAG8B,CAAS,CAAC,CAAA,CACvD,KAAA,CAAOlB,CAAAA,CAAoBZ,CAAAA,CAAS,MAAM8B,CAAAA,CAAY,CAAC,CAAC,CAC1D,CACF,CAYO,SAASC,CAAAA,CAAuB/B,EAA2B,CAEhE,IAAMgC,CAAAA,CADajC,CAAAA,CAA6BC,CAAQ,CAAA,CAAE,WAAA,EAAY,CAAE,IAAA,GAC7C,OAAA,CAAQ,MAAA,CAAQ,GAAG,CAAA,CAU9C,OARI,CAAA,EAAA,kCAAA,CAAmC,IAAA,CAAKgC,CAAO,GAC/C,gCAAA,CAAiC,IAAA,CAAKA,CAAO,CAAA,EAC7C,gBAAgB,IAAA,CAAKA,CAAO,CAAA,EAC5B,0BAAA,CAA2B,KAAKA,CAAO,CAAA,EACvC,UAAA,CAAW,IAAA,CAAKA,CAAO,CAAA,EAGvB1B,EAAAA,CAAuB,GAAA,CAAI0B,CAAO,GAClC,qCAAA,CAAsC,IAAA,CAAKA,CAAO,CAAA,CAExD,CAgJA,IAAMC,EAAAA,CAAc,OAAA,CACdC,EAAAA,CAAkB,sBAClBC,EAAAA,CAAa,eAAA,CACbC,EAAAA,CAAY,cAAA,CACZC,EAAAA,CAAiB,sBAAA,CACjBC,EAAAA,CAAgB,wBAAA,CAChBC,GAAmB,YAAA,CAEzB,SAASnB,EAAAA,CAAmBD,CAAAA,CAA2B,CACrD,GAAIc,EAAAA,CAAY,IAAA,CAAKd,CAAI,EACvB,OAAO,CAAE,IAAA,CAAM,QAAA,CAAU,OAAQ,MAAA,CAAOA,CAAI,CAAE,CAAA,CAEhD,IAAMqB,CAAAA,CAAYN,EAAAA,CAAgB,IAAA,CAAKf,CAAI,EAC3C,GAAIqB,CAAAA,CACF,OAAO,CAAE,KAAM,OAAA,CAAS,IAAA,CAAM,MAAA,CAAOA,CAAAA,CAAU,CAAC,CAAC,CAAA,CAAG,KAAA,CAAO,OAAOA,CAAAA,CAAU,CAAC,CAAC,CAAE,EAElF,IAAMC,CAAAA,CAAQN,EAAAA,CAAW,IAAA,CAAKhB,CAAI,CAAA,CAClC,GAAIsB,CAAAA,CACF,OAAO,CAAE,IAAA,CAAM,OAAA,CAAS,KAAA,CAAO,MAAA,CAAOA,EAAM,CAAC,CAAC,CAAE,CAAA,CAElD,IAAMC,CAAAA,CAAON,EAAAA,CAAU,IAAA,CAAKjB,CAAI,EAChC,GAAIuB,CAAAA,CACF,OAAO,CAAE,IAAA,CAAM,MAAA,CAAQ,IAAA,CAAM,MAAA,CAAOA,EAAK,CAAC,CAAC,CAAE,CAAA,CAE/C,GAAIvB,CAAAA,GAAS,MAAA,EAAUA,CAAAA,GAAS,OAAA,CAC9B,OAAO,CAAE,IAAA,CAAM,aAAA,CAAe,IAAA,CAAMA,CAAK,CAAA,CAE3C,IAAMwB,CAAAA,CAAWN,GAAe,IAAA,CAAKlB,CAAI,CAAA,CACzC,GAAIwB,EACF,OAAO,CACL,IAAA,CAAM,WAAA,CACN,KAAMA,CAAAA,CAAS,CAAC,CAAA,CAChB,KAAA,CAAO,MAAA,CAAOA,CAAAA,CAAS,CAAC,CAAC,CAC3B,CAAA,CAEF,IAAMC,CAAAA,CAAUN,EAAAA,CAAc,KAAKnB,CAAI,CAAA,CACvC,GAAIyB,CAAAA,CACF,OAAO,CACL,IAAA,CAAM,UAAA,CACN,EAAA,CAAIA,EAAQ,CAAC,CAAA,CACb,KAAA,CAAO,MAAA,CAAOA,EAAQ,CAAC,CAAC,CAC1B,CAAA,CAEF,GAAIzB,CAAAA,GAAS,cAAA,CACX,OAAO,CAAE,KAAM,cAAe,CAAA,CAEhC,IAAM0B,CAAAA,CAAaN,EAAAA,CAAiB,IAAA,CAAKpB,CAAI,CAAA,CAC7C,GAAI0B,CAAAA,CACF,OAAO,CAAE,IAAA,CAAM,cAAe,KAAA,CAAOjC,CAAAA,CAAoBiC,CAAAA,CAAW,CAAC,CAAC,CAAE,CAAA,CAK1E,GAAI1B,CAAAA,CAAK,SAAS,GAAG,CAAA,CAAG,CACtB,GAAM,CAAE,KAAA,CAAAK,CAAAA,CAAO,KAAA,CAAAF,CAAM,EAAIO,EAAAA,CAAmBV,CAAI,CAAA,CAChD,OAAO,CAAE,IAAA,CAAM,MAAA,CAAQ,KAAA,CAAAK,CAAAA,CAAO,KAAA,CAAAF,CAAM,CACtC,CACA,OAAO,CAAE,IAAA,CAAM,MAAA,CAAQ,QAAA,CAAU,IAAK,CACxC,CAOA,SAASwB,EAAAA,CAAgBC,EAAmC,CAC1D,OAAIA,CAAAA,CAAM,IAAA,GAAS,OACVA,CAAAA,CAAM,QAAA,CAAW,EAAC,CAAI,CAAC,CAAE,IAAA,CAAM,MAAA,CAAQ,QAAA,CAAU,IAAK,CAAC,CAAA,CAEzD,CAAC,CAAE,KAAM,MAAA,CAAQ,QAAA,CAAU,IAAK,CAAC,CAC1C,CAQO,SAASC,CAAAA,CAAchD,EAAqD,CACjF,IAAMiD,CAAAA,CAAMjD,CAAAA,EAAY,GAClBkD,CAAAA,CAAiBrD,EAAAA,CAA0B,IAAA,CAAKoD,CAAG,EACnDE,CAAAA,CAAiBD,CAAAA,CACnBD,CAAAA,CAAI,KAAA,CAAM,CAAA,CAAGA,CAAAA,CAAI,MAAA,CAASC,CAAAA,CAAe,CAAC,CAAA,CAAE,MAAM,CAAA,CAClDD,CAAAA,CACEG,EAAYtD,EAAAA,CAAyB,IAAA,CAAKqD,CAAc,CAAA,CACxDhC,EAAOiC,CAAAA,CACTD,CAAAA,CAAe,KAAA,CAAM,CAAA,CAAGA,CAAAA,CAAe,MAAA,CAASC,CAAAA,CAAU,CAAC,EAAE,MAAM,CAAA,CACnED,CAAAA,CACEJ,CAAAA,CAAQ3B,GAAmBD,CAAI,CAAA,CAC/BkC,CAAAA,CAAyB,CAAE,IAAAJ,CAAAA,CAAK,IAAA,CAAA9B,CAAAA,CAAM,KAAA,CAAA4B,CAAM,CAAA,CAC9CG,CAAAA,GACFG,CAAAA,CAAO,SAAA,CAAYH,EAAe,CAAC,CAAA,CAAE,WAAA,EAAY,CAAA,CAE/CE,IACFC,CAAAA,CAAO,SAAA,CAAYD,CAAAA,CAAU,CAAC,EAAE,WAAA,EAAY,CAAA,CAE9C,IAAME,CAAAA,CAAaR,EAAAA,CAAgBC,CAAK,CAAA,CACxC,OAAIO,EAAW,MAAA,CAAS,CAAA,GACtBD,CAAAA,CAAO,UAAA,CAAaC,GAEfD,CACT,CCjfA,IAAME,EAAAA,CAAYC,GAChB,OAAOA,CAAAA,EAAU,QAAA,EAAYA,CAAAA,GAAU,IAAA,EAAQ,CAAC,KAAA,CAAM,OAAA,CAAQA,CAAK,CAAA,CAE/DC,CAAAA,CAAYD,CAAAA,EAAuC,CACvD,GAAI,OAAOA,CAAAA,EAAU,QAAA,EAAY,MAAA,CAAO,SAASA,CAAK,CAAA,CACpD,OAAOA,CAAAA,CAET,GAAI,OAAOA,CAAAA,EAAU,QAAA,EAAYA,EAAM,IAAA,EAAK,CAAE,MAAA,CAAS,CAAA,CAAG,CACxD,IAAMH,CAAAA,CAAS,MAAA,CAAOG,CAAK,EAC3B,GAAI,MAAA,CAAO,QAAA,CAASH,CAAM,EACxB,OAAOA,CAEX,CAEF,CAAA,CAEMK,GAAmCF,CAAAA,EACvCA,CAAAA,CACG,IAAA,EAAK,CACL,aAAY,CACZ,KAAA,CAAM,YAAY,CAAA,CAClB,OAAQG,CAAAA,EAAUA,CAAAA,CAAM,MAAA,CAAS,CAAC,CAAA,CAEjCC,EAAAA,CAAyBD,CAAAA,EAAiE,CAC9F,GAAIA,CAAAA,GAAU,KAAA,EAASA,CAAAA,GAAU,IAAA,CAC/B,OAAO,KAAA,CAET,GAAIA,CAAAA,GAAU,OAAA,CACZ,OAAO,OAAA,CAET,GAAIA,CAAAA,GAAU,QAAA,EAAYA,CAAAA,GAAU,MAAA,CAClC,OAAO,QAAA,CAET,GAAIA,CAAAA,GAAU,MAAA,CACZ,OAAO,MAGX,EAEME,EAAAA,CAAkBC,CAAAA,EACtBA,CAAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,QAAQ,GAAKA,CAAAA,CAAO,QAAA,CAAS,KAAK,CAAA,CAExGC,GAA2B,CAC/BC,CAAAA,CACAF,CAAAA,GACuB,CACvB,IAAMG,CAAAA,CAAUH,CAAAA,CAAO,QAAA,CAAS,MAAM,EAChCI,CAAAA,CAAWJ,CAAAA,CAAO,QAAA,CAAS,OAAO,EAClCK,CAAAA,CAASL,CAAAA,CAAO,QAAA,CAAS,KAAK,GAAKA,CAAAA,CAAO,QAAA,CAAS,IAAI,CAAA,CACvDM,EAAYN,CAAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,MAAM,CAAA,CAC/DO,EAAYR,EAAAA,CAAeC,CAAM,CAAA,CAEvC,GAAIE,IAAgB,KAAA,EAASA,CAAAA,GAAgB,QAAA,CAC3C,OAAIC,GAAWI,CAAAA,EAAa,CAACH,CAAAA,CAAiB,EAAA,CAC1CA,GAAYG,CAAAA,EAAa,CAACJ,CAAAA,CAAgB,EAAA,CAC1CA,GAAW,CAACC,CAAAA,CAAiB,EAAA,CAC7BA,CAAAA,EAAY,CAACD,CAAAA,CAAgB,EAAA,CAC7BI,CAAAA,CAAkB,EAAA,CACtB,OAGF,GAAIF,CAAAA,EAAUE,CAAAA,EAAa,CAACD,CAAAA,CAAW,OAAO,EAAA,CAC9C,GAAIA,GAAaC,CAAAA,EAAa,CAACF,CAAAA,CAAQ,UACvC,GAAIA,CAAAA,EAAU,CAACC,CAAAA,CAAW,OAAO,EAAA,CACjC,GAAIA,CAAAA,EAAa,CAACD,EAAQ,OAAO,EAAA,CACjC,GAAIE,CAAAA,CAAW,OAAO,EAExB,CAAA,CAEMC,EAAAA,CAAmCR,CAAAA,EAA+C,CAEtF,GADsBA,CAAAA,CAAO,QAAA,CAAS,OAAO,GAAKA,CAAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,MAAM,CAAA,CAEjG,OAGF,IAAME,CAAAA,CAAcF,CAAAA,CACjB,GAAA,CAAIF,EAAqB,CAAA,CACzB,IAAA,CAAMW,CAAAA,EAAoDA,CAAAA,GAAS,MAAS,CAAA,CAE/E,GAAI,CAACP,CAAAA,CACH,OAGF,IAAMQ,CAAAA,CAAgBT,EAAAA,CAAyBC,EAAaF,CAAM,CAAA,CAClE,GAAIU,CAAAA,GAAkB,OAItB,OAAO,CACL,IAAA,CAAMR,CAAAA,CACN,cAAAQ,CACF,CACF,CAAA,CAEMC,EAAAA,CAAwCX,CAAAA,EAA+C,CAC3F,GAAIA,CAAAA,CAAO,SAAW,CAAA,CACpB,OAGF,IAAMG,CAAAA,CAAUH,EAAO,QAAA,CAAS,MAAM,CAAA,CAChCI,CAAAA,CAAWJ,EAAO,QAAA,CAAS,OAAO,CAAA,CAClCO,CAAAA,CAAYR,GAAeC,CAAM,CAAA,CACjCY,CAAAA,CAAWZ,CAAAA,CAAO,SAAS,OAAO,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,IAAI,EACrFa,CAAAA,CAAUb,CAAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAKA,CAAAA,CAAO,QAAA,CAAS,MAAM,GAAKA,CAAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAKA,EAAO,QAAA,CAAS,MAAM,CAAA,CAErHc,CAAAA,CACAX,GAAWI,CAAAA,EAAa,CAACH,CAAAA,CAC3BU,CAAAA,CAAW,GACFV,CAAAA,EAAYG,CAAAA,EAAa,CAACJ,CAAAA,CACnCW,EAAW,EAAA,CACFX,CAAAA,EAAW,CAACC,CAAAA,CACrBU,EAAW,EAAA,CACFV,CAAAA,EAAY,CAACD,CAAAA,CACtBW,EAAW,EAAA,CACFP,CAAAA,GACTO,CAAAA,CAAW,EAAA,CAAA,CAGb,IAAIC,CAAAA,CASJ,GARIH,CAAAA,EAAY,CAACC,CAAAA,CACfE,CAAAA,CAAW,EAAA,CACFF,CAAAA,EAAW,CAACD,CAAAA,CACrBG,CAAAA,CAAW,EAAA,CACFR,CAAAA,GACTQ,EAAW,EAAA,CAAA,CAGT,EAAAD,CAAAA,GAAa,MAAA,EAAaC,IAAa,MAAA,CAAA,CAI3C,OAAO,CAAE,QAAA,CAAAD,EAAU,QAAA,CAAAC,CAAS,CAC9B,CAAA,CAEaC,GAA6BtB,CAAAA,EAA4C,CACpF,IAAMM,CAAAA,CAASJ,GAAgCF,CAAK,CAAA,CACpD,GAAIM,CAAAA,CAAO,MAAA,GAAW,CAAA,CAItB,CAAA,GAAIA,CAAAA,CAAO,SAAW,CAAA,CAAG,CACvB,IAAMS,CAAAA,CAAOX,GAAsBE,CAAAA,CAAO,CAAC,CAAC,CAAA,CAC5C,OAAOS,CAAAA,CAAO,CAAE,IAAA,CAAAA,CAAK,CAAA,CAAI,MAC3B,CAEA,OAAOD,GAAgCR,CAAM,CAAA,EAAKW,EAAAA,CAAqCX,CAAM,EAC/F,CAAA,CAEaiB,EAAAA,CAA8BvB,CAAAA,EAA6C,CACtF,GAAI,OAAOA,CAAAA,EAAU,QAAA,CACnB,OAAOsB,EAAAA,CAA0BtB,CAAK,CAAA,CAGxC,GAAI,CAACD,EAAAA,CAASC,CAAK,CAAA,CACjB,OAGF,IAAMwB,CAAAA,CAAUxB,CAAAA,CAAM,IAAA,EAAQA,CAAAA,CAAM,WAAaA,CAAAA,CAAM,QAAA,CACjDyB,CAAAA,CAAmB,OAAOD,GAAY,QAAA,CAAWF,EAAAA,CAA0BE,CAAO,CAAA,CAAI,OAGtFT,CAAAA,CAAAA,CAFa,OAAOS,CAAAA,EAAY,QAAA,CAAWpB,GAAsBoB,CAAAA,CAAQ,IAAA,EAAK,CAAE,WAAA,EAAa,CAAA,CAAI,MAAA,GAE5EC,CAAAA,EAAkB,IAAA,CACvCT,CAAAA,CAAgBf,CAAAA,CACpBD,CAAAA,CAAM,aAAA,EACNA,EAAM,cAAA,EACNA,CAAAA,CAAM,SAAA,EACNA,CAAAA,CAAM,UACR,CAAA,EAAKyB,CAAAA,EAAkB,aAAA,CACjBC,CAAAA,CAAWzB,EACfD,CAAAA,CAAM,QAAA,EACNA,CAAAA,CAAM,SAAA,EACNA,EAAM,YAAA,EACNA,CAAAA,CAAM,aACR,CAAA,EAAKyB,GAAkB,QAAA,CACjBL,CAAAA,CAAWnB,CAAAA,CACfD,CAAAA,CAAM,UACNA,CAAAA,CAAM,SAAA,EACNA,CAAAA,CAAM,IAAA,EACNA,EAAM,KAAA,EACNA,CAAAA,CAAM,iBAAA,EACNA,CAAAA,CAAM,kBAAA,EACNA,CAAAA,CAAM,CACR,CAAA,EAAKyB,GAAkB,QAAA,CACjBJ,CAAAA,CAAWpB,CAAAA,CACfD,CAAAA,CAAM,UACNA,CAAAA,CAAM,SAAA,EACNA,CAAAA,CAAM,IAAA,EACNA,EAAM,KAAA,EACNA,CAAAA,CAAM,eAAA,EACNA,CAAAA,CAAM,kBACNA,CAAAA,CAAM,CACR,CAAA,EAAKyB,CAAAA,EAAkB,SACjBE,CAAAA,CAAe1B,CAAAA,CACnBD,CAAAA,CAAM,YAAA,EACNA,EAAM,aAAA,EACNA,CAAAA,CAAM,QAAA,EACNA,CAAAA,CAAM,WACNA,CAAAA,CAAM,QAAA,EACNA,CAAAA,CAAM,SACR,CAAA,CACM4B,CAAAA,CAAgB3B,CAAAA,CACpBD,CAAAA,CAAM,eACNA,CAAAA,CAAM,cAAA,EACNA,CAAAA,CAAM,SAAA,EACNA,EAAM,UAAA,EACNA,CAAAA,CAAM,QAAA,EACNA,CAAAA,CAAM,SACR,CAAA,CAEA,GACE,EAAAe,CAAAA,GAAS,MAAA,EACTC,CAAAA,GAAkB,MAAA,EAClBU,CAAAA,GAAa,QACbN,CAAAA,GAAa,MAAA,EACbC,CAAAA,GAAa,MAAA,EACbM,IAAiB,MAAA,EACjBC,CAAAA,GAAkB,MAAA,CAAA,CAKpB,OAAO,CACL,IAAA,CAAAb,CAAAA,CACA,aAAA,CAAAC,CAAAA,CACA,QAAA,CAAAU,CAAAA,CACA,QAAA,CAAAN,CAAAA,CACA,SAAAC,CAAAA,CACA,YAAA,CAAAM,CAAAA,CACA,aAAA,CAAAC,CACF,CACF,CAAA,CCnOO,IAAMC,EAAAA,CAAyD,CACpE,GAAA,CAAK,CACH,IAAA,CAAM,KAAA,CACN,MAAO,SAAA,CACP,WAAA,CAAa,SAAA,CACb,QAAA,CAAU,GACV,OAAA,CAAS,GAAA,CACT,MAAA,CAAQ,CACV,EACA,GAAA,CAAK,CACH,IAAA,CAAM,KAAA,CACN,MAAO,SAAA,CACP,WAAA,CAAa,SAAA,CACb,QAAA,CAAU,EAAA,CACV,OAAA,CAAS,GAAA,CACT,MAAA,CAAQ,CACV,CAAA,CACA,GAAA,CAAK,CACH,IAAA,CAAM,MACN,KAAA,CAAO,SAAA,CACP,WAAA,CAAa,SAAA,CACb,SAAU,EAAA,CACV,OAAA,CAAS,GAAA,CACT,MAAA,CAAQ,CACV,CAAA,CACA,GAAA,CAAK,CACH,KAAM,KAAA,CACN,KAAA,CAAO,SAAA,CACP,WAAA,CAAa,UACb,QAAA,CAAU,EAAA,CACV,OAAA,CAAS,GAAA,CACT,OAAQ,CACV,CAAA,CACA,EAAA,CAAI,CACF,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,SAAA,CACP,YAAa,SAAA,CACb,QAAA,CAAU,EAAA,CACV,OAAA,CAAS,IACT,MAAA,CAAQ,CACV,CACF,CAAA,CAkEA,IAAMC,EAAAA,CAAsB,+BAAA,CAYrB,SAASC,EAAAA,CAA2BpF,EAAuC,CAChF,GAAI,OAAOA,CAAAA,EAAa,SAAU,OAClC,IAAMqF,CAAAA,CAAQF,EAAAA,CAAoB,KAAKnF,CAAQ,CAAA,CAC/C,OAAOqF,CAAAA,CAAQ,IAAIA,CAAAA,CAAM,CAAC,CAAC,CAAA,CAAA,CAAK,MAClC,CAYO,SAASC,EAAAA,CAAYC,EAAwB,CAClD,GAAI,CAACA,CAAAA,EAAQ,OAAOA,CAAAA,EAAS,QAAA,CAAU,OAAO,MAAA,CAC9C,GAAM,CAAE,cAAA,CAAAC,CAAAA,CAAgB,SAAA,CAAAC,CAAAA,CAAW,WAAA,CAAAC,CAAY,CAAA,CAAIH,EAMnD,GADI,OAAOC,CAAAA,EAAmB,QAAA,EAAYA,EAAe,IAAA,EAAK,CAAE,MAAA,CAAS,CAAA,EACrE,OAAOC,CAAAA,EAAc,QAAA,EAAY,MAAA,CAAO,QAAA,CAASA,CAAS,CAAA,CAAG,OAAO,KAAA,CACxE,IAAME,CAAAA,CACJ,OAAOD,CAAAA,EAAgB,QAAA,CAAWA,EAAY,IAAA,EAAK,CAAE,WAAA,EAAY,CAAI,OACvE,OAAOC,CAAAA,GAAW,MAAA,EAAUA,CAAAA,GAAW,QACzC,CAMO,SAASC,EAAAA,CAAeC,CAAAA,CAA2B,CAExD,OADsBA,CAAAA,CAAY,EAAA,CACX,CAAA,CAAI,MAAQ,KACrC,CAUO,SAASC,EAAAA,CACdC,EACAC,CAAAA,CACQ,CACR,IAAMC,CAAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,CAAG,IAAA,CAAK,MAAMD,CAAQ,CAAA,CAAI,CAAC,CAAA,CAC5CE,EAAWH,CAAAA,GAAYE,CAAK,CAAA,CAClC,OAAI,OAAOC,CAAAA,EAAa,QAAA,EAAYA,CAAAA,CAAS,IAAA,GAAO,MAAA,CAAS,CAAA,CACpDA,CAAAA,CAAS,IAAA,GAAO,WAAA,EAAY,CAE9BN,EAAAA,CAAeK,CAAK,CAC7B,CAcO,SAASE,EAAAA,CAAmBH,CAAAA,CAAkBI,EAAwB,CAC3E,IAAMC,CAAAA,CACJ,MAAA,CAAO,QAAA,CAASD,CAAM,CAAA,EAAKA,CAAAA,EAAU,EAAI,IAAA,CAAK,KAAA,CAAMA,CAAM,CAAA,CAAI,EAEhE,OAAA,CAAA,CAAA,CADa,MAAA,CAAO,QAAA,CAASJ,CAAQ,EAAI,IAAA,CAAK,KAAA,CAAMA,CAAQ,CAAA,CAAI,GAC/C,CAAA,EAAKK,CAAAA,CAASA,CAAAA,EAASA,CAC1C,CCzLA,IAAMC,EAAAA,CAA2B,eAAA,CAC3BC,EAAAA,CAA0B,qBAC1BC,EAAAA,CAA2B,aAAA,CAC3BC,EAAAA,CAAiC,CAAA,CACjCC,GAAiC,EAAA,CAEvC,SAASC,CAAAA,CAAatD,CAAAA,CAAuB,CAC3C,OAAO,IAAA,CAAK,GAAA,CAAI,EAAG,IAAA,CAAK,GAAA,CAAI,GAAA,CAAKA,CAAK,CAAC,CACzC,CAEA,SAASuD,EAAAA,CAAoBvD,EAA2E,CACtG,GAAIA,CAAAA,GAAU,KAAA,EAASA,CAAAA,GAAU,OAAA,EAAWA,CAAAA,GAAU,QAAA,EAAYA,IAAU,MAAA,CAC1E,OAAOA,CAGX,CAEA,SAASwD,EAAAA,CAA0Bb,CAAAA,CAA6C,CAC9E,OAAOpB,GAA2BoB,CAAQ,CAC5C,CAEA,SAASc,EAAAA,CAA+Bd,CAAAA,CAA6C,CACnF,OACE,OAAOA,CAAAA,EAAU,QAAA,EAAa,QAAA,EAC9B,OAAOA,GAAU,QAAA,EAAa,QAElC,CAEA,SAASe,GAAkBf,CAAAA,CAA6C,CACtE,OACE,OAAOA,GAAU,aAAA,EAAkB,QAAA,EACnC,OAAOA,CAAAA,EAAU,UAAa,QAElC,CAEA,SAASgB,EAAAA,CAAsBvF,EAA8B,CAC3D,OAAIA,CAAAA,CAAK,IAAA,GAAS,QAAUA,CAAAA,CAAK,IAAA,GAAS,MAAA,CACjC,MAAA,CAELA,CAAAA,CAAK,IAAA,GAAS,OAAA,CACT,OAAA,CAEF1B,EAAgB0B,CAAAA,CAAK,IAAI,CAAA,CAAI,MAAA,CAAS,OAC/C,CAEA,SAASwF,EAAAA,CAA2BxF,CAAAA,CAAkByF,EAA0B,CAC9E,OAAI,OAAOzF,CAAAA,CAAK,OAAU,QAAA,EAAYA,CAAAA,CAAK,KAAA,CAAM,IAAA,GAAO,MAAA,CAAS,CAAA,CACxDA,CAAAA,CAAK,KAAA,CAAM,MAAK,CAErB1B,CAAAA,CAAgB0B,CAAAA,CAAK,IAAI,EACpB+E,EAAAA,CAEFU,CAAAA,GAAS,MAAA,CAASX,EAAAA,CAA0BD,EACrD,CAEA,SAASa,EAAAA,CAAkBC,EAA4B,CACrD,OAAO,OAAOA,CAAAA,CAAM,KAAK,KAAA,EAAU,QAAA,EAAY,MAAA,CAAO,QAAA,CAASA,EAAM,IAAA,CAAK,KAAK,CAAA,CAC3EA,CAAAA,CAAM,KAAK,KAAA,CACXA,CAAAA,CAAM,KACZ,CAEA,SAASC,EAAAA,CAA2BpB,CAAAA,CAAeI,CAAAA,CAAuB,CACxE,OAAIA,CAAAA,EAAS,CAAA,CACJ,EAAA,CAGPI,EAAAA,CACCR,GAASS,EAAAA,CAAiCD,EAAAA,CAAAA,EAAoCJ,CAAAA,CAAQ,CAAA,CAE3F,CAEA,SAASiB,EAAAA,CAA0BC,CAAAA,CAA6C,CAC9E,IAAMC,CAAAA,CAAqBD,CAAAA,CAAQ,MAAA,CAAQH,GAElC,CAAC,CADOR,EAAAA,CAAoBQ,CAAAA,CAAM,UAAU,IAAI,CAAA,EACpC,CAACN,EAAAA,CAA+BM,CAAAA,CAAM,QAAQ,CAAA,EAAK,CAACL,GAAkBK,CAAAA,CAAM,QAAQ,CACxG,CAAA,CACKK,EAAuB,IAAI,GAAA,CAC3BC,CAAAA,CAAkB,IAAI,IAE5B,OAAAF,CAAAA,CAAmB,OAAA,CAASJ,CAAAA,EAAU,CACpC,IAAMO,CAAAA,CAASf,EAAAA,CAAoBQ,EAAM,QAAA,EAAU,IAAI,CAAA,CACvD,GAAI,CAACO,CAAAA,CACH,OAEF,IAAMtG,CAAAA,CAAQqG,EAAgB,GAAA,CAAIC,CAAM,CAAA,CACxC,GAAItG,EAAO,CACTA,CAAAA,CAAM,IAAA,CAAK+F,CAAK,EAChB,MACF,CACAM,CAAAA,CAAgB,GAAA,CAAIC,EAAQ,CAACP,CAAK,CAAC,EACrC,CAAC,CAAA,CAEDM,CAAAA,CAAgB,OAAA,CAASE,CAAAA,EAAkB,CACzC,IAAMC,CAAAA,CAAgB,CAAC,GAAGD,CAAa,CAAA,CAAE,IAAA,CAAK,CAACE,EAAMC,CAAAA,GAAU,CAC7D,IAAMC,CAAAA,CAAkBb,GAAkBW,CAAI,CAAA,CAAIX,EAAAA,CAAkBY,CAAK,EACzE,OAAIC,CAAAA,GAAoB,CAAA,CACfA,CAAAA,CAEFF,EAAK,KAAA,CAAQC,CAAAA,CAAM,KAC5B,CAAC,EACDF,CAAAA,CAAc,OAAA,CAAQ,CAACT,CAAAA,CAAOnB,IAAU,CACtCwB,CAAAA,CAAqB,GAAA,CAAIL,CAAAA,CAAM,KAAA,CAAOC,EAAAA,CAA2BpB,CAAAA,CAAO4B,CAAAA,CAAc,MAAM,CAAC,EAC/F,CAAC,EACH,CAAC,CAAA,CAEMJ,CACT,CAEA,SAASQ,GACPC,CAAAA,CACAC,CAAAA,CAC0B,CAC1B,IAAMR,EAASf,EAAAA,CAAoBsB,CAAAA,EAAoB,IAAI,CAAA,CAC3D,GAAIpB,EAAAA,CAA+BoB,CAAkB,CAAA,CAAG,CACtD,IAAMlC,CAAAA,CAAyB,CAC7B,QAAA,CAAUW,CAAAA,CAAauB,GAAoB,QAAkB,CAAA,CAC7D,QAAA,CAAUvB,CAAAA,CAAauB,CAAAA,EAAoB,QAAkB,CAC/D,CAAA,CACA,OAAI,OAAOA,CAAAA,EAAoB,YAAA,EAAiB,QAAA,EAAY,OAAO,QAAA,CAASA,CAAAA,CAAmB,YAAY,CAAA,GACzGlC,EAAS,YAAA,CAAeW,CAAAA,CAAauB,CAAAA,CAAmB,YAAY,CAAA,CAAA,CAElE,OAAOA,CAAAA,EAAoB,aAAA,EAAkB,UAAY,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAmB,aAAa,IAC3GlC,CAAAA,CAAS,aAAA,CAAgBW,CAAAA,CAAauB,CAAAA,CAAmB,aAAa,CAAA,CAAA,CAEpEP,CAAAA,GACF3B,CAAAA,CAAS,IAAA,CAAO2B,CAAAA,CAAAA,CAEX3B,CACT,CAEA,GAAI,CAAC2B,CAAAA,CACH,OAGF,IAAM3B,CAAAA,CAAyB,CAAE,IAAA,CAAM2B,CAAO,CAAA,CAC9C,GAAI,OAAOO,CAAAA,EAAoB,QAAA,EAAa,QAAA,EAAY,MAAA,CAAO,SAASA,CAAAA,CAAmB,QAAQ,CAAA,CACjG,OAAAlC,EAAS,QAAA,CAAWkC,CAAAA,CAAmB,QAAA,CAChClC,CAAAA,CAOT,IAAMoC,CAAAA,CAAAA,CAHJ,OAAOF,CAAAA,EAAoB,aAAA,EAAkB,UAAY,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAmB,aAAa,CAAA,CACrGvB,CAAAA,CAAauB,CAAAA,CAAmB,aAAa,EAC7C,MAAA,IAEsB,OAAOC,CAAAA,EAA6B,QAAA,CAC1DxB,EAAawB,CAAwB,CAAA,CACrC,MAAA,CAAA,CAEN,OAAI,OAAOC,CAAAA,EAA0B,QAAA,GACnCpC,CAAAA,CAAS,aAAA,CAAgBoC,CAAAA,CAAAA,CAGpBpC,CACT,CAEA,SAASqC,GAAOjB,CAAAA,CAAoBe,CAAAA,CAAyC,CAC3E,IAAMnC,EAAWiC,EAAAA,CAAeb,CAAAA,CAAM,QAAA,CAAUe,CAAwB,EACxE,OAAO,CACL,EAAA,CAAIf,CAAAA,CAAM,IAAA,CAAK,EAAA,CACf,KAAA,CAAOA,CAAAA,CAAM,KAAK,KAAA,CAClB,eAAA,CAAiBA,CAAAA,CAAM,IAAA,CAAK,gBAC5B,SAAA,CAAWA,CAAAA,CAAM,IAAA,CAAK,SAAA,EAAa,MACnC,IAAA,CAAMA,CAAAA,CAAM,IAAA,CAAK,IAAA,CACjB,UAAWA,CAAAA,CAAM,IAAA,CAAK,SAAA,CACtB,aAAA,CAAeA,EAAM,IAAA,CAAK,aAAA,CAC1B,aAAA,CAAe,KAAA,CAAM,QAAQA,CAAAA,CAAM,IAAA,CAAK,aAAa,CAAA,CAAI,CAAC,GAAGA,CAAAA,CAAM,IAAA,CAAK,aAAa,CAAA,CAAI,MAAA,CACzF,GAAIpB,CAAAA,CAAW,CAAE,QAAA,CAAAA,CAAS,CAAA,CAAI,EAChC,CACF,CAEA,SAASsC,CAAAA,CAA6BC,EAAsC,CAC1E,IAAMC,CAAAA,CAAmBD,CAAAA,CAAW,GAAA,CAAKE,CAAAA,GAAW,CAClD,GAAGA,EACH,KAAA,CAAOA,CAAAA,CAAM,KAAA,CAAM,GAAA,CAAKhH,GAAS,CAC/B,IAAMuE,CAAAA,CAAWa,EAAAA,CAA0BpF,EAAK,QAAQ,CAAA,CACxD,OAAO,CACL,GAAGA,CAAAA,CACH,GAAIuE,CAAAA,CAAW,CAAE,QAAA,CAAAA,CAAS,CAAA,CAAI,CAAE,SAAU,MAAU,CACtD,CACF,CAAC,CACH,CAAA,CAAE,CAAA,CAII0C,CAAAA,CAA0B,IAAI,IAIpC,OAAAF,CAAAA,CAAiB,OAAA,CAASC,CAAAA,EAAU,CAClCA,CAAAA,CAAM,KAAA,CAAM,OAAA,CAAShH,CAAAA,EAAS,CAC5B,IAAMuE,CAAAA,CAAWvE,CAAAA,CAAK,QAAA,CAChB2C,EAAOwC,EAAAA,CAAoBZ,CAAAA,EAAU,IAAI,CAAA,CAC/C,GACE,CAAC5B,CAAAA,EACD0C,EAAAA,CAA+Bd,CAAQ,CAAA,EACvCe,EAAAA,CAAkBf,CAAQ,CAAA,CAE1B,OAEF,IAAM2C,CAAAA,CAAQD,CAAAA,CAAwB,GAAA,CAAItE,CAAI,CAAA,CAC1CuE,CAAAA,CACFA,CAAAA,CAAM,IAAA,CAAKlH,CAAI,CAAA,CAEfiH,CAAAA,CAAwB,GAAA,CAAItE,CAAAA,CAAM,CAAC3C,CAAI,CAAC,EAE5C,CAAC,EACH,CAAC,CAAA,CAIDiH,CAAAA,CAAwB,OAAA,CAASC,GAAU,CACzCA,CAAAA,CAAM,OAAA,CAAQ,CAAClH,CAAAA,CAAMwE,CAAAA,GAAU,CAC7BxE,CAAAA,CAAK,SAAW,CACd,GAAIA,CAAAA,CAAK,QAAA,CACT,cAAe4F,EAAAA,CAA2BpB,CAAAA,CAAO0C,CAAAA,CAAM,MAAM,CAC/D,EACF,CAAC,EACH,CAAC,EAEMH,CACT,CAEA,SAASI,EAAAA,CAAwBvF,EAAsC,CACrE,OACE,KAAA,CAAM,OAAA,CAAQA,CAAK,CAAA,EACnBA,CAAAA,CAAM,MAAA,CAAS,CAAA,EACfA,EAAM,KAAA,CACH+D,CAAAA,EACCA,CAAAA,GAAU,IAAA,EACV,OAAOA,CAAAA,EAAU,QAAA,EACjB,OAAQA,EAAoB,IAAA,EAAS,QAAA,EACrC,KAAA,CAAM,OAAA,CAASA,EAAoB,KAAK,CAC5C,CAEJ,CAEA,SAASyB,EAAAA,CAAsBxF,CAAAA,CAAuC,CACpE,OACE,KAAA,CAAM,OAAA,CAAQA,CAAK,CAAA,EACnBA,EAAM,MAAA,CAAS,CAAA,EACfA,CAAAA,CAAM,KAAA,CACH+D,GACCA,CAAAA,GAAU,IAAA,EACV,OAAOA,CAAAA,EAAU,UACjB,OAAQA,CAAAA,CAAqB,KAAA,EAAU,QAAA,EACvC,EACE,OAAQA,CAAAA,CAA6B,IAAA,EAAS,UAC9C,KAAA,CAAM,OAAA,CAASA,CAAAA,CAA8B,KAAK,EAExD,CAEJ,CAQO,SAAS0B,EAAAA,CACdC,EACgD,CAChD,IAAMC,CAAAA,CAAe,IAAI,IAGnBC,CAAAA,CAA8B,EAAC,CACrCF,CAAAA,CAAY,QAAQ,CAACtH,CAAAA,CAAMwE,CAAAA,GAAU,CAC/B,OAAOxE,CAAAA,CAAK,KAAA,EAAU,QAAA,EAAYA,CAAAA,CAAK,MAAM,IAAA,EAAK,CAAE,MAAA,GAAW,CAAA,EACnEwH,CAAAA,CAAa,IAAA,CAAK,CAChB,KAAA,CAAAhD,EACA,IAAA,CAAM,CAAE,GAAGxE,CAAAA,CAAM,MAAOA,CAAAA,CAAK,KAAA,CAAM,IAAA,EAAO,EAC1C,QAAA,CAAUoF,EAAAA,CAA0BpF,CAAAA,CAAK,QAAQ,CACnD,CAAC,EACH,CAAC,CAAA,CACD,IAAMyH,CAAAA,CAA2B,CAC/B,KAAA,CAAO5B,EAAAA,CACL2B,EAAa,MAAA,CAAO,CAAC,CAAE,IAAA,CAAAxH,CAAK,CAAA,GAAMuF,EAAAA,CAAsBvF,CAAI,CAAA,GAAM,OAAO,CAC3E,CAAA,CACA,IAAA,CAAM6F,GACJ2B,CAAAA,CAAa,MAAA,CAAO,CAAC,CAAE,KAAAxH,CAAK,CAAA,GAAMuF,EAAAA,CAAsBvF,CAAI,IAAM,MAAM,CAC1E,CACF,CAAA,CAEAwH,EAAa,OAAA,CAAS7B,CAAAA,EAAU,CAC9B,IAAMF,EAAOF,EAAAA,CAAsBI,CAAAA,CAAM,IAAI,CAAA,CACvC+B,EAAYlC,EAAAA,CAA2BG,CAAAA,CAAM,IAAA,CAAMF,CAAI,EACvDkC,CAAAA,CAAM,CAAA,EAAGlC,CAAI,CAAA,CAAA,EAAIiC,CAAS,CAAA,CAAA,CAC1BE,CAAAA,CAAiBL,CAAAA,CAAa,IAAII,CAAG,CAAA,CACvCC,CAAAA,CACFA,CAAAA,CAAe,QAAQ,IAAA,CAAKjC,CAAK,CAAA,CAEjC4B,CAAAA,CAAa,IAAII,CAAAA,CAAK,CAAE,IAAA,CAAAlC,CAAAA,CAAM,SAAA,CAAAiC,CAAAA,CAAW,OAAA,CAAS,CAAC/B,CAAK,CAAE,CAAC,EAE/D,CAAC,EAED,IAAMuB,CAAAA,CAAqB,EAAC,CACtBW,EAAyB,EAAC,CAEhC,OAAAN,CAAAA,CAAa,OAAA,CAASO,CAAAA,EAAW,CAC/B,IAAM1B,EAAgB,CAAC,GAAG0B,CAAAA,CAAO,OAAO,EAAE,IAAA,CAAK,CAACzB,CAAAA,CAAMC,CAAAA,GAAU,CAC9D,IAAMC,CAAAA,CAAkBb,EAAAA,CAAkBW,CAAI,EAAIX,EAAAA,CAAkBY,CAAK,CAAA,CACzE,OAAIC,IAAoB,CAAA,CACfA,CAAAA,CAEFF,CAAAA,CAAK,KAAA,CAAQC,EAAM,KAC5B,CAAC,CAAA,CACKyB,CAAAA,CAA2BN,EAAyBK,CAAAA,CAAO,IAAI,CAAA,CAC/Dd,CAAAA,CAAmB,CACvB,IAAA,CAAMc,CAAAA,CAAO,SAAA,CACb,MAAO1B,CAAAA,CAAc,GAAA,CAAKT,CAAAA,EACxBiB,EAAAA,CAAOjB,EAAOoC,CAAAA,CAAyB,GAAA,CAAIpC,CAAAA,CAAM,KAAK,CAAC,CACzD,CACF,CAAA,CACA,GAAImC,EAAO,IAAA,GAAS,MAAA,CAAQ,CAC1BD,CAAAA,CAAU,KAAKb,CAAK,CAAA,CACpB,MACF,CACAE,EAAM,IAAA,CAAKF,CAAK,EAClB,CAAC,EAEM,CAAE,KAAA,CAAAE,CAAAA,CAAO,SAAA,CAAAW,CAAU,CAC5B,CAqBO,SAASG,GACdlE,CAAAA,CACgD,CAChD,OAAIqD,EAAAA,CAAwBrD,GAAM,KAAK,CAAA,CAC9B,CACL,KAAA,CAAO+C,EAA6B/C,CAAAA,CAAK,KAAK,CAAA,CAC9C,SAAA,CAAW+C,EACT,KAAA,CAAM,OAAA,CAAQ/C,CAAAA,EAAM,cAAc,EAC9BA,CAAAA,CAAK,cAAA,CACLqD,EAAAA,CAAwBrD,CAAAA,EAAM,SAAS,CAAA,CACrCA,CAAAA,CAAK,SAAA,CACL,EACR,CACF,CAAA,CAGEsD,EAAAA,CAAsBtD,CAAAA,EAAM,KAAK,CAAA,CAC5BuD,EAAAA,CAAyBvD,CAAAA,CAAK,KAAK,CAAA,CAGrC,CACL,KAAA,CAAO+C,CAAAA,CAA6B,MAAM,OAAA,CAAQ/C,CAAAA,EAAM,UAAU,CAAA,CAAIA,EAAK,UAAA,CAAa,EAAE,CAAA,CAC1F,SAAA,CAAW+C,CAAAA,CACT,KAAA,CAAM,OAAA,CAAQ/C,GAAM,cAAc,CAAA,CAC9BA,CAAAA,CAAK,cAAA,CACLqD,GAAwBrD,CAAAA,EAAM,SAAS,CAAA,CACrCA,CAAAA,CAAK,UACL,EACR,CACF,CACF,CC7VO,IAAMmE,EAAAA,CAAkB,GAAA,CCoC/B,IAAMC,EAAAA,CAAsB,IAAI,OAAA,CAE1BC,EAAAA,CAA2B,IAAI,GAAA,CAOrC,SAASC,EAAAA,CAAsBhK,CAAAA,CAAwB,CACjD+J,EAAAA,CAAyB,GAAA,CAAI/J,CAAQ,CAAA,GAGzC+J,GAAyB,GAAA,CAAI/J,CAAQ,CAAA,CACrC,OAAA,CAAQ,KACN,CAAA,qCAAA,EAAwC,IAAA,CAAK,SAAA,CAAUA,CAAQ,CAAC,CAAA,mIAAA,CAGlE,CAAA,EACF,CAEA,SAASiK,EAA4BzG,CAAAA,CAAoC,CACvE,GAAI,OAAOA,GAAU,QAAA,CACnB,OAGF,IAAM0G,CAAAA,CAAa1G,CAAAA,CAAM,IAAA,EAAK,CAAE,WAAA,GAAc,OAAA,CAAQ,SAAA,CAAW,GAAG,CAAA,CACpE,OAAO0G,CAAAA,CAAW,MAAA,CAAS,CAAA,CAAIA,CAAAA,CAAa,MAC9C,CAEA,SAASC,EAAAA,CAAarG,CAAAA,CAA6C,CACjE,IAAMsG,CAAAA,CAAS,IAAI,IACnB,OAAAtG,CAAAA,CAAO,OAAA,CAASH,CAAAA,EAAU,CACpBA,CAAAA,EACFyG,CAAAA,CAAO,GAAA,CAAIzG,CAAK,EAEpB,CAAC,CAAA,CACM,CAAC,GAAGyG,CAAM,CACnB,CAEA,SAASC,MAAgCD,CAAAA,CAA4C,CACnF,IAAME,CAAAA,CAAmBF,EACtB,GAAA,CAAK5G,CAAAA,EAAUyG,CAAAA,CAA4BzG,CAAK,CAAC,CAAA,CACjD,MAAA,CAAQA,CAAAA,EAA2B,CAAC,CAACA,CAAK,CAAA,CAE7C,GAAI8G,CAAAA,CAAiB,KAAM9G,CAAAA,EAAUA,CAAAA,CAAM,QAAA,CAAS,KAAK,CAAC,CAAA,CAAG,OAAO,KAAA,CACpE,GAAI8G,EAAiB,IAAA,CAAM9G,CAAAA,EAAUA,CAAAA,CAAM,QAAA,CAAS,KAAK,CAAC,CAAA,CAAG,OAAO,MACpE,GAAI8G,CAAAA,CAAiB,IAAA,CAAM9G,CAAAA,EAAUA,EAAM,QAAA,CAAS,KAAK,CAAC,CAAA,CAAG,OAAO,KAAA,CACpE,GAAI8G,CAAAA,CAAiB,IAAA,CAAM9G,GAAUA,CAAAA,CAAM,QAAA,CAAS,KAAK,CAAC,EAAG,OAAO,KAAA,CACpE,GAAI8G,CAAAA,CAAiB,KAAM9G,CAAAA,EAAUA,CAAAA,CAAM,QAAA,CAAS,IAAI,CAAC,CAAA,CAAG,OAAO,IAGrE,CAEA,SAAS+G,EAAAA,CAA+BC,CAAAA,CAKjB,CACrB,IAAMC,CAAAA,CAAa,CACjBR,CAAAA,CAA4BO,CAAAA,CAAM,IAAI,CAAA,CACtCP,CAAAA,CAA4BO,CAAAA,CAAM,SAAS,EAC3CP,CAAAA,CAA4BO,CAAAA,CAAM,aAAa,CAAA,CAC/CP,EAA4BO,CAAAA,CAAM,QAAQ,CAC5C,CAAA,CAAE,OAAQhH,CAAAA,EAA2B,CAAC,CAACA,CAAK,EAE5C,GAAIiH,CAAAA,CAAW,IAAA,CAAMjH,CAAAA,EAAUA,EAAM,QAAA,CAAS,OAAO,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAS,KAAK,CAAA,EAAKA,CAAAA,CAAM,SAAS,KAAK,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAS,KAAK,GAAKA,CAAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAS,IAAI,CAAC,EACzM,OAAO,OAAA,CAGT,GAAIiH,CAAAA,CAAW,KAAMjH,CAAAA,EAAUA,CAAAA,CAAM,QAAA,CAAS,UAAU,GAAKA,CAAAA,CAAM,UAAA,CAAW,KAAK,CAAA,EAAKA,CAAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAKA,EAAM,QAAA,CAAS,QAAQ,CAAC,CAAA,CACxI,OAAO,UAIX,CAEO,SAASkH,EAAAA,CAAiCF,EAOpC,CACX,GAAI,KAAA,CAAM,OAAA,CAAQA,EAAM,aAAa,CAAA,EAAKA,CAAAA,CAAM,aAAA,CAAc,OAAS,CAAA,CACrE,OAAOL,EAAAA,CAAaK,CAAAA,CAAM,cAAc,GAAA,CAAKhH,CAAAA,EAAUyG,CAAAA,CAA4BzG,CAAK,CAAC,CAAC,CAAA,CAG5F,GAAIgH,CAAAA,CAAM,IAAA,GAAS,OAAA,EAAWtK,CAAAA,CAAgBsK,CAAAA,CAAM,IAAI,CAAA,CAAG,CACzD,IAAMG,CAAAA,CAAiBN,GAA6BG,CAAAA,CAAM,aAAA,CAAeA,CAAAA,CAAM,IAAA,CAAMA,EAAM,QAAQ,CAAA,CACnG,OAAOG,CAAAA,CAAiB,CAAC,CAAA,MAAA,EAASA,CAAc,CAAA,CAAE,CAAA,CAAI,CAAC,OAAO,CAChE,CAEA,GAAIH,EAAM,IAAA,GAAS,SAAA,CAAW,CAC5B,IAAMI,EAAcL,EAAAA,CAA+BC,CAAK,CAAA,CACxD,OAAOI,CAAAA,CAAc,CAAC,CAAA,QAAA,EAAWA,CAAW,EAAE,CAAA,CAAI,CAAC,SAAS,CAC9D,CAEA,IAAMC,CAAAA,CACJ3K,CAAAA,CAAgBsK,CAAAA,CAAM,IAAI,CAAA,CACtB,OAAA,CACAD,EAAAA,CAA+BC,CAAK,EAClC,SAAA,CACA,MAAA,CAER,GAAIK,CAAAA,GAAiB,QAAS,CAC5B,IAAMF,CAAAA,CAAiBN,EAAAA,CAA6BG,EAAM,aAAA,CAAeA,CAAAA,CAAM,IAAA,CAAMA,CAAAA,CAAM,QAAQ,CAAA,CACnG,OAAOG,CAAAA,CAAiB,CAAC,CAAA,MAAA,EAASA,CAAc,CAAA,CAAE,CAAA,CAAI,CAAC,OAAO,CAChE,CAEA,GAAIE,IAAiB,SAAA,CAAW,CAC9B,IAAMD,CAAAA,CAAcL,GAA+BC,CAAK,CAAA,CACxD,OAAOI,CAAAA,CAAc,CAAC,CAAA,QAAA,EAAWA,CAAW,CAAA,CAAE,EAAI,CAAC,SAAS,CAC9D,CAEA,OAAO,EACT,CAEA,SAASE,GAAyBC,CAAAA,CAAgC,CAChE,IAAMrF,CAAAA,CAAOqF,CAAAA,CAAK,IAAA,EAAQ,EAAC,CACrBC,EACJ,OAAOtF,CAAAA,CAAK,UAAA,EAAe,QAAA,EAAY,OAAO,QAAA,CAASA,CAAAA,CAAK,UAAU,CAAA,EAAKA,EAAK,UAAA,CAAa,CAAA,CACzF,IAAA,CAAK,GAAA,CAAImE,GAAiBnE,CAAAA,CAAK,UAAU,CAAA,CACzC,EAAA,CAEAQ,EAAY,KAAA,CAAM,OAAA,CAAQR,CAAAA,CAAK,SAAS,EAAIA,CAAAA,CAAK,SAAA,CAAY,MAAA,CAEnE,OAAO,CACL,CACE,IAAA,CAAM,OAAA,CACN,KAAA,CAAO,KAAA,CAAM,IAAA,CAAK,CAAE,MAAA,CAAQsF,CAAe,CAAA,CAAG,CAACC,CAAAA,CAAG7E,CAAAA,GAAU,CAC1D,IAAM8E,CAAAA,CAAmBjF,EAAAA,CAA0BC,CAAAA,CAAWE,EAAQ,CAAC,CAAA,CACvE,OAAO,CACL,GAAI,CAAA,EAAGA,CAAAA,CAAQ,CAAC,CAAA,CAAA,CAChB,MAAO,CAAA,EAAGA,CAAAA,CAAQ,CAAC,CAAA,CAAA,CACnB,UAAW,KAAA,CACX,IAAA,CAAM8E,CAAAA,GAAqB,KAAA,CAAQ,YAAc,WAAA,CACjD,aAAA,CAAeA,CACjB,CACF,CAAC,CACH,CACF,CACF,CAEA,SAASC,EAAAA,CAAiBJ,CAAAA,CAAgC,CACxD,IAAMrF,CAAAA,CAAOqF,CAAAA,CAAK,IAAA,EAAQ,GAC1B,GAAI,KAAA,CAAM,OAAA,CAAQrF,CAAAA,CAAK,aAAa,CAAA,EAAKA,CAAAA,CAAK,aAAA,CAAc,MAAA,CAAS,EACnE,OAAOA,CAAAA,CAAK,aAAA,CAGd,GAAM,CAAE,KAAA,CAAAoD,CAAAA,CAAO,SAAA,CAAAW,CAAU,EAAIG,EAAAA,CAAsBlE,CAAI,CAAA,CACvD,OAAI+D,CAAAA,CAAU,MAAA,CAAS,CAAA,CACdA,CAAAA,CAELX,EAAM,MAAA,CAAS,CAAA,CACVA,CAAAA,CAGFgC,EAAAA,CAAyBC,CAAI,CACtC,CAEA,SAASK,EAAAA,CAAuBL,EAAuD,CACrF,IAAMrF,CAAAA,CAAOqF,CAAAA,EAAM,IAAA,CACnB,GAAI,CAACA,CAAAA,EAAQ,CAACrF,CAAAA,EAAQ,OAAOA,CAAAA,EAAS,QAAA,CACpC,OAAO,IAAI,GAAA,CAGb,IAAM2F,CAAAA,CAASvB,GAAoB,GAAA,CAAIpE,CAAc,CAAA,CACrD,GAAI2F,CAAAA,CACF,OAAOA,CAAAA,CAGT,IAAMC,EAAW,IAAI,GAAA,CAIjBC,CAAAA,CAAiB,CAAA,CAEfC,EAAY,CAACnE,CAAAA,CAAkBoE,CAAAA,CAAqBC,CAAAA,CAAgB,QAAU,CAClFD,CAAAA,CAAO,OAAA,CAAS7C,CAAAA,EAAU,CACxBA,CAAAA,CAAM,KAAA,CAAM,OAAA,CAAShH,CAAAA,EAAS,CAC5B,IAAMuE,CAAAA,CAAWuF,CAAAA,CAAiBH,CAAAA,EAAkB,EAAK,MAAA,CACnDvL,CAAAA,CAAW0B,EAAAA,CAAkBkH,CAAAA,CAAM,KAAM,CAC7C,EAAA,CAAIhH,CAAAA,CAAK,EAAA,CACT,KAAA,CAAOA,CAAAA,CAAK,KAAA,CACZ,IAAA,CAAM1B,EAAgB0B,CAAAA,CAAK,IAAI,CAAA,CAAIA,CAAAA,CAAK,KAAO,MACjD,CAAC,CAAA,CAMD,GAAI0J,EAAS,GAAA,CAAItL,CAAQ,CAAA,CAAG,CAC1BgK,EAAAA,CAAsBhK,CAAQ,CAAA,CAC9B,MACF,CAEAsL,CAAAA,CAAS,GAAA,CAAItL,CAAAA,CAAU,CACrB,KAAAqH,CAAAA,CACA,IAAA,CAAMnH,CAAAA,CAAgB0B,CAAAA,CAAK,IAAI,CAAA,CAAI,OAAA,CAAU,SAAA,CAC7C,aAAA,CAAe8I,EAAAA,CAAiC,CAC9C,aAAA,CAAe9I,CAAAA,CAAK,cACpB,IAAA,CAAM1B,CAAAA,CAAgB0B,CAAAA,CAAK,IAAI,EAAI,OAAA,CAAU,SAAA,CAC7C,IAAA,CAAMA,CAAAA,CAAK,KACX,SAAA,CAAWA,CAAAA,CAAK,SAAA,CAChB,aAAA,CAAeA,EAAK,aAAA,CACpB,QAAA,CAAA5B,CACF,CAAC,EACD,QAAA,CAAU,OAAO4B,CAAAA,CAAK,IAAA,EAAS,SAAWA,CAAAA,CAAK,IAAA,CAAO,MAAA,CAKtD,aAAA,CACE,OAAOA,CAAAA,CAAK,aAAA,EAAkB,QAAA,EAAYA,CAAAA,CAAK,aAAA,CAAc,MAAA,CAAS,CAAA,CAClEA,CAAAA,CAAK,cACL2D,EAAAA,CAA2B3D,CAAAA,CAAK,IAAI,CAAA,CAC1C,GAAIuE,CAAAA,GAAa,MAAA,CAAY,CAAE,QAAA,CAAAA,CAAS,CAAA,CAAI,EAC9C,CAAC,EACH,CAAC,EACH,CAAC,EACH,CAAA,CAEA,GAAI4E,CAAAA,CAAK,IAAA,GAAS,eAChBS,CAAAA,CAAU,MAAA,CAAQL,EAAAA,CAAiBJ,CAAI,EAAG,IAAI,CAAA,CAAA,KACzC,CACL,GAAM,CAAE,KAAA,CAAAjC,CAAAA,CAAO,SAAA,CAAAW,CAAU,CAAA,CAAIG,EAAAA,CAAsBlE,CAAI,CAAA,CACvD8F,EAAU,OAAA,CAAS1C,CAAK,CAAA,CACxB0C,CAAAA,CAAU,OAAQ/B,CAAS,EAC7B,CAEA,OAAAK,GAAoB,GAAA,CAAIpE,CAAAA,CAAgB4F,CAAQ,CAAA,CACzCA,CACT,CAEA,SAASK,EAAAA,CAAgB3L,CAAAA,CAAkB+K,EAA4C,CACrF,GAAI,CAAC/K,CAAAA,CACH,OAAO,SAAA,CAOT,GAJI+K,CAAAA,EAAM,IAAA,GAAS,cAAA,EAAkBA,CAAAA,EAAM,IAAA,GAAS,oBAAA,EAIhDhJ,EAAuB/B,CAAQ,CAAA,EAAK,OAAA,CAAQ,IAAA,CAAKA,CAAQ,CAAA,CAC3D,OAAO,OAAA,CAGT,IAAM4L,EAAc5L,CAAAA,CAAS,WAAA,EAAY,CACnC6L,CAAAA,CACJD,EAAY,UAAA,CAAW,MAAM,CAAA,EAC7BA,CAAAA,CAAY,WAAW,OAAO,CAAA,EAC9BA,CAAAA,CAAY,UAAA,CAAW,SAAS,CAAA,EAChCA,CAAAA,CAAY,QAAA,CAAS,OAAO,GAC5BA,CAAAA,CAAY,QAAA,CAAS,OAAO,CAAA,CACxBE,CAAAA,CACJ,kBAAA,CAAmB,IAAA,CAAK9L,CAAQ,GAChC,CAAC4L,CAAAA,CAAY,UAAA,CAAW,KAAK,GAC7B,CAACA,CAAAA,CAAY,UAAA,CAAW,OAAO,EAEjC,OAAIC,CAAAA,EAA2BC,CAAAA,CACtB,SAAA,CAGF,SACT,CAGA,IAAMC,EAAAA,CAAoB,OAAA,CAO1B,SAASC,EAAAA,CACPjB,CAAAA,CACA5E,CAAAA,CACQ,CACR,IAAMD,CAAAA,CAAY6E,CAAAA,EAAM,IAAA,EAAM,SAAA,CAC9B,OAAO9E,EAAAA,CACL,KAAA,CAAM,OAAA,CAAQC,CAAS,CAAA,CAAIA,CAAAA,CAAY,MAAA,CACvCC,CACF,CACF,CAOA,SAAS8F,EAAAA,CACPlB,CAAAA,CACAmB,EACAC,CAAAA,CACoB,CACpB,GAAIpB,CAAAA,EAAM,OAAS,cAAA,CACnB,OAAIoB,CAAAA,EAAgB,QAAA,GAAa,MAAA,CAAkBA,CAAAA,CAAe,QAAA,CAC3DJ,EAAAA,CAAkB,KAAKG,CAAgB,CAAA,CAAI,MAAA,CAAOA,CAAgB,EAAI,MAC/E,CAEA,SAASE,EAAAA,CACPpM,EACAqM,CAAAA,CACAtB,CAAAA,CACU,CACV,GAAI,CAAC/K,CAAAA,CACH,OAAO,GAGT,GAAIqM,CAAAA,GAAS,OAAA,CAMX,OAAItB,GAAM,IAAA,GAAS,oBAAA,CACV,EAAC,CAGNA,GAAM,IAAA,GAAS,cAAA,EAAkBgB,EAAAA,CAAkB,IAAA,CAAK/L,CAAQ,CAAA,CAC3D0K,EAAAA,CAAiC,CACtC,IAAA,CAAA2B,EACA,aAAA,CAAeL,EAAAA,CAA4BjB,CAAAA,CAAM,MAAA,CAAO/K,CAAQ,CAAC,CAAA,CACjE,QAAA,CAAAA,CACF,CAAC,CAAA,CAGI0K,EAAAA,CAAiC,CACtC,IAAA,CAAA2B,CAAAA,CACA,QAAA,CAAArM,CACF,CAAC,EAGH,GAAIqM,CAAAA,GAAS,SAAA,CAAW,CACtB,IAAMT,CAAAA,CAAc5L,CAAAA,CAAS,WAAA,EAAY,CACzC,OAAO0K,EAAAA,CAAiC,CACtC,IAAA,CAAA2B,CAAAA,CACA,SAAArM,CAAAA,CACA,IAAA,CACE4L,CAAAA,CAAY,QAAA,CAAS,OAAO,CAAA,EAAKA,CAAAA,CAAY,QAAA,CAAS,MAAM,GAAKA,CAAAA,CAAY,QAAA,CAAS,KAAK,CAAA,CACvF,QACAA,CAAAA,CAAY,UAAA,CAAW,KAAK,CAAA,EAAKA,CAAAA,CAAY,UAAA,CAAW,MAAM,CAAA,EAAKA,EAAY,UAAA,CAAW,OAAO,CAAA,CAC/F,UAAA,CACA,MACV,CAAC,CACH,CAEA,OAAO,EACT,CAMA,IAAMU,EAAAA,CAAgC,IAAI,GAAA,CAAY,CACpD,cAAA,CACA,oBACF,CAAC,CAAA,CAGD,SAASC,EAAAA,CAAsBpM,CAAAA,CAAuC,CACpE,OAAO,OAAOA,CAAAA,EAAa,QAAA,EAAY,UAAU,IAAA,CAAKA,CAAQ,CAChE,CAOA,SAASqM,EAAAA,CACPzB,CAAAA,CACAmB,CAAAA,CACAG,EACAF,CAAAA,CACmB,CACnB,GAAI,CAACD,EAAkB,OAAO,SAAA,CAG9B,GAAIlJ,CAAAA,CAAckJ,CAAgB,CAAA,CAAE,KAAA,CAAM,IAAA,GAAS,aAAA,CACjD,OAAO,gBAAA,CAGT,GAAIG,CAAAA,GAAS,QAAS,OAAO,SAAA,CAE7B,IAAMI,CAAAA,CAAW1B,GAAM,IAAA,CACvB,OAAI0B,CAAAA,EAAYH,EAAAA,CAA8B,IAAIG,CAAQ,CAAA,CACpDA,CAAAA,GAAa,cAAA,GASXV,EAAAA,CAAkB,IAAA,CAAKG,CAAgB,CAAA,EACvCC,GAAuB,aAAA,CAEzB5L,EAAAA,CAAqB2L,CAAgB,CAAA,CAAU,eAC5C,SAAA,CAKLK,EAAAA,CAAsBJ,CAAAA,EAAgB,QAAQ,GAC9C,gCAAA,CAAiC,IAAA,CAAKD,CAAgB,CAAA,CAAU,cAChEhM,CAAAA,CAAgBiM,CAAAA,EAAgB,QAAQ,CAAA,EACxC,YAAY,IAAA,CAAKD,CAAgB,CAAA,EACjCnK,CAAAA,CAAuBmK,CAAgB,CAAA,CAAU,WAAA,CAE9C,SACT,CAUO,SAASQ,EAAAA,CACd3B,CAAAA,CACA4B,CAAAA,CACmB,CACnB,OAAOC,EAAAA,CAAoB7B,CAAAA,CAAiC4B,CAAM,EAAE,SACtE,CAEO,SAASC,EAAAA,CACd7B,EACA4B,CAAAA,CACkB,CAClB,IAAME,CAAAA,CAAgC9M,EAA6B4M,CAAM,CAAA,CACzE,GAAI,CAACE,EACH,OAAO,CACL,gBAAA,CAAkB,EAAA,CAClB,KAAM,SAAA,CACN,aAAA,CAAe,EAAC,CAChB,UAAW,SACb,CAAA,CAKF,IAAMC,CAAAA,CAAe9J,EAAc6J,CAA6B,CAAA,CAC1DX,CAAAA,CAAmBY,CAAAA,CAAa,IAAA,CAChCC,CAAAA,CAAuCD,CAAAA,CAAa,SAAA,CAEpDX,EAAiBf,EAAAA,CAAuBL,CAAI,CAAA,CAAE,GAAA,CAAImB,CAAgB,CAAA,CAKpE7E,CAAAA,CAAAA,CAJS0D,CAAAA,EAAM,IAAA,EAAQ,EAAC,EACmD,aAAA,GAC5CmB,CAAgB,CAAA,EAE1Ba,GAAgBZ,CAAAA,EAAgB,IAAA,CACpD9E,CAAAA,GACC6E,CAAAA,GAAqB,WAAaA,CAAAA,GAAqB,YAAA,CACzD7E,CAAAA,CAAO,OAAA,CAAA,CAEP6E,IAAqB,YAAA,EACrBA,CAAAA,GAAqB,YAAA,EACrBA,CAAAA,GAAqB,eACrBA,CAAAA,GAAqB,aAAA,EACrBnK,CAAAA,CAAuBmK,CAAgB,CAAA,IAEvC7E,CAAAA,CAAO,MAAA,CAAA,CAAA,CAIX,IAAMgF,EAAOF,CAAAA,EAAgB,IAAA,EAAQR,EAAAA,CAAgBO,CAAAA,CAAkBnB,CAAI,CAAA,CAErEQ,CAAAA,CAAiBU,EAAAA,CAAsBlB,CAAAA,CAAMmB,EAAkBC,CAAc,CAAA,CAI7Ea,CAAAA,CACJb,CAAAA,EAAgB,aAAA,GACfZ,CAAAA,GAAmB,MAAA,CAChBS,EAAAA,CAA4BjB,EAAMQ,CAAc,CAAA,CAChD,MAAA,CAAA,CAEN,OAAO,CACL,gBAAA,CAAAW,CAAAA,CACA,IAAA,CAAA7E,CAAAA,CACA,KAAAgF,CAAAA,CACA,aAAA,CACEF,CAAAA,EAAgB,aAAA,CAAc,MAAA,CAC1BA,CAAAA,CAAe,aAAA,CACfC,EAAAA,CAAyBF,EAAkBG,CAAAA,CAAMtB,CAAI,CAAA,CAC3D,SAAA,CAAWyB,GAAyBzB,CAAAA,CAAMmB,CAAAA,CAAkBG,CAAAA,CAAMF,CAAc,EAChF,QAAA,CAAUA,CAAAA,EAAgB,QAAA,CAC1B,aAAA,CAAAa,EACA,cAAA,CAAAzB,CACF,CACF,CAWO,SAAS0B,EAAAA,CACdlC,CAAAA,CACA4B,CAAAA,CACoB,CACpB,OAAOC,EAAAA,CAAoB7B,CAAAA,CAAiC4B,CAAM,CAAA,CAAE,cACtE,CCtjBO,IAAMO,EAAAA,CAGT,CACF,GAAA,CAAK,CAAE,GAAA,CAAK,EAAA,CAAI,GAAI,EAAG,CAAA,CACvB,GAAA,CAAK,CAAE,IAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,IAAK,CAAE,GAAA,CAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,GAAA,CAAK,CAAE,IAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,IAAK,CAAE,GAAA,CAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,GAAA,CAAK,CAAE,GAAA,CAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,IAAK,CAAE,GAAA,CAAK,EAAA,CAAI,EAAA,CAAI,EAAG,CAAA,CACvB,GAAA,CAAK,CAAE,GAAA,CAAK,GAAI,EAAA,CAAI,EAAG,CACzB,CAAA,CAGaC,GAAmB,CAAC,KAAA,CAAO,KAAA,CAAO,KAAA,CAAO,KAAK,CAAA,CAE9CC,EAAAA,CAAuB,CAAC,KAAA,CAAO,MAAO,KAAA,CAAO,KAAK,CAAA,CAclDC,EAAAA,CAAqD,CAChE,GAAA,CAAK,CAAC,KAAA,CAAO,KAAK,CAAA,CAClB,GAAA,CAAK,CAAC,KAAK,EACX,GAAA,CAAK,CAAC,KAAA,CAAO,KAAK,EAClB,GAAA,CAAK,CAAC,KAAK,CACb,EAGO,SAASC,EAAAA,CACdC,CAAAA,CACmC,CACnC,IAAMC,CAAAA,CAAQD,CAAAA,CAAK,WAAA,GACnB,OAAKJ,EAAAA,CAAuC,QAAA,CAASK,CAAK,EAAU,OAAA,CAC/DJ,EAAAA,CAA2C,QAAA,CAASI,CAAK,EACrD,WAAA,CACF,SACT,CASO,SAASC,EAAAA,CACdF,CAAAA,CACAG,CAAAA,CAA+B,KAAA,CACX,CACpB,IAAMF,CAAAA,CAAQD,CAAAA,CAAK,WAAA,GACbI,CAAAA,CAAWT,EAAAA,CAAmBM,CAAK,CAAA,GAAIE,CAAM,CAAA,CACnD,GAAI,OAAOC,CAAAA,EAAa,SAAU,OAAOA,CAAAA,CACzC,IAAMC,CAAAA,CAAWvI,GAAiBmI,CAAK,CAAA,CACvC,OAAO,OAAOI,GAAU,QAAA,EAAa,QAAA,CAAWA,CAAAA,CAAS,QAAA,CAAW,MACtE,CAGO,SAASC,EAAAA,CAAeC,CAAAA,CAAeC,CAAAA,CAA4B,CACxE,IAAMC,CAAAA,CAAWX,GAAgBS,CAAAA,CAAM,WAAA,EAAa,CAAA,CACpD,OAAKE,CAAAA,CACEA,CAAAA,CAAS,QAAA,CAASD,CAAAA,CAAU,aAAa,CAAA,CAD1B,IAExB,KCnEaE,EAAAA,CAAqD,CAChE,oBAAA,CACA,cACF,EAEMC,EAAAA,CAAoB,IAO1B,SAASC,EAAAA,CAAgBC,EAAwB,CAC/C,OAAO,MAAA,CAAO,IAAA,CAAKA,CAAM,CAAA,CACrBA,CAAAA,CAAO,OAAA,CAAQ,IAAA,CAAM,KAAK,CAAA,CAAE,OAAA,CAAQ,IAAA,CAAM,KAAK,CAAA,CAC/CA,CACN,CAEA,SAASC,GAAkBC,CAAAA,CAAyB,CAClD,OAAOA,CAAAA,CAAQ,SAAS,GAAG,CAAA,CACvBA,CAAAA,CAAQ,OAAA,CAAQ,QAAS,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAA,CAAQ,GAAG,CAAA,CACjDA,CACN,CAQO,SAASC,EAAgBH,CAAAA,CAAgBI,CAAAA,CAAiC,CAC/E,IAAMzD,EAAOoD,EAAAA,CAAgBC,CAAM,CAAA,CACnC,OAAOI,CAAAA,CAAQ,CAAA,EAAGzD,CAAI,CAAA,EAAGmD,EAAiB,CAAA,EAAGM,CAAK,CAAA,CAAA,CAAKzD,CACzD,CAGO,SAAS0D,CAAAA,CAAqBlF,CAAAA,CAGnC,CACA,IAAMnD,CAAAA,CAAQmD,CAAAA,CAAI,OAAA,CAAQ2E,EAAiB,CAAA,CAC3C,OAAI9H,CAAAA,CAAQ,CAAA,CAAU,CAAE,MAAA,CAAQiI,EAAAA,CAAkB9E,CAAG,CAAE,EAChD,CACL,MAAA,CAAQ8E,EAAAA,CAAkB9E,CAAAA,CAAI,MAAM,CAAA,CAAGnD,CAAK,CAAC,CAAA,CAC7C,KAAA,CAAOmD,CAAAA,CAAI,KAAA,CAAMnD,CAAAA,CAAQ,CAAC,CAC5B,CACF,CAUO,SAASsI,GACd3D,CAAAA,CACA/K,CAAAA,CACA0N,CAAAA,CAA+B,KAAA,CACK,CACpC,GAAI,CAAC3C,CAAAA,CAAM,OACX,IAAIwC,CAAAA,CACJ,GAAIvN,CAAAA,CAAU,CACZ,IAAMsL,CAAAA,CAAWsB,EAAAA,CACf7B,CAAAA,CACA/K,CACF,EACI,OAAOsL,CAAAA,CAAS,aAAA,EAAkB,QAAA,EAAYA,EAAS,aAAA,GACzDiC,CAAAA,CAAOjC,CAAAA,CAAS,aAAA,EAEpB,CACA,GAAI,CAACiC,CAAAA,CAAM,CAET,IAAMoB,CAAAA,CAAW5D,CAAAA,CAAK,IAAA,EAAM,cACxB,OAAO4D,CAAAA,EAAa,QAAA,EAAYA,CAAAA,GAAUpB,EAAOoB,CAAAA,EACvD,CACA,GAAI,CAACpB,EAAM,OACX,IAAMC,CAAAA,CAAQD,CAAAA,CAAK,aAAY,CAC/B,OAAO,CACL,IAAA,CAAMC,EACN,QAAA,CAAUC,EAAAA,CAAqBD,CAAAA,CAAOE,CAAM,EAC5C,IAAA,CAAMJ,EAAAA,CAAiBE,CAAK,CAC9B,CACF,CAGO,SAASoB,EAAAA,CAAYC,EAA+B,CACzD,OAAIA,CAAAA,CAAK,IAAA,GAAS,QAAgB,IAAA,CACrBA,CAAAA,CAAK,IAAA,EAAM,IAAA,GACR,OAClB,CAEA,SAASC,EAAAA,CAAgBnC,CAAAA,CAAuD,CAC9E,GAAI,CAACA,CAAAA,CAAQ,OACb,IAAM5J,CAAAA,CAAQC,CAAAA,CAAc2J,CAAM,CAAA,CAAE,MACpC,OAAO5J,CAAAA,CAAM,IAAA,GAAS,aAAA,CAAgBA,EAAM,KAAA,CAAQ,MACtD,CAEA,SAASgM,EAAAA,CAAWvL,CAAAA,CAAoC,CACtD,OAAO,OAAOA,CAAAA,EAAU,QAAA,EAAY,MAAA,CAAO,QAAA,CAASA,CAAK,CAAA,CAAIA,CAAAA,CAAQ,MACvE,CAoBO,SAASwL,EAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAAkC,EAAC,CACX,CACxB,IAAMzB,EAASyB,CAAAA,CAAQ,eAAA,EAAmB,KAAA,CACpCC,CAAAA,CAAW,IAAI,GAAA,CACrB,IAAA,IAAWrE,CAAAA,IAAQkE,CAAAA,CAAOG,EAAS,GAAA,CAAIrE,CAAAA,CAAK,EAAA,CAAIA,CAAI,CAAA,CAQpD,IAAMsE,CAAAA,CANaH,CAAAA,CAAM,OACtBL,CAAAA,EACCD,EAAAA,CAAYC,CAAI,CAAA,EAAKO,EAAS,GAAA,CAAIP,CAAAA,CAAK,MAAM,CAAA,EAAKO,EAAS,GAAA,CAAIP,CAAAA,CAAK,MAAM,CAC9E,EAGsC,GAAA,CAAKA,CAAAA,GAAU,CACnD,IAAA,CAAAA,EACA,MAAA,CAAQA,CAAAA,CAAK,EAAA,CACb,YAAA,CAAcA,EAAK,MAAA,CACnB,UAAA,CAAYA,CAAAA,CAAK,MAAA,CACjB,aAAcA,CAAAA,CAAK,YAAA,EAAgB,MAAA,CACnC,UAAA,CAAYA,CAAAA,CAAK,YAAA,EAAgB,MACnC,CAAA,CAAE,EAEIS,CAAAA,CAAY,IAAI,GAAA,CAChBC,CAAAA,CAAe,CAACnB,CAAAA,CAAgB7G,CAAAA,GAA2B,CAC/D,IAAMiI,EAAOF,CAAAA,CAAU,GAAA,CAAIlB,CAAM,CAAA,CAC7BoB,EAAMA,CAAAA,CAAK,IAAA,CAAKjI,CAAK,CAAA,CACpB+H,EAAU,GAAA,CAAIlB,CAAAA,CAAQ,CAAC7G,CAAK,CAAC,EACpC,CAAA,CACA,IAAA,IAAWkI,CAAAA,IAAQJ,EACjBE,CAAAA,CAAaE,CAAAA,CAAK,YAAA,CAAc,CAC9B,IAAA,CAAAA,CAAAA,CACA,MAAA,CAAQA,CAAAA,CAAK,WACb,WAAA,CAAaA,CAAAA,CAAK,YAAA,CAClB,UAAA,CAAYX,GAAgBW,CAAAA,CAAK,YAAY,CAAA,GAAM,MACrD,CAAC,CAAA,CACDF,CAAAA,CAAaE,CAAAA,CAAK,UAAA,CAAY,CAC5B,IAAA,CAAAA,CAAAA,CACA,MAAA,CAAQA,CAAAA,CAAK,aACb,WAAA,CAAaA,CAAAA,CAAK,UAAA,CAClB,UAAA,CAAYX,GAAgBW,CAAAA,CAAK,UAAU,CAAA,GAAM,MACnD,CAAC,CAAA,CAGH,IAAMC,CAAAA,CAAmBtB,CAAAA,EAA4B,CACnD,IAAMb,CAAAA,CAAO6B,CAAAA,CAAS,IAAIhB,CAAM,CAAA,EAAG,IAAA,CACnC,OAAOb,EAAOU,EAAAA,CAAgC,QAAA,CAASV,CAAI,CAAA,CAAI,KACjE,CAAA,CAMIoC,CAAAA,CACAR,CAAAA,CAAQ,aAAA,GAAkB,MAAA,CAC5BQ,CAAAA,CAAgBR,CAAAA,CAAQ,aAAA,CAAc,OAAQS,CAAAA,EAAOR,CAAAA,CAAS,GAAA,CAAIQ,CAAE,CAAC,CAAA,CAErED,CAAAA,CAAgBV,CAAAA,CACb,MAAA,CAAQlE,GAAS2E,CAAAA,CAAgB3E,CAAAA,CAAK,EAAE,CAAC,CAAA,CACzC,MAAA,CAAQA,CAAAA,EAEA,CAAA,CADUuE,EAAU,GAAA,CAAIvE,CAAAA,CAAK,EAAE,CAAA,EAAK,EAAC,EAC3B,IAAA,CACdxD,CAAAA,EAAU,CAACA,EAAM,UAAA,EAAcmI,CAAAA,CAAgBnI,CAAAA,CAAM,MAAM,CAC9D,CACD,CAAA,CACA,GAAA,CAAKwD,CAAAA,EAASA,EAAK,EAAE,CAAA,CAI1B,IAAM8E,CAAAA,CAAkB,IAAI,GAAA,CACtBC,CAAAA,CAAY,IAAI,GAAA,CAChBC,EAAkB,EAAC,CACzB,IAAA,IAAWH,CAAAA,IAAMD,CAAAA,CACVG,CAAAA,CAAU,GAAA,CAAIF,CAAE,IACnBE,CAAAA,CAAU,GAAA,CAAIF,CAAAA,CAAI,CAAC,EACnBG,CAAAA,CAAM,IAAA,CAAKH,CAAE,CAAA,CAAA,CAGjB,QAASI,CAAAA,CAAO,CAAA,CAAGA,CAAAA,CAAOD,CAAAA,CAAM,OAAQC,CAAAA,EAAQ,CAAA,CAAG,CACjD,IAAMC,EAAUF,CAAAA,CAAMC,CAAI,CAAA,CACpBE,CAAAA,CAAQJ,EAAU,GAAA,CAAIG,CAAO,CAAA,EAAK,CAAA,CACxC,QAAW1I,CAAAA,IAAS+H,CAAAA,CAAU,GAAA,CAAIW,CAAO,CAAA,EAAK,EAAC,CAAG,CAChD,GAAM,CAAE,IAAA,CAAAR,CAAK,CAAA,CAAIlI,EACjB,GAAI,CAACsI,CAAAA,CAAgB,GAAA,CAAIJ,EAAK,MAAM,CAAA,CAAG,CACrCI,CAAAA,CAAgB,IAAIJ,CAAAA,CAAK,MAAM,CAAA,CAO/B,IAAMU,EACJ,CAACT,CAAAA,CAAgBO,CAAO,CAAA,EAAKP,EAAgBnI,CAAAA,CAAM,MAAM,CAAA,CACrD6I,CAAAA,CAAeD,EAAsB5I,CAAAA,CAAM,MAAA,CAAS0I,CAAAA,CACpDI,CAAAA,CAAaF,CAAAA,CAAsBF,CAAAA,CAAU1I,CAAAA,CAAM,MAAA,CACzD,GAAIkI,CAAAA,CAAK,YAAA,GAAiBW,CAAAA,CAAc,CACtC,IAAME,CAAAA,CAAeb,CAAAA,CAAK,UAAA,CACpBc,CAAAA,CAAad,EAAK,YAAA,CACxBA,CAAAA,CAAK,YAAA,CAAeW,CAAAA,CACpBX,CAAAA,CAAK,UAAA,CAAaY,CAAAA,CAClBZ,CAAAA,CAAK,aAAea,CAAAA,CACpBb,CAAAA,CAAK,UAAA,CAAac,EACpB,CACF,CACKT,CAAAA,CAAU,GAAA,CAAIvI,CAAAA,CAAM,MAAM,CAAA,GAC7BuI,CAAAA,CAAU,GAAA,CAAIvI,CAAAA,CAAM,MAAA,CAAQ2I,CAAAA,CAAQ,CAAC,CAAA,CACrCH,EAAM,IAAA,CAAKxI,CAAAA,CAAM,MAAM,CAAA,EAE3B,CACF,CAIA,IAAA,IAAWkI,CAAAA,IAAQJ,CAAAA,CAAO,CACxB,IAAMb,CAAAA,CAAQM,EAAAA,CAAgBW,CAAAA,CAAK,YAAY,CAAA,CAC3CjB,CAAAA,EAASY,CAAAA,CAAS,GAAA,CAAIK,EAAK,YAAY,CAAA,EAAG,IAAA,GAAS,oBAAA,GACrDA,EAAK,KAAA,CAAQjB,CAAAA,CACbiB,CAAAA,CAAK,MAAA,CAASlB,EAAgBkB,CAAAA,CAAK,YAAA,CAAcjB,CAAK,CAAA,EAE1D,CAEA,IAAMgC,CAAAA,CAAW,IAAI,IACfC,CAAAA,CAAc,IAAI,GAAA,CAClBC,CAAAA,CAAmB,IAAI,GAAA,CACvBC,CAAAA,CAAa,IAAI,GAAA,CACvB,QAAWlB,CAAAA,IAAQJ,CAAAA,CAAO,CACxBmB,CAAAA,CAAS,IAAIf,CAAAA,CAAK,MAAA,CAAQA,CAAI,CAAA,CAC9B,IAAMmB,CAAAA,CAAQH,CAAAA,CAAY,GAAA,CAAIhB,CAAAA,CAAK,UAAU,CAAA,CACzCmB,CAAAA,CAAOA,CAAAA,CAAM,IAAA,CAAKnB,CAAI,CAAA,CACrBgB,CAAAA,CAAY,GAAA,CAAIhB,CAAAA,CAAK,UAAA,CAAY,CAACA,CAAI,CAAC,EAC5C,IAAMoB,CAAAA,CAAOH,CAAAA,CAAiB,GAAA,CAAIjB,EAAK,YAAY,CAAA,CAGnD,GAFIoB,CAAAA,CAAMA,EAAK,IAAA,CAAKpB,CAAI,CAAA,CACnBiB,CAAAA,CAAiB,IAAIjB,CAAAA,CAAK,YAAA,CAAc,CAACA,CAAI,CAAC,CAAA,CAC/CA,CAAAA,CAAK,MAAA,CAAQ,CACf,IAAMqB,CAAAA,CAAWH,CAAAA,CAAW,GAAA,CAAIlB,CAAAA,CAAK,MAAM,CAAA,CACvCqB,CAAAA,CAAUA,CAAAA,CAAS,IAAA,CAAKrB,CAAI,CAAA,CAC3BkB,CAAAA,CAAW,GAAA,CAAIlB,EAAK,MAAA,CAAQ,CAACA,CAAI,CAAC,EACzC,CACF,CAEA,IAAMsB,CAAAA,CAAY3C,GAAgCqC,CAAAA,CAAY,GAAA,CAAIrC,CAAM,CAAA,EAAK,EAAC,CACxE4C,CAAAA,CAAsB5C,CAAAA,EAC1BsC,EAAiB,GAAA,CAAItC,CAAM,CAAA,EAAK,GAE5B6C,CAAAA,CAAwB7C,CAAAA,EAA6B,CACzD,IAAM8C,EAAO,IAAI,GAAA,CAAY,CAAC9C,CAAM,CAAC,CAAA,CAC/B+C,CAAAA,CAAgB,GAChBC,CAAAA,CAAQ,CAAChD,CAAM,CAAA,CACrB,KAAOgD,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAG,CACvB,IAAMnB,CAAAA,CAAUmB,CAAAA,CAAM,GAAA,EAAI,CAC1B,QAAW3B,CAAAA,IAAQuB,CAAAA,CAAmBf,CAAO,CAAA,CACvCiB,EAAK,GAAA,CAAIzB,CAAAA,CAAK,UAAU,CAAA,GAC5ByB,EAAK,GAAA,CAAIzB,CAAAA,CAAK,UAAU,CAAA,CACxB0B,EAAI,IAAA,CAAK1B,CAAAA,CAAK,UAAU,CAAA,CACxB2B,CAAAA,CAAM,IAAA,CAAK3B,CAAAA,CAAK,UAAU,GAE9B,CACA,OAAO0B,CACT,CAAA,CAEME,EAAsBjD,CAAAA,EAA6B,CACvD,IAAM8C,CAAAA,CAAO,IAAI,GAAA,CAAY,CAAC9C,CAAM,CAAC,EAC/B+C,CAAAA,CAAgB,EAAC,CACjBC,CAAAA,CAAQ,CAAChD,CAAM,CAAA,CACrB,KAAOgD,CAAAA,CAAM,OAAS,CAAA,EAAG,CACvB,IAAMnB,CAAAA,CAAUmB,EAAM,GAAA,EAAI,CAC1B,IAAA,IAAW3B,CAAAA,IAAQsB,CAAAA,CAASd,CAAO,CAAA,CAC7BiB,CAAAA,CAAK,IAAIzB,CAAAA,CAAK,YAAY,CAAA,GAC9ByB,CAAAA,CAAK,IAAIzB,CAAAA,CAAK,YAAY,CAAA,CAC1B0B,CAAAA,CAAI,KAAK1B,CAAAA,CAAK,YAAY,CAAA,CAC1B2B,CAAAA,CAAM,KAAK3B,CAAAA,CAAK,YAAY,CAAA,EAEhC,CACA,OAAO0B,CACT,CAAA,CAEMG,CAAAA,CAA8BC,CAAAA,EAA6B,CAC/D,IAAM9B,CAAAA,CAAOe,CAAAA,CAAS,GAAA,CAAIe,CAAM,CAAA,CAChC,OAAK9B,CAAAA,CACE,CAACA,CAAAA,CAAK,UAAA,CAAY,GAAGwB,CAAAA,CAAqBxB,EAAK,UAAU,CAAC,CAAA,CAD/C,EAEpB,CAAA,CAEM+B,CAAAA,CAAgB,IAAI,GAAA,CACpBC,EAAsBF,CAAAA,EAAyC,CACnE,IAAMlG,CAAAA,CAASmG,CAAAA,CAAc,GAAA,CAAID,CAAM,CAAA,CACvC,GAAIlG,CAAAA,CAAQ,OAAOA,CAAAA,CACnB,IAAM8F,EAAM,IAAI,GAAA,CACVO,CAAAA,CAAU,CAACH,CAAM,CAAA,CACjBI,CAAAA,CAAU,IAAI,GAAA,CACpB,KAAOD,CAAAA,CAAQ,MAAA,CAAS,CAAA,EAAG,CACzB,IAAME,CAAAA,CAAYF,CAAAA,CAAQ,GAAA,GAC1B,GAAIC,CAAAA,CAAQ,GAAA,CAAIC,CAAS,EAAG,SAC5BD,CAAAA,CAAQ,GAAA,CAAIC,CAAS,EACrB,IAAMnC,CAAAA,CAAOe,CAAAA,CAAS,GAAA,CAAIoB,CAAS,CAAA,CACnC,GAAI,CAACnC,CAAAA,CAAM,SACPA,CAAAA,CAAK,MAAA,EAAQ0B,CAAAA,CAAI,GAAA,CAAI1B,EAAK,MAAM,CAAA,CACpC0B,CAAAA,CAAI,GAAA,CAAI5C,CAAAA,CAAgBkB,CAAAA,CAAK,YAAY,CAAC,EAG1C,IAAMmB,CAAAA,CAAQG,CAAAA,CAAStB,CAAAA,CAAK,YAAY,CAAA,CACxC,IAAA,IAAS3O,CAAAA,CAAI8P,CAAAA,CAAM,OAAS,CAAA,CAAG9P,CAAAA,EAAK,CAAA,CAAGA,CAAAA,EAAAA,CAAK4Q,EAAQ,IAAA,CAAKd,CAAAA,CAAM9P,CAAC,CAAA,CAAE,MAAM,EAC1E,CAGA,OAAA0Q,CAAAA,CAAc,IAAID,CAAAA,CAAQJ,CAAG,CAAA,CACtBA,CACT,EAEMU,EAAAA,CAAgBzD,CAAAA,EAAyC,CAC7D,IAAM+C,CAAAA,CAAM,IAAI,GAAA,CAChB,IAAA,IAAWW,KAAQf,CAAAA,CAAS3C,CAAM,CAAA,CAChC,IAAA,IAAW7E,KAAOkI,CAAAA,CAAmBK,CAAAA,CAAK,MAAM,CAAA,CAAGX,EAAI,GAAA,CAAI5H,CAAG,CAAA,CAEhE,OAAO4H,CACT,CAAA,CAGMY,EAAAA,CAAiBC,CAAAA,EACrB,OAAOlC,EAAU,GAAA,CAAIrB,CAAAA,CAAqBuD,CAAU,CAAA,CAAE,MAAM,CAAA,EAAM,QAAA,CAE9DC,EAAAA,CAAYD,CAAAA,EAAwC,CACxD,GAAM,CAAE,MAAA,CAAA5D,CAAAA,CAAQ,KAAA,CAAAI,CAAM,CAAA,CAAIC,CAAAA,CAAqBuD,CAAU,CAAA,CACnD7Q,CAAAA,CAAO2O,CAAAA,CAAU,GAAA,CAAI1B,CAAM,CAAA,CAC3B8B,CAAAA,CAAQ,OAAO/O,CAAAA,EAAS,SAAWA,CAAAA,CAAO,MAAA,CAAO,gBAAA,CAAmB,CAAA,CAG1E,OAAOqN,CAAAA,CAAQ0B,CAAAA,CAAQ,EAAA,CAAMA,CAC/B,CAAA,CAEMgC,EAAAA,CAAmB,CACvB,CAAA,CACAC,IACgC,CAChC,IAAMC,CAAAA,CAAOD,CAAAA,YAAa,IAAMA,CAAAA,CAAI,IAAI,GAAA,CAAIA,CAAC,CAAA,CACzCE,CAAAA,CACAC,CAAAA,CAAY,EAAA,CAAA,CAAA,CACZC,EAAkB,IAAA,CACtB,IAAA,IAAWhJ,CAAAA,IAAO,CAAA,CAAG,CACnB,GAAI,CAAC6I,CAAAA,CAAK,GAAA,CAAI7I,CAAG,CAAA,CAAG,SAGpB,IAAMiJ,CAAAA,CAAcT,GAAcxI,CAAG,CAAA,CACrC,GAAI8I,CAAAA,GAAS,QAAaG,CAAAA,EAAe,CAACD,CAAAA,CAAiB,SAC3D,IAAMrC,CAAAA,CAAQ+B,EAAAA,CAAS1I,CAAG,CAAA,CAAA,CACtB8I,IAAS,MAAA,EAAcE,CAAAA,EAAmB,CAACC,CAAAA,EAAgBtC,CAAAA,CAAQoC,CAAAA,IACrED,CAAAA,CAAO9I,CAAAA,CACP+I,EAAYpC,CAAAA,CACZqC,CAAAA,CAAkBC,CAAAA,EAEtB,CACA,OAAOH,CACT,CAAA,CAEMI,EAAAA,CAAU,IAAqB,CACnC,IAAMtB,CAAAA,CAAqB,EAAC,CAC5B,IAAA,IAAWpG,CAAAA,IAAQkE,CAAAA,CAAO,CACxB,GAAIlE,CAAAA,CAAK,IAAA,GAAS,oBAAA,CAAsB,SACxC,IAAM2H,CAAAA,CAAO3H,CAAAA,CAAK,IAAA,EAAM,IAAA,CACxB,GAAI,CAAC,KAAA,CAAM,OAAA,CAAQ2H,CAAI,CAAA,CAAG,SAC1B,IAAMC,CAAAA,CAAY5H,EAAK,IAAA,EAAM,QAAA,EAAY,EAAC,CAI1C,QAAW9H,CAAAA,IAAOyP,CAAAA,CAAM,CACtB,IAAME,EAAM3P,CAAAA,CACR,CAAC2P,CAAAA,EAAO,OAAOA,EAAI,EAAA,EAAO,QAAA,EAC9BzB,CAAAA,CAAI,IAAA,CAAK,CACP,OAAA,CAASpG,CAAAA,CAAK,EAAA,CACd,KAAA,CAAOA,EACP,GAAA,CAAA6H,CAAAA,CACA,MAAA,CAAQrE,CAAAA,CAAgBxD,EAAK,EAAA,CAAI6H,CAAAA,CAAI,EAAE,CAAA,CACvC,KAAA,CAAOD,CAAAA,CAASC,CAAAA,CAAI,EAAE,CACxB,CAAC,EACH,CACF,CACA,OAAOzB,CACT,CAAA,CAEM0B,EAAAA,CAAeC,CAAAA,EACnBnC,EAAW,GAAA,CAAImC,CAAM,CAAA,EAAK,EAAC,CAEvBC,EAAAA,CAAc3E,CAAAA,EAClB2C,CAAAA,CAAS3C,CAAM,CAAA,CAAE,MAAA,CAAS,CAAA,EAC1B4C,CAAAA,CAAmB5C,CAAM,CAAA,CAAE,MAAA,GAAW,CAAA,EACtC,CAACsB,EAAgBtB,CAAM,CAAA,CAEnB4E,EAAAA,CAAqB,CACzBF,CAAAA,CACAG,CAAAA,CACAC,CAAAA,GACW,CACX,IAAMhC,CAAAA,CAAO,IAAI,GAAA,CACbiC,CAAAA,CAAQ,EACZ,IAAA,IAAW1D,CAAAA,IAAQoD,EAAAA,CAAYC,CAAM,EACnC,IAAA,IAAW1E,CAAAA,IAAUkD,CAAAA,CAA2B7B,CAAAA,CAAK,MAAM,CAAA,CAAG,CAC5D,GAAIyB,CAAAA,CAAK,IAAI9C,CAAM,CAAA,CAAG,SACtB8C,CAAAA,CAAK,IAAI9C,CAAM,CAAA,CACf,IAAM1I,CAAAA,CAAO0J,EAAS,GAAA,CAAIhB,CAAM,CAAA,EAAG,IAAA,CAE7B5K,CAAAA,CADUuL,EAAAA,CAAWrJ,CAAAA,GAAOuN,CAAK,CAAC,CAAA,GAE1BC,CAAAA,CAAgBnE,EAAAA,CAAWrJ,CAAAA,GAAOwN,CAAa,CAAC,CAAA,CAAI,MAAA,CAAA,CAC9D,OAAO1P,GAAU,QAAA,GAAU2P,CAAAA,EAAS3P,CAAAA,EAC1C,CAEF,OAAO2P,CACT,CAAA,CAEMC,EAAAA,CAAuB,CAC3BhF,EACApO,CAAAA,GAEA0O,EAAAA,CAA8BU,CAAAA,CAAS,GAAA,CAAIhB,CAAM,CAAA,CAAGpO,CAAAA,CAAU0N,CAAM,CAAA,CAEhE2F,GACJ9B,CAAAA,EACuE,CACvE,IAAM9B,CAAAA,CAAOe,CAAAA,CAAS,GAAA,CAAIe,CAAM,CAAA,CAChC,OAAK9B,CAAAA,CACE,CACL,MAAA,CAAQ2D,EAAAA,CAAqB3D,EAAK,YAAA,CAAcA,CAAAA,CAAK,YAAY,CAAA,CACjE,KAAM2D,EAAAA,CAAqB3D,CAAAA,CAAK,UAAA,CAAYA,CAAAA,CAAK,UAAU,CAC7D,CAAA,CAJkB,EAKpB,EAEM6D,EAAAA,CAAmB/J,CAAAA,EAAiC,CACxD,GAAI,CAAE,MAAA,CAAA6E,CAAAA,CAAQ,KAAA,CAAAI,CAAM,EAAIC,CAAAA,CAAqBlF,CAAG,CAAA,CAG5CiF,CAAAA,GAAU,MAAA,EAAa,CAACY,CAAAA,CAAS,GAAA,CAAIhB,CAAM,CAAA,EAAKgB,CAAAA,CAAS,GAAA,CAAI7F,CAAG,IAClE6E,CAAAA,CAAS7E,CAAAA,CACTiF,CAAAA,CAAQ,MAAA,CAAA,CAEV,IAAMzD,CAAAA,CAAOqE,CAAAA,CAAS,GAAA,CAAIhB,CAAM,CAAA,CAC1B9M,CAAAA,CAAAA,CACH,OAAOyJ,CAAAA,EAAM,MAAM,KAAA,EAAU,QAAA,CAAWA,CAAAA,CAAK,IAAA,CAAK,MAAQ,MAAA,GAC3DqD,CAAAA,CACF,GAAI,CAACI,EAAO,OAAOlN,CAAAA,CAInB,IAAMsR,CAAAA,CAAAA,CAHO,KAAA,CAAM,OAAA,CAAQ7H,CAAAA,EAAM,IAAA,EAAM,IAAI,CAAA,CACtCA,CAAAA,EAAM,IAAA,EAAM,IAAA,CACb,EAAC,EACY,IAAA,CAAMwI,CAAAA,EAAcA,CAAAA,EAAW,KAAO/E,CAAK,CAAA,CAC5D,OAAO,CAAA,EAAGlN,CAAK,CAAA,KAAA,EAAQsR,CAAAA,EAAK,KAAA,EAASpE,CAAK,EAC5C,CAAA,CAEA,OAAO,CACL,KAAA,CAAAS,EACA,KAAA,CAAAC,CAAAA,CACA,KAAA,CAAAG,CAAAA,CACA,cAAAM,CAAAA,CACA,OAAA,CAAS,EAAC,CACV,QAAA,CAAU,SAAA,CACV,YAAA,CAAcR,CAAAA,CAAQ,aACtB,OAAA,CAAUf,CAAAA,EAAWgB,CAAAA,CAAS,GAAA,CAAIhB,CAAM,CAAA,CACxC,WAAA,CAAAQ,EAAAA,CACA,eAAA,CAAAc,EACA,UAAA,CAAAqD,EAAAA,CACA,QAAA,CAAAhC,CAAAA,CACA,mBAAAC,CAAAA,CACA,oBAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAI,EACA,0BAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAG,CAAAA,CACA,aAAAI,EAAAA,CACA,QAAA,CAAAI,EAAAA,CACA,gBAAA,CAAAC,GACA,OAAA,CAAAO,EAAAA,CACA,WAAA,CAAAI,EAAAA,CACA,kBAAA,CAAAG,EAAAA,CACA,oBAAA,CAAAI,EAAAA,CACA,kBAAAC,EAAAA,CACA,eAAA,CAAiB5E,CAAAA,CACjB,UAAA,CAAYF,EACZ,eAAA,CAAA+E,EACF,CACF,CC1fA,SAASE,EAAAA,CAAWhQ,CAAAA,CAAkD,CACpE,OAAOA,IAAU,KAAA,EAASA,CAAAA,GAAU,IAAA,CAAOA,CAAAA,CAAQ,MACrD,CAEA,SAASiQ,EAAAA,CACPC,CAAAA,CACAC,EACkB,CAClB,OAAO,CACL,GAAID,EAAK,cAAA,EAAkB,EAAC,CAC5B,GAAIC,CAAAA,EAAQ,QAAA,EAAY,EAAC,CACzB,GAAIA,CAAAA,EAAQ,KAAA,GAAQD,CAAAA,CAAK,EAAE,GAAG,OAAA,EAAW,EAC3C,CACF,CAWO,SAASE,EAAAA,CACd3E,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACuB,CACvB,GAAM,CAAE,MAAA0E,CAAAA,CAAO,MAAA,CAAAF,CAAO,CAAA,CAAIxE,EACpB2E,CAAAA,CAAqB,EAAC,CACtBC,CAAAA,CAA6B,EAAC,CAC9BC,CAAAA,CAA2B,EAAC,CAC5BC,CAAAA,CAAuB,EAAC,CAExBC,CAAAA,CAAaV,GAAWG,CAAAA,EAAQ,QAAA,EAAU,MAAM,CAAA,EAAK,MACrDQ,CAAAA,CAAcnF,EAAAA,CAA4BC,CAAAA,CAAOC,CAAAA,CAAO,CAC5D,aAAA,CAAeyE,CAAAA,EAAQ,aAAA,CACvB,YAAA,CAAcA,GAAQ,YAAA,CACtB,eAAA,CAAiBO,CACnB,CAAC,EACK1D,CAAAA,CAAW,IAAI,GAAA,CAAI2D,CAAAA,CAAY,MAAM,GAAA,CAAK1E,CAAAA,EAAS,CAACA,CAAAA,CAAK,OAAQA,CAAI,CAAC,CAAC,CAAA,CAMvE2E,CAAAA,CACJ1G,CAAAA,EAC+E,CAC/E,GAAIA,IAAWwG,CAAAA,CACb,OAAO,CACL,oBAAA,CAAsBC,EAAY,oBAAA,CAClC,iBAAA,CAAmBA,CAAAA,CAAY,iBACjC,EAEF,IAAMf,CAAAA,CAAuE,CAC3EhF,CAAAA,CACApO,IACG0O,EAAAA,CAA8ByF,CAAAA,CAAY,OAAA,CAAQ/F,CAAM,EAAGpO,CAAAA,CAAU0N,CAAM,CAAA,CAChF,OAAO,CACL,oBAAA,CAAA0F,CAAAA,CACA,iBAAA,CAAoB7B,CAAAA,EAAW,CAC7B,IAAM9B,CAAAA,CAAOe,CAAAA,CAAS,GAAA,CAAIe,CAAM,CAAA,CAChC,OAAK9B,CAAAA,CACE,CACL,MAAA,CAAQ2D,CAAAA,CAAqB3D,CAAAA,CAAK,YAAA,CAAcA,EAAK,YAAY,CAAA,CACjE,IAAA,CAAM2D,CAAAA,CAAqB3D,EAAK,UAAA,CAAYA,CAAAA,CAAK,UAAU,CAC7D,EAJkB,EAKpB,CACF,CACF,EAEM4E,CAAAA,CAAc,IAAI,GAAA,CAClBC,CAAAA,CAAe,IAAI,GAAA,CAEzB,IAAA,IAAWZ,CAAAA,IAAQG,CAAAA,CAAO,CACpBQ,CAAAA,CAAY,GAAA,CAAIX,CAAAA,CAAK,EAAE,CAAA,EACzBI,CAAAA,CAAS,IAAA,CAAK,CAAA,mBAAA,EAAsBJ,EAAK,EAAE,CAAA,kCAAA,CAA+B,CAAA,CAE5EW,CAAAA,CAAY,IAAIX,CAAAA,CAAK,EAAE,CAAA,CAEvB,IAAMa,EAAaZ,CAAAA,EAAQ,KAAA,GAAQD,CAAAA,CAAK,EAAE,CAAA,CAC1C,GAAIa,CAAAA,EAAY,OAAA,GAAY,MAAO,CACjCP,CAAAA,CAAe,IAAA,CAAKN,CAAAA,CAAK,EAAE,CAAA,CAC3B,QACF,CAEA,IAAMc,EAAWD,CAAAA,EAAY,QAAA,EAAYb,CAAAA,CAAK,eAAA,CACxCe,CAAAA,CAAchB,EAAAA,CAAaC,CAAAA,CAAMC,CAAM,EACvCe,CAAAA,CAA8B,CAClC,GAAGP,CAAAA,CACH,GAAGC,CAAAA,CAAoBZ,EAAAA,CAAWiB,CAAAA,CAAY,MAAM,GAAKP,CAAU,CAAA,CACnE,OAAA,CAASO,CAAAA,CACT,SAAAD,CACF,CAAA,CAEIG,CAAAA,CACJ,GAAI,CACFA,CAAAA,CAASjB,CAAAA,CAAK,QAAA,CAASgB,CAAG,EAC5B,CAAA,MAASE,CAAAA,CAAO,CACdd,CAAAA,CAAS,KACP,CAAA,MAAA,EAASJ,CAAAA,CAAK,EAAE,CAAA,yBAAA,EACdkB,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,OAAOA,CAAK,CACvD,CAAA,CACF,CAAA,CACA,QACF,CACAb,CAAAA,CAAiB,IAAA,CAAKL,CAAAA,CAAK,EAAE,CAAA,CAE7B,IAAA,IAAWmB,CAAAA,IAASF,CAAAA,EAAU,EAAC,CAAG,CAGhC,IAAMG,EACJP,CAAAA,EAAY,QAAA,EAAYM,CAAAA,CAAM,QAAA,EAAYnB,EAAK,eAAA,CAC7C9D,CAAAA,CAAKiF,CAAAA,CAAM,EAAA,EAAM,GAAGnB,CAAAA,CAAK,EAAE,CAAA,CAAA,EAAIO,CAAAA,CAAO,MAAM,CAAA,CAAA,CAChD,GAAIK,CAAAA,CAAa,IAAI1E,CAAE,CAAA,CAAG,CACxB,IAAImF,EAAS,CAAA,CACb,KAAOT,CAAAA,CAAa,GAAA,CAAI,GAAG1E,CAAE,CAAA,CAAA,EAAImF,CAAM,CAAA,CAAE,GAAGA,CAAAA,EAAU,CAAA,CACtDnF,CAAAA,CAAK,CAAA,EAAGA,CAAE,CAAA,CAAA,EAAImF,CAAM,CAAA,EACtB,CACAT,EAAa,GAAA,CAAI1E,CAAE,CAAA,CACnBqE,CAAAA,CAAO,KAAK,CACV,GAAGY,CAAAA,CACH,EAAA,CAAAjF,CAAAA,CACA,MAAA,CAAQiF,CAAAA,CAAM,MAAA,EAAUnB,EAAK,EAAA,CAC7B,QAAA,CAAUoB,CAAAA,CACV,KAAA,CAAOD,EAAM,KAAA,EAASnB,CAAAA,CAAK,KAC7B,CAAC,EACH,CACF,CAEA,OAAO,CAAE,MAAA,CAAAO,CAAAA,CAAQ,gBAAA,CAAAF,CAAAA,CAAkB,eAAAC,CAAAA,CAAgB,QAAA,CAAAF,CAAS,CAC9D,CCtFO,IAAMkB,EAAAA,CAA0B,CAAC,cAAA,CAAgB,oBAAoB,CAAA,CAE7C,IAAI,GAAA,CAAYA,EAAuB,EAY/D,IAAMC,EAAAA,CACX,gFAQWC,EAAAA,CACX,iGAAA,CAOWC,EAAAA,CACX,kGAAA,CAOWC,GACX,iFAAA,CClGK,SAASC,CAAAA,CAAI7R,CAAAA,CAAoC,CACtD,OAAO,OAAOA,CAAAA,EAAU,QAAA,EAAY,OAAO,QAAA,CAASA,CAAK,CAAA,CAAIA,CAAAA,CAAQ,MACvE,CAEO,SAAS8R,CAAAA,CACdnG,CAAAA,CACA5F,EACAoF,CAAAA,CACQ,CACR,OAAO0G,CAAAA,CAAIlG,EAAQ5F,CAAG,CAAC,CAAA,EAAKoF,CAC9B,CAGO,IAAM4G,CAAAA,CAAqB,MAGlC,SAASC,EAAAA,CAAgB5C,CAAAA,CAA0B,CACjD,OAAOA,EAAI,KAAA,GAAU,IAAA,EAAQA,CAAAA,CAAI,KAAA,GAAU,CAC7C,CAMO,SAAS6C,EAAAA,CAAY7C,CAAAA,CAAyB,CACnD,OAAO4C,EAAAA,CAAgB5C,CAAG,CAAA,CAAI2C,EAAqB,CACrD,CASO,SAASG,EAAAA,CACdC,EACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAAc,CAAA,CACN,CACR,IAAMC,CAAAA,CAAKD,CAAAA,CAAc,CAAA,EAAKA,CAAAA,EAAe,CAAA,CAAIA,CAAAA,CAAc,CAAA,CAC/D,OAAIF,CAAAA,EAAY,CAAA,EAAKC,CAAAA,EAAU,CAAA,CAAU,EAClCF,CAAAA,EAASC,CAAAA,CAAWC,CAAAA,CAASE,CAAAA,CACtC,CAGO,IAAMC,EAAAA,CAAmE,CAC9E,GAAA,CAAK,IACL,GAAA,CAAK,GACP,EAkDO,SAASC,EACdC,CAAAA,CACA/G,CAAAA,CACkB,CAClB,IAAMgH,EAAYD,CAAAA,CAAI,KAAA,CAAM,IAAA,EAAQ,GAC9BE,CAAAA,CACHjH,CAAAA,CAAQ,QAAA,EACRgH,CAAAA,CAAU,QAAA,EACX,KAAA,CACIE,CAAAA,CAAcD,CAAAA,GAAa,MAAQ,GAAA,CAAM,EAAA,CACzCE,CAAAA,CACJjB,CAAAA,CAAIlG,EAAQ,cAAc,CAAA,EAAKkG,CAAAA,CAAIc,CAAAA,CAAU,cAAc,CAAA,EAAKE,CAAAA,CAE5DE,CAAAA,CAAgBC,CAAAA,CAASrH,CAAAA,CAAQ,aAAa,CAAA,EAAKqH,CAAAA,CAASL,EAAU,aAAa,CAAA,CACnFM,CAAAA,CACJD,CAAAA,CAASrH,EAAQ,kBAAkB,CAAA,EAAKqH,CAAAA,CAASL,CAAAA,CAAU,kBAAkB,CAAA,CACzEO,CAAAA,CAASF,CAAAA,CAASrH,CAAAA,CAAQ,QAAQ,CAAA,EAAKqH,CAAAA,CAASL,CAAAA,CAAU,QAAQ,CAAA,CAEpEQ,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAN,IAAkB,MAAA,EAAaE,CAAAA,GAAiB,MAAA,EAClDE,CAAAA,CAAgBJ,GAAiB,IAAA,CAAK,KAAA,CAAOE,CAAAA,CAA0BlB,CAAkB,EACzFqB,CAAAA,CAAqBH,CAAAA,EAAgB,IAAA,CAAK,KAAA,CAAOF,EAA2BhB,CAAkB,CAAA,CAC9FsB,CAAAA,CAAe,UAAA,EACNH,IAAW,MAAA,EACpBC,CAAAA,CAAgBD,CAAAA,CAChBE,CAAAA,CAAqBF,EACrBG,CAAAA,CAAe,QAAA,GAEfF,CAAAA,CAAgBX,EAAAA,CAAwBI,CAAQ,CAAA,CAChDQ,CAAAA,CAAqB,IAAA,CAAK,MAAMD,CAAAA,CAAgBpB,CAAkB,CAAA,CAClEsB,CAAAA,CAAe,WAGjB,IAAMf,CAAAA,CACJgB,EAAAA,CAAa3H,CAAAA,CAAQ,WAAW,CAAA,EAAK2H,EAAAA,CAAaX,CAAAA,CAAU,WAAW,GAAK,CAAA,CAC9E,OAAO,CACL,QAAA,CAAAC,EACA,cAAA,CAAAE,CAAAA,CACA,eAAA,CAAiBhB,CAAAA,CAAInG,EAAS,iBAAA,CAAmB,CAAC,CAAA,CAClD,QAAA,CAAUuH,GAAUC,CAAAA,CACpB,aAAA,CAAAA,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,WAAA,CAAAd,CAAAA,CACA,YAAA,CAAAe,CACF,CACF,CAGO,SAASE,EAAAA,CAAenE,EAA6D,CAC1F,OAAI4C,EAAAA,CAAgB5C,CAAG,EAAU,aAAA,CAC7BA,CAAAA,CAAI,KAAA,GAAU,CAAA,CAAU,WACrB,aACT,CAYO,SAASoE,EAAAA,CACdpE,EACAqE,CAAAA,CACyG,CACzG,IAAMpB,CAAAA,CAASJ,GAAY7C,CAAG,CAAA,CACxBsE,CAAAA,CAAaV,CAAAA,CAAU5D,EAA+B,QAAQ,CAAA,CACpE,GAAIsE,CAAAA,GAAe,MAAA,CAAW,OAAO,CAAE,QAAA,CAAUA,EAAY,MAAA,CAAArB,CAAAA,CAAQ,KAAA,CAAO,KAAM,EAClF,IAAMxJ,CAAAA,CAAO0K,EAAAA,CAAenE,CAAG,EAC/B,OAAIvG,CAAAA,GAAS,aAAA,CACJ,CAAE,QAAA,CAAU4K,CAAAA,CAAS,kBAAA,CAAoB,MAAA,CAAApB,EAAQ,KAAA,CAAO,aAAc,CAAA,CAE3ExJ,CAAAA,GAAS,WACJ,CAAE,QAAA,CAAU4K,CAAAA,CAAS,kBAAA,CAAoB,OAAQ,CAAA,CAAG,KAAA,CAAO,cAAe,CAAA,CAE5E,CAAE,QAAA,CAAUA,CAAAA,CAAS,aAAA,CAAe,OAAQ,CAAA,CAAG,KAAA,CAAO,iBAAkB,CACjF,CAEA,SAAST,CAAAA,CAAShT,CAAAA,CAAoC,CACpD,IAAM2T,CAAAA,CAAI9B,CAAAA,CAAI7R,CAAK,CAAA,CACnB,OAAO2T,CAAAA,GAAM,MAAA,EAAaA,CAAAA,CAAI,CAAA,CAAIA,EAAI,MACxC,CAEA,SAASL,EAAAA,CAAatT,EAAoC,CACxD,IAAM2T,CAAAA,CAAI9B,CAAAA,CAAI7R,CAAK,CAAA,CACnB,OAAO2T,CAAAA,GAAM,MAAA,EAAaA,CAAAA,CAAI,CAAA,EAAKA,CAAAA,EAAK,CAAA,CAAIA,EAAI,MAClD,CAGO,SAASC,CAAAA,CAAU1C,EAA4C,CACpE,OAAOA,CAAAA,CAAI,OAAA,GAAU,MAAA,CAAQwB,CAAAA,EAAAA,CAASb,CAAAA,CAAIa,CAAAA,CAAI,IAAI,OAAO,CAAA,EAAK,CAAA,EAAK,CAAC,CACtE,CAGO,SAASmB,EAAAA,CACd3C,CAAAA,CACAnD,EACoB,CACpB,GAAM,CAAE,MAAA,CAAA+F,EAAQ,IAAA,CAAAC,CAAK,CAAA,CAAI7C,CAAAA,CAAI,iBAAA,CAAkBnD,CAAM,CAAA,CAC/CnH,CAAAA,CAAS,CAACkN,CAAAA,EAAQ,QAAA,CAAUC,CAAAA,EAAM,QAAQ,EAAE,MAAA,CAC/C/T,CAAAA,EAA2B,OAAOA,CAAAA,EAAU,QAC/C,CAAA,CACA,OAAO4G,CAAAA,CAAO,MAAA,CAAS,EAAI,IAAA,CAAK,GAAA,CAAI,GAAGA,CAAM,EAAI,MACnD,CAEO,SAASoN,CAAAA,CAAMhU,EAAeiU,CAAAA,CAAS,CAAA,CAAW,CACvD,IAAM5B,EAAS,EAAA,EAAM4B,CAAAA,CACrB,OAAO,IAAA,CAAK,KAAA,CAAMjU,CAAAA,CAAQqS,CAAM,CAAA,CAAIA,CACtC,CAQO,SAAS6B,EAAAA,CACdhD,CAAAA,CACA5B,EACa,CACb,IAAM3B,CAAAA,CAAM,IAAI,IACVwG,CAAAA,CAAY,IAAI,GAAA,CACtB,IAAA,IAAWlI,CAAAA,IAAQiF,CAAAA,CAAI,WAAA,CAAY5B,CAAM,EAAG,CAC1C3B,CAAAA,CAAI,GAAA,CAAI1B,CAAAA,CAAK,MAAM,CAAA,CACnB,IAAA,IAAWrB,CAAAA,IAAUsG,CAAAA,CAAI,2BAA2BjF,CAAAA,CAAK,MAAM,CAAA,CAC7D,GAAI,CAAAkI,CAAAA,CAAU,GAAA,CAAIvJ,CAAM,EACxB,CAAAuJ,CAAAA,CAAU,GAAA,CAAIvJ,CAAM,EACpB,IAAA,IAAWyC,CAAAA,IAAQ6D,CAAAA,CAAI,kBAAA,CAAmBtG,CAAM,CAAA,CAAG+C,CAAAA,CAAI,GAAA,CAAIN,CAAAA,CAAK,MAAM,EAAA,CAE1E,CACA,OAAOM,CACT,CCtNA,SAASyG,EAAAA,CAAUlD,CAAAA,CAAuC,CACxD,OAAOA,CAAAA,CAAI,KAAA,CAAM,MAAA,CAAQ3J,CAAAA,EAAS2J,EAAI,UAAA,CAAW3J,CAAAA,CAAK,EAAE,CAAC,CAAA,CAAE,GAAA,CAAKA,CAAAA,EAASA,CAAAA,CAAK,EAAE,CAClF,CAEA,SAASzJ,CAAAA,CAAMoT,EAA6BtG,CAAAA,CAAwB,CAClE,OAAOsG,CAAAA,CAAI,gBAAgBtG,CAAM,CACnC,CAWO,IAAMyJ,EAAAA,CAAkC,CAC7C,EAAA,CAAI,SAAA,CACJ,KAAM,qCAAA,CACN,KAAA,CAAO,UAAA,CACP,eAAA,CAAiB,QACjB,WAAA,CACE,wGAAA,CACF,QAAA,CAASnD,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,EAAC,CACnC,IAAA,IAAW7F,CAAAA,IAAUwJ,EAAAA,CAAUlD,CAAG,EAAG,CACnC,IAAMoD,CAAAA,CAAcC,EAAAA,CAAoBrD,EAAKtG,CAAM,CAAA,CAC9C0J,CAAAA,EACL7D,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW7F,CAAM,GACrB,OAAA,CAAS,CAAA,EAAG9M,CAAAA,CAAMoT,CAAAA,CAAKtG,CAAM,CAAC,CAAA,kDAAA,EAAqD9M,CAAAA,CACjFoT,CAAAA,CACAoD,EAAY,QACd,CAAC,CAAA,qCAAA,CAAA,CACD,OAAA,CAAS,CAAC1J,CAAAA,CAAQ0J,CAAAA,CAAY,QAAQ,CAAA,CACtC,OAAA,CAASA,CAAAA,CAAY,OACvB,CAAC,EACH,CACA,OAAO7D,CACT,CACF,EAUA,SAAS8D,EAAAA,CACPrD,CAAAA,CACAtG,CAAAA,CACqD,CACrD,IAAM4J,CAAAA,CAAY,IAAI,GAAA,CAChBC,CAAAA,CAAS,IAAI,GAAA,CACbC,CAAAA,CAAO,CACXtG,CAAAA,CACAuG,CAAAA,GACwD,CACxD,IAAMvH,EAAQ8D,CAAAA,CAAI,QAAA,CAAS9C,CAAS,CAAA,CACpC,GAAIhB,CAAAA,CAAM,MAAA,GAAW,CAAA,CAEnB,OAAO,CAAE,QAAA,CAAUgB,CAAAA,CAAW,OAAA,CAAAuG,CAAQ,CAAA,CAExCF,CAAAA,CAAO,GAAA,CAAIrG,CAAS,EACpB,IAAA,IAAWE,CAAAA,IAAQlB,CAAAA,CAAO,CAExB,GAAIkB,CAAAA,CAAK,MAAA,GAAW,MAAA,CAAW,SAC/B,IAAMsG,CAAAA,CAAOtG,CAAAA,CAAK,YAAA,CAClB,GAAImG,EAAO,GAAA,CAAIG,CAAI,CAAA,EAAKJ,CAAAA,CAAU,IAAII,CAAI,CAAA,CAAG,SAC7C,IAAMC,EAAQH,CAAAA,CAAKE,CAAAA,CAAM,CAAC,GAAGD,CAAAA,CAASrG,CAAAA,CAAK,MAAM,CAAC,EAClD,GAAIuG,CAAAA,CAAO,OAAOA,CACpB,CACAJ,CAAAA,CAAO,MAAA,CAAOrG,CAAS,CAAA,CACvBoG,EAAU,GAAA,CAAIpG,CAAS,EAEzB,CAAA,CACA,OAAOsG,CAAAA,CAAK9J,CAAAA,CAAQ,EAAE,CACxB,CAWO,IAAMkK,EAAAA,CAAkC,CAC7C,GAAI,SAAA,CACJ,IAAA,CAAM,8BAAA,CACN,KAAA,CAAO,aACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CACE,2HAAA,CACF,QAAA,CAAS5D,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CAC7BsE,CAAAA,CAAU,IAAI,IAAI7D,CAAAA,CAAI,aAAa,CAAA,CACzC,IAAA,IAAWtG,KAAUwJ,EAAAA,CAAUlD,CAAG,CAAA,CAAG,CACnC,IAAM9D,CAAAA,CAAQ8D,CAAAA,CAAI,QAAA,CAAStG,CAAM,EACjC,GAAI,EAAAwC,CAAAA,CAAM,MAAA,CAAS,GACnB,IAAA,IAAS9P,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAI8P,EAAM,MAAA,CAAQ9P,CAAAA,EAAK,CAAA,CACrC,IAAA,IAAS0X,CAAAA,CAAI1X,CAAAA,CAAI,CAAA,CAAG0X,CAAAA,CAAI5H,EAAM,MAAA,CAAQ4H,CAAAA,EAAK,CAAA,CAAG,CAC5C,IAAMC,CAAAA,CAAI/D,CAAAA,CAAI,kBAAA,CAAmB9D,CAAAA,CAAM9P,CAAC,CAAA,CAAE,MAAM,CAAA,CAC1CqR,CAAAA,CAAIuC,CAAAA,CAAI,kBAAA,CAAmB9D,CAAAA,CAAM4H,CAAC,EAAE,MAAM,CAAA,CAC1CE,CAAAA,CAAShE,CAAAA,CAAI,iBAAiB+D,CAAAA,CAAGtG,CAAC,CAAA,CACxC,GAAI,CAACuG,CAAAA,CAAQ,SACb,GAAM,CAAE,MAAA,CAAQC,CAAAA,CAAc,KAAA,CAAAnK,CAAM,EAAIkG,CAAAA,CAAI,eAAA,CAAgBgE,CAAM,CAAA,CAC5DE,EAAc,CAACpK,CAAAA,EAAS+J,CAAAA,CAAQ,GAAA,CAAII,CAAY,CAAA,CACtD1E,CAAAA,CAAO,IAAA,CAAK,CACV,GAAI,CAAA,QAAA,EAAW7F,CAAM,CAAA,CAAA,EAAIwC,CAAAA,CAAM9P,CAAC,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI8P,CAAAA,CAAM4H,CAAC,CAAA,CAAE,MAAM,CAAA,CAAA,CAC3D,QAAA,CAAUI,EAAc,SAAA,CAAY,OAAA,CACpC,OAAA,CAASA,CAAAA,CACL,CAAA,EAAGtX,CAAAA,CAAMoT,CAAAA,CAAKtG,CAAM,CAAC,CAAA,qCAAA,EAAwCsG,CAAAA,CAAI,eAAA,CAC/DgE,CACF,CAAC,CAAA,iDAAA,CAAA,CACD,CAAA,EAAGpX,CAAAA,CAAMoT,CAAAA,CAAKtG,CAAM,CAAC,CAAA,qCAAA,EAAwCsG,CAAAA,CAAI,eAAA,CAC/DgE,CACF,CAAC,CAAA,mDAAA,CAAA,CACL,OAAA,CAAS,CAACtK,EAAQuK,CAAY,CAAA,CAC9B,OAAA,CAAS,CAAC/H,EAAM9P,CAAC,CAAA,CAAE,MAAA,CAAQ8P,CAAAA,CAAM4H,CAAC,CAAA,CAAE,MAAM,CAAA,CAC1C,MAAA,CAAQhK,CAAAA,CAAQ,CAACkK,CAAM,CAAA,CAAI,OAC3B,OAAA,CAAS,CAAE,aAAA,CAAeA,CAAO,CACnC,CAAC,EACH,CAEJ,CACA,OAAOzE,CACT,CACF,CAAA,CAOa4E,EAAAA,CAA2B,CACtC,EAAA,CAAI,SAAA,CACJ,IAAA,CAAM,4BAAA,CACN,MAAO,YAAA,CACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CAAa,8DACb,QAAA,CAASnE,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CACnC,IAAA,IAAW7F,CAAAA,IAAUwJ,EAAAA,CAAUlD,CAAG,CAAA,CAAG,CACnC,IAAM9D,CAAAA,CAAQ8D,CAAAA,CAAI,QAAA,CAAStG,CAAM,EACjC,GAAIwC,CAAAA,CAAM,MAAA,CAAS,CAAA,CAAG,SACtB,IAAMkI,CAAAA,CAAQ,IAAI,GAAA,CAClB,IAAA,IAAWhH,CAAAA,IAAQlB,CAAAA,CAAO,CACxB,GAAI8D,CAAAA,CAAI,OAAA,CAAQ5C,CAAAA,CAAK,YAAY,GAAG,IAAA,GAAS,cAAA,CAAgB,SAC7D,IAAMtC,EAAOsJ,CAAAA,CAAM,GAAA,CAAIhH,CAAAA,CAAK,YAAY,CAAA,CACpCtC,CAAAA,CAAMA,CAAAA,CAAK,IAAA,CAAKsC,EAAK,MAAM,CAAA,CAC1BgH,CAAAA,CAAM,GAAA,CAAIhH,EAAK,YAAA,CAAc,CAACA,CAAAA,CAAK,MAAM,CAAC,EACjD,CACA,IAAA,GAAW,CAACiH,EAAOZ,CAAO,CAAA,GAAKW,CAAAA,CACzBX,CAAAA,CAAQ,OAAS,CAAA,EACrBlE,CAAAA,CAAO,IAAA,CAAK,CACV,GAAI,CAAA,QAAA,EAAW7F,CAAM,CAAA,CAAA,EAAI2K,CAAK,GAC9B,OAAA,CAAS,CAAA,EAAGzX,CAAAA,CAAMoT,CAAAA,CAAKtG,CAAM,CAAC,CAAA,OAAA,EAAU+J,CAAAA,CAAQ,MAAM,CAAA,YAAA,EAAe7W,CAAAA,CACnEoT,CAAAA,CACAqE,CACF,CAAC,CAAA,2CAAA,CAAA,CACD,OAAA,CAAS,CAAC3K,CAAAA,CAAQ2K,CAAK,CAAA,CACvB,OAAA,CAAAZ,CACF,CAAC,EAEL,CACA,OAAOlE,CACT,CACF,EAWa+E,EAAAA,CAAgC,CAC3C,EAAA,CAAI,SAAA,CACJ,KAAM,0CAAA,CACN,KAAA,CAAO,YAAA,CACP,eAAA,CAAiB,UACjB,cAAA,CAAgB,CAAE,aAAA,CAAe,CAAE,CAAA,CACnC,WAAA,CAAa,0DAAA,CACb,QAAA,CAAStE,EAAK,CACZ,IAAMT,CAAAA,CAA4B,GAC5BgF,CAAAA,CAAmB3D,CAAAA,CAAIZ,CAAAA,CAAI,OAAA,CAAS,gBAAiB,CAAC,CAAA,CAC5D,IAAA,IAAW3J,CAAAA,IAAQ2J,EAAI,KAAA,CAAO,CAC5B,GAAIA,CAAAA,CAAI,gBAAgB3J,CAAAA,CAAK,EAAE,CAAA,CAAG,SAClC,IAAMmO,CAAAA,CAAW7D,CAAAA,CAAItK,CAAAA,CAAK,IAAA,EAAM,aAAa,CAAA,CACvC6F,CAAAA,CAAQ8D,CAAAA,CAAI,QAAA,CAAS3J,CAAAA,CAAK,EAAE,CAAA,CAAE,MAAA,CAGpC,GAAI6F,CAAAA,GAAU,CAAA,EAAKsI,CAAAA,GAAa,MAAA,CAAW,SAC3C,IAAMC,CAAAA,CAAWD,CAAAA,EAAYD,CAAAA,CACzBrI,GAASuI,CAAAA,EACblF,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWlJ,CAAAA,CAAK,EAAE,GACtB,QAAA,CAAUmO,CAAAA,GAAa,CAAA,CAAI,MAAA,CAAS,OACpC,OAAA,CAAS,CAAA,EAAG5X,CAAAA,CAAMoT,CAAAA,CAAK3J,EAAK,EAAE,CAAC,CAAA,KAAA,EAAQ6F,CAAK,CAAA,WAAA,EAC1CA,CAAAA,GAAU,CAAA,CAAI,EAAA,CAAK,GACrB,CAAA,wBAAA,EAA2BuI,CAAQ,CAAA,kDAAA,CAAA,CACnC,OAAA,CAAS,CAACpO,CAAAA,CAAK,EAAE,CAAA,CACjB,OAAA,CAAS,CAAE,KAAA,CAAA6F,CAAAA,CAAO,QAAA,CAAAuI,CAAS,CAC7B,CAAC,EACH,CACA,OAAOlF,CACT,CACF,CAAA,CAWamF,EAAAA,CAAqC,CAChD,GAAI,SAAA,CACJ,IAAA,CAAM,4BAAA,CACN,KAAA,CAAO,WACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CAAa,0EAAA,CACb,QAAA,CAAS1E,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CACnC,IAAA,IAAWxE,CAAAA,IAAQiF,EAAI,KAAA,CAAO,CAC5B,GAAM,CAAE,OAAA4C,CAAAA,CAAQ,IAAA,CAAAC,CAAK,CAAA,CAAI7C,EAAI,iBAAA,CAAkBjF,CAAAA,CAAK,MAAM,CAAA,CAC1D,GAAI,CAAC6H,CAAAA,EAAU,CAACC,CAAAA,CAAM,SACtB,IAAIzJ,CAAAA,CACAC,CAAAA,CACJ,GAAIuJ,EAAO,IAAA,GAAS,OAAA,EAAWC,CAAAA,CAAK,IAAA,GAAS,WAAA,CAC3CzJ,CAAAA,CAAQwJ,CAAAA,CACRvJ,CAAAA,CAAYwJ,UACHA,CAAAA,CAAK,IAAA,GAAS,OAAA,EAAWD,CAAAA,CAAO,OAAS,WAAA,CAClDxJ,CAAAA,CAAQyJ,CAAAA,CACRxJ,CAAAA,CAAYuJ,OAEZ,SAEEzJ,EAAAA,CAAeC,CAAAA,CAAM,IAAA,CAAMC,EAAU,IAAI,CAAA,EAC7CkG,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWxE,CAAAA,CAAK,MAAM,GAC1B,OAAA,CAAS,CAAA,EAAG1B,CAAAA,CAAU,IAAI,8BAA8BD,CAAAA,CAAM,IAAI,CAAA,6CAAA,CAAA,CAClE,OAAA,CAAS,CAAC2B,CAAAA,CAAK,YAAA,CAAcA,CAAAA,CAAK,UAAU,CAAA,CAC5C,OAAA,CAAS,CAACA,CAAAA,CAAK,MAAM,CAAA,CACrB,OAAA,CAAS,CAAE,KAAA,CAAO3B,EAAM,IAAA,CAAM,SAAA,CAAWC,CAAAA,CAAU,IAAK,CAC1D,CAAC,EACH,CACA,OAAOkG,CACT,CACF,EAOA,SAASoF,GACP3E,CAAAA,CACAtG,CAAAA,CACoB,CACpB,IAAMrD,EAAO2J,CAAAA,CAAI,OAAA,CAAQtG,CAAM,CAAA,CAC/B,GAAIrD,CAAAA,EAAM,IAAA,GAAS,cAAA,CAAgB,OACnC,IAAMuO,CAAAA,CAASjE,CAAAA,CAAItK,CAAAA,CAAK,MAAM,iBAAiB,CAAA,CAC/C,OAAOuO,CAAAA,GAAW,QAAaA,CAAAA,CAAS,CAAA,CAAIA,CAAAA,CAAS,MACvD,CAQA,SAASC,EAAAA,CACP7E,CAAAA,CACA8E,CAAAA,CACAC,EACa,CACb,IAAMC,CAAAA,CAAU,IAAI,IAAIF,CAAAA,CAAO,GAAA,CAAK/J,CAAAA,EAASA,CAAAA,CAAK,MAAM,CAAC,CAAA,CACzD,GAAIgK,CAAAA,GAAU,QAAA,CAAU,OAAOC,CAAAA,CAC/B,IAAMxI,EAAO,IAAI,GAAA,CACXE,CAAAA,CAAQoI,CAAAA,CAAO,IAAK/J,CAAAA,EAASA,CAAAA,CAAK,UAAU,CAAA,CAClD,KAAO2B,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAG,CACvB,IAAMhD,CAAAA,CAASgD,CAAAA,CAAM,GAAA,EAAI,CACzB,GAAI,CAAAF,CAAAA,CAAK,GAAA,CAAI9C,CAAM,IACnB8C,CAAAA,CAAK,GAAA,CAAI9C,CAAM,CAAA,CACXsG,EAAI,OAAA,CAAQtG,CAAM,CAAA,EAAG,IAAA,GAAS,oBAAA,EAC9BiL,EAAAA,CAAuB3E,CAAAA,CAAKtG,CAAM,IAAM,MAAA,CAAA,CAC5C,IAAA,IAAWgK,CAAAA,IAAQ1D,CAAAA,CAAI,mBAAmBtG,CAAM,CAAA,CAC9CsL,CAAAA,CAAQ,GAAA,CAAItB,EAAK,MAAM,CAAA,CACvBhH,CAAAA,CAAM,IAAA,CAAKgH,EAAK,UAAU,EAE9B,CACA,OAAOsB,CACT,CAeO,IAAMC,EAAAA,CAAyC,CACpD,GAAI,SAAA,CACJ,IAAA,CAAM,uCAAA,CACN,KAAA,CAAO,WACP,eAAA,CAAiB,OAAA,CACjB,cAAA,CAAgB,CAAE,KAAA,CAAO,YAAa,CAAA,CACtC,WAAA,CAAa,uEACb,QAAA,CAASjF,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CAC7BwF,CAAAA,CAAQ/E,CAAAA,CAAI,QAAQ,KAAA,GAAU,QAAA,CAAW,QAAA,CAAW,YAAA,CAC1D,IAAA,IAAWwB,CAAAA,IAAOkB,CAAAA,CAAU1C,CAAG,EAAG,CAChC,IAAM4E,CAAAA,CAASjE,CAAAA,CAAIa,EAAI,GAAA,CAAI,OAAO,CAAA,CAC5BwD,CAAAA,CAAUH,GAAqB7E,CAAAA,CAAKA,CAAAA,CAAI,WAAA,CAAYwB,CAAAA,CAAI,MAAM,CAAA,CAAGuD,CAAK,CAAA,CAC5E,QAAWlI,CAAAA,IAAUmI,CAAAA,CAAS,CAC5B,IAAME,EAAWvC,EAAAA,CAAgB3C,CAAAA,CAAKnD,CAAM,CAAA,CACxCqI,IAAa,MAAA,EAAaA,CAAAA,EAAYN,CAAAA,EAC1CrF,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWiC,CAAAA,CAAI,MAAM,CAAA,CAAA,EAAI3E,CAAM,CAAA,CAAA,CACnC,OAAA,CAAS,KAAKqI,CAAQ,CAAA,uBAAA,EAA0BN,CAAM,CAAA,cAAA,EAAiB5E,EAAI,eAAA,CACzEwB,CAAAA,CAAI,MACN,CAAC,CAAA,uDAAA,CAAA,CACD,OAAA,CAAS,CAACA,CAAAA,CAAI,OAAO,CAAA,CACrB,OAAA,CAAS,CAAC3E,CAAM,EAChB,MAAA,CAAQ,CAAC2E,CAAAA,CAAI,MAAM,EACnB,OAAA,CAAS,CAAE,YAAA,CAAc0D,CAAAA,CAAU,QAASN,CAAO,CACrD,CAAC,EACH,CACF,CACA,IAAA,IAAWvO,CAAAA,IAAQ2J,CAAAA,CAAI,MAAO,CAC5B,IAAM4E,CAAAA,CAASD,EAAAA,CAAuB3E,EAAK3J,CAAAA,CAAK,EAAE,CAAA,CAClD,GAAIuO,CAAAA,GAAW,MAAA,CAAW,SAC1B,IAAMI,EAAUH,EAAAA,CAAqB7E,CAAAA,CAAKA,CAAAA,CAAI,kBAAA,CAAmB3J,EAAK,EAAE,CAAA,CAAG0O,CAAK,CAAA,CAChF,QAAWlI,CAAAA,IAAUmI,CAAAA,CAAS,CAC5B,IAAME,EAAWvC,EAAAA,CAAgB3C,CAAAA,CAAKnD,CAAM,CAAA,CACxCqI,IAAa,MAAA,EAAaA,CAAAA,EAAYN,CAAAA,EAC1CrF,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWS,CAAAA,CAAI,WAAW3J,CAAAA,CAAK,EAAE,CAAC,CAAA,CAAA,EAAIwG,CAAM,CAAA,CAAA,CAChD,OAAA,CAAS,CAAA,EAAA,EAAKqI,CAAQ,CAAA,uBAAA,EAA0BN,CAAM,CAAA,wBAAA,EAA2B5E,CAAAA,CAAI,gBACnF3J,CAAAA,CAAK,EACP,CAAC,CAAA,0DAAA,CAAA,CACD,QAAS,CAACA,CAAAA,CAAK,EAAE,CAAA,CACjB,OAAA,CAAS,CAACwG,CAAM,CAAA,CAChB,QAAS,CAAE,YAAA,CAAcqI,CAAAA,CAAU,OAAA,CAASN,EAAQ,UAAA,CAAY,YAAa,CAC/E,CAAC,EACH,CACF,CACA,OAAOrF,CACT,CACF,CAAA,CAWa4F,EAAAA,CAAsC,CACjD,GAAI,SAAA,CACJ,IAAA,CAAM,iDAAA,CACN,KAAA,CAAO,aACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CACE,2EAAA,CACF,SAASnF,CAAAA,CAAK,CACZ,IAAMoF,CAAAA,CAAYpF,EAAI,YAAA,EAAc,OAAA,CACpC,GAAI,CAACoF,EAAW,OAAO,EAAC,CACxB,IAAM7F,EAA4B,EAAC,CAC7B8F,CAAAA,CAAW,IAAI,IAAIrF,CAAAA,CAAI,OAAA,EAAQ,CAAE,GAAA,CAAKwB,CAAAA,EAAQ,CAACA,CAAAA,CAAI,MAAA,CAAQA,CAAG,CAAC,CAAC,CAAA,CACtE,IAAA,GAAW,CAAC9H,CAAAA,CAAQ4L,CAAK,CAAA,GAAK,MAAA,CAAO,QAAQF,CAAS,CAAA,CAAG,CACvD,GAAIE,IAAU,WAAA,EAAeA,CAAAA,GAAU,UAAA,CAAY,SACnD,IAAMC,CAAAA,CAAYvF,CAAAA,CAAI,YAAA,CAAatG,CAAM,EACnC8L,CAAAA,CAAoB,EAAC,CACrBC,CAAAA,CAAiB,EAAC,CACxB,IAAA,IAAW5Q,CAAAA,IAAO0Q,CAAAA,CAAW,CAC3B,IAAM/D,CAAAA,CAAM6D,CAAAA,CAAS,IAAIxQ,CAAG,CAAA,CACvB2M,CAAAA,GACDA,CAAAA,CAAI,OAAO,WAAA,GAAgB,SAAA,CAAWgE,CAAAA,CAAQ,IAAA,CAAK3Q,CAAG,CAAA,CACjD2M,CAAAA,CAAI,KAAA,EAAO,WAAA,GAAgB,QAAQiE,CAAAA,CAAK,IAAA,CAAK5Q,CAAG,CAAA,EAC3D,CACA,GAAIyQ,CAAAA,GAAU,UAAA,CAAY,CACxB,GAAIE,CAAAA,CAAQ,MAAA,GAAW,CAAA,EAAKC,CAAAA,CAAK,SAAW,CAAA,CAAG,SAC/ClG,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW7F,CAAM,GACrB,QAAA,CAAU,SAAA,CACV,OAAA,CAAS,CAAA,EAAG9M,EAAMoT,CAAAA,CAAKtG,CAAM,CAAC,CAAA,8BAAA,EAC5B8L,EAAQ,MAAA,CAAS,CAAA,CAAI,WAAA,CAAc,SACrC,CAAA,iDAAA,CAAA,CACA,OAAA,CAAS,CAAC9L,CAAM,EAChB,MAAA,CAAQ,CAAC,GAAG8L,CAAAA,CAAS,GAAGC,CAAI,CAC9B,CAAC,CAAA,CACD,QACF,CACAlG,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW7F,CAAM,CAAA,CAAA,CACrB,SAAU8L,CAAAA,CAAQ,MAAA,CAAS,CAAA,CAAI,OAAA,CAAU,UACzC,OAAA,CACEA,CAAAA,CAAQ,MAAA,CAAS,CAAA,CACb,GAAG5Y,CAAAA,CAAMoT,CAAAA,CAAKtG,CAAM,CAAC,wBAAmBsG,CAAAA,CAAI,eAAA,CAC1CwF,CAAAA,CAAQ,CAAC,CACX,CAAC,CAAA,aAAA,CAAA,CACD,CAAA,EAAG5Y,CAAAA,CAAMoT,EAAKtG,CAAM,CAAC,CAAA,qBAAA,EACnB+L,CAAAA,CAAK,OAAS,CAAA,CACV,CAAA,EAAGzF,CAAAA,CAAI,eAAA,CAAgByF,CAAAA,CAAK,CAAC,CAAC,CAAC,WAC/B,gCACN,CAAA,CAAA,CAAA,CACN,OAAA,CAAS,CAAC/L,CAAM,CAAA,CAChB,MAAA,CAAQ,CAAC,GAAG8L,EAAS,GAAGC,CAAI,CAC9B,CAAC,EACH,CACA,OAAOlG,CACT,CACF,CAAA,CAcamG,EAAAA,CAA6C,CACxD,CAAA,CAAG,EAAG,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,GAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,GAAA,CAAK,GACrD,CAAA,CAEaC,GAA6C,CACxD,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,GAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,GAAI,EAAA,CAAI,EAAA,CAAI,EAAA,CAAI,GAAA,CAAK,IAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CACzE,IAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CAAK,IAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CAAK,GAAA,CAAK,IAAM,IAAA,CAAM,IAAA,CAAM,GAAA,CAAM,IAAA,CAC1E,GAAA,CAAM,GAAA,CAAM,GAAA,CAAM,GACpB,EAEaC,EAAAA,CAAqC,CAChD,EAAA,CAAI,SAAA,CACJ,KAAM,6BAAA,CACN,KAAA,CAAO,UAAA,CACP,eAAA,CAAiB,OACjB,cAAA,CAAgB,CACd,SAAA,CAAWF,EAAAA,CACX,SAAA,CAAWC,EACb,CAAA,CACA,WAAA,CACE,uHACF,QAAA,CAAS3F,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CACnC,IAAA,IAAWiC,CAAAA,IAAOkB,EAAU1C,CAAG,CAAA,CAAG,CAChC,IAAM4E,CAAAA,CAASjE,CAAAA,CAAIa,CAAAA,CAAI,GAAA,CAAI,OAAO,CAAA,CAC5B,CAAE,QAAA,CAAAE,CAAS,EAAIH,CAAAA,CAAgBC,CAAAA,CAAKxB,CAAAA,CAAI,OAAO,EAC/C6F,CAAAA,CACJnE,CAAAA,GAAa,KAAA,CAAQ1B,CAAAA,CAAI,QAAQ,SAAA,CAAYA,CAAAA,CAAI,OAAA,CAAQ,SAAA,CAAA,CAE9C,MAAM,OAAA,CAAQ6F,CAAM,CAAA,CAC7BA,CAAAA,CACAnE,IAAa,KAAA,CACXiE,EAAAA,CACAD,EAAAA,EACG,QAAA,CAASd,CAAM,CAAA,EACxBrF,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWiC,CAAAA,CAAI,MAAM,GACzB,OAAA,CAAS,CAAA,EAAGxB,CAAAA,CAAI,eAAA,CACdwB,EAAI,MACN,CAAC,CAAA,UAAA,EAAaoD,CAAM,sBAAsBlD,CAAQ,CAAA,6EAAA,CAAA,CAClD,OAAA,CAAS,CAACF,EAAI,OAAO,CAAA,CACrB,MAAA,CAAQ,CAACA,EAAI,MAAM,CAAA,CACnB,OAAA,CAAS,CAAE,QAASoD,CAAAA,CAAQ,QAAA,CAAAlD,CAAS,CACvC,CAAC,EACH,CACA,OAAOnC,CACT,CACF,CAAA,CAYauG,EAAAA,CAA6B,CACxC,GAAI,SAAA,CACJ,IAAA,CAAM,iCAAA,CACN,KAAA,CAAO,YACP,eAAA,CAAiB,SAAA,CACjB,WAAA,CAAa,mFAAA,CACb,SAAS9F,CAAAA,CAAK,CACZ,IAAM+F,CAAAA,CAAS,IAAI,GAAA,CACbC,CAAAA,CAAQ9K,CAAAA,EAAuB,CACnC,IAAI+K,CAAAA,CAAO/K,CAAAA,CACX,KAAO6K,CAAAA,CAAO,IAAIE,CAAI,CAAA,EAAKF,CAAAA,CAAO,GAAA,CAAIE,CAAI,CAAA,GAAMA,CAAAA,EAC9CA,CAAAA,CAAOF,CAAAA,CAAO,GAAA,CAAIE,CAAI,CAAA,CAExB,IAAIC,EAAShL,CAAAA,CACb,KAAO6K,CAAAA,CAAO,GAAA,CAAIG,CAAM,CAAA,EAAKH,CAAAA,CAAO,GAAA,CAAIG,CAAM,IAAMA,CAAAA,EAAQ,CAC1D,IAAMxC,CAAAA,CAAOqC,CAAAA,CAAO,GAAA,CAAIG,CAAM,CAAA,CAC9BH,EAAO,GAAA,CAAIG,CAAAA,CAAQD,CAAI,CAAA,CACvBC,EAASxC,EACX,CACA,OAAOuC,CACT,EACM1G,CAAAA,CAA4B,EAAC,CACnC,IAAA,IAAWxE,CAAAA,IAAQiF,CAAAA,CAAI,KAAA,CAAO,CAC5B,GAAIA,CAAAA,CAAI,UAAA,CAAWjF,CAAAA,CAAK,YAAY,GAAKiF,CAAAA,CAAI,UAAA,CAAWjF,CAAAA,CAAK,UAAU,EACrE,SAEGgL,CAAAA,CAAO,GAAA,CAAIhL,CAAAA,CAAK,YAAY,CAAA,EAAGgL,CAAAA,CAAO,GAAA,CAAIhL,CAAAA,CAAK,aAAcA,CAAAA,CAAK,YAAY,CAAA,CAC9EgL,CAAAA,CAAO,IAAIhL,CAAAA,CAAK,UAAU,CAAA,EAAGgL,CAAAA,CAAO,IAAIhL,CAAAA,CAAK,UAAA,CAAYA,CAAAA,CAAK,UAAU,CAAA,CAC7E,IAAMgJ,CAAAA,CAAIiC,CAAAA,CAAKjL,EAAK,YAAY,CAAA,CAC1B0C,CAAAA,CAAIuI,CAAAA,CAAKjL,EAAK,UAAU,CAAA,CAC9B,GAAIgJ,CAAAA,GAAMtG,EAAG,CACX8B,CAAAA,CAAO,IAAA,CAAK,CACV,GAAI,CAAA,QAAA,EAAWxE,CAAAA,CAAK,MAAM,CAAA,CAAA,CAC1B,QAAS,CAAA,gCAAA,EAAmCnO,CAAAA,CAC1CoT,CAAAA,CACAjF,CAAAA,CAAK,YACP,CAAC,CAAA,KAAA,EAAQnO,CAAAA,CAAMoT,CAAAA,CAAKjF,EAAK,UAAU,CAAC,CAAA,yDAAA,CAAA,CACpC,OAAA,CAAS,CAACA,CAAAA,CAAK,YAAA,CAAcA,CAAAA,CAAK,UAAU,CAAA,CAC5C,OAAA,CAAS,CAACA,CAAAA,CAAK,MAAM,CACvB,CAAC,CAAA,CACD,QACF,CACAgL,CAAAA,CAAO,GAAA,CAAIhC,CAAAA,CAAGtG,CAAC,EACjB,CACA,OAAO8B,CACT,CACF,EAWa4G,EAAAA,CAAqC,CAChD,EAAA,CAAI,SAAA,CACJ,KAAM,wCAAA,CACN,KAAA,CAAO,WAAA,CACP,eAAA,CAAiB,UACjB,WAAA,CAAa,sFAAA,CACb,QAAA,CAASnG,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,GAClC,IAAA,IAAWiC,CAAAA,IAAOkB,CAAAA,CAAU1C,CAAG,EAAG,CAChC,IAAM4E,CAAAA,CAASjE,CAAAA,CAAIa,EAAI,GAAA,CAAI,OAAO,CAAA,CAC5B,CAAE,cAAA,CAAAI,CAAe,CAAA,CAAIL,CAAAA,CAAgBC,EAAKxB,CAAAA,CAAI,OAAO,CAAA,CACrDoG,CAAAA,CAASxB,EAAShD,CAAAA,CAAkB,GAAA,CACtCnD,CAAAA,CAAQ,CAAA,CACNgF,EAAoB,EAAC,CAIrB4C,CAAAA,CAAiBrD,EAAAA,CAAsBhD,CAAAA,CAAKwB,CAAAA,CAAI,MAAM,CAAA,CACtD8E,EAAY,IAAI,GAAA,CACtB,IAAA,IAAWvL,CAAAA,IAAQiF,EAAI,WAAA,CAAYwB,CAAAA,CAAI,MAAM,CAAA,CAC3C,QAAW9H,CAAAA,IAAUsG,CAAAA,CAAI,0BAAA,CAA2BjF,CAAAA,CAAK,MAAM,CAAA,CAC7D,GAAKiF,CAAAA,CAAI,UAAA,CAAWtG,CAAM,CAAA,CAC1B,IAAA,IAAW0D,CAAAA,IAAQ4C,CAAAA,CAAI,SAAStG,CAAM,CAAA,CAAG,CAEvC,GADI,CAAC2M,CAAAA,CAAe,GAAA,CAAIjJ,CAAAA,CAAK,MAAM,CAAA,EAC/BkJ,CAAAA,CAAU,GAAA,CAAIlJ,CAAAA,CAAK,MAAM,CAAA,CAAG,SAChCkJ,CAAAA,CAAU,GAAA,CAAIlJ,EAAK,MAAM,CAAA,CACzB,IAAM8H,CAAAA,CAAWvC,GAAgB3C,CAAAA,CAAK5C,CAAAA,CAAK,MAAM,CAAA,CAC7C8H,IAAa,MAAA,GACjBzG,CAAAA,EAASyG,CAAAA,CACTzB,CAAAA,CAAQ,KAAKrG,CAAAA,CAAK,MAAM,CAAA,EAC1B,CAGAqB,IAAU,CAAA,EAAKA,CAAAA,EAAS2H,CAAAA,EAC5B7G,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWiC,CAAAA,CAAI,MAAM,CAAA,CAAA,CACzB,OAAA,CAAS,CAAA,oBAAA,EAAuBxB,EAAI,eAAA,CAClCwB,CAAAA,CAAI,MACN,CAAC,UAAUsB,CAAAA,CAAMrE,CAAK,CAAC,CAAA,6BAAA,EAAgCqE,EACrDsD,CACF,CAAC,CAAA,sDAAA,CAAA,CACD,OAAA,CAAS,CAAC5E,CAAAA,CAAI,OAAO,CAAA,CACrB,OAAA,CAAAiC,EACA,MAAA,CAAQ,CAACjC,CAAAA,CAAI,MAAM,EACnB,OAAA,CAAS,CAAE,WAAA,CAAasB,CAAAA,CAAMrE,CAAK,CAAA,CAAG,MAAA,CAAQqE,CAAAA,CAAMsD,CAAK,CAAE,CAC7D,CAAC,EACH,CACA,OAAO7G,CACT,CACF,CAAA,CAWagH,GAAkC,CAC7C,EAAA,CAAI,SAAA,CACJ,IAAA,CAAM,6BACN,KAAA,CAAO,WAAA,CACP,eAAA,CAAiB,MAAA,CACjB,cAAA,CAAgB,CAAE,WAAA,CAAa,GAAI,EACnC,WAAA,CAAa,wDAAA,CACb,QAAA,CAASvG,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,EAAC,CAC7BiH,EAAc5F,CAAAA,CAAIZ,CAAAA,CAAI,OAAA,CAAS,aAAA,CAAe,GAAG,CAAA,CACvD,IAAA,IAAWjF,CAAAA,IAAQiF,EAAI,KAAA,CAAO,CAC5B,IAAMyG,CAAAA,CACJ9F,EAAI5F,CAAAA,CAAK,IAAA,CAAK,IAAA,EAAM,QAAQ,GAAK4F,CAAAA,CAAI5F,CAAAA,CAAK,IAAA,CAAK,IAAA,EAAM,MAAM,CAAA,CACzD0L,CAAAA,GAAW,MAAA,EAAaA,CAAAA,EAAUD,GACtCjH,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,WAAWxE,CAAAA,CAAK,MAAM,CAAA,CAAA,CAC1B,OAAA,CAAS,gBAAgB0L,CAAM,CAAA,cAAA,EAAiBD,CAAW,CAAA,iFAAA,CAAA,CAC3D,OAAA,CAAS,CAACzL,CAAAA,CAAK,YAAA,CAAcA,EAAK,UAAU,CAAA,CAC5C,OAAA,CAAS,CAACA,EAAK,MAAM,CAAA,CACrB,OAAA,CAAS,CAAE,SAAU0L,CAAAA,CAAQ,WAAA,CAAAD,CAAY,CAC3C,CAAC,EACH,CACA,OAAOjH,CACT,CACF,CAAA,CAqCamH,EAAAA,CAAsC,CACjD,GAAI,SAAA,CACJ,IAAA,CAAM,qBAAA,CACN,KAAA,CAAO,aACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CACE,mHAAA,CACF,QAAA,CAAS1G,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CAG7BoH,CAAAA,CAAY,CAACjN,EAAgBzB,CAAAA,GAAuC,CACxE,GAAI,OAAOA,GAAW,QAAA,CAAU,OAAO+H,CAAAA,CAAI,UAAA,CAAWtG,CAAM,CAAA,CAC5D,IAAMrL,CAAAA,CAAQC,CAAAA,CAAc2J,CAAM,CAAA,CAAE,KAAA,CACpC,OAAO5J,CAAAA,CAAM,OAAS,aAAA,CAClB2R,CAAAA,CAAI,UAAA,CAAWtG,CAAAA,CAAQrL,EAAM,KAAK,CAAA,CAClC2R,CAAAA,CAAI,UAAA,CAAWtG,CAAM,CAC3B,CAAA,CAEA,IAAA,IAAWqB,KAAQiF,CAAAA,CAAI,KAAA,CAAO,CAC5B,IAAM4G,EAAO,CACX,CAAE,MAAA,CAAQ7L,CAAAA,CAAK,aAAc,MAAA,CAAQA,CAAAA,CAAK,YAAa,CAAA,CACvD,CAAE,MAAA,CAAQA,CAAAA,CAAK,UAAA,CAAY,OAAQA,CAAAA,CAAK,UAAW,CACrD,CAAA,CAAE,IAAK8L,CAAAA,GAAS,CACd,GAAGA,CAAAA,CACH,KAAM7O,EAAAA,CAAqBgI,CAAAA,CAAI,OAAA,CAAQ6G,CAAAA,CAAI,MAAM,CAAA,CAAGA,CAAAA,CAAI,MAAM,CAChE,CAAA,CAAE,CAAA,CAGF,GAAID,CAAAA,CAAK,KAAMC,CAAAA,EAAQA,CAAAA,CAAI,IAAA,GAAS,SAAS,EAAG,SAEhD,IAAMC,CAAAA,CAAeC,CAAAA,EACnBH,EAAK,SAAA,CAAWC,CAAAA,EAAQA,CAAAA,CAAI,IAAA,GAASE,CAAI,CAAA,CAErCC,CAAAA,CAAQ7G,CAAAA,EAMF,CAGV,IAAM8G,CAAAA,CAAS,CAAC9G,CAAAA,CAAM,IAAA,CAAMA,EAAM,GAAG,CAAA,CAClC,MAAA,CAAQ0G,CAAAA,EAAQA,CAAAA,CAAI,IAAA,GAAS,gBAAgB,CAAA,CAC7C,IAAKA,CAAAA,EAAQF,CAAAA,CAAUE,CAAAA,CAAI,MAAA,CAAQA,EAAI,MAAM,CAAC,CAAA,CACjDtH,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWxE,CAAAA,CAAK,MAAM,CAAA,CAAA,CAC1B,OAAA,CAASoF,CAAAA,CAAM,OAAA,CACf,QAAS,CAACA,CAAAA,CAAM,IAAA,CAAK,MAAA,CAAQA,EAAM,GAAA,CAAI,MAAM,CAAA,CAC7C,OAAA,CAAS,CAACpF,CAAAA,CAAK,MAAM,CAAA,CACrB,GAAIkM,CAAAA,CAAO,MAAA,CAAS,CAAA,CAAI,CAAE,OAAAA,CAAO,CAAA,CAAI,EAAC,CACtC,GAAI9G,CAAAA,CAAM,QAAA,CAAW,CAAE,QAAA,CAAUA,EAAM,QAAS,CAAA,CAAI,EAAC,CACrD,QAASA,CAAAA,CAAM,OACjB,CAAC,EACH,EAEM+G,CAAAA,CAAWJ,CAAAA,CAAY,gBAAgB,CAAA,CAC7C,GAAII,CAAAA,EAAY,CAAA,CAAG,CACjB,IAAMC,EAAOP,CAAAA,CAAKM,CAAQ,CAAA,CACpBE,CAAAA,CAAMR,CAAAA,CAAK,CAAA,CAAIM,CAAQ,CAAA,CAC7B,GAAIE,CAAAA,CAAI,IAAA,GAAS,cAAA,CAAgB,SAEjC,GAAIA,CAAAA,CAAI,IAAA,GAAS,gBAAA,CAAkB,CACjCJ,EAAK,CACH,IAAA,CAAAG,CAAAA,CACA,GAAA,CAAAC,CAAAA,CACA,OAAA,CAAS,CAAA,EAAGpH,CAAAA,CAAI,gBACd2G,CAAAA,CAAUQ,CAAAA,CAAK,MAAA,CAAQA,CAAAA,CAAK,MAAM,CACpC,CAAC,CAAA,aAAA,EAAgBnH,CAAAA,CAAI,gBACnB2G,CAAAA,CAAUS,CAAAA,CAAI,MAAA,CAAQA,CAAAA,CAAI,MAAM,CAClC,CAAC,CAAA,EAAA,EAAK3G,EAAiB,CAAA,CAAA,CAAA,CACvB,OAAA,CAAS,CAAE,WAAA,CAAa2G,EAAI,IAAA,CAAM,OAAA,CAAS,YAAa,CAC1D,CAAC,CAAA,CACD,QACF,CAIA,IAAMC,EAAUrH,CAAAA,CAAI,OAAA,CAAQoH,CAAAA,CAAI,MAAM,EAChCE,CAAAA,CACJF,CAAAA,CAAI,IAAA,GAAS,aAAA,EACbC,GAAS,IAAA,GAAS,cAAA,EAClB,CAACtW,EAAAA,CAAYsW,EAAQ,IAAI,CAAA,CACrBE,CAAAA,CAAUH,CAAAA,CAAI,IAAA,GAAS,WAAA,CAAc,sBAAA,CAAyB,cAAA,CAC9DI,EAAWxH,CAAAA,CAAI,eAAA,CAAgB2G,CAAAA,CAAUQ,CAAAA,CAAK,OAAQA,CAAAA,CAAK,MAAM,CAAC,CAAA,CACxEH,EAAK,CACH,IAAA,CAAAG,CAAAA,CACA,GAAA,CAAAC,EACA,QAAA,CAAUE,CAAAA,CAAgB,SAAA,CAAY,OAAA,CACtC,QAASA,CAAAA,CACL,CAAA,EAAGE,CAAQ,CAAA,UAAA,EAAaD,CAAO,CAAA,wFAAA,CAAA,CAC/B,CAAA,EAAGC,CAAQ,CAAA,UAAA,EAAaD,CAAO,CAAA,EAAA,EAAKhH,EAA0B,CAAA,CAAA,CAAA,CAClE,OAAA,CAAS+G,CAAAA,CACL,CAAE,WAAA,CAAaF,CAAAA,CAAI,KAAM,gBAAA,CAAkB,KAAM,CAAA,CACjD,CAAE,YAAaA,CAAAA,CAAI,IAAK,CAC9B,CAAC,EACD,QACF,CAEA,IAAMK,CAAAA,CAAaX,EAAY,cAAc,CAAA,CAC7C,GAAIW,CAAAA,EAAc,EAAG,CAEnB,IAAMN,CAAAA,CAAOP,CAAAA,CAAKa,CAAU,CAAA,CACtBL,CAAAA,CAAMR,CAAAA,CAAK,CAAA,CAAIa,CAAU,CAAA,CAC/BT,CAAAA,CAAK,CACH,IAAA,CAAAG,CAAAA,CACA,GAAA,CAAAC,CAAAA,CACA,OAAA,CAAS,GAAGpH,CAAAA,CAAI,eAAA,CACdmH,CAAAA,CAAK,MACP,CAAC,CAAA,aAAA,EAAgBnH,CAAAA,CAAI,eAAA,CAAgBoH,CAAAA,CAAI,MAAM,CAAC,CAAA,EAAA,EAAK5G,EAAqB,CAAA,CAAA,CAAA,CAC1E,OAAA,CAAS,CAAE,WAAA,CAAa4G,CAAAA,CAAI,KAAM,OAAA,CAAS,iBAAkB,CAC/D,CAAC,EACD,QACF,CAEIR,CAAAA,CAAK,CAAC,EAAE,IAAA,GAAS,aAAA,EAAiBA,CAAAA,CAAK,CAAC,CAAA,CAAE,IAAA,GAAS,aAAA,EACrDI,CAAAA,CAAK,CACH,IAAA,CAAMJ,CAAAA,CAAK,CAAC,CAAA,CACZ,IAAKA,CAAAA,CAAK,CAAC,CAAA,CACX,OAAA,CAAS,GAAG5G,CAAAA,CAAI,eAAA,CACd4G,CAAAA,CAAK,CAAC,EAAE,MACV,CAAC,CAAA,KAAA,EAAQ5G,CAAAA,CAAI,gBACX4G,CAAAA,CAAK,CAAC,CAAA,CAAE,MACV,CAAC,CAAA,8BAAA,EAAiClG,EAAuB,CAAA,CAAA,CAAA,CACzD,OAAA,CAAS,CAAE,WAAA,CAAa,aAAA,CAAe,OAAA,CAAS,kBAAmB,CACrE,CAAC,EAEL,CACA,OAAOnB,CACT,CACF,CAAA,CAGamI,EAAAA,CAAwC,CACnDvE,EAAAA,CACAS,EAAAA,CACAO,EAAAA,CACAG,EAAAA,CACAI,GACAO,EAAAA,CACAE,EAAAA,CACAS,EAAAA,CACAE,EAAAA,CACAK,GACAI,EAAAA,CACAG,EACF,EC3zBA,SAASiB,GACP3H,CAAAA,CACAwB,CAAAA,CACAjD,CAAAA,CACAtE,CAAAA,CACqB,CACrB,IAAM2K,CAAAA,CAASjE,CAAAA,CAAIa,CAAAA,CAAI,IAAI,OAAO,CAAA,CAClC,GAAI,CAACoD,CAAAA,CAAQ,OACb,IAAM3D,CAAAA,CAAQjB,EAAI,kBAAA,CAAmBwB,CAAAA,CAAI,MAAA,CAAQjD,CAAAA,CAAOtE,CAAQ,CAAA,CAChE,GAAIgH,CAAAA,EAAS,CAAA,CAAG,OAChB,IAAMsB,CAAAA,CAAWhB,CAAAA,CAAgBC,CAAAA,CAAKxB,EAAI,OAAO,CAAA,CAC3C,CAAE,cAAA,CAAA4B,EAAgB,eAAA,CAAAgG,CAAAA,CAAiB,WAAA,CAAAxG,CAAY,EAAImB,CAAAA,CACnDsF,CAAAA,CAAUvF,EAAAA,CAAkBd,CAAAA,CAAI,IAAKe,CAAQ,CAAA,CAGnD,OAAO,CAAE,IAAA,CADPvB,EAAAA,CAAYC,CAAAA,CAAO4G,CAAAA,CAAQ,SAAUA,CAAAA,CAAQ,MAAA,CAAQzG,CAAW,CAAA,CAAIwG,EACvD,KAAA,CAAA3G,CAAAA,CAAO,MAAA,CAAS2D,CAAAA,CAAShD,EAAkB,GAAI,CAChE,CAYO,IAAMkG,EAAAA,CAAyC,CACpD,EAAA,CAAI,SAAA,CACJ,KAAM,qCAAA,CACN,KAAA,CAAO,UAAA,CACP,eAAA,CAAiB,QACjB,cAAA,CAAgB,CAAE,eAAA,CAAiB,CAAE,EACrC,WAAA,CACE,wFAAA,CACF,QAAA,CAAS9H,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,GAClC,IAAA,IAAWiC,CAAAA,IAAOkB,CAAAA,CAAU1C,CAAG,EAAG,CAChC,IAAM6C,CAAAA,CAAO8E,EAAAA,CAAe3H,EAAKwB,CAAAA,CAAK,cAAc,CAAA,CAChD,CAACqB,GAAQA,CAAAA,CAAK,IAAA,EAAQA,CAAAA,CAAK,MAAA,EAC/BtD,EAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWiC,EAAI,MAAM,CAAA,CAAA,CACzB,OAAA,CAAS,CAAA,EAAGxB,EAAI,eAAA,CAAgBwB,CAAAA,CAAI,MAAM,CAAC,CAAA,SAAA,EAAYsB,CAAAA,CACrDD,CAAAA,CAAK,IACP,CAAC,CAAA,+BAAA,EAAkCC,CAAAA,CACjCD,CAAAA,CAAK,MACP,CAAC,CAAA,oEAAA,CAAA,CACD,OAAA,CAAS,CAACrB,CAAAA,CAAI,OAAO,CAAA,CACrB,MAAA,CAAQ,CAACA,CAAAA,CAAI,MAAM,CAAA,CACnB,OAAA,CAAS,CACP,MAAOqB,CAAAA,CAAK,KAAA,CACZ,IAAA,CAAMC,CAAAA,CAAMD,EAAK,IAAI,CAAA,CACrB,MAAA,CAAQC,CAAAA,CAAMD,EAAK,MAAM,CAC3B,CACF,CAAC,EACH,CACA,OAAOtD,CACT,CACF,CAAA,CAWawI,EAAAA,CAAwC,CACnD,EAAA,CAAI,UACJ,IAAA,CAAM,iCAAA,CACN,KAAA,CAAO,YAAA,CACP,gBAAiB,SAAA,CACjB,cAAA,CAAgB,CAAE,eAAA,CAAiB,CAAE,CAAA,CACrC,WAAA,CAAa,oEAAA,CACb,QAAA,CAAS/H,EAAK,CACZ,IAAMT,CAAAA,CAA4B,GAClC,IAAA,IAAWiC,CAAAA,IAAOkB,CAAAA,CAAU1C,CAAG,EAAG,CAChC,IAAM6C,CAAAA,CAAO8E,EAAAA,CAAe3H,CAAAA,CAAKwB,CAAAA,CAAK,gBAAA,CAAkB,cAAc,EAClE,CAACqB,CAAAA,EAAQA,CAAAA,CAAK,IAAA,EAAQA,EAAK,MAAA,EAC/BtD,CAAAA,CAAO,IAAA,CAAK,CACV,GAAI,CAAA,QAAA,EAAWiC,CAAAA,CAAI,MAAM,CAAA,CAAA,CACzB,OAAA,CAAS,CAAA,EAAGxB,CAAAA,CAAI,eAAA,CAAgBwB,EAAI,MAAM,CAAC,CAAA,KAAA,EAAQsB,CAAAA,CACjDD,EAAK,IACP,CAAC,CAAA,uBAAA,EAA0BC,CAAAA,CACzBD,EAAK,MACP,CAAC,CAAA,iEAAA,CAAA,CACD,OAAA,CAAS,CAACrB,CAAAA,CAAI,OAAO,CAAA,CACrB,OAAQ,CAACA,CAAAA,CAAI,MAAM,CAAA,CACnB,QAAS,CACP,KAAA,CAAOqB,CAAAA,CAAK,KAAA,CACZ,KAAMC,CAAAA,CAAMD,CAAAA,CAAK,IAAI,CAAA,CACrB,OAAQC,CAAAA,CAAMD,CAAAA,CAAK,MAAM,CAC3B,CACF,CAAC,EACH,CACA,OAAOtD,CACT,CACF,CAAA,CAkBayI,EAAAA,CAAgC,CAC3C,GAAI,SAAA,CACJ,IAAA,CAAM,wBAAA,CACN,KAAA,CAAO,UAAA,CACP,eAAA,CAAiB,MAAA,CACjB,cAAA,CAAgB,CAAE,eAAA,CAAiB,CAAE,CAAA,CACrC,WAAA,CAAa,+EACb,QAAA,CAAShI,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CAC7B0I,CAAAA,CAAS,IAAI,IACnB,IAAA,IAAWzG,CAAAA,IAAOxB,CAAAA,CAAI,OAAA,GACfiI,CAAAA,CAAO,GAAA,CAAIzG,CAAAA,CAAI,OAAO,GAAGyG,CAAAA,CAAO,GAAA,CAAIzG,CAAAA,CAAI,OAAA,CAASA,CAAG,CAAA,CAE3D,IAAA,GAAW,CAAC0G,CAAAA,CAAS1G,CAAG,CAAA,GAAKyG,CAAAA,CAAQ,CACnC,IAAMjX,CAAAA,CAAOwQ,CAAAA,CAAI,KAAA,CAAM,IAAA,EAAQ,EAAC,CAC1B,CAAE,QAAA,CAAAE,CAAAA,CAAU,eAAAE,CAAAA,CAAgB,eAAA,CAAAgG,CAAgB,CAAA,CAAIrG,EACpDC,CAAAA,CACAxB,CAAAA,CAAI,OACN,CAAA,CAAA,CACqBhP,EAAK,QAAA,EAAYgP,CAAAA,CAAI,OAAA,CAAQ,QAAA,GAEhDT,EAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW2I,CAAO,CAAA,KAAA,CAAA,CACtB,OAAA,CAAS,CAAA,EAAGlI,CAAAA,CAAI,eAAA,CACdkI,CACF,CAAC,CAAA,8DAAA,EAA4DxG,CAAQ,CAAA,UAAA,EAAaE,CAAc,CAAA,UAAA,CAAA,CAChG,OAAA,CAAS,CAACsG,CAAO,CAAA,CACjB,OAAA,CAAS,CAAE,SAAAxG,CAAAA,CAAU,cAAA,CAAAE,CAAe,CACtC,CAAC,CAAA,CAEH,IAAMW,CAAAA,CAAWhB,EAAgBC,CAAAA,CAAKxB,CAAAA,CAAI,OAAO,CAAA,CACjD,GAAIuC,CAAAA,CAAS,YAAA,GAAiB,SAAA,CAC5BhD,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW2I,CAAO,CAAA,QAAA,CAAA,CACtB,OAAA,CAAS,CAAA,EAAGlI,CAAAA,CAAI,gBACdkI,CACF,CAAC,CAAA,8CAAA,EAA4C3F,CAAAA,CAAS,aAAa,CAAA,qBAAA,EAAwBA,CAAAA,CAAS,kBAAkB,CAAA,qBAAA,EAAwBb,CAAQ,CAAA,SAAA,CAAA,CACtJ,OAAA,CAAS,CAACwG,CAAO,EACjB,OAAA,CAAS,CACP,aAAA,CAAe3F,CAAAA,CAAS,cACxB,kBAAA,CAAoBA,CAAAA,CAAS,kBAC/B,CACF,CAAC,CAAA,CAAA,KAAA,GACQA,CAAAA,CAAS,YAAA,GAAiB,QAAA,CAAU,CAI7C,IAAM4F,CAAAA,CAAQ,IAAI,GAAA,CAChBnI,CAAAA,CACG,OAAA,EAAQ,CACR,MAAA,CAAQ9B,GAAQA,CAAAA,CAAI,OAAA,GAAYgK,CAAAA,EAAW,CAAChK,EAAI,GAAA,CAAI,QAAQ,CAAA,CAC5D,GAAA,CAAKA,GAASmE,EAAAA,CAAenE,CAAAA,CAAI,GAAG,CAAA,GAAM,cAAgB,OAAA,CAAU,MAAO,CAChF,CAAA,CACMkK,EAAQ7F,CAAAA,CAAS,QAAA,CACjB8F,CAAAA,CAASF,CAAAA,CAAM,KAAO,CAAA,CACxB,CAAA,+DAAA,EAAkEC,CAAK,CAAA,GAAA,CAAA,CACvED,EAAM,GAAA,CAAI,OAAO,CAAA,EAAKC,CAAAA,EAAS,GAAA,CAC7B,CAAA,mCAAA,EAAsCA,CAAK,CAAA,2CAAA,CAAA,CAC3CD,EAAM,GAAA,CAAI,MAAM,CAAA,EAAKzG,CAAAA,GAAa,OAAS0G,CAAAA,EAAS,GAAA,CAClD,CAAA,0CAAA,EAA6CA,CAAK,iDAClD,MAAA,CACJC,CAAAA,EACF9I,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW2I,CAAO,CAAA,cAAA,CAAA,CACtB,MAAO,WAAA,CACP,OAAA,CAAS,CAAA,EAAGlI,CAAAA,CAAI,gBACdkI,CACF,CAAC,CAAA,CAAA,EAAIG,CAAM,6HACX,OAAA,CAAS,CAACH,CAAO,CAAA,CACjB,OAAA,CAAS,CAAE,QAAA,CAAUE,CAAM,CAC7B,CAAC,EAEL,CACA,IAAME,EAAsBtX,CAAAA,CAAK,mBAAA,GAAwB,IAAA,CACrD0Q,CAAAA,GAAa,OAASE,CAAAA,EAAkB,GAAA,EAAO,CAAC0G,CAAAA,EAClD/I,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,WAAW2I,CAAO,CAAA,IAAA,CAAA,CACtB,OAAA,CAAS,CAAA,EAAGlI,EAAI,eAAA,CACdkI,CACF,CAAC,CAAA,SAAA,EAAYtG,CAAc,CAAA,oGAAA,CAAA,CAC3B,OAAA,CAAS,CAACsG,CAAO,CAAA,CACjB,OAAA,CAAS,CAAE,QAAA,CAAAxG,EAAU,cAAA,CAAAE,CAAe,CACtC,CAAC,EAECF,CAAAA,GAAa,KAAA,EAASE,CAAAA,CAAiB,GAAA,EACzCrC,EAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW2I,CAAO,CAAA,IAAA,CAAA,CACtB,KAAA,CAAO,YAAA,CACP,OAAA,CAAS,GAAGlI,CAAAA,CAAI,eAAA,CACdkI,CACF,CAAC,cAActG,CAAc,CAAA,yFAAA,CAAA,CAC7B,OAAA,CAAS,CAACsG,CAAO,CAAA,CACjB,OAAA,CAAS,CAAE,QAAA,CAAAxG,CAAAA,CAAU,cAAA,CAAAE,CAAe,CACtC,CAAC,CAAA,CAECgG,CAAAA,GAAoB,CAAA,EACtBrI,CAAAA,CAAO,KAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAW2I,CAAO,aACtB,KAAA,CAAO,WAAA,CACP,OAAA,CAAS,CAAA,EAAGlI,EAAI,eAAA,CACdkI,CACF,CAAC,CAAA,+BAAA,EAAkCN,CAAe,CAAA,4FAAA,CAAA,CAClD,OAAA,CAAS,CAACM,CAAO,EACjB,OAAA,CAAS,CAAE,eAAA,CAAAN,CAAgB,CAC7B,CAAC,EAEL,CACA,OAAOrI,CACT,CACF,EAaA,SAASgJ,GACPvI,CAAAA,CACc,CACd,IAAMwI,CAAAA,CAAsB,EAAC,CACvBC,CAAAA,CAAQ,IAAI,GAAA,CAAIzI,EAAI,OAAA,EAAQ,CAAE,GAAA,CAAKwB,CAAAA,EAAQ,CAACA,CAAAA,CAAI,MAAA,CAAQA,CAAG,CAAC,CAAC,CAAA,CAK7DhF,CAAAA,CAAO,IAAI,GAAA,CACjB,QAAWzB,CAAAA,IAAQiF,CAAAA,CAAI,KAAA,CAAO,CAC5B,GAAI,CAACjF,CAAAA,CAAK,MAAA,CAAQ,SAClB,IAAM2N,CAAAA,CAAWD,CAAAA,CAAM,GAAA,CAAI1N,EAAK,MAAM,CAAA,CAGtC,GAFI,EAAA,CAAC2N,GACS1I,CAAAA,CAAI,OAAA,CAAQjF,CAAAA,CAAK,UAAU,GAC9B,IAAA,GAAS,oBAAA,CAAA,CACpB,IAAA,IAAWyG,CAAAA,IAAOxB,CAAAA,CAAI,OAAA,EAAQ,CAAG,CAC/B,GAAIwB,CAAAA,CAAI,OAAA,GAAYzG,CAAAA,CAAK,UAAA,CAAY,SACrC,IAAM4N,CAAAA,CAAU,CAAA,EAAGD,CAAAA,CAAS,MAAM,CAAA,CAAA,EAAIlH,CAAAA,CAAI,MAAM,CAAA,CAAA,CAC5ChF,CAAAA,CAAK,GAAA,CAAImM,CAAO,CAAA,GACpBnM,EAAK,GAAA,CAAImM,CAAO,CAAA,CAChBH,CAAAA,CAAM,KAAK,CACT,QAAA,CAAAE,CAAAA,CACA,iBAAA,CAAmBlH,EAAI,OAAA,CACvB,aAAA,CAAeA,CAAAA,CAAI,GAAA,CACnB,cAAeA,CAAAA,CAAI,MACrB,CAAC,CAAA,EACH,CACF,CACA,OAAOgH,CACT,KASaI,EAAAA,CAAqC,CAChD,EAAA,CAAI,SAAA,CACJ,KAAM,iCAAA,CACN,KAAA,CAAO,WAAA,CACP,eAAA,CAAiB,SAAA,CACjB,cAAA,CAAgB,CAAE,QAAA,CAAU,EAAG,SAAA,CAAW,GAAI,CAAA,CAC9C,WAAA,CACE,gHACF,QAAA,CAAS5I,CAAAA,CAAK,CACZ,IAAMT,EAA4B,EAAC,CAC7BsJ,CAAAA,CAAWjI,CAAAA,CAAIZ,EAAI,OAAA,CAAS,UAAA,CAAY,CAAC,CAAA,CACzC8I,EAAYlI,CAAAA,CAAIZ,CAAAA,CAAI,OAAA,CAAS,WAAA,CAAa,GAAG,CAAA,CACnD,IAAA,IAAW+I,CAAAA,IAAQR,EAAAA,CAAevI,CAAG,CAAA,CAAG,CACtC,IAAMgJ,CAAAA,CAAKrI,CAAAA,CAAIoI,CAAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,CAClC5M,CAAAA,CAAOwE,CAAAA,CAAIoI,CAAAA,CAAK,cAAc,OAAO,CAAA,CAC3C,GAAI,CAACC,GAAM,CAAC7M,CAAAA,CAAM,SAClB,IAAM8M,EAAQD,CAAAA,CAAK7M,CAAAA,CAEnB,GAAI8M,CAAAA,CAAQ,EAAG,SAIf,IAAMC,CAAAA,CAFHH,CAAAA,CAAK,SAAS,GAAA,CAA2C,cAAA,GAC1D,MAAA,CACyBD,CAAAA,CAAYD,EACnCI,CAAAA,EAASC,CAAAA,EACb3J,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWwJ,CAAAA,CAAK,SAAS,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAK,aAAa,GACzD,OAAA,CAAS,CAAA,EAAG/I,CAAAA,CAAI,eAAA,CACd+I,EAAK,QAAA,CAAS,MAChB,CAAC,CAAA,EAAA,EAAKC,CAAE,CAAA,SAAA,EAAYhJ,CAAAA,CAAI,eAAA,CACtB+I,EAAK,aACP,CAAC,CAAA,EAAA,EAAK5M,CAAI,YAAY2G,CAAAA,CACpBmG,CAAAA,CACA,CACF,CAAC,mIACD,OAAA,CAAS,CAACF,CAAAA,CAAK,QAAA,CAAS,OAAA,CAASA,CAAAA,CAAK,iBAAiB,CAAA,CACvD,OAAQ,CAACA,CAAAA,CAAK,QAAA,CAAS,MAAA,CAAQA,EAAK,aAAa,CAAA,CACjD,OAAA,CAAS,CAAE,UAAWC,CAAAA,CAAI,WAAA,CAAa7M,CAAAA,CAAM,KAAA,CAAO2G,EAAMmG,CAAAA,CAAO,CAAC,CAAE,CACtE,CAAC,EACH,CACA,OAAO1J,CACT,CACF,CAAA,CAGa4J,EAAAA,CAA2C,CACtD,EAAA,CAAI,UACJ,IAAA,CAAM,kCAAA,CACN,KAAA,CAAO,UAAA,CACP,eAAA,CAAiB,OAAA,CACjB,WAAA,CAAa,mEAAA,CACb,SAASnJ,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,EAAC,CACnC,IAAA,IAAWwJ,CAAAA,IAAQR,EAAAA,CAAevI,CAAG,CAAA,CAAG,CACtC,IAAMgJ,CAAAA,CAAKrI,EAAIoI,CAAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,EAClC5M,CAAAA,CAAOwE,CAAAA,CAAIoI,CAAAA,CAAK,aAAA,CAAc,OAAO,CAAA,CACvC,CAACC,CAAAA,EAAM,CAAC7M,GAAQ6M,CAAAA,EAAM7M,CAAAA,EAC1BoD,CAAAA,CAAO,IAAA,CAAK,CACV,EAAA,CAAI,CAAA,QAAA,EAAWwJ,CAAAA,CAAK,SAAS,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAK,aAAa,GACzD,OAAA,CAAS,CAAA,EAAG/I,CAAAA,CAAI,eAAA,CACd+I,EAAK,QAAA,CAAS,MAChB,CAAC,CAAA,EAAA,EAAKC,CAAE,CAAA,UAAA,EAAahJ,CAAAA,CAAI,eAAA,CACvB+I,CAAAA,CAAK,aACP,CAAC,CAAA,EAAA,EAAK5M,CAAI,CAAA,0EAAA,CAAA,CACV,QAAS,CAAC4M,CAAAA,CAAK,QAAA,CAAS,OAAA,CAASA,EAAK,iBAAiB,CAAA,CACvD,MAAA,CAAQ,CAACA,CAAAA,CAAK,QAAA,CAAS,MAAA,CAAQA,CAAAA,CAAK,aAAa,CAAA,CACjD,OAAA,CAAS,CAAE,SAAA,CAAWC,EAAI,WAAA,CAAa7M,CAAK,CAC9C,CAAC,EACH,CACA,OAAOoD,CACT,CACF,EAgBA,SAAS1I,EAAAA,CACPmJ,CAAAA,CACAtG,EACAzB,CAAAA,CACoB,CACpB,OAAOM,EAAAA,CAAqByH,EAAI,OAAA,CAAQtG,CAAM,CAAA,CAAGzB,CAAM,CACzD,CAGA,SAASmR,EAAAA,CACPpJ,CAAAA,CACAnD,CAAAA,CACQ,CACR,IAAI4B,CAAAA,CAAQ,EACZ,IAAA,IAAW/E,CAAAA,IAAUsG,CAAAA,CAAI,0BAAA,CAA2BnD,CAAM,CAAA,CAAG,CAC3D,IAAM7L,CAAAA,CAAOgP,EAAI,OAAA,CAAQtG,CAAM,CAAA,EAAG,IAAA,CAC5BuH,EAAQN,CAAAA,CAAI3P,CAAAA,EAAM,YAAY,CAAA,EAAK2P,EAAI3P,CAAAA,EAAM,cAAc,CAAA,CAC7D,OAAOiQ,GAAU,QAAA,GAAUxC,CAAAA,EAASwC,CAAAA,EAC1C,CACA,OAAOxC,CACT,CAqBO,IAAM4K,EAAAA,CAAkC,CAC7C,EAAA,CAAI,SAAA,CACJ,IAAA,CAAM,wBACN,KAAA,CAAO,YAAA,CACP,eAAA,CAAiB,SAAA,CACjB,eAAgB,CAAE,eAAA,CAAiB,EAAG,CAAA,CACtC,YACE,wGAAA,CACF,QAAA,CAASrJ,CAAAA,CAAK,CACZ,IAAMT,CAAAA,CAA4B,EAAC,CAC7B+J,CAAAA,CAAkB1I,EAAIZ,CAAAA,CAAI,OAAA,CAAS,iBAAA,CAAmB,EAAE,EAC9D,IAAA,IAAW3J,CAAAA,IAAQ2J,CAAAA,CAAI,KAAA,CAAO,CAC5B,GAAI3J,CAAAA,CAAK,IAAA,GAAS,cAAA,CAAgB,SAClC,IAAMxE,CAAAA,CAAS8O,CAAAA,CAAItK,EAAK,IAAA,EAAM,MAAM,CAAA,CACpC,GAAIxE,IAAW,MAAA,EAAaA,CAAAA,CAAS,CAAA,CAAG,SAExC,IAAM0X,CAAAA,CAAY,IAAI,KAAA,CAAc,IAAA,CAAK,MAAM1X,CAAM,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,CACxD2X,CAAAA,CAAkBxJ,CAAAA,CAAI,kBAAA,CAAmB3J,EAAK,EAAE,CAAA,CAClDoT,CAAAA,CAAa,CAAA,CACjB,QAAW1O,CAAAA,IAAQyO,CAAAA,CAAiB,CAIlC,IAAM/X,CAAAA,CAAWoF,EAAAA,CAAemJ,CAAAA,CAAK3J,CAAAA,CAAK,GAAI0E,CAAAA,CAAK,YAAY,CAAA,CAC3DtJ,CAAAA,GAAa,SACjB8X,CAAAA,CAAU3X,EAAAA,CAAmBH,CAAAA,CAAUI,CAAM,CAAC,CAAA,EAAKuX,EAAAA,CACjDpJ,CAAAA,CACAjF,CAAAA,CAAK,MACP,CAAA,CACA0O,CAAAA,EAAc,CAAA,EAChB,CAGA,GAAIA,CAAAA,GAAe,CAAA,EAAKF,CAAAA,CAAU,KAAMtI,CAAAA,EAAUA,CAAAA,EAAS,CAAC,CAAA,CAAG,SAE/D,IAAMyI,CAAAA,CAAM,IAAA,CAAK,GAAA,CAAI,GAAGH,CAAS,CAAA,CAC3BI,CAAAA,CAAM,KAAK,GAAA,CAAI,GAAGJ,CAAS,CAAA,CAC3BK,GAAiBF,CAAAA,CAAMC,CAAAA,EAAOD,CAAAA,CAAO,GAAA,CACvCE,GAAgBN,CAAAA,EAEpB/J,CAAAA,CAAO,IAAA,CAAK,CACV,GAAI,CAAA,QAAA,EAAWlJ,CAAAA,CAAK,EAAE,CAAA,CAAA,CACtB,QAAS,CAAA,EAAG2J,CAAAA,CAAI,eAAA,CAAgB3J,CAAAA,CAAK,EAAE,CAAC,CAAA,SAAA,EAAYyM,CAAAA,CAClD8G,CAAAA,CACA,CACF,CAAC,CAAA,sCAAA,EAAyC,IAAA,CAAK,KAAA,CAC7C/X,CACF,CAAC,CAAA,kBAAA,EAAqByX,CAAe,oGACrC,OAAA,CAAS,CAACjT,CAAAA,CAAK,EAAE,EACjB,OAAA,CAAS,CACP,MAAA,CAAQ,IAAA,CAAK,MAAMxE,CAAM,CAAA,CACzB,WAAA,CAAa0X,CAAAA,CAAU,IAAKtI,CAAAA,EAAU6B,CAAAA,CAAM7B,CAAAA,CAAO,CAAC,CAAC,CAAA,CACrD,YAAA,CAAc6B,CAAAA,CAAM8G,CAAAA,CAAc,CAAC,CAAA,CACnC,eAAA,CAAAN,CAAAA,CAEA,eAAA,CAAiBG,EACjB,UAAA,CAAYD,CAAAA,CAAgB,MAC9B,CACF,CAAC,EACH,CACA,OAAOjK,CACT,CACF,CAAA,CAGasK,EAAAA,CAAwC,CACnD/B,GACAC,EAAAA,CACAC,EAAAA,CACAY,EAAAA,CACAO,EAAAA,CACAE,EACF,EC5dO,IAAMS,EAAAA,CAA2C,CACtD,GAAGpC,EAAAA,CACH,GAAGmC,EACL,EAGO,SAASE,EAAAA,CAAqB7O,CAAAA,CAAmC,CACtE,OAAO4O,GAAmB,IAAA,CAAM9K,CAAAA,EAASA,CAAAA,CAAK,EAAA,GAAO9D,CAAE,CACzD","file":"validation.mjs","sourcesContent":["import type { NetworkEdge, Port } from \"../types\";\n\nconst DIRECTIONAL_HANDLE_SUFFIX = /-(source|target)$/i;\nconst FACE_ALIAS_HANDLE_SUFFIX = /--(rear|front)$/i;\n\n/**\n * Normalize source/target directional suffixes used for bidirectional handles.\n * Example: `Rear Ports-1-target` -\\> `Rear Ports-1`.\n */\nexport function normalizeDirectionalHandleId(\n  handleId: string | null | undefined\n): string {\n  if (!handleId) return \"\";\n  return handleId.replace(DIRECTIONAL_HANDLE_SUFFIX, \"\");\n}\n\n/**\n * Normalize rendered handle ids back to their canonical endpoint id.\n * This removes the directional suffix and the split-face alias, in the order\n * the renderer composes them: `<base>[--rear|--front][-source|-target]`.\n * (Previously only `--rear` was stripped; `--front` ghost aliases now\n * normalize too.)\n *\n * Losslessness: the strip is unconditional, so it is only safe when the base\n * itself cannot end in a reserved suffix. {@link buildPortHandleId} guarantees\n * this for device ports — a label like `target` or `foo-target` is escaped at\n * build time (`<group>-%74arget`, `<group>-foo%2Dtarget`) so it survives\n * normalization intact. Bare power labels and explicit `port.id`s are emitted\n * verbatim, so a caller that chooses an id ending in `-source`/`-target`/\n * `--rear`/`--front` owns that collision.\n */\nexport function normalizeHandleId(handleId: string | null | undefined): string {\n  return normalizeDirectionalHandleId(handleId).replace(\n    FACE_ALIAS_HANDLE_SUFFIX,\n    \"\"\n  );\n}\n\n/**\n * Returns true when a port type is considered power-related.\n */\nexport function isPowerPortType(portType: string | undefined): boolean {\n  return (\n    typeof portType === \"string\" && portType.toLowerCase().includes(\"power\")\n  );\n}\n\n/**\n * Handle id of a vertical PDU's supply inlet — the single point where a branch\n * circuit from a distribution-board breaker way lands.\n *\n * Deliberately non-numeric so it can never collide with an outlet id, which is\n * always a bare 1-based number (`\"1\"…\"N\"`).\n * @public\n */\nexport const PDU_INLET_HANDLE_ID = \"inlet\";\n\n/**\n * Handle id of a distribution board's incomer — the board's own supply inlet,\n * mounted by `DistributionBoardNode`. The board analogue of\n * {@link PDU_INLET_HANDLE_ID}.\n * @public\n */\nexport const BOARD_INCOMER_HANDLE_ID = \"incomer\";\n\nconst POWER_INLET_HANDLE_IDS = new Set<string>([\n  PDU_INLET_HANDLE_ID,\n  BOARD_INCOMER_HANDLE_ID,\n]);\n\n/**\n * Whether a handle id names a power-distribution node's supply inlet\n * (`inlet` on a PDU strip, `incomer` on a distribution board). Directional and\n * face-alias suffixes are normalized away first.\n * @public\n */\nexport function isPowerInletHandleId(handleId: string | null | undefined): boolean {\n  if (!handleId) return false;\n  return POWER_INLET_HANDLE_IDS.has(normalizeHandleId(handleId).trim().toLowerCase());\n}\n\n// ---------------------------------------------------------------------------\n// Device-port group/label codec\n//\n// buildPortHandleId joins a non-power port's group and label with `-`. A raw\n// join is non-injective — group `A` + label `B-C` and group `A-B` + label `C`\n// both produce `A-B-C` — and a clean join can also reproduce a fixed grammar\n// production (group `fiber` + label `3` -> `fiber-3`) or a directional/face\n// suffix (label `target` -> `<group>-target`, which normalizeHandleId would\n// truncate). Each segment is therefore percent-escaped over the two structural\n// characters only — the delimiter `-` (`%2D`) and the escape introducer `%`\n// (`%25`) — so the joined base has exactly one literal `-` (the join) and is\n// unambiguously splittable. A segment containing neither character is emitted\n// verbatim, so the overwhelmingly common case is byte-for-byte unchanged.\n// ---------------------------------------------------------------------------\n\n/** Two hex digits, for the tolerant percent-decoder. */\nconst HEX_PAIR = /^[0-9a-fA-F]{2}$/;\n\n/** A character the join grammar reserves and must escape (`-` or `%`). */\nconst RESERVED_SEGMENT_CHAR = /[-%]/;\n\n/**\n * Percent-escape the two structural characters (`%` then `-`) in a group/label\n * segment. Segments with neither are returned unchanged.\n */\nfunction encodeHandleSegment(segment: string): string {\n  if (!RESERVED_SEGMENT_CHAR.test(segment)) {\n    return segment;\n  }\n  return segment.replace(/%/g, \"%25\").replace(/-/g, \"%2D\");\n}\n\n/**\n * Inverse of {@link encodeHandleSegment}: a tolerant single-pass percent-decode\n * that reverses `%XX` escapes and leaves any malformed `%` literal, so it never\n * throws.\n */\nfunction decodeHandleSegment(segment: string): string {\n  if (!segment.includes(\"%\")) {\n    return segment;\n  }\n  let decoded = \"\";\n  for (let i = 0; i < segment.length; i += 1) {\n    if (\n      segment[i] === \"%\" &&\n      i + 2 < segment.length &&\n      HEX_PAIR.test(segment.slice(i + 1, i + 3))\n    ) {\n      decoded += String.fromCharCode(Number.parseInt(segment.slice(i + 1, i + 3), 16));\n      i += 2;\n    } else {\n      decoded += segment[i];\n    }\n  }\n  return decoded;\n}\n\n/** Percent-escape a single (ASCII) character, e.g. `3` -> `%33`. */\nfunction percentEscapeChar(ch: string): string {\n  const hex = ch.charCodeAt(0).toString(16).toUpperCase();\n  return `%${hex.length === 1 ? `0${hex}` : hex}`;\n}\n\n/**\n * True when a freshly joined device-port base would be misread as a fixed\n * grammar shape or stripped by a normalizer (it ends in a directional or face\n * suffix). Such a base must be disambiguated at build time.\n */\nfunction baseCollidesWithGrammar(base: string): boolean {\n  return (\n    classifyHandleBase(base).kind !== \"port\" ||\n    DIRECTIONAL_HANDLE_SUFFIX.test(base) ||\n    FACE_ALIAS_HANDLE_SUFFIX.test(base)\n  );\n}\n\n/**\n * Escape a label whose plain join would collide with the fixed grammar or a\n * reserved suffix. The first character — always ASCII in the colliding cases\n * (a digit, or the leading letter of `source`/`target`/`cable-button`) — is\n * percent-escaped, which no fixed shape or reserved suffix can begin with; the\n * remainder is escaped normally. The standard decoder reverses it unchanged.\n */\nfunction escapeCollidingLabel(label: string): string {\n  return percentEscapeChar(label[0]) + encodeHandleSegment(label.slice(1));\n}\n\n/**\n * Build the `<group>-<label>` base for a non-power device port with both\n * segments escaped (see the codec note above). Injective and — via\n * `decodePortHandleId` — reversible.\n */\nfunction buildDevicePortBase(group: string, label: string): string {\n  const encodedGroup = encodeHandleSegment(group);\n  const base = `${encodedGroup}-${encodeHandleSegment(label)}`;\n  if (label.length > 0 && baseCollidesWithGrammar(base)) {\n    return `${encodedGroup}-${escapeCollidingLabel(label)}`;\n  }\n  return base;\n}\n\n/**\n * Build a stable handle id for a port.\n *\n * Three shapes, in priority order:\n * 1. An explicit `port.id` is returned verbatim — it is the caller's contract,\n *    including any collision with a fixed grammar shape.\n * 2. A power port returns its `label` verbatim, preserving legacy ids (bare\n *    outlet numbers, `PSU-A`, ...).\n * 3. Otherwise the group and label are percent-escaped and joined\n *    (`<group>-<label>`) so the result is injective and never aliases a fixed\n *    grammar production or a directional/face suffix. Invert with\n *    `decodePortHandleId`.\n *\n * NOTE (pre-1.0 wire-format revision): device-port segments containing `-` or\n * `%` now encode those characters (`%2D` / `%25`), and a clean label that would\n * otherwise collide (e.g. group `fiber` + label `3` becomes `fiber-%33`) gets a\n * first-character escape. Segments with no reserved character are unchanged, so\n * common ids are stable.\n */\nexport function buildPortHandleId(\n  portBlockName: string,\n  port: Pick<Port, \"label\" | \"id\"> & { type?: string }\n): string {\n  if (typeof port.id === \"string\" && port.id.length > 0) {\n    return port.id;\n  }\n  if (isPowerPortType(port.type)) {\n    return port.label;\n  }\n  return buildDevicePortBase(portBlockName, port.label);\n}\n\n/**\n * Invert {@link buildPortHandleId}'s device-port group/label encoding, returning\n * the original `{ group, label }`. Only meaningful for the `<group>-<label>`\n * form: power labels and explicit `port.id`s are emitted verbatim by\n * {@link buildPortHandleId} and must not be decoded (there is no group to\n * recover). The split is unambiguous because the encoding leaves exactly one\n * literal `-` (the join delimiter) in the id.\n *\n * Intentionally NOT part of the public entry surface: it is a partial inverse\n * (meaningless on power labels and explicit ids), so it stays internal to\n * avoid a foot-gun. Exported from the module for the grammar's own tests only.\n * @internal\n */\nexport function decodePortHandleId(handleId: string): { group: string; label: string } {\n  const delimiter = handleId.indexOf(\"-\");\n  if (delimiter === -1) {\n    return { group: decodeHandleSegment(handleId), label: \"\" };\n  }\n  return {\n    group: decodeHandleSegment(handleId.slice(0, delimiter)),\n    label: decodeHandleSegment(handleId.slice(delimiter + 1)),\n  };\n}\n\n/**\n * Heuristic for whether a handle id represents a power endpoint.\n *\n * Prefer {@link applyDirectionalPortHandleId}'s `portType` option when the port\n * is available — this string heuristic is a fallback only.\n *\n * Matches library power-id shapes (`Power Ports-1`, `pwr-1`, `power_ac-1`,\n * `PSU-A`, `outlet-3`) without treating every label that merely contains the\n * word \"power\" (e.g. `Power Room Uplinks-1`) as a power endpoint.\n */\nexport function looksLikePowerHandleId(handleId: string): boolean {\n  const normalized = normalizeDirectionalHandleId(handleId).toLowerCase().trim();\n  const compact = normalized.replace(/\\s+/g, \" \");\n\n  if (/^(pwr|power_ac|power_dc)([-_]|$)/.test(compact)) return true;\n  if (/^power([-_ ]?ports)?[-_ ]?\\d+$/.test(compact)) return true;\n  if (/^pwr[-_]?\\d+$/.test(compact)) return true;\n  if (/^(psu|outlet)([-_]|\\d|$)/.test(compact)) return true;\n  if (/^way-.+$/.test(compact)) return true;\n  // The supply inlet of a PDU strip / distribution board is a power endpoint,\n  // so (like a way) it never takes a directional suffix.\n  if (POWER_INLET_HANDLE_IDS.has(compact)) return true;\n  if (/(^|[-_/ ])(psu|outlet)([-_/ ]|\\d|$)/.test(compact)) return true;\n  return false;\n}\n\n/**\n * Options for {@link applyDirectionalPortHandleId}.\n */\nexport interface ApplyDirectionalPortHandleIdOptions {\n  /**\n   * The port's `type` (e.g. `rj45`, `power_ac`). When provided, power-ness is\n   * decided from it via {@link isPowerPortType} — the same rule the renderer\n   * uses to decide whether a port mounts directional handles at all. Always\n   * pass this when the port is available.\n   */\n  portType?: string;\n}\n\n/**\n * Apply a directional (`source`/`target`) suffix to a rendered handle id — the\n * inverse of {@link normalizeDirectionalHandleId}. Any existing directional\n * suffix is replaced first, and passing no direction is a no-op.\n * Example: `Rear Ports-1` + `target` -\\> `Rear Ports-1-target`.\n *\n * Power endpoints are matched without a direction, so power handles are\n * returned unchanged. Whether a handle is a power handle is decided from\n * `options.portType` when given (exact — the renderer's rule). Without it the\n * decision falls back to {@link looksLikePowerHandleId}, which recognises\n * library power-id shapes (`Power Ports-1`, `pwr-*`, `PSU-*`, `outlet-*`)\n * without treating labels that merely contain the word \"power\" as power.\n * Pass `portType` whenever you have the port.\n */\nexport function applyDirectionalPortHandleId(\n  handleId: string,\n  direction?: \"source\" | \"target\" | null,\n  options?: ApplyDirectionalPortHandleIdOptions\n): string {\n  if (!direction) return handleId;\n  const hasPortType =\n    typeof options?.portType === \"string\" && options.portType.length > 0;\n  const isPower = hasPortType\n    ? isPowerPortType(options.portType)\n    : looksLikePowerHandleId(handleId);\n  if (isPower) return handleId;\n  return `${normalizeDirectionalHandleId(handleId)}-${direction}`;\n}\n\n// ---------------------------------------------------------------------------\n// Typed handle-id grammar\n//\n// Handle ids are the library's de-facto wire protocol: they are what React\n// Flow stores on edges, what documents persist, and what consumers match\n// against. This section is the single authority for that grammar. The full\n// composed form is:\n//\n//   <base>[--rear|--front][-source|-target]\n//\n// where <base> is one of the shapes below. Everything here is *structural* —\n// it reads only the string. Shapes whose tokens can collide with port group\n// names (a group literally named `fiber`, a port label `left-3`) cannot be\n// told apart from the string alone; callers that have the node should combine\n// this with the node type (as `classifyFibreEndpoint` does).\n// ---------------------------------------------------------------------------\n\n/**\n * The structural shape of a handle id's base (suffixes already stripped).\n *\n * - `outlet` — vertical-PDU outlets: a bare number (`\"4\"`).\n * - `fiber` — `fiber-<n>` (tube/cable nodes) or `fiber-<tube>-<n>`\n *   (FibreFlowMap rows).\n * - `tube` — `tube-<n>` (tube and multi-tube-cable nodes).\n * - `splice-side` — bare `left` / `right` (splice nodes).\n * - `side-port` — `left-<n>` / `right-<n>` (coupler ports).\n * - `split-io` — `input-<n>` / `output-<n>` (fibre-split nodes).\n * - `cable-button` — the literal `cable-button` connector on cable nodes.\n * - `breaker-way` — `way-<wayId>` (distribution-board breaker ways).\n * - `port` — everything else: device-family ports built by\n *   {@link buildPortHandleId} (`<group>-<label>`, a bare power label, or an\n *   explicit `port.id`). A dashed base is decoded into `group`/`label` (the\n *   segments are percent-escaped so the join delimiter is unambiguous); a\n *   dash-free base, which can never be a group-label join, is flagged\n *   `verbatim` (an opaque `port.id` or bare power label).\n * @public\n */\nexport type HandleShape =\n  | { kind: \"outlet\"; outlet: number }\n  | { kind: \"fiber\"; fiber: number; tube?: number }\n  | { kind: \"tube\"; tube: number }\n  | { kind: \"splice-side\"; side: \"left\" | \"right\" }\n  | { kind: \"side-port\"; side: \"left\" | \"right\"; index: number }\n  | { kind: \"split-io\"; io: \"input\" | \"output\"; index: number }\n  | { kind: \"cable-button\" }\n  | { kind: \"breaker-way\"; wayId: string }\n  | {\n      kind: \"port\";\n      /**\n       * Decoded port group, when the base is a `<group>-<label>` join (has a\n       * dash). Best-effort structural decode: for a verbatim `port.id` or power\n       * label that happens to contain a dash (`PSU-A`) this is the split before\n       * the first dash, not necessarily a real group.\n       */\n      group?: string;\n      /** Decoded port label — same best-effort decode as `group` above. */\n      label?: string;\n      /**\n       * `true` when the base has no dash, so it cannot be a group-label join —\n       * it is an opaque `port.id` or a bare power label with no `group`/`label`\n       * reading.\n       */\n      verbatim?: boolean;\n    };\n\n/**\n * A structurally parsed handle id.\n * @public\n */\nexport interface ParsedHandleId {\n  /** The id exactly as given. */\n  raw: string;\n  /** `raw` with the directional suffix and face alias stripped. */\n  base: string;\n  /** Directional suffix, when present (`X-source` / `X-target`). */\n  direction?: \"source\" | \"target\";\n  /**\n   * Split-face alias, when present (`X--rear` / `X--front`) — added by the\n   * renderer when both faces of a device are shown and a handle id would\n   * otherwise collide with its opposite face.\n   */\n  faceAlias?: \"front\" | \"rear\";\n  /** Structural classification of {@link ParsedHandleId.base}. */\n  shape: HandleShape;\n  /**\n   * Other shapes the base could structurally be, when it is genuinely\n   * ambiguous. Present (and non-empty) only when there is a second reading;\n   * absent otherwise. The primary {@link ParsedHandleId.shape} is never changed\n   * by this — `alternates` is a pure signal.\n   *\n   * The classic case is a verbatim `port.id` that happens to match a fixed\n   * shape: `tube-5` classifies as `{ kind: \"tube\", tube: 5 }` but could be an\n   * explicit port id, so `alternates` is `[{ kind: \"port\", verbatim: true }]`.\n   * A dashed group-label port carries the same verbatim alternate (it could be\n   * one opaque id). Only node-context-aware classification (which handle-ids\n   * cannot do from the string alone) can pick the right reading.\n   */\n  alternates?: HandleShape[];\n}\n\nconst OUTLET_BASE = /^\\d+$/;\nconst FIBER_TUBE_BASE = /^fiber-(\\d+)-(\\d+)$/;\nconst FIBER_BASE = /^fiber-(\\d+)$/;\nconst TUBE_BASE = /^tube-(\\d+)$/;\nconst SIDE_PORT_BASE = /^(left|right)-(\\d+)$/;\nconst SPLIT_IO_BASE = /^(input|output)-(\\d+)$/;\nconst BREAKER_WAY_BASE = /^way-(.+)$/;\n\nfunction classifyHandleBase(base: string): HandleShape {\n  if (OUTLET_BASE.test(base)) {\n    return { kind: \"outlet\", outlet: Number(base) };\n  }\n  const fiberTube = FIBER_TUBE_BASE.exec(base);\n  if (fiberTube) {\n    return { kind: \"fiber\", tube: Number(fiberTube[1]), fiber: Number(fiberTube[2]) };\n  }\n  const fiber = FIBER_BASE.exec(base);\n  if (fiber) {\n    return { kind: \"fiber\", fiber: Number(fiber[1]) };\n  }\n  const tube = TUBE_BASE.exec(base);\n  if (tube) {\n    return { kind: \"tube\", tube: Number(tube[1]) };\n  }\n  if (base === \"left\" || base === \"right\") {\n    return { kind: \"splice-side\", side: base };\n  }\n  const sidePort = SIDE_PORT_BASE.exec(base);\n  if (sidePort) {\n    return {\n      kind: \"side-port\",\n      side: sidePort[1] as \"left\" | \"right\",\n      index: Number(sidePort[2]),\n    };\n  }\n  const splitIo = SPLIT_IO_BASE.exec(base);\n  if (splitIo) {\n    return {\n      kind: \"split-io\",\n      io: splitIo[1] as \"input\" | \"output\",\n      index: Number(splitIo[2]),\n    };\n  }\n  if (base === \"cable-button\") {\n    return { kind: \"cable-button\" };\n  }\n  const breakerWay = BREAKER_WAY_BASE.exec(base);\n  if (breakerWay) {\n    return { kind: \"breaker-way\", wayId: decodeHandleSegment(breakerWay[1]) };\n  }\n  // A dashed base is always a possible `<group>-<label>` join — decode it (the\n  // percent-escapes are reversed). A dash-free base cannot be a join, so it is\n  // an opaque verbatim id / bare power label.\n  if (base.includes(\"-\")) {\n    const { group, label } = decodePortHandleId(base);\n    return { kind: \"port\", group, label };\n  }\n  return { kind: \"port\", verbatim: true };\n}\n\n/**\n * The alternate structural readings of a classified base, when it is genuinely\n * ambiguous. Every fixed shape, and every dashed group-label port, could also\n * be an explicit verbatim `port.id`; a bare verbatim port has no second reading.\n */\nfunction alternateShapes(shape: HandleShape): HandleShape[] {\n  if (shape.kind === \"port\") {\n    return shape.verbatim ? [] : [{ kind: \"port\", verbatim: true }];\n  }\n  return [{ kind: \"port\", verbatim: true }];\n}\n\n/**\n * Parse a rendered/stored handle id into its structural parts: base, optional\n * directional suffix, optional split-face alias, and the {@link HandleShape}\n * of the base. Total — never throws; unrecognized bases classify as `port`.\n * @public\n */\nexport function parseHandleId(handleId: string | null | undefined): ParsedHandleId {\n  const raw = handleId ?? \"\";\n  const directionMatch = DIRECTIONAL_HANDLE_SUFFIX.exec(raw);\n  const afterDirection = directionMatch\n    ? raw.slice(0, raw.length - directionMatch[0].length)\n    : raw;\n  const faceMatch = FACE_ALIAS_HANDLE_SUFFIX.exec(afterDirection);\n  const base = faceMatch\n    ? afterDirection.slice(0, afterDirection.length - faceMatch[0].length)\n    : afterDirection;\n  const shape = classifyHandleBase(base);\n  const parsed: ParsedHandleId = { raw, base, shape };\n  if (directionMatch) {\n    parsed.direction = directionMatch[1].toLowerCase() as \"source\" | \"target\";\n  }\n  if (faceMatch) {\n    parsed.faceAlias = faceMatch[1].toLowerCase() as \"front\" | \"rear\";\n  }\n  const alternates = alternateShapes(shape);\n  if (alternates.length > 0) {\n    parsed.alternates = alternates;\n  }\n  return parsed;\n}\n\n/**\n * Build a fiber handle id: `fiber-<n>`, or `fiber-<tube>-<n>` when a tube\n * index is given (the FibreFlowMap row form).\n * @public\n */\nexport function buildFibreHandleId(fiber: number, tube?: number): string {\n  return tube === undefined ? `fiber-${fiber}` : `fiber-${tube}-${fiber}`;\n}\n\n/**\n * Build a tube handle id: `tube-<n>`.\n * @public\n */\nexport function buildTubeHandleId(tube: number): string {\n  return `tube-${tube}`;\n}\n\n/**\n * Build a splice handle id: the bare `left` / `right` sides of a splice node.\n * @public\n */\nexport function buildSpliceHandleId(side: \"left\" | \"right\"): string {\n  return side;\n}\n\n/**\n * Build a coupler port handle id: `left-<n>` / `right-<n>` (1-based).\n * @public\n */\nexport function buildCouplerHandleId(side: \"left\" | \"right\", index: number): string {\n  return `${side}-${index}`;\n}\n\n/**\n * Build a fibre-split io handle id: `input-<n>` / `output-<n>`.\n * @public\n */\nexport function buildSplitIoHandleId(io: \"input\" | \"output\", index: number): string {\n  return `${io}-${index}`;\n}\n\n/**\n * Build a distribution-board breaker way handle id: `way-<wayId>`.\n *\n * The way id is percent-escaped exactly as {@link buildPortHandleId} escapes\n * its segments, so a way id containing `-` or `%` (or the `--rear` face alias\n * suffix) cannot be misread by {@link parseHandleId}; the parse side decodes\n * it symmetrically. Ids with no reserved character are unchanged.\n *\n * Ways are power endpoints, so the id carries no directional suffix (see\n * {@link applyDirectionalPortHandleId}) and {@link looksLikePowerHandleId}\n * recognises the prefix.\n * @public\n */\nexport function buildBreakerWayHandleId(wayId: string): string {\n  return `way-${encodeHandleSegment(wayId)}`;\n}\n\n/**\n * Append a split-face alias to a handle id: `X` + `rear` -\\> `X--rear`.\n * Any existing alias is replaced. The renderer applies this when a hidden or\n * ghost face would mount a handle whose id collides with the visible face.\n * @public\n */\nexport function applyFaceAliasHandleId(\n  handleId: string,\n  face: \"front\" | \"rear\"\n): string {\n  return `${handleId.replace(FACE_ALIAS_HANDLE_SUFFIX, \"\")}--${face}`;\n}\n\n/**\n * Normalize a handle id for tolerant matching by stripping `+` characters.\n * Port labels like `SFP+-1` historically produced handles that some producers\n * emitted `+`-stripped (`SFP-1`), so comparisons between independently built\n * handles must be `+`-insensitive. Apply to BOTH sides of a comparison — this\n * is a matching normalization, not part of the build grammar:\n * {@link buildPortHandleId} keeps `+` so built ids always equal rendered ids.\n *\n * This mapping is intentionally MANY-TO-ONE and lossy: it cannot be inverted\n * and must never be persisted as a canonical id. Distinct handles collapse\n * together — e.g. `SFP+-1` and `SFP-1` both sanitize to `SFP-1`, so a port\n * genuinely labelled `SFP-1` is indistinguishable from one labelled `SFP+-1`\n * under this comparison. Use it only as a last-resort tolerant match, applied\n * to both sides.\n *\n * Interaction with the injective build grammar (pre-1.0 wire-format revision):\n * {@link buildPortHandleId} percent-escapes `-` (`%2D`) and `%` (`%25`) inside\n * group/label segments. This sanitizer strips ONLY `+` and deliberately does\n * NOT decode those escapes, so tolerant matching requires both sides to share\n * the same escaping discipline. A lookup built through {@link buildPortHandleId}\n * (or a bare, unescaped label) still matches `+`-insensitively — a `+`-dropped\n * but still-escaped composite `Group-SFP%2D1` matches the registered\n * `Group-SFP+%2D1`. But a fully hand-built composite that both drops `+` AND\n * writes a raw `-` where the builder now writes `%2D` — `Group-SFP-1` against\n * `Group-SFP+%2D1` — no longer matches. That narrowed case is unreachable\n * through the builder (the single source of composite ids) and is pinned as a\n * boundary in the tests. Decoding is intentionally kept OUT of this function:\n * mounts.\n *\n * @internal Internal building block of {@link handlesMatch}; not part of the\n * public API. Consumers must compare handles with {@link handlesMatch} (or find\n * edges with {@link findEdgesForPortHandle}), which apply this rule to both\n * sides along with directional/face normalization.\n */\nexport function sanitizeHandleIdForMatching(handleId: string): string {\n  return handleId.replace(/\\+/g, \"\");\n}\n\n/**\n * True iff two handle ids refer to the same handle under the library's own\n * equivalence relation. This is the one function to use for \"is this edge on\n * this port?\" — it replaces bespoke candidate-set matchers.\n *\n * @remarks\n * The relation, applied to BOTH sides: strip the directional suffix and the\n * split-face alias ({@link normalizeHandleId} semantics), then apply the\n * narrowed `+`-insensitive rule (drop `+`, escaping-aware), then\n * compare. So `Rear Ports-1-target`, `Rear Ports-1--rear` and `Rear Ports-1`\n * all match, and `SFP+-1` matches `SFP-1`.\n *\n * It is intentionally string-structural and does NOT percent-decode: it matches\n * builder-produced ids (the single source of composite ids), where both sides\n * share the same escaping discipline. A hand-built composite that drops `+` AND\n * writes a raw `-` where the builder writes `%2D` (`Group-SFP-1` vs\n * `Group-SFP+%2D1`) is a deliberate non-match — that form is unreachable through\n * {@link buildPortHandleId}. Prefer this over the raw sanitizer, which leaks\n * matching concerns onto callers.\n *\n * @example\n * ```ts\n * handlesMatch(\"Rear Ports-1-target\", \"Rear Ports-1\"); // true\n * handlesMatch(\"Ports-1--rear\", \"Ports-1\");            // true\n * handlesMatch(\"SFP+-1\", \"SFP-1\");                      // true\n * handlesMatch(\"Ports-1\", \"Ports-2\");                   // false\n * ```\n * @public\n */\nexport function handlesMatch(a: string, b: string): boolean {\n  return (\n    sanitizeHandleIdForMatching(normalizeHandleId(a)) ===\n    sanitizeHandleIdForMatching(normalizeHandleId(b))\n  );\n}\n\n/**\n * Return the ids of every edge attached to the given `handleId` on `nodeId`,\n * matching handles with {@link handlesMatch}. An edge qualifies when\n * `source === nodeId` and its `sourceHandle` matches, or `target === nodeId` and\n * its `targetHandle` matches. Edges with an undefined `sourceHandle`/\n * `targetHandle` on the relevant side are skipped.\n *\n * @remarks\n * This is the multi-edge companion to {@link handlesMatch} and the replacement\n * for consumers' hand-rolled per-port edge scans. A self-loop edge (both ends on\n * `nodeId`, both handles matching) is returned once. The scan is a single linear\n * pass over `edges`; it does not build an index, so for repeated lookups over a\n * large edge set a caller may still want its own Map.\n *\n * @example\n * ```ts\n * const handle = buildPortHandleId(\"Rear Ports\", { label: \"1\" });\n * findEdgesForPortHandle(edges, \"device-1\", handle); // [\"e1\", \"e2\"]\n * ```\n * @public\n */\nexport function findEdgesForPortHandle(\n  edges: NetworkEdge[],\n  nodeId: string,\n  handleId: string\n): string[] {\n  const matched: string[] = [];\n  for (const edge of edges) {\n    if (\n      edge.source === nodeId &&\n      edge.sourceHandle !== undefined &&\n      handlesMatch(edge.sourceHandle, handleId)\n    ) {\n      matched.push(edge.id);\n      continue;\n    }\n    if (\n      edge.target === nodeId &&\n      edge.targetHandle !== undefined &&\n      handlesMatch(edge.targetHandle, handleId)\n    ) {\n      matched.push(edge.id);\n    }\n  }\n  return matched;\n}\n\n/**\n * For pass-through nodes with two sides, return the handle id of the opposite\n * side: splice `left` \\<-\\> `right`, coupler `left-<n>` \\<-\\> `right-<n>`.\n * Returns `undefined` for every other shape. Directional suffixes and face\n * aliases are stripped (neither occurs on splice/coupler handles in practice).\n * @public\n */\nexport function getOppositeSideHandleId(handleId: string): string | undefined {\n  const { shape } = parseHandleId(handleId);\n  if (shape.kind === \"splice-side\") {\n    return shape.side === \"left\" ? \"right\" : \"left\";\n  }\n  if (shape.kind === \"side-port\") {\n    return buildCouplerHandleId(shape.side === \"left\" ? \"right\" : \"left\", shape.index);\n  }\n  return undefined;\n}\n\n/**\n * Options for {@link encodeHandle}: the optional suffixes composed onto the\n * base, in grammar order `<base>[--rear|--front][-source|-target]`.\n * @public\n */\nexport interface EncodeHandleOptions {\n  /** Split-face alias to append (`--rear` / `--front`). */\n  face?: \"front\" | \"rear\";\n  /**\n   * Directional suffix to append (`-source` / `-target`). Applied structurally\n   * regardless of power-ness — use {@link applyDirectionalPortHandleId} instead\n   * when you need power ports left un-suffixed.\n   */\n  direction?: \"source\" | \"target\";\n}\n\n/**\n * The single structured encoder: turn a {@link HandleShape} into its handle-id\n * string, dispatching to the individual typed builders. The inverse of\n * {@link decodeHandle}/{@link parseHandleId} for the base classification.\n *\n * @remarks\n * Use this when you hold structured handle data (e.g. from a `DiagramEndpoint`)\n * and need the React Flow boundary string, instead of remembering which builder\n * a kind maps to. A `port` shape REQUIRES string `group` and `label` — it is\n * encoded through {@link buildPortHandleId} (so the same escaping/injectivity\n * applies). A `verbatim` port has no id to emit and cannot be re-encoded: pass\n * an explicit id to {@link buildPortHandleId} yourself for that case.\n *\n * Round-trip: `decodeHandle(encodeHandle(shape)).shape` deep-equals `shape` for\n * every non-verbatim kind.\n *\n * @throws TypeError when a `port` shape is missing `group` or `label`.\n * @example\n * ```ts\n * encodeHandle({ kind: \"fiber\", tube: 2, fiber: 7 });          // \"fiber-2-7\"\n * encodeHandle({ kind: \"port\", group: \"Rear Ports\", label: \"1\" }); // \"Rear Ports-1\"\n * encodeHandle({ kind: \"port\", group: \"Ports\", label: \"1\" }, { face: \"rear\", direction: \"source\" });\n * // \"Ports-1--rear-source\"\n * ```\n * @public\n */\nexport function encodeHandle(\n  shape: HandleShape,\n  opts?: EncodeHandleOptions\n): string {\n  let base: string;\n  switch (shape.kind) {\n    case \"outlet\":\n      base = String(shape.outlet);\n      break;\n    case \"fiber\":\n      base = buildFibreHandleId(shape.fiber, shape.tube);\n      break;\n    case \"tube\":\n      base = buildTubeHandleId(shape.tube);\n      break;\n    case \"splice-side\":\n      base = buildSpliceHandleId(shape.side);\n      break;\n    case \"side-port\":\n      base = buildCouplerHandleId(shape.side, shape.index);\n      break;\n    case \"split-io\":\n      base = buildSplitIoHandleId(shape.io, shape.index);\n      break;\n    case \"cable-button\":\n      base = \"cable-button\";\n      break;\n    case \"breaker-way\":\n      base = buildBreakerWayHandleId(shape.wayId);\n      break;\n    case \"port\": {\n      if (typeof shape.group !== \"string\" || typeof shape.label !== \"string\") {\n        throw new TypeError(\n          \"encodeHandle: a 'port' shape requires string 'group' and 'label'. \" +\n            \"Verbatim/opaque port ids cannot be re-encoded (there is no id in the \" +\n            \"shape) — pass the explicit id to buildPortHandleId instead.\"\n        );\n      }\n      base = buildPortHandleId(shape.group, { label: shape.label });\n      break;\n    }\n  }\n  let out = opts?.face ? applyFaceAliasHandleId(base, opts.face) : base;\n  if (opts?.direction) {\n    out = `${out}-${opts.direction}`;\n  }\n  return out;\n}\n\n/**\n * Decode a handle-id string into its structural parts. A documented alias of\n * {@link parseHandleId} — identical behavior, named for symmetry with\n * {@link encodeHandle}.\n *\n * @remarks\n * Total (never throws). Use the {@link encodeHandle}/`decodeHandle` pair when\n * you think in structured {@link HandleShape}s; use `parseHandleId` directly\n * when the \"parse a rendered id\" framing reads better. See\n * {@link ParsedHandleId.alternates} for the ambiguity signal on verbatim ids.\n *\n * @example\n * ```ts\n * decodeHandle(\"fiber-2-7\").shape; // { kind: \"fiber\", tube: 2, fiber: 7 }\n * decodeHandle(encodeHandle(x)).shape; // deep-equals x for non-verbatim x\n * ```\n * @public\n */\nexport function decodeHandle(\n  handleId: string | null | undefined\n): ParsedHandleId {\n  return parseHandleId(handleId);\n}\n","import type { PortPosition } from '../types';\n\nconst EDGE_OFFSET_LOW_PERCENT = 12;\nconst EDGE_OFFSET_CENTER_LOW_PERCENT = 34;\nconst EDGE_OFFSET_CENTER_PERCENT = 50;\nconst EDGE_OFFSET_CENTER_HIGH_PERCENT = 66;\nconst EDGE_OFFSET_HIGH_PERCENT = 88;\nconst FACE_POSITION_FRONT_PERCENT = 22;\nconst FACE_POSITION_REAR_PERCENT = 78;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst toNumber = (value: unknown): number | undefined => {\n  if (typeof value === 'number' && Number.isFinite(value)) {\n    return value;\n  }\n  if (typeof value === 'string' && value.trim().length > 0) {\n    const parsed = Number(value);\n    if (Number.isFinite(parsed)) {\n      return parsed;\n    }\n  }\n  return undefined;\n};\n\nconst normalizeSemanticPositionTokens = (value: string): string[] =>\n  value\n    .trim()\n    .toLowerCase()\n    .split(/[^a-z0-9]+/)\n    .filter((token) => token.length > 0);\n\nconst canonicalizeSideToken = (token: string): NonNullable<PortPosition['side']> | undefined => {\n  if (token === 'top' || token === 'up') {\n    return 'top';\n  }\n  if (token === 'right') {\n    return 'right';\n  }\n  if (token === 'bottom' || token === 'down') {\n    return 'bottom';\n  }\n  if (token === 'left') {\n    return 'left';\n  }\n  return undefined;\n};\n\nconst hasCenterToken = (tokens: string[]): boolean =>\n  tokens.includes('center') || tokens.includes('centre') || tokens.includes('middle') || tokens.includes('mid');\n\nconst resolveAxisOffsetPercent = (\n  primarySide: NonNullable<PortPosition['side']>,\n  tokens: string[],\n): number | undefined => {\n  const hasLeft = tokens.includes('left');\n  const hasRight = tokens.includes('right');\n  const hasTop = tokens.includes('top') || tokens.includes('up');\n  const hasBottom = tokens.includes('bottom') || tokens.includes('down');\n  const hasCenter = hasCenterToken(tokens);\n\n  if (primarySide === 'top' || primarySide === 'bottom') {\n    if (hasLeft && hasCenter && !hasRight) return EDGE_OFFSET_CENTER_LOW_PERCENT;\n    if (hasRight && hasCenter && !hasLeft) return EDGE_OFFSET_CENTER_HIGH_PERCENT;\n    if (hasLeft && !hasRight) return EDGE_OFFSET_LOW_PERCENT;\n    if (hasRight && !hasLeft) return EDGE_OFFSET_HIGH_PERCENT;\n    if (hasCenter) return EDGE_OFFSET_CENTER_PERCENT;\n    return undefined;\n  }\n\n  if (hasTop && hasCenter && !hasBottom) return EDGE_OFFSET_CENTER_LOW_PERCENT;\n  if (hasBottom && hasCenter && !hasTop) return EDGE_OFFSET_CENTER_HIGH_PERCENT;\n  if (hasTop && !hasBottom) return EDGE_OFFSET_LOW_PERCENT;\n  if (hasBottom && !hasTop) return EDGE_OFFSET_HIGH_PERCENT;\n  if (hasCenter) return EDGE_OFFSET_CENTER_PERCENT;\n  return undefined;\n};\n\nconst resolveSemanticEdgePortPosition = (tokens: string[]): PortPosition | undefined => {\n  const hasFaceTokens = tokens.includes('front') || tokens.includes('rear') || tokens.includes('back');\n  if (hasFaceTokens) {\n    return undefined;\n  }\n\n  const primarySide = tokens\n    .map(canonicalizeSideToken)\n    .find((side): side is NonNullable<PortPosition['side']> => side !== undefined);\n\n  if (!primarySide) {\n    return undefined;\n  }\n\n  const offsetPercent = resolveAxisOffsetPercent(primarySide, tokens);\n  if (offsetPercent === undefined) {\n    return undefined;\n  }\n\n  return {\n    side: primarySide,\n    offsetPercent,\n  };\n};\n\nconst resolveSemanticCompositePortPosition = (tokens: string[]): PortPosition | undefined => {\n  if (tokens.length === 0) {\n    return undefined;\n  }\n\n  const hasLeft = tokens.includes('left');\n  const hasRight = tokens.includes('right');\n  const hasCenter = hasCenterToken(tokens);\n  const hasFront = tokens.includes('front') || tokens.includes('top') || tokens.includes('up');\n  const hasRear = tokens.includes('rear') || tokens.includes('back') || tokens.includes('bottom') || tokens.includes('down');\n\n  let xPercent: number | undefined;\n  if (hasLeft && hasCenter && !hasRight) {\n    xPercent = EDGE_OFFSET_CENTER_LOW_PERCENT;\n  } else if (hasRight && hasCenter && !hasLeft) {\n    xPercent = EDGE_OFFSET_CENTER_HIGH_PERCENT;\n  } else if (hasLeft && !hasRight) {\n    xPercent = EDGE_OFFSET_LOW_PERCENT;\n  } else if (hasRight && !hasLeft) {\n    xPercent = EDGE_OFFSET_HIGH_PERCENT;\n  } else if (hasCenter) {\n    xPercent = EDGE_OFFSET_CENTER_PERCENT;\n  }\n\n  let yPercent: number | undefined;\n  if (hasFront && !hasRear) {\n    yPercent = FACE_POSITION_FRONT_PERCENT;\n  } else if (hasRear && !hasFront) {\n    yPercent = FACE_POSITION_REAR_PERCENT;\n  } else if (hasCenter) {\n    yPercent = EDGE_OFFSET_CENTER_PERCENT;\n  }\n\n  if (xPercent === undefined || yPercent === undefined) {\n    return undefined;\n  }\n\n  return { xPercent, yPercent };\n};\n\nexport const parseSemanticPortPosition = (value: string): PortPosition | undefined => {\n  const tokens = normalizeSemanticPositionTokens(value);\n  if (tokens.length === 0) {\n    return undefined;\n  }\n\n  if (tokens.length === 1) {\n    const side = canonicalizeSideToken(tokens[0]);\n    return side ? { side } : undefined;\n  }\n\n  return resolveSemanticEdgePortPosition(tokens) ?? resolveSemanticCompositePortPosition(tokens);\n};\n\nexport const normalizePortPositionInput = (value: unknown): PortPosition | undefined => {\n  if (typeof value === 'string') {\n    return parseSemanticPortPosition(value);\n  }\n\n  if (!isRecord(value)) {\n    return undefined;\n  }\n\n  const sideRaw = value.side ?? value.port_side ?? value.portSide;\n  const semanticPosition = typeof sideRaw === 'string' ? parseSemanticPortPosition(sideRaw) : undefined;\n  const simpleSide = typeof sideRaw === 'string' ? canonicalizeSideToken(sideRaw.trim().toLowerCase()) : undefined;\n\n  const side = simpleSide ?? semanticPosition?.side;\n  const offsetPercent = toNumber(\n    value.offsetPercent ??\n    value.offset_percent ??\n    value.offsetPct ??\n    value.offset_pct\n  ) ?? semanticPosition?.offsetPercent;\n  const offsetPx = toNumber(\n    value.offsetPx ??\n    value.offset_px ??\n    value.offsetPixels ??\n    value.offset_pixels\n  ) ?? semanticPosition?.offsetPx;\n  const xPercent = toNumber(\n    value.xPercent ??\n    value.x_percent ??\n    value.xPct ??\n    value.x_pct ??\n    value.horizontalPercent ??\n    value.horizontal_percent ??\n    value.x\n  ) ?? semanticPosition?.xPercent;\n  const yPercent = toNumber(\n    value.yPercent ??\n    value.y_percent ??\n    value.yPct ??\n    value.y_pct ??\n    value.verticalPercent ??\n    value.vertical_percent ??\n    value.y\n  ) ?? semanticPosition?.yPercent;\n  const widthPercent = toNumber(\n    value.widthPercent ??\n    value.width_percent ??\n    value.widthPct ??\n    value.width_pct ??\n    value.wPercent ??\n    value.w_percent\n  );\n  const heightPercent = toNumber(\n    value.heightPercent ??\n    value.height_percent ??\n    value.heightPct ??\n    value.height_pct ??\n    value.hPercent ??\n    value.h_percent\n  );\n\n  if (\n    side === undefined &&\n    offsetPercent === undefined &&\n    offsetPx === undefined &&\n    xPercent === undefined &&\n    yPercent === undefined &&\n    widthPercent === undefined &&\n    heightPercent === undefined\n  ) {\n    return undefined;\n  }\n\n  return {\n    side,\n    offsetPercent,\n    offsetPx,\n    xPercent,\n    yPercent,\n    widthPercent,\n    heightPercent,\n  };\n};\n","/**\n * Power connector utilities for consistent styling across devices and PDUs\n */\n\nexport interface PowerConnectorConfig {\n  type: 'C13' | 'C14' | 'C19' | 'C20' | 'AC';\n  color: string;\n  borderColor: string;\n  amperage: number;\n  voltage: number;\n  phases: number;\n}\n\nexport const POWER_CONNECTORS: Record<string, PowerConnectorConfig> = {\n  C13: {\n    type: 'C13',\n    color: '#f59e0b', // Orange\n    borderColor: '#d97706',\n    amperage: 10,\n    voltage: 230,\n    phases: 1\n  },\n  C14: {\n    type: 'C14',\n    color: '#f59e0b', // Orange\n    borderColor: '#d97706',\n    amperage: 10,\n    voltage: 230,\n    phases: 1\n  },\n  C19: {\n    type: 'C19',\n    color: '#fbbf24', // Amber\n    borderColor: '#d97706',\n    amperage: 16,\n    voltage: 230,\n    phases: 1\n  },\n  C20: {\n    type: 'C20',\n    color: '#fbbf24', // Amber\n    borderColor: '#d97706',\n    amperage: 16,\n    voltage: 230,\n    phases: 1\n  },\n  AC: {\n    type: 'AC',\n    color: '#60a5fa', // Blue (generic AC/NEMA)\n    borderColor: '#2563eb',\n    amperage: 15,\n    voltage: 120,\n    phases: 1\n  }\n};\n\nexport interface PowerPortStyle {\n  width: number;\n  height: number;\n  borderRadius?: string;\n  clipPath?: string;\n  color: string;\n  borderColor: string;\n  fontSize: string;\n}\n\n/**\n * Get consistent power port styling for any connector type\n */\nexport function getPowerPortStyle(connectorType: string): PowerPortStyle {\n  const connector = POWER_CONNECTORS[connectorType];\n  if (!connector) {\n    throw new Error(`Unknown connector type: ${connectorType}`);\n  }\n\n  const isHighPower = connector.amperage === 16; // C19/C20 are 16A\n  const isGenericAc = connector.type === 'AC';\n\n  // Size: C13/C14 are intentionally 30% larger while keeping C19/C20 unchanged.\n  const width = isHighPower ? 12 : isGenericAc ? 10 : 8 * 1.3;\n  const height = isHighPower ? 10 : isGenericAc ? 7 : 5 * 1.3;\n\n  // Shape: C13/C14 have chamfered corners, C19/C20 and generic AC use rounded corners.\n  const cornerStyle = isHighPower || isGenericAc\n    ? { borderRadius: isGenericAc ? '2px' : '3px' }\n    : {\n        clipPath: `polygon(2px 0, calc(100% - 2px) 0, 100% 2px, 100% 100%, 0 100%, 0 2px)`,\n        borderRadius: '0px'\n      };\n\n  return {\n    width,\n    height,\n    color: connector.color,\n    borderColor: connector.borderColor,\n    fontSize: isHighPower ? '3px' : isGenericAc ? '3.5px' : '4px',\n    ...cornerStyle\n  };\n}\n\n/**\n * Get connector configuration by type\n */\nexport function getConnectorConfig(connectorType: string): PowerConnectorConfig {\n  const connector = POWER_CONNECTORS[connectorType];\n  if (!connector) {\n    throw new Error(`Unknown connector type: ${connectorType}`);\n  }\n  return connector;\n}\n\n/**\n * Determine if a connector is high power (16A)\n */\nexport function isHighPowerConnector(connectorType: string): boolean {\n  const connector = getConnectorConfig(connectorType);\n  return connector.amperage === 16;\n}\n\n/** IEC 60320 designation embedded in a port `type` such as `power_c13`. */\nconst PORT_TYPE_CONNECTOR = /c(13|14|15|16|19|20|21|22)\\b/i;\n\n/**\n * Recover a connector designation from a port's `type` when the port carries no\n * explicit `connectorType` — `power_c13` becomes `C13`.\n *\n * The single implementation of that inference: the endpoint classifier\n * (`endpoint-metadata`), the validation graph and the renderer all read a\n * typed-only port through here, so a rule can never resolve a different\n * connector than the canvas drew.\n * @public\n */\nexport function inferConnectorFromPortType(portType: unknown): string | undefined {\n  if (typeof portType !== 'string') return undefined;\n  const match = PORT_TYPE_CONNECTOR.exec(portType);\n  return match ? `C${match[1]}` : undefined;\n}\n\n/**\n * Whether a PDU strip's data declares a supply inlet.\n *\n * The three fields the renderer reads before it mounts the inlet handle\n * (`inletConnector` / `inletAmps` / `inletGender`) — `feedType`, `voltage` and\n * `phases` only enrich the tooltip and are not an inlet on their own. Stated\n * once so the renderer, the canvas validator and PWR-A13 agree on when a strip\n * has somewhere for a branch circuit to land.\n * @public\n */\nexport function hasPduInlet(data: unknown): boolean {\n  if (!data || typeof data !== 'object') return false;\n  const { inletConnector, inletAmps, inletGender } = data as {\n    inletConnector?: unknown;\n    inletAmps?: unknown;\n    inletGender?: unknown;\n  };\n  if (typeof inletConnector === 'string' && inletConnector.trim().length > 0) return true;\n  if (typeof inletAmps === 'number' && Number.isFinite(inletAmps)) return true;\n  const gender =\n    typeof inletGender === 'string' ? inletGender.trim().toLowerCase() : undefined;\n  return gender === 'male' || gender === 'female';\n}\n\n/**\n * Get PDU port type based on position in cycle\n * PDU cycle: [8xC13, 2xC19] = 10 ports per cycle\n */\nexport function getPDUPortType(portIndex: number): string {\n  const cyclePosition = portIndex % 10; // 10 ports per cycle\n  return cyclePosition < 8 ? 'C13' : 'C19';\n}\n\n/**\n * Connector designation of a vertical-PDU outlet at a 1-based `position`.\n *\n * Prefers the strip's explicit `data.portTypes` sequence and falls back to the\n * default 8×C13 + 2×C19 cycle — the same precedence the renderer uses, so the\n * connector a rule reasons about is the connector that was drawn.\n * @public\n */\nexport function getPduOutletConnectorType(\n  portTypes: ReadonlyArray<string | undefined> | undefined,\n  position: number\n): string {\n  const index = Math.max(0, Math.floor(position) - 1);\n  const explicit = portTypes?.[index];\n  if (typeof explicit === 'string' && explicit.trim().length > 0) {\n    return explicit.trim().toUpperCase();\n  }\n  return getPDUPortType(index);\n}\n\n/**\n * Zero-based phase index of a PDU outlet at a 1-based `position` on an\n * `phases`-phase strip.\n *\n * Convention: outlets are attributed to phases **round-robin from the top** —\n * `(position - 1) % phases`. Real strips vary (some group outlets in per-phase\n * banks), but round-robin is the convention the consumer application uses, and\n * a phase-balance number is only meaningful if both sides attribute outlets the\n * same way. A non-positive or non-finite `phases` is read as single-phase, for\n * which every outlet is phase 0.\n * @public\n */\nexport function toOutletPhaseIndex(position: number, phases: number): number {\n  const count =\n    Number.isFinite(phases) && phases >= 1 ? Math.floor(phases) : 1;\n  const slot = Number.isFinite(position) ? Math.floor(position) : 1;\n  return (((slot - 1) % count) + count) % count;\n}\n\n/** Phase labels in round-robin order, indexed by {@link toOutletPhaseIndex}. */\nexport const PHASE_LABELS = ['L1', 'L2', 'L3'] as const;\n\n/**\n * Phase label (`L1`/`L2`/`L3`) of a PDU outlet at a 1-based `position`, or\n * `undefined` on a single-phase strip (where there is nothing to attribute) or\n * beyond the three labelled phases.\n * @public\n */\nexport function getPduOutletPhaseLabel(\n  position: number,\n  phases: number\n): (typeof PHASE_LABELS)[number] | undefined {\n  if (!Number.isFinite(phases) || phases < 2) return undefined;\n  return PHASE_LABELS[toOutletPhaseIndex(position, phases)];\n}\n\n/**\n * Get device connector type based on device name\n */\nexport function getDeviceConnectorType(deviceName: string): string {\n  const name = deviceName.toLowerCase();\n  \n  // High power devices use C20\n  if (name.includes('mx304') || name.includes('mx480') || name.includes('mx960')) {\n    return 'C20';\n  }\n  \n  // Standard devices use C14\n  return 'C14';\n}\n","import type {\n  DevicePort,\n  DeviceFace,\n  NetworkNode,\n  Port,\n  PortBlock,\n  PortPosition,\n} from '../types';\nimport { isPowerPortType } from './handle-ids';\nimport { normalizePortPositionInput } from './port-position';\n\ntype IndexedPort = {\n  index: number;\n  port: DevicePort;\n  position: PortPosition | undefined;\n};\n\ntype GroupedPortBucket = {\n  face: DeviceFace;\n  groupName: string;\n  entries: IndexedPort[];\n};\n\nconst DEFAULT_FRONT_GROUP_NAME = 'Network Ports';\nconst DEFAULT_REAR_GROUP_NAME = 'Rear Network Ports';\nconst DEFAULT_POWER_GROUP_NAME = 'Power Ports';\nconst DISTRIBUTED_OFFSET_MIN_PERCENT = 8;\nconst DISTRIBUTED_OFFSET_MAX_PERCENT = 92;\n\nfunction clampPercent(value: number): number {\n  return Math.max(0, Math.min(100, value));\n}\n\nfunction normalizePortAnchor(value: PortPosition['side'] | undefined): PortPosition['side'] | undefined {\n  if (value === 'top' || value === 'right' || value === 'bottom' || value === 'left') {\n    return value;\n  }\n  return undefined;\n}\n\nfunction getNormalizedPortPosition(position: unknown): PortPosition | undefined {\n  return normalizePortPositionInput(position);\n}\n\nfunction hasExplicitAbsoluteCoordinates(position: PortPosition | undefined): boolean {\n  return (\n    typeof position?.xPercent === 'number' &&\n    typeof position?.yPercent === 'number'\n  );\n}\n\nfunction hasExplicitOffset(position: PortPosition | undefined): boolean {\n  return (\n    typeof position?.offsetPercent === 'number' ||\n    typeof position?.offsetPx === 'number'\n  );\n}\n\nfunction resolveRenderPortFace(port: DevicePort): DeviceFace {\n  if (port.face === 'rear' || port.face === 'back') {\n    return 'rear';\n  }\n  if (port.face === 'front') {\n    return 'front';\n  }\n  return isPowerPortType(port.type) ? 'rear' : 'front';\n}\n\nfunction resolveRenderPortGroupName(port: DevicePort, face: DeviceFace): string {\n  if (typeof port.group === 'string' && port.group.trim().length > 0) {\n    return port.group.trim();\n  }\n  if (isPowerPortType(port.type)) {\n    return DEFAULT_POWER_GROUP_NAME;\n  }\n  return face === 'rear' ? DEFAULT_REAR_GROUP_NAME : DEFAULT_FRONT_GROUP_NAME;\n}\n\nfunction getEntrySortValue(entry: IndexedPort): number {\n  return typeof entry.port.order === 'number' && Number.isFinite(entry.port.order)\n    ? entry.port.order\n    : entry.index;\n}\n\nfunction toDistributedOffsetPercent(index: number, count: number): number {\n  if (count <= 1) {\n    return 50;\n  }\n  return (\n    DISTRIBUTED_OFFSET_MIN_PERCENT +\n    (index * (DISTRIBUTED_OFFSET_MAX_PERCENT - DISTRIBUTED_OFFSET_MIN_PERCENT)) / (count - 1)\n  );\n}\n\nfunction buildDistributedOffsetMap(entries: IndexedPort[]): Map<number, number> {\n  const distributedEntries = entries.filter((entry) => {\n    const anchor = normalizePortAnchor(entry.position?.side);\n    return !!anchor && !hasExplicitAbsoluteCoordinates(entry.position) && !hasExplicitOffset(entry.position);\n  });\n  const offsetPercentByIndex = new Map<number, number>();\n  const entriesByAnchor = new Map<NonNullable<PortPosition['side']>, IndexedPort[]>();\n\n  distributedEntries.forEach((entry) => {\n    const anchor = normalizePortAnchor(entry.position?.side);\n    if (!anchor) {\n      return;\n    }\n    const group = entriesByAnchor.get(anchor);\n    if (group) {\n      group.push(entry);\n      return;\n    }\n    entriesByAnchor.set(anchor, [entry]);\n  });\n\n  entriesByAnchor.forEach((anchorEntries) => {\n    const sortedEntries = [...anchorEntries].sort((left, right) => {\n      const orderDifference = getEntrySortValue(left) - getEntrySortValue(right);\n      if (orderDifference !== 0) {\n        return orderDifference;\n      }\n      return left.index - right.index;\n    });\n    sortedEntries.forEach((entry, index) => {\n      offsetPercentByIndex.set(entry.index, toDistributedOffsetPercent(index, sortedEntries.length));\n    });\n  });\n\n  return offsetPercentByIndex;\n}\n\nfunction toPortPosition(\n  normalizedPosition: PortPosition | undefined,\n  distributedOffsetPercent?: number,\n): PortPosition | undefined {\n  const anchor = normalizePortAnchor(normalizedPosition?.side);\n  if (hasExplicitAbsoluteCoordinates(normalizedPosition)) {\n    const position: PortPosition = {\n      xPercent: clampPercent(normalizedPosition?.xPercent as number),\n      yPercent: clampPercent(normalizedPosition?.yPercent as number),\n    };\n    if (typeof normalizedPosition?.widthPercent === 'number' && Number.isFinite(normalizedPosition.widthPercent)) {\n      position.widthPercent = clampPercent(normalizedPosition.widthPercent);\n    }\n    if (typeof normalizedPosition?.heightPercent === 'number' && Number.isFinite(normalizedPosition.heightPercent)) {\n      position.heightPercent = clampPercent(normalizedPosition.heightPercent);\n    }\n    if (anchor) {\n      position.side = anchor;\n    }\n    return position;\n  }\n\n  if (!anchor) {\n    return undefined;\n  }\n\n  const position: PortPosition = { side: anchor };\n  if (typeof normalizedPosition?.offsetPx === 'number' && Number.isFinite(normalizedPosition.offsetPx)) {\n    position.offsetPx = normalizedPosition.offsetPx;\n    return position;\n  }\n\n  const explicitOffsetPercent =\n    typeof normalizedPosition?.offsetPercent === 'number' && Number.isFinite(normalizedPosition.offsetPercent)\n      ? clampPercent(normalizedPosition.offsetPercent)\n      : undefined;\n  const resolvedOffsetPercent =\n    explicitOffsetPercent ?? (typeof distributedOffsetPercent === 'number'\n      ? clampPercent(distributedOffsetPercent)\n      : undefined);\n\n  if (typeof resolvedOffsetPercent === 'number') {\n    position.offsetPercent = resolvedOffsetPercent;\n  }\n\n  return position;\n}\n\nfunction toPort(entry: IndexedPort, distributedOffsetPercent?: number): Port {\n  const position = toPortPosition(entry.position, distributedOffsetPercent);\n  return {\n    id: entry.port.id,\n    label: entry.port.label,\n    sourceTypeLabel: entry.port.sourceTypeLabel,\n    connected: entry.port.connected ?? false,\n    type: entry.port.type,\n    cableType: entry.port.cableType,\n    connectorType: entry.port.connectorType,\n    compatibility: Array.isArray(entry.port.compatibility) ? [...entry.port.compatibility] : undefined,\n    ...(position ? { position } : {}),\n  };\n}\n\nfunction normalizePortBlockCollection(portBlocks: PortBlock[]): PortBlock[] {\n  const normalizedBlocks = portBlocks.map((block) => ({\n    ...block,\n    ports: block.ports.map((port) => {\n      const position = getNormalizedPortPosition(port.position);\n      return {\n        ...port,\n        ...(position ? { position } : { position: undefined }),\n      };\n    }),\n  }));\n\n  // A side without an offset is an automatic placement request. Distribute it\n  // across the whole face collection rather than restarting inside each block.\n  const portsByUnpositionedSide = new Map<\n    NonNullable<PortPosition['side']>,\n    Port[]\n  >();\n  normalizedBlocks.forEach((block) => {\n    block.ports.forEach((port) => {\n      const position = port.position as PortPosition | undefined;\n      const side = normalizePortAnchor(position?.side);\n      if (\n        !side ||\n        hasExplicitAbsoluteCoordinates(position) ||\n        hasExplicitOffset(position)\n      ) {\n        return;\n      }\n      const ports = portsByUnpositionedSide.get(side);\n      if (ports) {\n        ports.push(port);\n      } else {\n        portsByUnpositionedSide.set(side, [port]);\n      }\n    });\n  });\n\n  // These are owned output ports from the normalization pass above. Fill in\n  // automatic offsets in place instead of cloning every block a second time.\n  portsByUnpositionedSide.forEach((ports) => {\n    ports.forEach((port, index) => {\n      port.position = {\n        ...(port.position as PortPosition),\n        offsetPercent: toDistributedOffsetPercent(index, ports.length),\n      };\n    });\n  });\n\n  return normalizedBlocks;\n}\n\nfunction isGroupedPortBlockArray(value: unknown): value is PortBlock[] {\n  return (\n    Array.isArray(value) &&\n    value.length > 0 &&\n    value.every(\n      (entry) =>\n        entry !== null &&\n        typeof entry === 'object' &&\n        typeof (entry as PortBlock).name === 'string' &&\n        Array.isArray((entry as PortBlock).ports),\n    )\n  );\n}\n\nfunction isFlatDevicePortArray(value: unknown): value is DevicePort[] {\n  return (\n    Array.isArray(value) &&\n    value.length > 0 &&\n    value.every(\n      (entry) =>\n        entry !== null &&\n        typeof entry === 'object' &&\n        typeof (entry as DevicePort).label === 'string' &&\n        !(\n          typeof (entry as { name?: unknown }).name === 'string' &&\n          Array.isArray((entry as { ports?: unknown }).ports)\n        ),\n    )\n  );\n}\n\n/**\n * Normalize flat `DevicePort[]` input into explicit front/rear grouped blocks.\n * This is the bridge between the recommended simple port schema and the\n * lower-level grouped renderer input.\n * @public\n */\nexport function buildPortBlocksFromPorts(\n  devicePorts: DevicePort[],\n): { ports: PortBlock[]; rearPorts: PortBlock[] } {\n  const bucketsByKey = new Map<string, GroupedPortBucket>();\n  // Normalize each accepted port once. The placement, grouping and output\n  // passes share this representation rather than repeatedly parsing aliases.\n  const indexedPorts: IndexedPort[] = [];\n  devicePorts.forEach((port, index) => {\n    if (typeof port.label !== 'string' || port.label.trim().length === 0) return;\n    indexedPorts.push({\n      index,\n      port: { ...port, label: port.label.trim() },\n      position: getNormalizedPortPosition(port.position),\n    });\n  });\n  const distributedOffsetsByFace = {\n    front: buildDistributedOffsetMap(\n      indexedPorts.filter(({ port }) => resolveRenderPortFace(port) === 'front'),\n    ),\n    rear: buildDistributedOffsetMap(\n      indexedPorts.filter(({ port }) => resolveRenderPortFace(port) === 'rear'),\n    ),\n  };\n\n  indexedPorts.forEach((entry) => {\n    const face = resolveRenderPortFace(entry.port);\n    const groupName = resolveRenderPortGroupName(entry.port, face);\n    const key = `${face}:${groupName}`;\n    const existingBucket = bucketsByKey.get(key);\n    if (existingBucket) {\n      existingBucket.entries.push(entry);\n    } else {\n      bucketsByKey.set(key, { face, groupName, entries: [entry] });\n    }\n  });\n\n  const ports: PortBlock[] = [];\n  const rearPorts: PortBlock[] = [];\n\n  bucketsByKey.forEach((bucket) => {\n    const sortedEntries = [...bucket.entries].sort((left, right) => {\n      const orderDifference = getEntrySortValue(left) - getEntrySortValue(right);\n      if (orderDifference !== 0) {\n        return orderDifference;\n      }\n      return left.index - right.index;\n    });\n    const distributedOffsetByIndex = distributedOffsetsByFace[bucket.face];\n    const block: PortBlock = {\n      name: bucket.groupName,\n      ports: sortedEntries.map((entry) =>\n        toPort(entry, distributedOffsetByIndex.get(entry.index)),\n      ),\n    };\n    if (bucket.face === 'rear') {\n      rearPorts.push(block);\n      return;\n    }\n    ports.push(block);\n  });\n\n  return { ports, rearPorts };\n}\n\n/**\n * Node-data subset accepted by {@link getResolvedPortBlocks}: a flat\n * `DevicePort[]` on `ports`, grouped `PortBlock[]` on\n * `ports`/`portGroups`/`rearPortGroups`, or a `rearPorts` block fallback.\n * @public\n */\nexport type ResolvedPortBlockInput = Pick<\n  NetworkNode['data'],\n  'ports' | 'portGroups' | 'rearPortGroups'\n> & {\n  rearPorts?: PortBlock[];\n};\n\n/**\n * Resolve the grouped front/rear port blocks from any supported input form:\n * flat `ports: DevicePort[]`, grouped `PortBlock[]`, or explicit\n * `portGroups` / `rearPortGroups`.\n * @public\n */\nexport function getResolvedPortBlocks(\n  data: ResolvedPortBlockInput | undefined,\n): { ports: PortBlock[]; rearPorts: PortBlock[] } {\n  if (isGroupedPortBlockArray(data?.ports)) {\n    return {\n      ports: normalizePortBlockCollection(data.ports),\n      rearPorts: normalizePortBlockCollection(\n        Array.isArray(data?.rearPortGroups)\n          ? data.rearPortGroups\n          : isGroupedPortBlockArray(data?.rearPorts)\n            ? data.rearPorts\n            : [],\n      ),\n    };\n  }\n\n  if (isFlatDevicePortArray(data?.ports)) {\n    return buildPortBlocksFromPorts(data.ports);\n  }\n\n  return {\n    ports: normalizePortBlockCollection(Array.isArray(data?.portGroups) ? data.portGroups : []),\n    rearPorts: normalizePortBlockCollection(\n      Array.isArray(data?.rearPortGroups)\n        ? data.rearPortGroups\n        : isGroupedPortBlockArray(data?.rearPorts)\n          ? data.rearPorts\n          : [],\n    ),\n  };\n}\n\n/**\n * Resolve the front-facing grouped port blocks for a node/device data object.\n * @public\n */\nexport function getFrontPortBlocks(\n  data: Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'> | undefined,\n): PortBlock[] {\n  return getResolvedPortBlocks(data).ports;\n}\n\n/**\n * Resolve the rear-facing grouped port blocks for a node/device data object.\n * @public\n */\nexport function getRearPortBlocks(\n  data: Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'> | undefined,\n): PortBlock[] {\n  return getResolvedPortBlocks(data).rearPorts;\n}\n\nexport function countPortBlocks(portBlocks: PortBlock[] | undefined): number {\n  if (!Array.isArray(portBlocks)) {\n    return 0;\n  }\n\n  return portBlocks.reduce((total, block) => {\n    return total + (Array.isArray(block.ports) ? block.ports.length : 0);\n  }, 0);\n}\n\n/**\n * Determine whether a node/device data object resolves to any front ports.\n * @public\n */\nexport function hasFrontPorts(\n  data: Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'> | undefined,\n): boolean {\n  return countPortBlocks(getResolvedPortBlocks(data).ports) > 0;\n}\n\n/**\n * Determine whether a node/device data object resolves to any rear ports.\n * @public\n */\nexport function hasRearPorts(\n  data: Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'> | undefined,\n): boolean {\n  return countPortBlocks(getResolvedPortBlocks(data).rearPorts) > 0;\n}\n\n/**\n * Choose the default device face based on the resolved front/rear port data.\n * @public\n */\nexport function getDefaultDeviceFace(\n  data: Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'> | undefined,\n): DeviceFace {\n  const hasFront = hasFrontPorts(data);\n  const hasRear = hasRearPorts(data);\n  return hasRear && !hasFront ? 'rear' : 'front';\n}\n","import type {\n  DevicePort,\n  DiagramDocument,\n  DiagramEdge,\n  DiagramEdgeEndpoint,\n  DiagramEndpoint,\n  DiagramExternalRef,\n  DiagramNode,\n  DiagramNodeKind,\n  DiagramNodePlacement,\n  DiagramNodeRender,\n  DiagramUiState,\n  NetworkEdge,\n  NetworkNode,\n  NetworkNodeType,\n  Port,\n  PortBlock,\n  PortPosition,\n  PortPositionInput,\n  PortType,\n} from '../types';\nimport {\n  applyFaceAliasHandleId,\n  buildBreakerWayHandleId,\n  buildPortHandleId,\n  isPowerPortType,\n  normalizeDirectionalHandleId,\n  normalizeHandleId,\n  parseHandleId,\n} from './handle-ids';\nimport { normalizePortPositionInput } from './port-position';\nimport { getPDUPortType } from './power-connectors';\nimport { getResolvedPortBlocks } from './render-ports';\n\nexport interface NetworkGraphSnapshot {\n  nodes: NetworkNode[];\n  edges: NetworkEdge[];\n}\n\n/**\n * Hard ceiling on how many power outlets a PDU may allocate. A physical PDU has\n * at most a few dozen outlets; this generous cap keeps a hostile persisted\n * `powerPorts` (e.g. `2_000_000_000`) from triggering an unbounded\n * `Array.from({ length })` allocation on either the serialize path here or the\n * client render in {@link file://../components/VerticalPDU.tsx}. Both sinks\n * share this single constant.\n */\nexport const MAX_POWER_PORTS = 256;\n\n/**\n * Lenient input accepted by {@link diagramDocumentToNetworkGraph}. Everything is\n * optional so partial / legacy documents degrade gracefully instead of throwing:\n * a missing `version` is treated as legacy v0 (identical to `1`), and missing\n * `nodes`/`edges` default to empty. A full {@link DiagramDocument} is assignable.\n */\nexport type DiagramDocumentInput = {\n  version?: number;\n  nodes?: DiagramNode[];\n  edges?: DiagramEdge[];\n  ui?: DiagramUiState;\n  metadata?: Record<string, unknown>;\n};\n\nconst KNOWN_DIAGRAM_NODE_KINDS: ReadonlySet<DiagramNodeKind> = new Set<DiagramNodeKind>([\n  'rack',\n  'equipment',\n  'pdu',\n  'distribution-board',\n  'fiber',\n  'tube',\n  'cable',\n  'multi-tube-cable',\n  'splice',\n  'coupler',\n  'splitter',\n  'container',\n  'fibre-flow-cable',\n  'fibre-flow-closure',\n]);\n\nconst CONTAINER_SUBTYPES = new Set(['patch-panel', 'closure', 'splice-tray']);\nconst IMAGE_RENDER_KEYS = [\n  'frontImageUrl',\n  'rearImageUrl',\n  'frontImageAttribution',\n  'rearImageAttribution',\n  'imageFit',\n  'imagePosition',\n  'imageRepeat',\n  'stretchToFit',\n] as const;\nconst DIMENSION_RENDER_KEYS = [\n  'panelWidth',\n  'panelHeight',\n  'closureWidth',\n  'closureHeight',\n  'headerHeight',\n] as const;\nconst GENERAL_RENDER_KEYS = [\n  'status',\n  'width',\n  'widthPx',\n  'height',\n  'header',\n  'color',\n  'colorMode',\n  'showUMarkers',\n  'customHeaderText',\n  'powerSide',\n  'rotation',\n  'rackWidthPx',\n  'rackFaceGapPx',\n] as const;\nconst UI_NODE_STATE_KEYS = ['face', 'handleSideMap'] as const;\nconst EXCLUDED_CONFIG_KEYS = new Set<string>([\n  'label',\n  'ports',\n  'portGroups',\n  'rearPortGroups',\n  // `rearPorts` is a documented alias for `rearPortGroups` (resolved by\n  // getResolvedPortBlocks). Exclude it too, else a rearPorts-only node leaks its\n  // whole port array into the opaque config blob.\n  'rearPorts',\n  'pduPortGroups',\n  'connections',\n  'netboxId',\n  'netboxType',\n  'inventoryId',\n  'inventoryType',\n  'uPosition',\n  'mountMode',\n  'rackLayout',\n  'slotPosition',\n  'holderPosition',\n  // Derived on the reverse pass from placement.rack; never re-persist it into\n  // config or round-trips accumulate a stale copy.\n  'isRackMounted',\n  ...IMAGE_RENDER_KEYS,\n  ...DIMENSION_RENDER_KEYS,\n  ...GENERAL_RENDER_KEYS,\n  ...UI_NODE_STATE_KEYS,\n]);\n\n/**\n * Assign an own, enumerable property — safe for keys such as `__proto__` that\n * plain assignment would treat as a prototype mutation.\n */\nfunction setOwn(target: Record<string, unknown>, key: string, value: unknown): void {\n  Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction clonePosition(position: { x?: number; y?: number } | undefined): { x: number; y: number } {\n  // Number.isFinite rejects NaN/Infinity (which typeof reports as 'number'),\n  // so a malformed coordinate degrades to 0 instead of corrupting layout.\n  return {\n    x: Number.isFinite(position?.x) ? (position!.x as number) : 0,\n    y: Number.isFinite(position?.y) ? (position!.y as number) : 0,\n  };\n}\n\nfunction clonePortPosition(position: PortPositionInput | undefined): PortPosition | undefined {\n  const normalized = normalizePortPositionInput(position);\n  if (!normalized) {\n    return undefined;\n  }\n\n  return { ...normalized };\n}\n\nfunction toDiagramNodeKind(node: NetworkNode): { kind: DiagramNodeKind; subtype?: string } {\n  if (node.type === 'vertical-pdu') {\n    return { kind: 'pdu' };\n  }\n\n  if (node.type === 'server' || node.type === 'switch' || node.type === 'router' || node.type === 'device') {\n    // `subtype` carries the runtime node TYPE so the reverse pass restores it\n    // exactly. The free-text `data.deviceType` (often a vendor model, e.g.\n    // `Catalyst 9300`) travels in `config` like any other data field — using it\n    // as the subtype made a `switch` with a model name come back as `device`.\n    return { kind: 'equipment', subtype: node.type };\n  }\n\n  if (node.type === 'patch-panel' || node.type === 'closure' || node.type === 'splice-tray') {\n    return { kind: 'container', subtype: node.type };\n  }\n\n  if (node.type === 'fibre-split') {\n    return { kind: 'splitter' };\n  }\n\n  if (node.type === 'fibre-flow-cable' || node.type === 'fibre-flow-closure') {\n    return { kind: node.type };\n  }\n\n  return { kind: node.type as DiagramNodeKind };\n}\n\nfunction toLegacyNodeType(node: DiagramNode): NetworkNodeType {\n  if (node.kind === 'equipment') {\n    const normalizedSubtype = (node.subtype ?? 'device').toLowerCase().replace(/_/g, '-');\n    if (normalizedSubtype === 'server' || normalizedSubtype === 'switch' || normalizedSubtype === 'router') {\n      return normalizedSubtype;\n    }\n    return 'device';\n  }\n\n  if (node.kind === 'pdu') {\n    return 'vertical-pdu';\n  }\n\n  if (node.kind === 'container') {\n    const subtype = node.subtype ?? 'patch-panel';\n    if (CONTAINER_SUBTYPES.has(subtype)) {\n      return subtype as NetworkNodeType;\n    }\n    return 'patch-panel';\n  }\n\n  if (node.kind === 'splitter') {\n    return 'fibre-split';\n  }\n\n  return node.kind as NetworkNodeType;\n}\n\nfunction toDiagramPlacement(node: NetworkNode): DiagramNodePlacement {\n  const placement: DiagramNodePlacement = {\n    position: clonePosition(node.position),\n  };\n\n  const parentId = node.parentId;\n  if (parentId) {\n    placement.parentId = parentId;\n  }\n  if (node.extent === 'parent') {\n    placement.extent = 'parent';\n  }\n\n  const data = node.data ?? {};\n  if (\n    typeof data.uPosition === 'number' ||\n    data.mountMode === 'front' ||\n    data.mountMode === 'rear' ||\n    data.mountMode === 'both' ||\n    data.rackLayout === 'single-face' ||\n    data.rackLayout === 'split-face'\n  ) {\n    placement.rack = {\n      ...(typeof data.uPosition === 'number' ? { uPosition: data.uPosition } : {}),\n      ...(data.mountMode ? { mountMode: data.mountMode } : {}),\n      ...(data.rackLayout ? { rackLayout: data.rackLayout } : {}),\n      ...(typeof data.rackWidthPx === 'number' ? { rackWidthPx: data.rackWidthPx } : {}),\n      ...(typeof data.rackFaceGapPx === 'number' ? { rackFaceGapPx: data.rackFaceGapPx } : {}),\n    };\n  }\n\n  if (typeof data.slotPosition === 'number' || typeof data.holderPosition === 'number') {\n    placement.container = {\n      ...(typeof data.slotPosition === 'number' ? { slotPosition: data.slotPosition } : {}),\n      ...(typeof data.holderPosition === 'number' ? { holderPosition: data.holderPosition } : {}),\n    };\n  }\n\n  return placement;\n}\n\nfunction toDiagramRender(node: NetworkNode): DiagramNodeRender | undefined {\n  const data = node.data ?? {};\n  const render: DiagramNodeRender = {};\n\n  GENERAL_RENDER_KEYS.forEach((key) => {\n    const value = data[key];\n    if (value !== undefined) {\n      (render as Record<string, unknown>)[key] = value;\n    }\n  });\n\n  const images = IMAGE_RENDER_KEYS.reduce<Record<string, unknown>>((acc, key) => {\n    const value = data[key];\n    if (value !== undefined) {\n      acc[key] = value;\n    }\n    return acc;\n  }, {});\n  if (Object.keys(images).length > 0) {\n    render.images = images as DiagramNodeRender['images'];\n  }\n\n  const dimensions = DIMENSION_RENDER_KEYS.reduce<Record<string, number | undefined>>((acc, key) => {\n    const value = data[key];\n    if (typeof value === 'number') {\n      acc[key] = value;\n    }\n    return acc;\n  }, {});\n  if (Object.keys(dimensions).length > 0) {\n    render.dimensions = dimensions;\n  }\n\n  return Object.keys(render).length > 0 ? render : undefined;\n}\n\nfunction toDiagramExternalRefs(node: NetworkNode): DiagramExternalRef[] | undefined {\n  const refs: DiagramExternalRef[] = [];\n  const data = node.data ?? {};\n\n  if (data.netboxId !== undefined) {\n    refs.push({\n      source: 'netbox',\n      id: data.netboxId,\n      type: data.netboxType,\n    });\n  }\n\n  if (data.inventoryId !== undefined) {\n    refs.push({\n      source: 'inventory',\n      id: data.inventoryId,\n      type: data.inventoryType,\n    });\n  }\n\n  return refs.length > 0 ? refs : undefined;\n}\n\nfunction toDiagramConfig(node: NetworkNode): Record<string, unknown> | undefined {\n  const data = node.data ?? {};\n  const config: Record<string, unknown> = {};\n\n  Object.entries(data).forEach(([key, value]) => {\n    // `ports` is excluded because port ARRAYS serialize as endpoints. A numeric\n    // `ports` (the rack header's port count) has no endpoint form, so it is\n    // kept as plain config instead of being dropped.\n    if (EXCLUDED_CONFIG_KEYS.has(key) && !(key === 'ports' && !Array.isArray(value))) {\n      return;\n    }\n    if ((key === 'rackWidthPx' || key === 'rackFaceGapPx') && node.type !== 'rack') {\n      return;\n    }\n    setOwn(config, key, value);\n  });\n\n  // Equipment without a device type is normalised to `deviceType = node.type`,\n  // the value the reverse pass has always filled in. Writing it here keeps\n  // re-serialisation idempotent (graph → doc → graph → doc is stable).\n  if (\n    (node.type === 'server' || node.type === 'switch' || node.type === 'router' || node.type === 'device') &&\n    !(typeof config.deviceType === 'string' && config.deviceType.trim().length > 0)\n  ) {\n    config.deviceType = node.type;\n  }\n\n  return Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction extractUiState(nodes: NetworkNode[], previousUi?: DiagramUiState): DiagramUiState | undefined {\n  const nodeState: NonNullable<DiagramUiState['nodeState']> = {};\n\n  nodes.forEach((node) => {\n    const data = node.data ?? {};\n    const entry: NonNullable<DiagramUiState['nodeState']>[string] = {};\n    const resolvedFace =\n      data.face === 'front' || data.face === 'rear'\n        ? data.face\n        : undefined;\n\n    if (resolvedFace) {\n      entry.face = resolvedFace;\n    }\n    if (data.handleSideMap && typeof data.handleSideMap === 'object') {\n      entry.handleSideMap = { ...data.handleSideMap };\n    }\n\n    if (Object.keys(entry).length > 0) {\n      setOwn(nodeState, node.id, entry);\n    }\n  });\n\n  if (Object.keys(nodeState).length === 0 && !previousUi?.metadata) {\n    return undefined;\n  }\n\n  return {\n    ...(Object.keys(nodeState).length > 0 ? { nodeState } : {}),\n    ...(previousUi?.metadata ? { metadata: previousUi.metadata } : {}),\n  };\n}\n\nfunction inferEndpointMedium(portType: string | undefined): DiagramEndpoint['medium'] {\n  const normalizedType = String(portType ?? '').toLowerCase();\n  if (normalizedType.includes('power')) return 'power';\n  if (normalizedType.includes('fiber')) return 'fiber';\n  if (normalizedType.includes('console') || normalizedType.includes('mgmt') || normalizedType.includes('usb')) {\n    return 'generic';\n  }\n  if (normalizedType.includes('ethernet') || normalizedType.includes('rj45')) return 'ethernet';\n  return 'generic';\n}\n\nfunction buildPduPortBlocks(node: NetworkNode): PortBlock[] {\n  const data = node.data ?? {};\n  if (Array.isArray(data.pduPortGroups) && data.pduPortGroups.length > 0) {\n    return data.pduPortGroups;\n  }\n\n  const { ports, rearPorts } = getResolvedPortBlocks(data);\n  if (rearPorts.length > 0) {\n    return rearPorts;\n  }\n  if (ports.length > 0) {\n    return ports;\n  }\n\n  const powerPorts = Math.min(\n    MAX_POWER_PORTS,\n    typeof data.powerPorts === 'number' && Number.isFinite(data.powerPorts) && data.powerPorts > 0\n      ? data.powerPorts\n      : 36\n  );\n\n  return [\n    {\n      name: 'Power',\n      ports: Array.from({ length: powerPorts }, (_, index) => {\n        const connectorType = getPDUPortType(index);\n        return {\n          id: `${index + 1}`,\n          label: `${index + 1}`,\n          connected: false,\n          type: connectorType === 'C19' ? 'power_c19' : 'power_c13',\n          connectorType: connectorType as Port['connectorType'],\n        };\n      }),\n    },\n  ];\n}\n\nfunction toPortBasedEndpoints(node: NetworkNode): DiagramEndpoint[] {\n  // Read the resolved blocks directly. Endpoint construction below already\n  // clones compatibility and normalized layout, so an intermediate deep clone\n  // of every block/port only duplicates work on each document round trip.\n  const blocksByFace: Array<{ face: 'front' | 'rear'; blocks: PortBlock[] }> =\n    node.type === 'vertical-pdu'\n      ? [{ face: 'rear', blocks: buildPduPortBlocks(node) }]\n      : (() => {\n          const { ports, rearPorts } = getResolvedPortBlocks(node.data);\n          return [\n            { face: 'front' as const, blocks: ports },\n            { face: 'rear' as const, blocks: rearPorts },\n          ];\n        })();\n\n  const endpoints: DiagramEndpoint[] = blocksByFace.flatMap(({ face, blocks }) =>\n    blocks.flatMap((block) =>\n      block.ports.map((port) => ({\n        // Mint the endpoint id through the same grammar the renderer mounts\n        // handles with ({@link buildPortHandleId}): an explicit `port.id`\n        // verbatim, a bare label for power ports, else a percent-escaped\n        // `<group>-<label>` join. Edges persist\n        // `endpointId = normalizeHandleId(edge.sourceHandle)` — i.e. this exact\n        // builder output — so a raw `${block.name}-${port.label}` join would\n        // diverge for any label/group containing `-` or `%` (or a label that\n        // collides with a fixed grammar shape, e.g. group `fiber` + label `3`),\n        // and the edge would dangle after a document round-trip. Building the id\n        // the same way keeps persisted endpoint ids and edge endpointIds equal\n        // by construction.\n        id: buildPortHandleId(block.name, port),\n        label: port.label,\n        face,\n        group: block.name,\n        role: isPowerPortType(port.type) ? 'power' : 'network',\n        medium: inferEndpointMedium(port.type),\n        type: port.type,\n        cableType: port.cableType,\n        connector: port.connectorType,\n        sourceTypeLabel: port.sourceTypeLabel,\n        compatibility: Array.isArray(port.compatibility) ? [...port.compatibility] : undefined,\n        layout: clonePortPosition(port.position),\n      }))\n    )\n  );\n\n  // A front and a rear port that resolve to the same handle id (same explicit\n  // `port.id`, or same group + label on both faces) are disambiguated by the\n  // renderer with the split-face alias: the rear handle mounts as\n  // `<id>--rear`. Mint the rear endpoint with that same alias so endpoint ids\n  // stay unique and an edge on the rear port keeps pointing at the rear port\n  // after a round trip (instead of collapsing onto the front one).\n  const frontIds = new Set(\n    endpoints.filter((endpoint) => endpoint.face === 'front').map((endpoint) => endpoint.id)\n  );\n  if (frontIds.size > 0) {\n    for (const endpoint of endpoints) {\n      if (endpoint.face === 'rear' && frontIds.has(endpoint.id)) {\n        endpoint.id = applyFaceAliasHandleId(endpoint.id, 'rear');\n      }\n    }\n  }\n  return endpoints;\n}\n\n/**\n * Whether `endpoint` is the rear twin of a front endpoint, minted by\n * {@link toPortBasedEndpoints} as `<frontId>--rear`. Returns the shared base id\n * (what the port's own `id` is) when it is, else `undefined`.\n */\nfunction rearAliasBase(\n  endpoint: DiagramEndpoint,\n  frontIds: ReadonlySet<string>\n): string | undefined {\n  if (endpoint.face !== 'rear' || !endpoint.id.endsWith('--rear')) return undefined;\n  const base = endpoint.id.slice(0, -'--rear'.length);\n  return frontIds.has(base) ? base : undefined;\n}\n\nfunction collectFrontEndpointIds(endpoints: readonly DiagramEndpoint[]): Set<string> {\n  return new Set(\n    endpoints.filter((endpoint) => endpoint.face !== 'rear').map((endpoint) => endpoint.id)\n  );\n}\n\nfunction createSyntheticEndpoints(node: NetworkNode): DiagramEndpoint[] {\n  const data = node.data ?? {};\n\n  if (node.type === 'distribution-board') {\n    const ways = Array.isArray(data.ways) ? data.ways : [];\n    return ways\n      .filter((way): way is { id: string; label?: string } => !!way && typeof way.id === 'string')\n      .map((way) => ({\n        id: buildBreakerWayHandleId(way.id),\n        label: typeof way.label === 'string' ? way.label : way.id,\n        role: 'power' as const,\n        medium: 'power' as const,\n      }));\n  }\n\n  if (node.type === 'tube') {\n    const fibersPerTube = Math.max(1, Math.min(24, Number(data.fibersPerTube) || 12));\n    const tubeIndex = Math.max(1, Number(data.tubeIndex) || 1);\n    return [\n      ...Array.from({ length: fibersPerTube }, (_, index) => ({\n        id: `fiber-${index + 1}`,\n        label: `${index + 1}`,\n        role: 'fiber' as const,\n        medium: 'fiber' as const,\n      })),\n      {\n        id: `tube-${tubeIndex}`,\n        label: `Tube ${tubeIndex}`,\n        role: 'tube',\n        medium: 'tube',\n      },\n    ];\n  }\n\n  if (node.type === 'cable') {\n    const fiberCount = Math.max(1, Math.min(24, Number(data.fiberCount) || 12));\n    return [\n      ...Array.from({ length: fiberCount }, (_, index) => ({\n        id: `fiber-${index + 1}`,\n        label: `${index + 1}`,\n        role: 'fiber' as const,\n        medium: 'fiber' as const,\n      })),\n      {\n        id: 'cable-button',\n        label: 'Cable',\n        role: 'cable',\n      },\n    ];\n  }\n\n  if (node.type === 'multi-tube-cable') {\n    const tubeCount = Math.max(1, Math.min(24, Number(data.tubeCount) || 12));\n    const tubeStartIndex = Math.max(1, Number(data.tubeStartIndex) || 1);\n    return [\n      ...Array.from({ length: tubeCount }, (_, index) => ({\n        id: `tube-${tubeStartIndex + index}`,\n        label: `${tubeStartIndex + index}`,\n        role: 'tube' as const,\n        medium: 'tube' as const,\n      })),\n      {\n        id: 'cable-button',\n        label: 'Cable',\n        role: 'cable',\n      },\n    ];\n  }\n\n  if (node.type === 'splice') {\n    return [\n      { id: 'left', label: 'Left', role: 'splice', medium: 'fiber' },\n      { id: 'right', label: 'Right', role: 'splice', medium: 'fiber' },\n    ];\n  }\n\n  if (node.type === 'coupler') {\n    const portCount = typeof data.couplerFiberCount === 'number' && [1, 2, 4].includes(data.couplerFiberCount)\n      ? data.couplerFiberCount\n      : 2;\n    return [\n      ...Array.from({ length: portCount }, (_, index) => ({\n        id: `left-${index + 1}`,\n        label: `${index + 1}`,\n        role: 'coupler' as const,\n        medium: 'fiber' as const,\n      })),\n      ...Array.from({ length: portCount }, (_, index) => ({\n        id: `right-${index + 1}`,\n        label: `${index + 1}`,\n        role: 'coupler' as const,\n        medium: 'fiber' as const,\n      })),\n    ];\n  }\n\n  if (node.type === 'fibre-split') {\n    const inputCount = Math.max(1, Math.min(2, Number(data.inputCount) || 1));\n    const outputCount = Math.max(1, Math.min(64, Number(data.outputCount) || 8));\n    return [\n      ...Array.from({ length: inputCount }, (_, index) => ({\n        id: `input-${index + 1}`,\n        label: `Input ${index + 1}`,\n        role: 'splitter' as const,\n        medium: 'fiber' as const,\n      })),\n      ...Array.from({ length: outputCount }, (_, index) => ({\n        id: `output-${index + 1}`,\n        label: `Output ${index + 1}`,\n        role: 'splitter' as const,\n        medium: 'fiber' as const,\n      })),\n    ];\n  }\n\n  return [];\n}\n\nfunction toDiagramEndpoints(node: NetworkNode): DiagramEndpoint[] | undefined {\n  const endpoints = node.type === 'vertical-pdu' || Array.isArray(node.data?.ports) || Array.isArray(node.data?.portGroups) || Array.isArray(node.data?.rearPortGroups) || Array.isArray(node.data?.rearPorts) || Array.isArray(node.data?.pduPortGroups)\n    ? toPortBasedEndpoints(node)\n    : createSyntheticEndpoints(node);\n\n  return endpoints.length > 0 ? endpoints : undefined;\n}\n\n/**\n * `node.data` fields the endpoint derivation reads. The cache fingerprints the\n * identity (and, for arrays, the length) of each, so replacing or growing a\n * port collection — even on the same `data` object — invalidates the entry.\n */\nconst ENDPOINT_SOURCE_KEYS = [\n  'ports',\n  'portGroups',\n  'rearPortGroups',\n  'rearPorts',\n  'pduPortGroups',\n  'powerPorts',\n  'ways',\n  'fibersPerTube',\n  'tubeIndex',\n  'fiberCount',\n  'tubeCount',\n  'tubeStartIndex',\n  'couplerFiberCount',\n  'inputCount',\n  'outputCount',\n] as const;\n\nfunction endpointSourceFingerprint(data: Record<string, unknown>): unknown[] {\n  const fingerprint: unknown[] = [];\n  for (const key of ENDPOINT_SOURCE_KEYS) {\n    const value = data[key];\n    fingerprint.push(value, Array.isArray(value) ? value.length : -1);\n  }\n  return fingerprint;\n}\n\nfunction sameFingerprint(a: readonly unknown[], b: readonly unknown[]): boolean {\n  if (a.length !== b.length) return false;\n  for (let i = 0; i < a.length; i += 1) {\n    if (!Object.is(a[i], b[i])) return false;\n  }\n  return true;\n}\n\n/**\n * One derivation of a node's endpoints, cached against the `node.data` object\n * that produced it. `nodeId`/`nodeType` are stored alongside so a data object\n * shared by two different nodes (a hazard, not the norm) is detected and\n * recomputed rather than aliased, and `fingerprint` catches an in-place\n * replacement of a port collection on the same data object.\n */\ninterface EndpointDerivation {\n  nodeId: string;\n  nodeType: NetworkNodeType;\n  fingerprint: unknown[];\n  endpoints: DiagramEndpoint[] | undefined;\n}\n\n/**\n * Module-scoped memo of {@link toDiagramEndpoints}, keyed on `node.data`\n * identity. A position-only drag reframes `node.position` but keeps `node.data`\n * referentially stable, so the (potentially large) endpoint derivation runs\n * once and later frames only copy the cached records. Keys are consumer\n * `node.data` objects, so entries die with them (no unbounded growth).\n *\n * Limitation: editing a port object's fields in place (e.g. `port.label = …`\n * on the same port object inside the same array) is not detected — replace\n * the port, its array, or `data` (the React immutable-update convention).\n */\nconst endpointDerivationCache = new WeakMap<object, EndpointDerivation>();\n\n/** Detached copy of an endpoint list so callers can never mutate the cache. */\nfunction copyEndpoints(endpoints: DiagramEndpoint[] | undefined): DiagramEndpoint[] | undefined {\n  return endpoints?.map((endpoint) => ({\n    ...endpoint,\n    ...(endpoint.compatibility ? { compatibility: [...endpoint.compatibility] } : {}),\n    ...(endpoint.layout ? { layout: { ...endpoint.layout } } : {}),\n  }));\n}\n\n/**\n * Memoized {@link toDiagramEndpoints}. Recomputes on any data-identity change,\n * on a port-collection change detected by the fingerprint, and when the same\n * data object surfaces under a different node id/type. Always returns a fresh\n * copy, so a consumer mutating an emitted document cannot corrupt later ones.\n * Nodes with no object `data` bypass the cache (nothing stable to key on).\n */\nfunction toDiagramEndpointsCached(node: NetworkNode): DiagramEndpoint[] | undefined {\n  const data: unknown = node.data;\n  if (!data || typeof data !== 'object') {\n    return toDiagramEndpoints(node);\n  }\n\n  const fingerprint = endpointSourceFingerprint(data as Record<string, unknown>);\n  const cached = endpointDerivationCache.get(data);\n  if (\n    cached &&\n    cached.nodeId === node.id &&\n    cached.nodeType === node.type &&\n    sameFingerprint(cached.fingerprint, fingerprint)\n  ) {\n    return copyEndpoints(cached.endpoints);\n  }\n\n  const endpoints = toDiagramEndpoints(node);\n  endpointDerivationCache.set(data, { nodeId: node.id, nodeType: node.type, fingerprint, endpoints });\n  return copyEndpoints(endpoints);\n}\n\nfunction mergeEndpoints(\n  previousEndpoints: DiagramEndpoint[] | undefined,\n  nextEndpoints: DiagramEndpoint[] | undefined\n): DiagramEndpoint[] | undefined {\n  if (!nextEndpoints || nextEndpoints.length === 0) {\n    return nextEndpoints;\n  }\n\n  const previousById = new Map((previousEndpoints ?? []).map((endpoint) => [endpoint.id, endpoint]));\n  return nextEndpoints.map((endpoint) => {\n    const previous = previousById.get(endpoint.id);\n    if (!previous) {\n      return endpoint;\n    }\n\n    return {\n      ...previous,\n      ...endpoint,\n      compatibility: endpoint.compatibility ?? previous.compatibility,\n      layout: endpoint.layout ?? previous.layout,\n    };\n  });\n}\n\nfunction mergeDocumentNode(previousNode: DiagramNode | undefined, nextNode: DiagramNode): DiagramNode {\n  if (!previousNode) {\n    return nextNode;\n  }\n\n  return {\n    ...previousNode,\n    ...nextNode,\n    placement: nextNode.placement,\n    render: nextNode.render,\n    config: nextNode.config,\n    externalRefs: nextNode.externalRefs,\n    metadata: nextNode.metadata ?? previousNode.metadata,\n    endpoints: mergeEndpoints(previousNode.endpoints, nextNode.endpoints),\n  };\n}\n\n/**\n * Convert a runtime {@link NetworkGraphSnapshot} into the canonical, persistable\n * {@link DiagramDocument}. The emitted document always carries `version: 1`.\n *\n * Container kinds (`patch-panel`/`closure`/`splice-tray`) serialize their ports\n * as face-tagged `endpoints`, which {@link diagramDocumentToNetworkGraph}\n * reconstructs back into `portGroups`/`rearPortGroups`.\n */\nexport function networkGraphToDiagramDocument(\n  snapshot: NetworkGraphSnapshot,\n  previousDocument?: DiagramDocument\n): DiagramDocument {\n  const previousNodesById = new Map((previousDocument?.nodes ?? []).map((node) => [node.id, node]));\n\n  const nodes = snapshot.nodes.map((node) =>\n    mergeDocumentNode(previousNodesById.get(node.id), {\n      id: node.id,\n      ...toDiagramNodeKind(node),\n      label: node.data?.label ?? node.id,\n      placement: toDiagramPlacement(node),\n      render: toDiagramRender(node),\n      config: toDiagramConfig(node),\n      externalRefs: toDiagramExternalRefs(node),\n      metadata: previousNodesById.get(node.id)?.metadata,\n      endpoints: toDiagramEndpointsCached(node),\n    })\n  );\n\n  // Per node: the endpoint ids that exist on both faces, i.e. whose rear twin\n  // was minted as `<id>--rear` (see toPortBasedEndpoints).\n  const aliasedIdsByNodeId = new Map<string, Set<string>>();\n  nodes.forEach((node) => {\n    const endpoints = node.endpoints ?? [];\n    const frontIds = collectFrontEndpointIds(endpoints);\n    const aliased = new Set<string>();\n    for (const endpoint of endpoints) {\n      const base = rearAliasBase(endpoint, frontIds);\n      if (base !== undefined) aliased.add(base);\n    }\n    if (aliased.size > 0) aliasedIdsByNodeId.set(node.id, aliased);\n  });\n\n  /**\n   * Endpoint id an edge handle persists as: the handle with its directional\n   * suffix and split-face alias stripped — except a `--rear` alias on a port id\n   * that exists on both faces, which names the rear twin endpoint.\n   */\n  const toEndpointId = (nodeId: string, handle: string | null | undefined): string | undefined => {\n    if (!handle) return undefined;\n    const normalized = normalizeHandleId(handle);\n    const aliased = aliasedIdsByNodeId.get(nodeId);\n    if (aliased?.has(normalized) && parseHandleId(normalizeDirectionalHandleId(handle)).faceAlias === 'rear') {\n      return applyFaceAliasHandleId(normalized, 'rear');\n    }\n    return normalized;\n  };\n\n  const previousEdgesById = new Map((previousDocument?.edges ?? []).map((edge) => [edge.id, edge]));\n  const edges = snapshot.edges.map((edge) => {\n    const nextEdge: DiagramEdge = {\n      id: edge.id,\n      source: {\n        nodeId: edge.source,\n        endpointId: toEndpointId(edge.source, edge.sourceHandle),\n      },\n      target: {\n        nodeId: edge.target,\n        endpointId: toEndpointId(edge.target, edge.targetHandle),\n      },\n      type: edge.type,\n      hidden: edge.hidden === true ? true : undefined,\n      deletable: typeof edge.deletable === 'boolean' ? edge.deletable : undefined,\n      interactionWidth:\n        typeof edge.interactionWidth === 'number' && Number.isFinite(edge.interactionWidth)\n          ? edge.interactionWidth\n          : undefined,\n      data: edge.data ? { ...edge.data } : undefined,\n      metadata: previousEdgesById.get(edge.id)?.metadata,\n    };\n\n    const previous = previousEdgesById.get(edge.id);\n    return previous ? { ...previous, ...nextEdge } : nextEdge;\n  });\n\n  return {\n    version: 1,\n    nodes,\n    edges,\n    ui: extractUiState(snapshot.nodes, previousDocument?.ui),\n    metadata: previousDocument?.metadata,\n  };\n}\n\n/**\n * Port type for a rebuilt port. An endpoint's own `type` wins. Without one,\n * only power endpoints get an inferred type (their connector family drives\n * power semantics); every other untyped endpoint stays untyped — inventing\n * `ethernet` changed the port's icon and compatibility on every load and made\n * re-serialisation non-idempotent.\n */\nfunction endpointToPortType(endpoint: DiagramEndpoint): PortType | undefined {\n  if (endpoint.type) {\n    return endpoint.type;\n  }\n\n  if (endpoint.role === 'power' || endpoint.medium === 'power') {\n    const connector = String(endpoint.connector ?? '').toLowerCase();\n    if (connector.includes('c19') || connector.includes('c20')) return 'power_c19';\n    if (connector.includes('c13') || connector.includes('c14')) return 'power_c13';\n    return 'power_ac';\n  }\n\n  // Legacy hand-authored documents that name a medium but no type.\n  if (endpoint.medium === 'fiber') return 'fiber';\n  if (endpoint.medium === 'ethernet') return 'ethernet';\n  return undefined;\n}\n\n/** Shared per-port fields of a rebuilt {@link DevicePort} / {@link Port}. */\nfunction endpointPortFields(\n  endpoint: DiagramEndpoint,\n  frontIds: ReadonlySet<string>,\n  connectedEndpointIds: ReadonlySet<string>\n) {\n  const type = endpointToPortType(endpoint);\n  return {\n    // The rear twin of a both-faces port id was persisted as `<id>--rear`; the\n    // port itself keeps the shared id (the renderer re-applies the alias).\n    id: rearAliasBase(endpoint, frontIds) ?? endpoint.id,\n    label: endpoint.label,\n    connected: connectedEndpointIds.has(endpoint.id),\n    ...(type ? { type } : {}),\n    cableType: endpoint.cableType,\n    connectorType: endpoint.connector as DevicePort['connectorType'],\n    sourceTypeLabel: endpoint.sourceTypeLabel,\n    compatibility:\n      endpoint.compatibility && endpoint.compatibility.length > 0\n        ? [...endpoint.compatibility]\n        : undefined,\n    position: clonePortPosition(endpoint.layout),\n  };\n}\n\nfunction endpointsToDevicePorts(\n  endpoints: DiagramEndpoint[],\n  frontIds: ReadonlySet<string>,\n  connectedEndpointIds: ReadonlySet<string>\n): DevicePort[] {\n  return endpoints.map((endpoint) => {\n    const fields = endpointPortFields(endpoint, frontIds, connectedEndpointIds);\n    return {\n      ...fields,\n      face: endpoint.face === 'rear' ? 'rear' : endpoint.face === 'front' ? 'front' : undefined,\n      group: endpoint.group,\n    } as DevicePort;\n  });\n}\n\nfunction endpointsToPortBlocks(\n  endpoints: DiagramEndpoint[],\n  frontIds: ReadonlySet<string>,\n  connectedEndpointIds: ReadonlySet<string>\n): PortBlock[] {\n  const grouped = new Map<string, Port[]>();\n\n  endpoints.forEach((endpoint) => {\n    const groupName = endpoint.group ?? 'Ports';\n    const port = endpointPortFields(endpoint, frontIds, connectedEndpointIds) as Port;\n    const existing = grouped.get(groupName);\n    if (existing) {\n      existing.push(port);\n      return;\n    }\n    grouped.set(groupName, [port]);\n  });\n\n  return [...grouped.entries()].map(([name, ports]) => ({ name, ports }));\n}\n\nfunction applyRenderToLegacyData(\n  data: NetworkNode['data'],\n  render: DiagramNodeRender | undefined,\n  uiEntry: NonNullable<DiagramUiState['nodeState']>[string] | undefined\n): NetworkNode['data'] {\n  if (!render && !uiEntry) {\n    return data;\n  }\n\n  const nextData = { ...data };\n  if (render) {\n    GENERAL_RENDER_KEYS.forEach((key) => {\n      const value = render[key];\n      if (value !== undefined) {\n        (nextData as Record<string, unknown>)[key] = value;\n      }\n    });\n\n    if (render.images) {\n      IMAGE_RENDER_KEYS.forEach((key) => {\n        const value = render.images?.[key];\n        if (value !== undefined) {\n          (nextData as Record<string, unknown>)[key] = value;\n        }\n      });\n    }\n\n    if (render.dimensions) {\n      DIMENSION_RENDER_KEYS.forEach((key) => {\n        const value = render.dimensions?.[key];\n        if (typeof value === 'number') {\n          (nextData as Record<string, unknown>)[key] = value;\n        }\n      });\n    }\n  }\n\n  const resolvedFace = uiEntry?.face;\n  if (resolvedFace) {\n    nextData.face = resolvedFace;\n  }\n  if (uiEntry?.handleSideMap) {\n    nextData.handleSideMap = { ...uiEntry.handleSideMap };\n  }\n\n  return nextData;\n}\n\nfunction applyExternalRefsToLegacyData(data: NetworkNode['data'], refs: DiagramExternalRef[] | undefined): NetworkNode['data'] {\n  if (!refs || refs.length === 0) {\n    return data;\n  }\n\n  const nextData = { ...data };\n  refs.forEach((ref) => {\n    if (ref.source === 'netbox') {\n      nextData.netboxId = ref.id;\n      if (ref.type) {\n        nextData.netboxType = ref.type;\n      }\n    }\n    if (ref.source === 'inventory') {\n      nextData.inventoryId = ref.id;\n      if (ref.type) {\n        nextData.inventoryType = ref.type;\n      }\n    }\n  });\n\n  return nextData;\n}\n\n/**\n * Build a per-node `endpointId -> endpoint` index for O(1) resolution. First\n * write wins so the lookup mirrors `Array.prototype.find` (first match) exactly;\n * nodes with no endpoints are omitted. Computing this once before the edge loop\n * turns {@link resolveEndpointHandleId} from an O(edges x endpoints) linear scan\n * into O(edges + endpoints).\n */\nfunction buildEndpointIndex(nodes: DiagramNode[]): Map<string, Map<string, DiagramEndpoint>> {\n  const index = new Map<string, Map<string, DiagramEndpoint>>();\n  for (const node of nodes) {\n    if (!node.endpoints || node.endpoints.length === 0) {\n      continue;\n    }\n    const byId = new Map<string, DiagramEndpoint>();\n    for (const endpoint of node.endpoints) {\n      if (!byId.has(endpoint.id)) {\n        byId.set(endpoint.id, endpoint);\n      }\n    }\n    index.set(node.id, byId);\n  }\n  return index;\n}\n\n/**\n * Resolve an edge's endpoint reference to the endpoint RECORD it names: by raw\n * id first (opaque ids, and the `<id>--rear` twin of a both-faces port), then\n * by the normalized id (a hand-authored edge may carry a directional/face\n * suffix the persisted endpoint id lacks).\n */\nfunction findEndpointRecord(\n  endpointId: string,\n  endpointById: Map<string, DiagramEndpoint> | undefined\n): DiagramEndpoint | undefined {\n  if (!endpointById) return undefined;\n  return endpointById.get(endpointId) ?? endpointById.get(normalizeHandleId(endpointId));\n}\n\n/**\n * The device type the renderer reads for an equipment node: `config.deviceType`\n * (what `DeviceNode` reads from `data.deviceType`), falling back to the subtype\n * for legacy/hand-authored documents that carried the device type there.\n */\nfunction effectiveEquipmentDeviceType(node: DiagramNode): string {\n  const configured = node.config?.deviceType;\n  const deviceType =\n    typeof configured === 'string' && configured.trim().length > 0 ? configured : node.subtype;\n  return (deviceType ?? '').toLowerCase().replace(/_/g, '-');\n}\n\n/**\n * Resolve a document edge's endpoint reference into the React Flow\n * `sourceHandle`/`targetHandle` the reconstructed port actually mounts.\n *\n * The endpoint {@link DiagramEndpoint.id} is treated as an *opaque* key, not a\n * handle-grammar string: the edge is resolved to its endpoint RECORD (via the\n * per-node index — {@link buildEndpointIndex}), and the handle base is the\n * record's own id, which is exactly what `endpointsToDevicePorts` /\n * `endpointsToPortBlocks` stamp onto `port.id` and therefore what\n * {@link buildPortHandleId} mounts (for the `<id>--rear` twin of a both-faces\n * port, the id IS the rear handle's split-face alias). Directional\n * (`-source`/`-target`) decisions are then derived from the record's\n * STRUCTURED fields (`face`/`role`/`medium`) plus the node's rendered device\n * type — never from the id text.\n *\n * This is the single resolution path for both id styles: opaque consumer keys\n * (uuid-like, including ones whose text coincidentally ends in a reserved\n * suffix) and the handle-grammar strings the shipped adapters mint round-trip\n * identically. When an edge references a missing endpoint, the base falls back\n * to the normalized edge id so a dangling legacy reference still resolves\n * byte-for-byte as before.\n */\nfunction resolveEndpointHandleId(\n  node: DiagramNode | undefined,\n  endpointId: string | null | undefined,\n  role: 'source' | 'target',\n  endpointById?: Map<string, DiagramEndpoint>\n): string | undefined {\n  if (!endpointId) {\n    return undefined;\n  }\n\n  const endpoint = endpointById\n    ? findEndpointRecord(endpointId, endpointById)\n    : node?.endpoints?.find(\n        (candidate) =>\n          candidate.id === endpointId || candidate.id === normalizeHandleId(endpointId)\n      );\n\n  // Base = the endpoint's own id (what the rebuilt port mounts its handle with),\n  // NOT the normalized edge string. Falls back to the normalized edge id only\n  // for a dangling reference with no matching endpoint.\n  const base = endpoint ? endpoint.id : normalizeHandleId(endpointId);\n\n  if (!node || node.kind !== 'equipment') {\n    return base;\n  }\n\n  const deviceType = effectiveEquipmentDeviceType(node);\n  const isPowerEndpoint = endpoint?.role === 'power' || endpoint?.medium === 'power';\n\n  if ((deviceType === 'switch' || deviceType === 'router') && role === 'target' && !isPowerEndpoint) {\n    return `${base}-target`;\n  }\n\n  if (deviceType === 'patch-panel') {\n    const face = endpoint?.face === 'rear' ? 'rear' : 'front';\n    if (role === 'source' && face === 'front') {\n      return `${base}-source`;\n    }\n    if (role === 'target' && face === 'rear') {\n      return `${base}-target`;\n    }\n  }\n\n  return base;\n}\n\n/**\n * Resolve a document node's placement defensively. Missing placement or a\n * missing/partial position degrades to `{ x: 0, y: 0 }` (with a warning) instead\n * of throwing on a partial document.\n */\nfunction resolveNodePlacement(node: DiagramNode): DiagramNodePlacement {\n  const placement = isRecord(node.placement) ? (node.placement as DiagramNodePlacement) : undefined;\n  if (!placement) {\n    console.warn(\n      `[react-networks] Document node \"${node.id}\" is missing placement; defaulting position to { x: 0, y: 0 }.`\n    );\n    return { position: { x: 0, y: 0 } };\n  }\n  const position = placement.position;\n  // Number.isFinite (not typeof === 'number') so NaN/Infinity coordinates from a\n  // malformed document are rejected rather than corrupting React Flow layout.\n  if (!position || !Number.isFinite(position.x) || !Number.isFinite(position.y)) {\n    console.warn(\n      `[react-networks] Document node \"${node.id}\" is missing a valid placement.position; defaulting to { x: 0, y: 0 }.`\n    );\n    return { ...placement, position: clonePosition(position) };\n  }\n  return placement;\n}\n\n/**\n * Coerce untrusted document nodes: drop non-record entries and entries without\n * a string id or a known kind (with a warning each), drop malformed endpoint\n * entries, and keep the first node for a duplicated id.\n */\nfunction sanitizeDocumentNodes(rawNodes: unknown): DiagramNode[] {\n  if (rawNodes === undefined || rawNodes === null) return [];\n  if (!Array.isArray(rawNodes)) {\n    console.warn('[react-networks] DiagramDocument `nodes` is not an array; treating it as empty.');\n    return [];\n  }\n  const out: DiagramNode[] = [];\n  const seenIds = new Set<string>();\n  rawNodes.forEach((raw, index) => {\n    if (!isRecord(raw) || typeof raw.id !== 'string' || raw.id.length === 0) {\n      console.warn(`[react-networks] Skipping malformed document node at index ${index} (not an object with a string id).`);\n      return;\n    }\n    const node = raw as unknown as DiagramNode;\n    if (!KNOWN_DIAGRAM_NODE_KINDS.has(node.kind)) {\n      console.warn(\n        `[react-networks] Skipping document node \"${node.id}\" with unknown kind \"${String(node.kind)}\".`\n      );\n      return;\n    }\n    if (seenIds.has(node.id)) {\n      console.warn(`[react-networks] Skipping duplicate document node id \"${node.id}\".`);\n      return;\n    }\n    seenIds.add(node.id);\n    const endpoints = Array.isArray(node.endpoints)\n      ? node.endpoints.filter(\n          (endpoint): endpoint is DiagramEndpoint =>\n            isRecord(endpoint) && typeof endpoint.id === 'string' && endpoint.id.length > 0\n        )\n      : undefined;\n    const label = typeof node.label === 'string' ? node.label : node.id;\n    out.push({\n      ...node,\n      label,\n      config: isRecord(node.config) ? node.config : undefined,\n      endpoints: endpoints && endpoints.length > 0 ? endpoints : undefined,\n      externalRefs: Array.isArray(node.externalRefs)\n        ? node.externalRefs.filter((ref): ref is DiagramExternalRef => isRecord(ref))\n        : undefined,\n    });\n  });\n  return out;\n}\n\nfunction isEdgeEnd(value: unknown): value is DiagramEdgeEndpoint {\n  return isRecord(value) && typeof value.nodeId === 'string' && value.nodeId.length > 0;\n}\n\n/**\n * Convert a persisted {@link DiagramDocument} (or a lenient\n * {@link DiagramDocumentInput}) back into a runtime graph.\n *\n * Untrusted input never throws: malformed nodes/endpoints/edges are skipped\n * with a `console.warn`, and edges whose source or target node is absent from\n * the produced graph (unknown kind, malformed, or simply missing) are dropped\n * instead of being emitted dangling.\n */\nexport function diagramDocumentToNetworkGraph(document: DiagramDocumentInput): NetworkGraphSnapshot {\n  const input: DiagramDocumentInput = isRecord(document) ? document : {};\n  if (typeof input.version === 'number' && input.version > 1) {\n    console.warn(\n      `[react-networks] Unknown DiagramDocument version ${input.version}; parsing best-effort as version 1.`\n    );\n  }\n\n  const documentNodes = sanitizeDocumentNodes(input.nodes);\n  const rawEdges: unknown[] = Array.isArray(input.edges) ? input.edges : [];\n  if (input.edges !== undefined && input.edges !== null && !Array.isArray(input.edges)) {\n    console.warn('[react-networks] DiagramDocument `edges` is not an array; treating it as empty.');\n  }\n  const documentEdges = rawEdges.filter((edge, index): edge is DiagramEdge => {\n    if (isRecord(edge) && typeof edge.id === 'string' && isEdgeEnd(edge.source) && isEdgeEnd(edge.target)) {\n      return true;\n    }\n    console.warn(\n      `[react-networks] Skipping malformed document edge at index ${index} (needs an id and source/target node ids).`\n    );\n    return false;\n  });\n\n  const nodesById = new Map(documentNodes.map((node) => [node.id, node]));\n  const endpointsByNodeId = buildEndpointIndex(documentNodes);\n\n  // Mark the endpoint RECORDS edges land on, so a both-faces port's front and\n  // rear twins are tracked independently.\n  const connectedByNodeId = new Map<string, Set<string>>();\n  const markConnected = (ref: DiagramEdgeEndpoint) => {\n    if (!ref.endpointId) return;\n    const record = findEndpointRecord(ref.endpointId, endpointsByNodeId.get(ref.nodeId));\n    if (!record) return;\n    let set = connectedByNodeId.get(ref.nodeId);\n    if (!set) {\n      set = new Set();\n      connectedByNodeId.set(ref.nodeId, set);\n    }\n    set.add(record.id);\n  };\n  documentEdges.forEach((edge) => {\n    markConnected(edge.source);\n    markConnected(edge.target);\n  });\n\n  const uiNodeState = isRecord(input.ui?.nodeState) ? input.ui!.nodeState! : {};\n  const noConnections: ReadonlySet<string> = new Set();\n\n  const nodes: NetworkNode[] = [];\n  for (const node of documentNodes) {\n    const placement = resolveNodePlacement(node);\n    const legacyType = toLegacyNodeType(node);\n    const connected = connectedByNodeId.get(node.id) ?? noConnections;\n    let data: NetworkNode['data'] = {\n      label: node.label,\n      ...(node.config ?? {}),\n    } as NetworkNode['data'];\n\n    const uiEntry = Object.prototype.hasOwnProperty.call(uiNodeState, node.id)\n      ? uiNodeState[node.id]\n      : undefined;\n    data = applyRenderToLegacyData(data, isRecord(node.render) ? node.render : undefined, isRecord(uiEntry) ? uiEntry : undefined);\n    data = applyExternalRefsToLegacyData(data, node.externalRefs);\n\n    if (legacyType === 'device' || legacyType === 'server' || legacyType === 'switch' || legacyType === 'router') {\n      // `config.deviceType` (already spread above) is authoritative. Legacy\n      // documents carried the device type in `subtype` instead.\n      if (typeof data.deviceType !== 'string' || data.deviceType.trim().length === 0) {\n        data.deviceType = node.subtype ?? (legacyType === 'device' ? 'device' : legacyType);\n      }\n      if (node.endpoints && node.endpoints.length > 0) {\n        data.ports = endpointsToDevicePorts(node.endpoints, collectFrontEndpointIds(node.endpoints), connected);\n      }\n    } else if (legacyType === 'vertical-pdu') {\n      const rearEndpoints = (node.endpoints ?? []).filter((endpoint) => endpoint.face !== 'front');\n      if (rearEndpoints.length > 0) {\n        data.pduPortGroups = endpointsToPortBlocks(rearEndpoints, noConnections, connected);\n        data.powerPorts = rearEndpoints.length;\n      }\n    } else if (legacyType === 'patch-panel' || legacyType === 'closure' || legacyType === 'splice-tray') {\n      // Mirror the forward pass, which serializes container ports as face-tagged\n      // endpoints, by reconstructing front -> portGroups and rear -> rearPortGroups.\n      const endpoints = node.endpoints ?? [];\n      const frontIds = collectFrontEndpointIds(endpoints);\n      const frontEndpoints = endpoints.filter((endpoint) => endpoint.face !== 'rear');\n      const rearEndpoints = endpoints.filter((endpoint) => endpoint.face === 'rear');\n      if (frontEndpoints.length > 0) {\n        data.portGroups = endpointsToPortBlocks(frontEndpoints, frontIds, connected);\n      }\n      if (rearEndpoints.length > 0) {\n        data.rearPortGroups = endpointsToPortBlocks(rearEndpoints, frontIds, connected);\n      }\n    }\n\n    if (placement.rack) {\n      if (placement.rack.rackLayout) {\n        data.rackLayout = placement.rack.rackLayout;\n      }\n      if (typeof placement.rack.rackWidthPx === 'number') {\n        data.rackWidthPx = placement.rack.rackWidthPx;\n      }\n      if (typeof placement.rack.rackFaceGapPx === 'number') {\n        data.rackFaceGapPx = placement.rack.rackFaceGapPx;\n      }\n    }\n\n    if (legacyType !== 'rack' && placement.rack) {\n      data.isRackMounted = true;\n      if (typeof placement.rack.uPosition === 'number') {\n        data.uPosition = placement.rack.uPosition;\n      }\n      if (placement.rack.mountMode) {\n        data.mountMode = placement.rack.mountMode;\n      }\n    }\n\n    if (placement.container) {\n      if (typeof placement.container.slotPosition === 'number') {\n        data.slotPosition = placement.container.slotPosition;\n      }\n      if (typeof placement.container.holderPosition === 'number') {\n        data.holderPosition = placement.container.holderPosition;\n      }\n    }\n\n    nodes.push({\n      id: node.id,\n      type: legacyType,\n      position: clonePosition(placement.position),\n      ...(placement.parentId ? { parentId: placement.parentId } : {}),\n      ...(placement.extent ? { extent: placement.extent } : {}),\n      data,\n    } as NetworkNode);\n  }\n\n  const producedNodeIds = new Set(nodes.map((node) => node.id));\n  const danglingEdgeIds: string[] = [];\n  const edges: NetworkEdge[] = [];\n  for (const edge of documentEdges) {\n    if (!producedNodeIds.has(edge.source.nodeId) || !producedNodeIds.has(edge.target.nodeId)) {\n      danglingEdgeIds.push(edge.id);\n      continue;\n    }\n    edges.push({\n      id: edge.id,\n      source: edge.source.nodeId,\n      target: edge.target.nodeId,\n      ...(edge.source.endpointId ? { sourceHandle: resolveEndpointHandleId(nodesById.get(edge.source.nodeId), edge.source.endpointId, 'source', endpointsByNodeId.get(edge.source.nodeId)) } : {}),\n      ...(edge.target.endpointId ? { targetHandle: resolveEndpointHandleId(nodesById.get(edge.target.nodeId), edge.target.endpointId, 'target', endpointsByNodeId.get(edge.target.nodeId)) } : {}),\n      ...(edge.type ? { type: edge.type } : {}),\n      ...(edge.hidden === true ? { hidden: true } : {}),\n      ...(typeof edge.deletable === 'boolean' ? { deletable: edge.deletable } : {}),\n      ...(typeof edge.interactionWidth === 'number' && Number.isFinite(edge.interactionWidth)\n        ? { interactionWidth: edge.interactionWidth }\n        : {}),\n      ...(isRecord(edge.data) ? { data: { ...edge.data } } : {}),\n    });\n  }\n  if (danglingEdgeIds.length > 0) {\n    console.warn(\n      `[react-networks] Dropped ${danglingEdgeIds.length} document edge(s) whose source or target node is not in the document: ${danglingEdgeIds\n        .slice(0, 5)\n        .map((id) => `\"${id}\"`)\n        .join(', ')}${danglingEdgeIds.length > 5 ? ', …' : ''}.`\n    );\n  }\n\n  return { nodes, edges };\n}\n","import type { DeviceFace, NetworkNode, Port, PortBlock } from '../types';\nimport {\n  buildPortHandleId,\n  isPowerInletHandleId,\n  isPowerPortType,\n  looksLikePowerHandleId,\n  normalizeDirectionalHandleId,\n  parseHandleId,\n} from './handle-ids';\nimport { MAX_POWER_PORTS } from './document-adapter';\nimport {\n  getPduOutletConnectorType,\n  inferConnectorFromPortType,\n} from './power-connectors';\nimport { getResolvedPortBlocks } from './render-ports';\n\nexport type EndpointConnectionKind = 'power' | 'network' | 'unknown';\n\n/**\n * The electrical part a power endpoint plays. Real installations have two\n * different kinds of power connection, and conflating them is the mistake this\n * vocabulary exists to prevent:\n *\n * - a **branch circuit** is fixed wiring from a distribution-board breaker way\n *   (`circuit-source`) to a PDU/board supply inlet (`circuit-sink`);\n * - a **cord** is a flexible IEC 60320 lead from a PDU outlet or device power\n *   outlet (`cord-source`) to a device power inlet (`cord-sink`).\n *\n * A way must never land on a device inlet or a PDU outlet, and a cord must\n * never land on a PDU inlet. Endpoints the model cannot place resolve to\n * `unknown`, and rules skip them rather than guessing.\n * @public\n */\nexport type PowerEndpointRole =\n  | 'circuit-source'\n  | 'circuit-sink'\n  | 'cord-source'\n  | 'cord-sink'\n  | 'unknown';\n\nexport interface EndpointMetadata {\n  normalizedHandle: string;\n  face?: DeviceFace;\n  kind: EndpointConnectionKind;\n  compatibility: string[];\n  /**\n   * The endpoint's electrical part, when it is a power endpoint. `unknown` for\n   * network endpoints and for power endpoints the model cannot place.\n   */\n  powerRole: PowerEndpointRole;\n  /** Resolved port `type` (e.g. `power_c13`, `power_outlet`), when known. */\n  portType?: string;\n  /**\n   * Resolved connector designation (e.g. `C13`, `C14`, `C19`, `C20`), when\n   * known. Resolved in the renderer's own precedence, so a rule always reasons\n   * about the connector that was drawn:\n   *\n   * 1. the port's explicit `connectorType`;\n   * 2. the designation embedded in its `type` (`power_c13` → `C13`), via\n   *    `inferConnectorFromPortType`;\n   * 3. for a vertical-PDU outlet only, the strip's `data.portTypes` at that\n   *    outlet position, falling back to the default 8×C13 + 2×C19 cycle.\n   */\n  connectorType?: string;\n  /**\n   * 1-based outlet position on a vertical PDU: the number a bare outlet handle\n   * names, or the port's index in the strip's declared port groups when the\n   * outlet carries an explicit id (`rear-power-1`). `undefined` on every other\n   * node type and for handles the strip does not declare.\n   */\n  outletPosition?: number;\n}\n\ninterface CachedHandleMetadata {\n  face?: DeviceFace;\n  kind: EndpointConnectionKind;\n  compatibility: string[];\n  portType?: string;\n  connectorType?: string;\n  /** 1-based position within a vertical PDU's declared outlets. */\n  position?: number;\n}\n\nconst handleMetadataCache = new WeakMap<object, Map<string, CachedHandleMetadata>>();\n\nconst warnedDuplicateHandleIds = new Set<string>();\n\n/**\n * Dev warning when two ports on one node resolve to the same handle id. The\n * first port's metadata is kept (see {@link buildHandleMetadataMap}); this\n * surfaces the ambiguity without spamming — at most once per id per process.\n */\nfunction warnDuplicateHandleId(handleId: string): void {\n  if (warnedDuplicateHandleIds.has(handleId)) {\n    return;\n  }\n  warnedDuplicateHandleIds.add(handleId);\n  console.warn(\n    `[react-networks] duplicate handle id ${JSON.stringify(handleId)} on a single node; ` +\n      \"keeping the first port's metadata. Give colliding ports distinct labels or \" +\n      'explicit ids (see buildPortHandleId).',\n  );\n}\n\nfunction normalizeCompatibilityToken(value: unknown): string | undefined {\n  if (typeof value !== 'string') {\n    return undefined;\n  }\n\n  const normalized = value.trim().toLowerCase().replace(/[\\s_]+/g, '-');\n  return normalized.length > 0 ? normalized : undefined;\n}\n\nfunction uniqueTokens(tokens: Array<string | undefined>): string[] {\n  const values = new Set<string>();\n  tokens.forEach((token) => {\n    if (token) {\n      values.add(token);\n    }\n  });\n  return [...values];\n}\n\nfunction inferPowerCompatibilityToken(...values: Array<unknown>): string | undefined {\n  const normalizedValues = values\n    .map((value) => normalizeCompatibilityToken(value))\n    .filter((value): value is string => !!value);\n\n  if (normalizedValues.some((value) => value.includes('c13'))) return 'c13';\n  if (normalizedValues.some((value) => value.includes('c14'))) return 'c14';\n  if (normalizedValues.some((value) => value.includes('c19'))) return 'c19';\n  if (normalizedValues.some((value) => value.includes('c20'))) return 'c20';\n  if (normalizedValues.some((value) => value.includes('ac'))) return 'ac';\n\n  return undefined;\n}\n\nfunction inferNetworkCompatibilityToken(input: {\n  type?: string;\n  cableType?: string;\n  connectorType?: string;\n  handleId?: string;\n}): string | undefined {\n  const candidates = [\n    normalizeCompatibilityToken(input.type),\n    normalizeCompatibilityToken(input.cableType),\n    normalizeCompatibilityToken(input.connectorType),\n    normalizeCompatibilityToken(input.handleId),\n  ].filter((value): value is string => !!value);\n\n  if (candidates.some((value) => value.includes('fiber') || value.includes('smf') || value.includes('mmf') || value.includes('qsfp') || value.includes('sfp') || value.includes('lc') || value.includes('sc'))) {\n    return 'fiber';\n  }\n\n  if (candidates.some((value) => value.includes('ethernet') || value.startsWith('eth') || value.includes('rj45') || value.includes('base-t'))) {\n    return 'ethernet';\n  }\n\n  return undefined;\n}\n\nexport function resolveEndpointCompatibilityKeys(input: {\n  compatibility?: string[];\n  kind?: EndpointConnectionKind;\n  type?: string;\n  cableType?: string;\n  connectorType?: string;\n  handleId?: string;\n}): string[] {\n  if (Array.isArray(input.compatibility) && input.compatibility.length > 0) {\n    return uniqueTokens(input.compatibility.map((value) => normalizeCompatibilityToken(value)));\n  }\n\n  if (input.kind === 'power' || isPowerPortType(input.type)) {\n    const connectorToken = inferPowerCompatibilityToken(input.connectorType, input.type, input.handleId);\n    return connectorToken ? [`power:${connectorToken}`] : ['power'];\n  }\n\n  if (input.kind === 'network') {\n    const mediumToken = inferNetworkCompatibilityToken(input);\n    return mediumToken ? [`network:${mediumToken}`] : ['network'];\n  }\n\n  const fallbackKind =\n    isPowerPortType(input.type)\n      ? 'power'\n      : inferNetworkCompatibilityToken(input)\n        ? 'network'\n        : undefined;\n\n  if (fallbackKind === 'power') {\n    const connectorToken = inferPowerCompatibilityToken(input.connectorType, input.type, input.handleId);\n    return connectorToken ? [`power:${connectorToken}`] : ['power'];\n  }\n\n  if (fallbackKind === 'network') {\n    const mediumToken = inferNetworkCompatibilityToken(input);\n    return mediumToken ? [`network:${mediumToken}`] : ['network'];\n  }\n\n  return [];\n}\n\nfunction getFallbackPduPortBlocks(node: NetworkNode): PortBlock[] {\n  const data = node.data ?? {};\n  const powerPortCount =\n    typeof data.powerPorts === 'number' && Number.isFinite(data.powerPorts) && data.powerPorts > 0\n      ? Math.min(MAX_POWER_PORTS, data.powerPorts)\n      : 36;\n\n  const portTypes = Array.isArray(data.portTypes) ? data.portTypes : undefined;\n\n  return [\n    {\n      name: 'Power',\n      ports: Array.from({ length: powerPortCount }, (_, index) => {\n        const defaultConnector = getPduOutletConnectorType(portTypes, index + 1);\n        return {\n          id: `${index + 1}`,\n          label: `${index + 1}`,\n          connected: false,\n          type: defaultConnector === 'C19' ? 'power_c19' : 'power_c13',\n          connectorType: defaultConnector as Port['connectorType'],\n        };\n      }),\n    },\n  ];\n}\n\nfunction getPduPortBlocks(node: NetworkNode): PortBlock[] {\n  const data = node.data ?? {};\n  if (Array.isArray(data.pduPortGroups) && data.pduPortGroups.length > 0) {\n    return data.pduPortGroups;\n  }\n\n  const { ports, rearPorts } = getResolvedPortBlocks(data);\n  if (rearPorts.length > 0) {\n    return rearPorts;\n  }\n  if (ports.length > 0) {\n    return ports;\n  }\n\n  return getFallbackPduPortBlocks(node);\n}\n\nfunction buildHandleMetadataMap(node?: NetworkNode): Map<string, CachedHandleMetadata> {\n  const data = node?.data;\n  if (!node || !data || typeof data !== 'object') {\n    return new Map();\n  }\n\n  const cached = handleMetadataCache.get(data as object);\n  if (cached) {\n    return cached;\n  }\n\n  const metadata = new Map<string, CachedHandleMetadata>();\n\n  // 1-based counter over a vertical PDU's declared outlets, in render order —\n  // the position the connector cycle and the phase attribution are indexed by.\n  let outletPosition = 0;\n\n  const addBlocks = (face: DeviceFace, blocks: PortBlock[], trackPosition = false) => {\n    blocks.forEach((block) => {\n      block.ports.forEach((port) => {\n        const position = trackPosition ? (outletPosition += 1) : undefined;\n        const handleId = buildPortHandleId(block.name, {\n          id: port.id,\n          label: port.label,\n          type: isPowerPortType(port.type) ? port.type : undefined,\n        });\n\n        // Collision safety: two ports can build the same handle id (e.g. the\n        // same group/label rendered on both faces, or an explicit port.id\n        // reused). Keep the FIRST entry deterministically instead of silently\n        // overwriting, and warn once per id so the ambiguity is diagnosable.\n        if (metadata.has(handleId)) {\n          warnDuplicateHandleId(handleId);\n          return;\n        }\n\n        metadata.set(handleId, {\n          face,\n          kind: isPowerPortType(port.type) ? 'power' : 'network',\n          compatibility: resolveEndpointCompatibilityKeys({\n            compatibility: port.compatibility,\n            kind: isPowerPortType(port.type) ? 'power' : 'network',\n            type: port.type,\n            cableType: port.cableType,\n            connectorType: port.connectorType,\n            handleId,\n          }),\n          portType: typeof port.type === 'string' ? port.type : undefined,\n          // A port that names no connector still names it in its `type`\n          // (`power_c13`), which the renderer honours before it falls back to\n          // the positional cycle — so resolve it here rather than dropping to\n          // the cycle and contradicting the drawn connector.\n          connectorType:\n            typeof port.connectorType === 'string' && port.connectorType.length > 0\n              ? port.connectorType\n              : inferConnectorFromPortType(port.type),\n          ...(position !== undefined ? { position } : {}),\n        });\n      });\n    });\n  };\n\n  if (node.type === 'vertical-pdu') {\n    addBlocks('rear', getPduPortBlocks(node), true);\n  } else {\n    const { ports, rearPorts } = getResolvedPortBlocks(data);\n    addBlocks('front', ports);\n    addBlocks('rear', rearPorts);\n  }\n\n  handleMetadataCache.set(data as object, metadata);\n  return metadata;\n}\n\nfunction inferHandleKind(handleId: string, node?: NetworkNode): EndpointConnectionKind {\n  if (!handleId) {\n    return 'unknown';\n  }\n\n  if (node?.type === 'vertical-pdu' || node?.type === 'distribution-board') {\n    return 'power';\n  }\n\n  if (looksLikePowerHandleId(handleId) || /^\\d+$/.test(handleId)) {\n    return 'power';\n  }\n\n  const lowerHandle = handleId.toLowerCase();\n  const isStandardNetworkHandle =\n    lowerHandle.startsWith('net-') ||\n    lowerHandle.startsWith('port-') ||\n    lowerHandle.startsWith('uplink-') ||\n    lowerHandle.includes('fiber') ||\n    lowerHandle.includes('ports');\n  const isCustomPortName =\n    /^[a-z0-9]+-\\d+$/i.test(handleId) &&\n    !lowerHandle.startsWith('pwr') &&\n    !lowerHandle.startsWith('power');\n\n  if (isStandardNetworkHandle || isCustomPortName) {\n    return 'network';\n  }\n\n  return 'unknown';\n}\n\n/** Bare 1-based outlet id on a vertical PDU (`\"1\"…\"N\"`). */\nconst PDU_OUTLET_HANDLE = /^\\d+$/;\n\n/**\n * The connector a PDU outlet at a 1-based `position` carries, honouring an\n * explicit `portTypes` sequence. Last resort only — a declared port's own\n * `connectorType`/`type` wins (see {@link EndpointMetadata.connectorType}).\n */\nfunction inferPduOutletConnectorType(\n  node: NetworkNode | undefined,\n  position: number\n): string {\n  const portTypes = node?.data?.portTypes;\n  return getPduOutletConnectorType(\n    Array.isArray(portTypes) ? portTypes : undefined,\n    position\n  );\n}\n\n/**\n * 1-based outlet position of a handle on a vertical PDU: the number a bare\n * outlet id names, or the port's index among the strip's declared ports when\n * the outlet carries an explicit id. `undefined` for anything else.\n */\nfunction resolveOutletPosition(\n  node: NetworkNode | undefined,\n  normalizedHandle: string,\n  handleMetadata?: CachedHandleMetadata\n): number | undefined {\n  if (node?.type !== 'vertical-pdu') return undefined;\n  if (handleMetadata?.position !== undefined) return handleMetadata.position;\n  return PDU_OUTLET_HANDLE.test(normalizedHandle) ? Number(normalizedHandle) : undefined;\n}\n\nfunction inferHandleCompatibility(\n  handleId: string,\n  kind: EndpointConnectionKind,\n  node?: NetworkNode\n): string[] {\n  if (!handleId) {\n    return [];\n  }\n\n  if (kind === 'power') {\n    // Breaker ways are hard-wired distribution points: they terminate in a\n    // conductor, not a connector, so they impose NO connector constraint.\n    // An empty compatibility list intersects with everything (see\n    // hasCompatibilityIntersection), which is exactly the intent — a way may\n    // feed a C13 PDU inlet, a C14 device inlet, or a busbar tap alike.\n    if (node?.type === 'distribution-board') {\n      return [];\n    }\n\n    if (node?.type === 'vertical-pdu' && PDU_OUTLET_HANDLE.test(handleId)) {\n      return resolveEndpointCompatibilityKeys({\n        kind,\n        connectorType: inferPduOutletConnectorType(node, Number(handleId)),\n        handleId,\n      });\n    }\n\n    return resolveEndpointCompatibilityKeys({\n      kind,\n      handleId,\n    });\n  }\n\n  if (kind === 'network') {\n    const lowerHandle = handleId.toLowerCase();\n    return resolveEndpointCompatibilityKeys({\n      kind,\n      handleId,\n      type:\n        lowerHandle.includes('fiber') || lowerHandle.includes('qsfp') || lowerHandle.includes('sfp')\n          ? 'fiber'\n          : lowerHandle.startsWith('eth') || lowerHandle.startsWith('net-') || lowerHandle.startsWith('port-')\n            ? 'ethernet'\n            : undefined,\n    });\n  }\n\n  return [];\n}\n\n/**\n * Node types that distribute power: their handles are ways, inlets and outlets\n * rather than equipment power inlets.\n */\nconst POWER_DISTRIBUTION_NODE_TYPES = new Set<string>([\n  'vertical-pdu',\n  'distribution-board',\n]);\n\n/** A device port that *supplies* a cord (NetBox's `power_outlet`). */\nfunction isPowerOutletPortType(portType: string | undefined): boolean {\n  return typeof portType === 'string' && /outlet/i.test(portType);\n}\n\n/**\n * Classify a power endpoint into the part it plays electrically. Pure and\n * total: an endpoint that cannot be placed returns `unknown`, which every\n * caller treats as \"skip\", never as \"invalid\".\n */\nfunction resolvePowerEndpointRole(\n  node: NetworkNode | undefined,\n  normalizedHandle: string,\n  kind: EndpointConnectionKind,\n  handleMetadata?: CachedHandleMetadata\n): PowerEndpointRole {\n  if (!normalizedHandle) return 'unknown';\n\n  // A breaker way is always a circuit source, whatever it is mounted on.\n  if (parseHandleId(normalizedHandle).shape.kind === 'breaker-way') {\n    return 'circuit-source';\n  }\n\n  if (kind !== 'power') return 'unknown';\n\n  const nodeType = node?.type;\n  if (nodeType && POWER_DISTRIBUTION_NODE_TYPES.has(nodeType)) {\n    if (nodeType === 'vertical-pdu') {\n      // Outlets: the bare 1-based ids, plus any port the strip declares\n      // through `pduPortGroups` / `rearPorts`. Anything else on a strip\n      // (an ad-hoc `OUT`, a management handle) stays unplaced.\n      //\n      // A declared port wins over the reserved inlet ids: a strip whose outlet\n      // is labelled `Inlet` mounts an outlet handle there, so classifying it as\n      // the supply inlet would make the canvas refuse the cords it actually\n      // carries. The reserved ids are documented on `VerticalPduNodeData`.\n      if (PDU_OUTLET_HANDLE.test(normalizedHandle)) return 'cord-source';\n      if (handleMetadata) return 'cord-source';\n    }\n    if (isPowerInletHandleId(normalizedHandle)) return 'circuit-sink';\n    return 'unknown';\n  }\n\n  // Equipment. A `power_outlet` port supplies a cord (a device acting as a\n  // power strip); every other power port on equipment is an inlet.\n  if (isPowerOutletPortType(handleMetadata?.portType)) return 'cord-source';\n  if (/(^|[-_/ ])outlet([-_/ ]|\\d|$)/i.test(normalizedHandle)) return 'cord-source';\n  if (isPowerPortType(handleMetadata?.portType)) return 'cord-sink';\n  if (/^pwr[-_]/i.test(normalizedHandle)) return 'cord-sink';\n  if (looksLikePowerHandleId(normalizedHandle)) return 'cord-sink';\n\n  return 'unknown';\n}\n\n/**\n * The electrical part a power endpoint plays — see {@link PowerEndpointRole}.\n *\n * Accepts any node-shaped value (`NetworkNode` and the validation engine's\n * looser `PowerGraphNode` both satisfy it) so the interactive connection\n * validator and the graph rules classify endpoints identically.\n * @public\n */\nexport function getPowerEndpointRole(\n  node: { type?: string; data?: unknown } | undefined,\n  handle: string | null | undefined\n): PowerEndpointRole {\n  return getEndpointMetadata(node as NetworkNode | undefined, handle).powerRole;\n}\n\nexport function getEndpointMetadata(\n  node?: NetworkNode,\n  handle?: string | null\n): EndpointMetadata {\n  const normalizedHandleWithDirection = normalizeDirectionalHandleId(handle);\n  if (!normalizedHandleWithDirection) {\n    return {\n      normalizedHandle: '',\n      kind: 'unknown',\n      compatibility: [],\n      powerRole: 'unknown',\n    };\n  }\n\n  // Face aliases (--rear/--front) carry the rendered face; parseHandleId\n  // strips them and reports which one was present.\n  const parsedHandle = parseHandleId(normalizedHandleWithDirection);\n  const normalizedHandle = parsedHandle.base;\n  const explicitFace: DeviceFace | undefined = parsedHandle.faceAlias;\n\n  const handleMetadata = buildHandleMetadataMap(node).get(normalizedHandle);\n  const data = node?.data ?? {};\n  const handleSideMap = (data as { handleSideMap?: Record<string, DeviceFace> }).handleSideMap;\n  const mappedFace = handleSideMap?.[normalizedHandle];\n\n  let face = mappedFace ?? explicitFace ?? handleMetadata?.face;\n  if (!face) {\n    if (normalizedHandle === 'net-top' || normalizedHandle === 'net-bottom') {\n      face = 'front';\n    } else if (\n      normalizedHandle === 'pwr-left-1' ||\n      normalizedHandle === 'pwr-left-2' ||\n      normalizedHandle === 'net-right-1' ||\n      normalizedHandle === 'net-right-2' ||\n      looksLikePowerHandleId(normalizedHandle)\n    ) {\n      face = 'rear';\n    }\n  }\n\n  const kind = handleMetadata?.kind ?? inferHandleKind(normalizedHandle, node);\n\n  const outletPosition = resolveOutletPosition(node, normalizedHandle, handleMetadata);\n\n  // Precedence: the port's own connectorType, then the designation in its\n  // `type`, then — outlets only — the strip's positional cycle.\n  const connectorType =\n    handleMetadata?.connectorType ??\n    (outletPosition !== undefined\n      ? inferPduOutletConnectorType(node, outletPosition)\n      : undefined);\n\n  return {\n    normalizedHandle,\n    face,\n    kind,\n    compatibility:\n      handleMetadata?.compatibility.length\n        ? handleMetadata.compatibility\n        : inferHandleCompatibility(normalizedHandle, kind, node),\n    powerRole: resolvePowerEndpointRole(node, normalizedHandle, kind, handleMetadata),\n    portType: handleMetadata?.portType,\n    connectorType,\n    outletPosition,\n  };\n}\n\n/**\n * 1-based outlet position of a vertical-PDU handle — see\n * {@link EndpointMetadata.outletPosition}. Accepts any node-shaped value so the\n * validation engine's looser `PowerGraphNode` resolves positions exactly as the\n * canvas does, including for strips whose outlets carry explicit ids.\n *\n * @internal Consumed by PWR-B08's phase attribution; not part of the public\n * entry surface.\n */\nexport function getPduOutletPosition(\n  node: { type?: string; data?: unknown } | undefined,\n  handle: string | null | undefined\n): number | undefined {\n  return getEndpointMetadata(node as NetworkNode | undefined, handle).outletPosition;\n}\n","/**\n * IEC 60320 coupler facts used by the connector rules (PWR-A05 / PWR-A06).\n *\n * Terminology (catalogue schema note): C14/C16/C20/C22 are **inlets** (male,\n * equipment side); C13/C15/C19/C21 are **connectors** (female, cord side). The\n * word \"plug\" is ambiguous and is not used.\n *\n * Why the table lives here and not in `utils/power-connectors.ts`:\n * `POWER_CONNECTORS` is the *rendering* table — it drives port geometry and\n * colour for the connector types the renderer can draw (C13/C14/C19/C20/AC).\n * The validation catalogue needs the high-temperature C15/C16 and C21/C22\n * families, which have no rendering entry yet, plus a directed mate table and\n * a region axis that mean nothing to the renderer. Amperages come from the\n * region table here first, falling back to `POWER_CONNECTORS` for types it\n * does not list, so the two never disagree on shared types.\n */\n\nimport { POWER_CONNECTORS } from \"../utils/power-connectors\";\n\n/** Region a connector's rating is quoted under. @public */\nexport type PowerConnectorRegion = \"IEC\" | \"UL\";\n\n/**\n * Ratings are region-dependent (C13 = 10 A IEC / 15 A UL, C19 = 16 A IEC /\n * 20 A NA), so the rating rules take a `region` option rather than hardcoding\n * one set: `config.defaults.region` for every rule, overridable per rule via\n * `config.rules[id].options.region`. NEMA (5-15P, L5-30P, L6-30P, L21-30P) is a documented follow-up.\n * @public\n */\nexport const IEC_60320_AMPERAGE: Record<\n  string,\n  Partial<Record<PowerConnectorRegion, number>>\n> = {\n  C13: { IEC: 10, UL: 15 },\n  C14: { IEC: 10, UL: 15 },\n  C15: { IEC: 10, UL: 15 },\n  C16: { IEC: 10, UL: 15 },\n  C19: { IEC: 16, UL: 20 },\n  C20: { IEC: 16, UL: 20 },\n  C21: { IEC: 16, UL: 20 },\n  C22: { IEC: 16, UL: 20 },\n};\n\n/** Inlets are the male, equipment-mounted half. @public */\nexport const IEC_60320_INLETS = [\"C14\", \"C16\", \"C20\", \"C22\"] as const;\n/** Connectors are the female, cord-mounted half. @public */\nexport const IEC_60320_CONNECTORS = [\"C13\", \"C15\", \"C19\", \"C21\"] as const;\n\n/**\n * Directed mate table: which cord connectors each inlet accepts.\n *\n * - `C14 ← C13, C15` — the high-temperature C15 is backward-compatible downward.\n * - `C16 ← C15` only; **`C16 ← C13` is rejected** (the C16 keying ridge).\n * - `C20 ← C19, C21` — the high-temperature C21 is backward-compatible\n *   downward, exactly like C15 → C14.\n * - `C22 ← C21` only; **`C22 ← C19` is rejected** (the C22 keying ridge).\n * - Cross-family `C13 ↔ C20` and `C19 ↔ C14` are hard errors: the 10 A / 16 A\n *   envelopes are deliberately keyed apart.\n * @public\n */\nexport const IEC_60320_MATES: Record<string, readonly string[]> = {\n  C14: [\"C13\", \"C15\"],\n  C16: [\"C15\"],\n  C20: [\"C19\", \"C21\"],\n  C22: [\"C21\"],\n};\n\n/** Role of a connector designation, or `unknown` for unmodelled types. @public */\nexport function getConnectorRole(\n  type: string,\n): \"inlet\" | \"connector\" | \"unknown\" {\n  const upper = type.toUpperCase();\n  if ((IEC_60320_INLETS as readonly string[]).includes(upper)) return \"inlet\";\n  if ((IEC_60320_CONNECTORS as readonly string[]).includes(upper))\n    return \"connector\";\n  return \"unknown\";\n}\n\n/**\n * Rated amperage for a connector designation. The region table above is\n * consulted first, because it is the only one that knows the IEC/UL split;\n * the renderer's region-agnostic `POWER_CONNECTORS` entry is the fallback for\n * types the region table does not list. Returns `undefined` for unmodelled\n * types (e.g. `AC` under a non-IEC region) rather than guessing.\n */\nexport function getConnectorAmperage(\n  type: string,\n  region: PowerConnectorRegion = \"IEC\",\n): number | undefined {\n  const upper = type.toUpperCase();\n  const regional = IEC_60320_AMPERAGE[upper]?.[region];\n  if (typeof regional === \"number\") return regional;\n  const rendered = POWER_CONNECTORS[upper];\n  return typeof rendered?.amperage === \"number\" ? rendered.amperage : undefined;\n}\n\n/** Whether an IEC 60320 pairing is mechanically valid. */\nexport function isValidIecMate(inlet: string, connector: string): boolean {\n  const accepted = IEC_60320_MATES[inlet.toUpperCase()];\n  if (!accepted) return true; // unmodelled inlet — do not invent a failure\n  return accepted.includes(connector.toUpperCase());\n}\n","/**\n * Power graph model shared by every validation rule.\n *\n * The input graph is undirected (React Flow edges carry no electrical\n * direction), so the builder orients it: it finds the supply roots, breadth-\n * first-searches outward, and records each power edge as a {@link PowerLink}\n * with a supply end and a load end. Everything else — feeds, descendants,\n * ancestor sets, per-way load sums — is derived from that orientation.\n */\n\nimport type { BreakerWay, BreakerWayInstanceState } from \"../types\";\nimport { getEndpointMetadata } from \"../utils/endpoint-metadata\";\nimport { parseHandleId } from \"../utils/handle-ids\";\nimport {\n  getConnectorAmperage,\n  getConnectorRole,\n  type PowerConnectorRegion,\n} from \"./connectors\";\nimport type {\n  PowerElementKey,\n  PowerEnergizationLike,\n  PowerGraphEdge,\n  PowerGraphNode,\n  PowerLink,\n  PowerValidationContext,\n  PowerWayRef,\n  ResolvedPowerConnector,\n} from \"./types\";\n\n/**\n * Node types that can originate or distribute power. Kept as a set so the\n * connection-rule widening in PRD C3 and this engine agree on one list.\n * @public\n */\nexport const POWER_SOURCE_CAPABLE_NODE_TYPES: readonly string[] = [\n  \"distribution-board\",\n  \"vertical-pdu\",\n];\n\nconst ELEMENT_SEPARATOR = \"#\";\n\n/**\n * Escape the node-id part of an element key so a node id containing `#` (or\n * `%`) can never be mistaken for the `<boardId>#<wayId>` separator. Ids with\n * neither character — the overwhelmingly common case — are unchanged.\n */\nfunction escapeKeyNodeId(nodeId: string): string {\n  return /[#%]/.test(nodeId)\n    ? nodeId.replace(/%/g, \"%25\").replace(/#/g, \"%23\")\n    : nodeId;\n}\n\nfunction unescapeKeyNodeId(escaped: string): string {\n  return escaped.includes(\"%\")\n    ? escaped.replace(/%23/gi, \"#\").replace(/%25/g, \"%\")\n    : escaped;\n}\n\n/**\n * Build a supply-element key: a node id, or `<boardId>#<wayId>`. A `#` or `%`\n * inside the node id is percent-escaped (`%23` / `%25`) so the key always\n * splits back correctly; the way id is kept verbatim.\n * @public\n */\nexport function powerElementKey(nodeId: string, wayId?: string): PowerElementKey {\n  const node = escapeKeyNodeId(nodeId);\n  return wayId ? `${node}${ELEMENT_SEPARATOR}${wayId}` : node;\n}\n\n/** Split a supply-element key back into its parts. @public */\nexport function parsePowerElementKey(key: PowerElementKey): {\n  nodeId: string;\n  wayId?: string;\n} {\n  const index = key.indexOf(ELEMENT_SEPARATOR);\n  if (index < 0) return { nodeId: unescapeKeyNodeId(key) };\n  return {\n    nodeId: unescapeKeyNodeId(key.slice(0, index)),\n    wayId: key.slice(index + 1),\n  };\n}\n\n/**\n * Resolve the connector at one end of a power edge: the port's own\n * `connectorType`, else the designation in its `type` (`power_c13`), else —\n * for a PDU outlet — the strip's declared outlet (`pduPortGroups` /\n * `rearPorts`) or its positional C13/C19 cycle, all via the shared endpoint\n * classifier the canvas uses. A node-level `data.connectorType` is the last\n * fallback. Ratings are read under `region`.\n */\nexport function resolvePowerEndpointConnector(\n  node: PowerGraphNode | undefined,\n  handleId: string | null | undefined,\n  region: PowerConnectorRegion = \"IEC\",\n): ResolvedPowerConnector | undefined {\n  if (!node) return undefined;\n  let type: string | undefined;\n  if (handleId) {\n    const metadata = getEndpointMetadata(\n      node as Parameters<typeof getEndpointMetadata>[0],\n      handleId,\n    );\n    if (typeof metadata.connectorType === \"string\" && metadata.connectorType) {\n      type = metadata.connectorType;\n    }\n  }\n  if (!type) {\n    // Node-level default, e.g. a device whose inlet family is uniform.\n    const fallback = node.data?.connectorType;\n    if (typeof fallback === \"string\" && fallback) type = fallback;\n  }\n  if (!type) return undefined;\n  const upper = type.toUpperCase();\n  return {\n    type: upper,\n    amperage: getConnectorAmperage(upper, region),\n    role: getConnectorRole(upper),\n  };\n}\n\n/** Whether an edge carries power. @public */\nexport function isPowerEdge(edge: PowerGraphEdge): boolean {\n  if (edge.type === \"power\") return true;\n  const kind = edge.data?.kind;\n  return kind === \"power\";\n}\n\nfunction wayIdFromHandle(handle: string | null | undefined): string | undefined {\n  if (!handle) return undefined;\n  const shape = parseHandleId(handle).shape;\n  return shape.kind === \"breaker-way\" ? shape.wayId : undefined;\n}\n\nfunction readNumber(value: unknown): number | undefined {\n  return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\ninterface Incidence {\n  link: PowerLink;\n  peerId: string;\n  localHandle?: string;\n  localIsWay: boolean;\n}\n\nexport interface BuildPowerGraphOptions {\n  sourceNodeIds?: readonly string[];\n  energization?: PowerEnergizationLike;\n  /** Region the connector ratings are read under (default `IEC`). */\n  connectorRegion?: PowerConnectorRegion;\n}\n\n/**\n * Build the shared rule context. `options`/`severity` are placeholders here —\n * the engine rebinds them per rule before calling `evaluate`.\n */\nexport function buildPowerValidationContext(\n  nodes: readonly PowerGraphNode[],\n  edges: readonly PowerGraphEdge[],\n  options: BuildPowerGraphOptions = {},\n): PowerValidationContext {\n  const region = options.connectorRegion ?? \"IEC\";\n  const nodeById = new Map<string, PowerGraphNode>();\n  for (const node of nodes) nodeById.set(node.id, node);\n\n  const powerEdges = edges.filter(\n    (edge) =>\n      isPowerEdge(edge) && nodeById.has(edge.source) && nodeById.has(edge.target),\n  );\n\n  // --- Provisional links (source → target), then reorient by BFS. -----------\n  const links: PowerLink[] = powerEdges.map((edge) => ({\n    edge,\n    edgeId: edge.id,\n    supplyNodeId: edge.source,\n    loadNodeId: edge.target,\n    supplyHandle: edge.sourceHandle ?? undefined,\n    loadHandle: edge.targetHandle ?? undefined,\n  }));\n\n  const incidence = new Map<string, Incidence[]>();\n  const addIncidence = (nodeId: string, entry: Incidence): void => {\n    const list = incidence.get(nodeId);\n    if (list) list.push(entry);\n    else incidence.set(nodeId, [entry]);\n  };\n  for (const link of links) {\n    addIncidence(link.supplyNodeId, {\n      link,\n      peerId: link.loadNodeId,\n      localHandle: link.supplyHandle,\n      localIsWay: wayIdFromHandle(link.supplyHandle) !== undefined,\n    });\n    addIncidence(link.loadNodeId, {\n      link,\n      peerId: link.supplyNodeId,\n      localHandle: link.loadHandle,\n      localIsWay: wayIdFromHandle(link.loadHandle) !== undefined,\n    });\n  }\n\n  const isSourceCapable = (nodeId: string): boolean => {\n    const type = nodeById.get(nodeId)?.type;\n    return type ? POWER_SOURCE_CAPABLE_NODE_TYPES.includes(type) : false;\n  };\n\n  // Roots: explicit config, else a source-capable node with no *incoming*\n  // supply — i.e. no incident power edge whose local end is a plain port and\n  // whose peer is itself source-capable. A board feeding PDUs lands on its own\n  // breaker-way handles, so it stays a root; the PDUs do not.\n  let sourceNodeIds: string[];\n  if (options.sourceNodeIds !== undefined) {\n    sourceNodeIds = options.sourceNodeIds.filter((id) => nodeById.has(id));\n  } else {\n    sourceNodeIds = nodes\n      .filter((node) => isSourceCapable(node.id))\n      .filter((node) => {\n        const incident = incidence.get(node.id) ?? [];\n        return !incident.some(\n          (entry) => !entry.localIsWay && isSourceCapable(entry.peerId),\n        );\n      })\n      .map((node) => node.id);\n  }\n\n  // --- Orient every reachable link away from the roots. --------------------\n  const orientedEdgeIds = new Set<string>();\n  const nodeDepth = new Map<string, number>();\n  const queue: string[] = [];\n  for (const id of sourceNodeIds) {\n    if (!nodeDepth.has(id)) {\n      nodeDepth.set(id, 0);\n      queue.push(id);\n    }\n  }\n  for (let head = 0; head < queue.length; head += 1) {\n    const current = queue[head];\n    const depth = nodeDepth.get(current) ?? 0;\n    for (const entry of incidence.get(current) ?? []) {\n      const { link } = entry;\n      if (!orientedEdgeIds.has(link.edgeId)) {\n        orientedEdgeIds.add(link.edgeId);\n        // A consumer never supplies its own PDU or board. Whichever endpoint\n        // the BFS happens to reach first, a link between a non-source-capable\n        // node and a source-capable one always points toward the consumer —\n        // otherwise a dual-corded device reached early down one path is made\n        // the supply of the PDU feeding its other cord, and it then reads as\n        // single-fed with a ring closing behind it.\n        const currentCannotSupply =\n          !isSourceCapable(current) && isSourceCapable(entry.peerId);\n        const supplyNodeId = currentCannotSupply ? entry.peerId : current;\n        const loadNodeId = currentCannotSupply ? current : entry.peerId;\n        if (link.supplyNodeId !== supplyNodeId) {\n          const supplyHandle = link.loadHandle;\n          const loadHandle = link.supplyHandle;\n          link.supplyNodeId = supplyNodeId;\n          link.loadNodeId = loadNodeId;\n          link.supplyHandle = supplyHandle;\n          link.loadHandle = loadHandle;\n        }\n      }\n      if (!nodeDepth.has(entry.peerId)) {\n        nodeDepth.set(entry.peerId, depth + 1);\n        queue.push(entry.peerId);\n      }\n    }\n  }\n\n  // Islands never reached from a root keep their authored source→target\n  // orientation; rules that need a source (A01) still report them correctly.\n  for (const link of links) {\n    const wayId = wayIdFromHandle(link.supplyHandle);\n    if (wayId && nodeById.get(link.supplyNodeId)?.type === \"distribution-board\") {\n      link.wayId = wayId;\n      link.wayKey = powerElementKey(link.supplyNodeId, wayId);\n    }\n  }\n\n  const linkById = new Map<string, PowerLink>();\n  const feedsByNode = new Map<string, PowerLink[]>();\n  const downstreamByNode = new Map<string, PowerLink[]>();\n  const linksByWay = new Map<string, PowerLink[]>();\n  for (const link of links) {\n    linkById.set(link.edgeId, link);\n    const feeds = feedsByNode.get(link.loadNodeId);\n    if (feeds) feeds.push(link);\n    else feedsByNode.set(link.loadNodeId, [link]);\n    const down = downstreamByNode.get(link.supplyNodeId);\n    if (down) down.push(link);\n    else downstreamByNode.set(link.supplyNodeId, [link]);\n    if (link.wayKey) {\n      const wayLinks = linksByWay.get(link.wayKey);\n      if (wayLinks) wayLinks.push(link);\n      else linksByWay.set(link.wayKey, [link]);\n    }\n  }\n\n  const getFeeds = (nodeId: string): PowerLink[] => feedsByNode.get(nodeId) ?? [];\n  const getDownstreamLinks = (nodeId: string): PowerLink[] =>\n    downstreamByNode.get(nodeId) ?? [];\n\n  const getDownstreamNodeIds = (nodeId: string): string[] => {\n    const seen = new Set<string>([nodeId]);\n    const out: string[] = [];\n    const stack = [nodeId];\n    while (stack.length > 0) {\n      const current = stack.pop() as string;\n      for (const link of getDownstreamLinks(current)) {\n        if (seen.has(link.loadNodeId)) continue;\n        seen.add(link.loadNodeId);\n        out.push(link.loadNodeId);\n        stack.push(link.loadNodeId);\n      }\n    }\n    return out;\n  };\n\n  const getUpstreamNodeIds = (nodeId: string): string[] => {\n    const seen = new Set<string>([nodeId]);\n    const out: string[] = [];\n    const stack = [nodeId];\n    while (stack.length > 0) {\n      const current = stack.pop() as string;\n      for (const link of getFeeds(current)) {\n        if (seen.has(link.supplyNodeId)) continue;\n        seen.add(link.supplyNodeId);\n        out.push(link.supplyNodeId);\n        stack.push(link.supplyNodeId);\n      }\n    }\n    return out;\n  };\n\n  const getDownstreamNodeIdsOfLink = (edgeId: string): string[] => {\n    const link = linkById.get(edgeId);\n    if (!link) return [];\n    return [link.loadNodeId, ...getDownstreamNodeIds(link.loadNodeId)];\n  };\n\n  const ancestorCache = new Map<string, Set<PowerElementKey>>();\n  const getAncestorsOfLink = (edgeId: string): Set<PowerElementKey> => {\n    const cached = ancestorCache.get(edgeId);\n    if (cached) return cached;\n    const out = new Set<PowerElementKey>();\n    const pending = [edgeId];\n    const visited = new Set<string>();\n    while (pending.length > 0) {\n      const currentId = pending.pop() as string;\n      if (visited.has(currentId)) continue;\n      visited.add(currentId);\n      const link = linkById.get(currentId);\n      if (!link) continue;\n      if (link.wayKey) out.add(link.wayKey);\n      out.add(powerElementKey(link.supplyNodeId));\n      // Reverse the stack insertion to retain the recursive traversal's\n      // feed order, including tie-breaking between equally deep ancestors.\n      const feeds = getFeeds(link.supplyNodeId);\n      for (let i = feeds.length - 1; i >= 0; i--) pending.push(feeds[i].edgeId);\n    }\n    // Cache only complete traversals. Caching a recursive partial set made\n    // later queries in a cycle depend on which edge was queried first.\n    ancestorCache.set(edgeId, out);\n    return out;\n  };\n\n  const getAncestors = (nodeId: string): Set<PowerElementKey> => {\n    const out = new Set<PowerElementKey>();\n    for (const feed of getFeeds(nodeId)) {\n      for (const key of getAncestorsOfLink(feed.edgeId)) out.add(key);\n    }\n    return out;\n  };\n\n  /** True when the element hangs off a node no source can reach. */\n  const isUnreachable = (elementKey: PowerElementKey): boolean =>\n    typeof nodeDepth.get(parsePowerElementKey(elementKey).nodeId) !== \"number\";\n\n  const getDepth = (elementKey: PowerElementKey): number => {\n    const { nodeId, wayId } = parsePowerElementKey(elementKey);\n    const base = nodeDepth.get(nodeId);\n    const depth = typeof base === \"number\" ? base : Number.MAX_SAFE_INTEGER / 2;\n    // A way sits just below its board, so a shared way always out-ranks a\n    // shared board when picking the deepest common ancestor.\n    return wayId ? depth + 0.5 : depth;\n  };\n\n  const getDeepestShared = (\n    a: Iterable<PowerElementKey>,\n    b: Iterable<PowerElementKey>,\n  ): PowerElementKey | undefined => {\n    const setB = b instanceof Set ? b : new Set(b);\n    let best: PowerElementKey | undefined;\n    let bestDepth = -Infinity;\n    let bestUnreachable = true;\n    for (const key of a) {\n      if (!setB.has(key)) continue;\n      // An element on an island has a synthetic near-infinite depth, so it\n      // would otherwise always win. Reachable elements outrank it outright.\n      const unreachable = isUnreachable(key);\n      if (best !== undefined && unreachable && !bestUnreachable) continue;\n      const depth = getDepth(key);\n      if (best === undefined || (bestUnreachable && !unreachable) || depth > bestDepth) {\n        best = key;\n        bestDepth = depth;\n        bestUnreachable = unreachable;\n      }\n    }\n    return best;\n  };\n\n  const getWays = (): PowerWayRef[] => {\n    const out: PowerWayRef[] = [];\n    for (const node of nodes) {\n      if (node.type !== \"distribution-board\") continue;\n      const ways = node.data?.ways;\n      if (!Array.isArray(ways)) continue;\n      const wayState = (node.data?.wayState ?? {}) as Record<\n        string,\n        BreakerWayInstanceState\n      >;\n      for (const raw of ways) {\n        const way = raw as BreakerWay;\n        if (!way || typeof way.id !== \"string\") continue;\n        out.push({\n          boardId: node.id,\n          board: node,\n          way,\n          wayKey: powerElementKey(node.id, way.id),\n          state: wayState[way.id],\n        });\n      }\n    }\n    return out;\n  };\n\n  const getWayLinks = (wayKey: PowerElementKey): PowerLink[] =>\n    linksByWay.get(wayKey) ?? [];\n\n  const isConsumer = (nodeId: string): boolean =>\n    getFeeds(nodeId).length > 0 &&\n    getDownstreamLinks(nodeId).length === 0 &&\n    !isSourceCapable(nodeId);\n\n  const sumDownstreamDrawW = (\n    wayKey: PowerElementKey,\n    field: \"maximumDrawW\" | \"allocatedDrawW\",\n    fallbackField?: \"maximumDrawW\" | \"allocatedDrawW\",\n  ): number => {\n    const seen = new Set<string>();\n    let total = 0;\n    for (const link of getWayLinks(wayKey)) {\n      for (const nodeId of getDownstreamNodeIdsOfLink(link.edgeId)) {\n        if (seen.has(nodeId)) continue;\n        seen.add(nodeId);\n        const data = nodeById.get(nodeId)?.data;\n        const primary = readNumber(data?.[field]);\n        const value =\n          primary ?? (fallbackField ? readNumber(data?.[fallbackField]) : undefined);\n        if (typeof value === \"number\") total += value;\n      }\n    }\n    return total;\n  };\n\n  const getEndpointConnector = (\n    nodeId: string,\n    handleId: string | null | undefined,\n  ): ResolvedPowerConnector | undefined =>\n    resolvePowerEndpointConnector(nodeById.get(nodeId), handleId, region);\n\n  const getLinkConnectors = (\n    edgeId: string,\n  ): { supply?: ResolvedPowerConnector; load?: ResolvedPowerConnector } => {\n    const link = linkById.get(edgeId);\n    if (!link) return {};\n    return {\n      supply: getEndpointConnector(link.supplyNodeId, link.supplyHandle),\n      load: getEndpointConnector(link.loadNodeId, link.loadHandle),\n    };\n  };\n\n  const describeElement = (key: PowerElementKey): string => {\n    let { nodeId, wayId } = parsePowerElementKey(key);\n    // Rules also pass bare node ids. A raw id that itself contains `#` would\n    // mis-split; when the split does not name a real board, read it as the id.\n    if (wayId !== undefined && !nodeById.has(nodeId) && nodeById.has(key)) {\n      nodeId = key;\n      wayId = undefined;\n    }\n    const node = nodeById.get(nodeId);\n    const label =\n      (typeof node?.data?.label === \"string\" ? node.data.label : undefined) ??\n      nodeId;\n    if (!wayId) return label;\n    const ways = Array.isArray(node?.data?.ways)\n      ? (node?.data?.ways as BreakerWay[])\n      : [];\n    const way = ways.find((candidate) => candidate?.id === wayId);\n    return `${label} way ${way?.label ?? wayId}`;\n  };\n\n  return {\n    nodes,\n    edges,\n    links,\n    sourceNodeIds,\n    options: {},\n    severity: \"warning\",\n    energization: options.energization,\n    getNode: (nodeId) => nodeById.get(nodeId),\n    isPowerEdge,\n    isSourceCapable,\n    isConsumer,\n    getFeeds,\n    getDownstreamLinks,\n    getDownstreamNodeIds,\n    getUpstreamNodeIds,\n    getDownstreamNodeIdsOfLink,\n    getAncestorsOfLink,\n    getAncestors,\n    getDepth,\n    getDeepestShared,\n    getWays,\n    getWayLinks,\n    sumDownstreamDrawW,\n    getEndpointConnector,\n    getLinkConnectors,\n    parseElementKey: parsePowerElementKey,\n    elementKey: powerElementKey,\n    describeElement,\n  };\n}\n","/**\n * The power validation engine.\n *\n * It ships **zero rules**: `runPowerValidation` evaluates exactly the rules the\n * consumer passes in. Its job is (1) build the shared graph context once, (2)\n * merge config over each rule's defaults, (3) run each rule in isolation so one\n * throwing rule cannot take the run down, and (4) normalise the drafts each\n * rule returns into fully-populated {@link PowerIssue}s.\n */\n\nimport { buildPowerValidationContext, resolvePowerEndpointConnector } from \"./graph\";\nimport type { PowerConnectorRegion } from \"./connectors\";\nimport type {\n  PowerGraphEdge,\n  PowerGraphNode,\n  PowerIssue,\n  PowerRule,\n  PowerRuleOptions,\n  PowerValidationConfig,\n  PowerValidationContext,\n  PowerValidationResult,\n  RunPowerValidationOptions,\n} from \"./types\";\n\nfunction readRegion(value: unknown): PowerConnectorRegion | undefined {\n  return value === \"IEC\" || value === \"UL\" ? value : undefined;\n}\n\nfunction mergeOptions(\n  rule: PowerRule,\n  config: PowerValidationConfig | undefined,\n): PowerRuleOptions {\n  return {\n    ...(rule.defaultOptions ?? {}),\n    ...(config?.defaults ?? {}),\n    ...(config?.rules?.[rule.id]?.options ?? {}),\n  };\n}\n\n/**\n * Run a set of power rules over a diagram graph.\n *\n * @param nodes - Diagram nodes (`NetworkNode[]` satisfies the input shape).\n * @param edges - Diagram edges; non-power edges are ignored by the graph model.\n * @param options - `rules` is required (no defaults); `config` carries per-rule\n * enablement, severity overrides, and thresholds.\n * @public\n */\nexport function runPowerValidation(\n  nodes: readonly PowerGraphNode[],\n  edges: readonly PowerGraphEdge[],\n  options: RunPowerValidationOptions,\n): PowerValidationResult {\n  const { rules, config } = options;\n  const warnings: string[] = [];\n  const evaluatedRuleIds: string[] = [];\n  const skippedRuleIds: string[] = [];\n  const issues: PowerIssue[] = [];\n\n  const baseRegion = readRegion(config?.defaults?.region) ?? \"IEC\";\n  const baseContext = buildPowerValidationContext(nodes, edges, {\n    sourceNodeIds: config?.sourceNodeIds,\n    energization: config?.energization,\n    connectorRegion: baseRegion,\n  });\n  const linkById = new Map(baseContext.links.map((link) => [link.edgeId, link]));\n  /**\n   * Connector lookups re-read under a per-rule `options.region` (e.g.\n   * `config.rules[\"PWR-A06\"].options.region = \"UL\"`), so the documented\n   * per-rule region is honoured rather than silently ignored.\n   */\n  const connectorLookupsFor = (\n    region: PowerConnectorRegion,\n  ): Pick<PowerValidationContext, \"getEndpointConnector\" | \"getLinkConnectors\"> => {\n    if (region === baseRegion) {\n      return {\n        getEndpointConnector: baseContext.getEndpointConnector,\n        getLinkConnectors: baseContext.getLinkConnectors,\n      };\n    }\n    const getEndpointConnector: PowerValidationContext[\"getEndpointConnector\"] = (\n      nodeId,\n      handleId,\n    ) => resolvePowerEndpointConnector(baseContext.getNode(nodeId), handleId, region);\n    return {\n      getEndpointConnector,\n      getLinkConnectors: (edgeId) => {\n        const link = linkById.get(edgeId);\n        if (!link) return {};\n        return {\n          supply: getEndpointConnector(link.supplyNodeId, link.supplyHandle),\n          load: getEndpointConnector(link.loadNodeId, link.loadHandle),\n        };\n      },\n    };\n  };\n\n  const seenRuleIds = new Set<string>();\n  const usedIssueIds = new Set<string>();\n\n  for (const rule of rules) {\n    if (seenRuleIds.has(rule.id)) {\n      warnings.push(`Duplicate rule id \"${rule.id}\" — the later copy still ran.`);\n    }\n    seenRuleIds.add(rule.id);\n\n    const ruleConfig = config?.rules?.[rule.id];\n    if (ruleConfig?.enabled === false) {\n      skippedRuleIds.push(rule.id);\n      continue;\n    }\n\n    const severity = ruleConfig?.severity ?? rule.defaultSeverity;\n    const ruleOptions = mergeOptions(rule, config);\n    const ctx: PowerValidationContext = {\n      ...baseContext,\n      ...connectorLookupsFor(readRegion(ruleOptions.region) ?? baseRegion),\n      options: ruleOptions,\n      severity,\n    };\n\n    let drafts;\n    try {\n      drafts = rule.evaluate(ctx);\n    } catch (error) {\n      warnings.push(\n        `Rule \"${rule.id}\" threw and was skipped: ${\n          error instanceof Error ? error.message : String(error)\n        }`,\n      );\n      continue;\n    }\n    evaluatedRuleIds.push(rule.id);\n\n    for (const draft of drafts ?? []) {\n      // An explicit config severity is a hard override: it wins even over a\n      // rule that grades its own findings (e.g. PWR-A02's error/warning split).\n      const resolvedSeverity =\n        ruleConfig?.severity ?? draft.severity ?? rule.defaultSeverity;\n      let id = draft.id || `${rule.id}:${issues.length}`;\n      if (usedIssueIds.has(id)) {\n        let suffix = 2;\n        while (usedIssueIds.has(`${id}#${suffix}`)) suffix += 1;\n        id = `${id}#${suffix}`;\n      }\n      usedIssueIds.add(id);\n      issues.push({\n        ...draft,\n        id,\n        ruleId: draft.ruleId ?? rule.id,\n        severity: resolvedSeverity,\n        basis: draft.basis ?? rule.basis,\n      });\n    }\n  }\n\n  return { issues, evaluatedRuleIds, skippedRuleIds, warnings };\n}\n","import type { NetworkEdge, NetworkNode } from '../types';\nimport { evaluateFibreConnectionRule, isFibreConnection } from '../utils/connection-rules';\nimport { normalizeDirectionalHandleId } from '../utils/handle-ids';\nimport type { EndpointMetadata, PowerEndpointRole } from '../utils/endpoint-metadata';\nimport { getEndpointMetadata } from '../utils/endpoint-metadata';\nimport { getConnectorRole, isValidIecMate } from '../validation/connectors';\nimport {\n  getRackMountMode,\n  getVisibleFacesForMountMode,\n  isSplitFaceRackLayout,\n} from '../utils/rack-layout';\nimport { getDefaultDeviceFace } from '../utils/render-ports';\nimport { findEndpointOccupants } from './endpoint-occupancy';\n\nexport type ViewFace = 'front' | 'rear';\n\nexport interface NetworkDiagramState {\n  nodes: NetworkNode[];\n  edges: NetworkEdge[];\n  debug?: boolean;\n}\n\nexport interface ValidateConnectionParams {\n  source: string | null;\n  target: string | null;\n  sourceHandle?: string | null;\n  targetHandle?: string | null;\n  /** Optional edge id to ignore during occupancy checks (used for reconnect). */\n  ignoreEdgeId?: string;\n}\n\n/**\n * Structured rejection codes on {@link ValidationResult}. `invalid` covers\n * every non-occupancy failure (kind mismatch, connector incompatibility,\n * power semantics, fibre rules). `occupied` means the only failure is\n * endpoint capacity — the connection plan may then apply documented\n * replacement. Custom validators simply omit `code`; their rejections are\n * treated as `invalid` and are never bypassed to reach replacement.\n * @public\n */\nexport type ConnectionRejectionCode = 'invalid' | 'occupied';\n\nexport interface ValidationResult {\n  valid: boolean;\n  reason?: string;\n  /**\n   * Optional structured code; only `defaultValidateConnection` emits\n   * it today (`occupied` for capacity failures). Absent always reads as\n   * `invalid`.\n   */\n  code?: ConnectionRejectionCode;\n}\n\nexport interface NetworkDiagramOptions {\n  validateConnection?: (params: ValidateConnectionParams, ctx: NetworkDiagramState) => ValidationResult;\n}\n\nfunction hasCompatibilityIntersection(sourceCompatibility: string[], targetCompatibility: string[]): boolean {\n  if (sourceCompatibility.length === 0 || targetCompatibility.length === 0) {\n    return true;\n  }\n\n  const targetSet = new Set(targetCompatibility);\n  return sourceCompatibility.some((token) => targetSet.has(token));\n}\n\n/**\n * Node types that can act as the source side of a power connection. A power\n * edge must touch at least one of these — a device-to-device power edge is\n * meaningless. Extensible alongside `rack-device-types.ts`.\n */\nexport const POWER_SOURCE_NODE_TYPES = ['vertical-pdu', 'distribution-board'] as const;\n\nconst powerSourceNodeTypeSet = new Set<string>(POWER_SOURCE_NODE_TYPES);\n\n/** Whether a node type can source power (PDU or distribution board). */\nexport function isPowerSourceNodeType(nodeType: string | undefined): boolean {\n  return typeof nodeType === 'string' && powerSourceNodeTypeSet.has(nodeType);\n}\n\n/**\n * The message a branch circuit landing in the wrong place gets. Stated once so\n * the interactive validator and PWR-A13 say exactly the same thing.\n * @public\n */\nexport const CIRCUIT_TERMINATION_REASON =\n  'Branch circuits terminate on a PDU inlet — feed devices from PDU outlets';\n\n/**\n * The message a cord landing on a PDU inlet gets. Daisy-chaining strips fails\n * the IEC 60320 mate table anyway (an inlet is not a cord connector), but the\n * mate reason would be cryptic — this names the actual mistake.\n * @public\n */\nexport const PDU_INLET_FEED_REASON =\n  'A PDU inlet takes a branch circuit from a breaker way — power strips are not daisy-chained';\n\n/**\n * The message wiring two breaker ways together gets. Stated once so the\n * interactive validator and PWR-A13 say exactly the same thing.\n * @public\n */\nexport const WAY_TO_WAY_REASON =\n  'Two breaker ways cannot be wired together — a branch circuit runs from a way to a PDU inlet';\n\n/**\n * The message a cord between two outlets gets. Stated once so the interactive\n * validator and PWR-A13 say exactly the same thing.\n * @public\n */\nexport const OUTLET_TO_OUTLET_REASON =\n  'Both ends are power outlets — a cord runs from an outlet to a device inlet';\n\n/**\n * Real-world power connection semantics, applied before the connector\n * compatibility screen so the reason names the wiring mistake rather than a\n * connector mismatch further down the chain.\n *\n * Two connection kinds exist and they do not mix:\n *\n * - **circuit** — `circuit-source` (breaker way) → `circuit-sink` (PDU/board\n *   inlet). Fixed wiring: IEC 60309, hardwired, C20, NEMA locking.\n * - **cord** — `cord-source` (PDU outlet / device power outlet) →\n *   `cord-sink` (device power inlet), under the IEC 60320 mate table.\n *\n * `unknown` endpoints (an unmodelled handle, a legacy ad-hoc id) are never\n * judged here: the function returns `undefined` and the existing kind and\n * compatibility screens decide. That is what keeps every graph authored before\n * the inlet model valid.\n */\nfunction evaluatePowerConnectionSemantics(\n  sourceMetadata: EndpointMetadata,\n  targetMetadata: EndpointMetadata,\n): ValidationResult | undefined {\n  const sourceRole = sourceMetadata.powerRole;\n  const targetRole = targetMetadata.powerRole;\n  if (sourceRole === 'unknown' || targetRole === 'unknown') return undefined;\n\n  const roles: PowerEndpointRole[] = [sourceRole, targetRole];\n  const has = (role: PowerEndpointRole): boolean => roles.includes(role);\n  const other = (role: PowerEndpointRole): PowerEndpointRole =>\n    sourceRole === role ? targetRole : sourceRole;\n\n  if (has('circuit-source')) {\n    const far = other('circuit-source');\n    if (far === 'circuit-sink') return undefined; // way -> PDU/board inlet\n    if (far === 'circuit-source') {\n      return { valid: false, reason: WAY_TO_WAY_REASON };\n    }\n    return { valid: false, reason: CIRCUIT_TERMINATION_REASON };\n  }\n\n  if (has('circuit-sink')) {\n    // Nothing but a way may feed an inlet; the way case already returned.\n    return { valid: false, reason: PDU_INLET_FEED_REASON };\n  }\n\n  if (sourceRole === 'cord-source' && targetRole === 'cord-source') {\n    return { valid: false, reason: OUTLET_TO_OUTLET_REASON };\n  }\n\n  // Two device inlets: already rejected by the power-source-endpoint screen\n  // below, which owns that message. Nothing to add here.\n  if (sourceRole === targetRole) return undefined;\n\n  return evaluateCordMate(\n    sourceRole === 'cord-source' ? sourceMetadata : targetMetadata,\n    sourceRole === 'cord-sink' ? sourceMetadata : targetMetadata,\n  );\n}\n\n/**\n * IEC 60320 mate check for a cord: the outlet supplies the cord connector, the\n * device supplies the inlet. Only judged when both ends resolve to a modelled\n * connector on the expected half of the pairing — an unmodelled connector is\n * skipped rather than guessed at, exactly as PWR-A05 does.\n */\nfunction evaluateCordMate(\n  outlet: EndpointMetadata,\n  deviceInlet: EndpointMetadata,\n): ValidationResult | undefined {\n  const cord = outlet.connectorType?.toUpperCase();\n  const inlet = deviceInlet.connectorType?.toUpperCase();\n  if (!cord || !inlet) return undefined;\n  if (getConnectorRole(cord) !== 'connector' || getConnectorRole(inlet) !== 'inlet') {\n    return undefined;\n  }\n  if (isValidIecMate(inlet, cord)) return undefined;\n  return {\n    valid: false,\n    reason: `${cord} cord will not mate with a ${inlet} inlet — fit the correct IEC 60320 cord`,\n  };\n}\n\nexport function createNodeMap(nodes: NetworkNode[]): Map<string, NetworkNode> {\n  const nodesById = new Map<string, NetworkNode>();\n  for (const node of nodes) {\n    nodesById.set(node.id, node);\n  }\n  return nodesById;\n}\n\nexport function defaultValidateConnection(\n  params: ValidateConnectionParams,\n  ctx: NetworkDiagramState,\n  nodesById = createNodeMap(ctx.nodes),\n): ValidationResult {\n  if (!params.source || !params.target || !params.sourceHandle || !params.targetHandle) return { valid: true };\n\n  const sourceNode = nodesById.get(params.source);\n  const targetNode = nodesById.get(params.target);\n  const fiberConnection = isFibreConnection(sourceNode, targetNode);\n\n  if (!fiberConnection) {\n    const sourceMetadata = getEndpointMetadata(sourceNode, params.sourceHandle);\n    const targetMetadata = getEndpointMetadata(targetNode, params.targetHandle);\n    const sourceKind = sourceMetadata.kind;\n    const targetKind = targetMetadata.kind;\n\n    if (sourceKind !== 'unknown' && targetKind !== 'unknown' && sourceKind !== targetKind) {\n      return { valid: false, reason: `Cannot connect ${sourceKind} handle to ${targetKind} handle` };\n    }\n\n    if (sourceKind === 'power' && targetKind === 'power') {\n      const semantics = evaluatePowerConnectionSemantics(sourceMetadata, targetMetadata);\n      if (semantics) return semantics;\n    }\n\n    if (!hasCompatibilityIntersection(sourceMetadata.compatibility, targetMetadata.compatibility)) {\n      return {\n        valid: false,\n        reason: `Incompatible endpoints: ${params.sourceHandle} does not match ${params.targetHandle}`,\n      };\n    }\n\n    const isPowerConnection = sourceKind === 'power' && targetKind === 'power';\n    const hasPowerSourceEndpoint =\n      isPowerSourceNodeType(sourceNode?.type) || isPowerSourceNodeType(targetNode?.type);\n    if (isPowerConnection && !hasPowerSourceEndpoint) {\n      return {\n        valid: false,\n        reason: 'Power connections must include a PDU or distribution board endpoint',\n      };\n    }\n  }\n\n  const sourceOccupants = findEndpointOccupants(\n    ctx.edges,\n    params.source,\n    sourceNode,\n    params.sourceHandle,\n    params.ignoreEdgeId,\n  );\n  const targetOccupants = findEndpointOccupants(\n    ctx.edges,\n    params.target,\n    targetNode,\n    params.targetHandle,\n    params.ignoreEdgeId,\n  );\n  const sourceHandleAlreadyConnected = sourceOccupants.length > 0;\n  const targetHandleAlreadyConnected = targetOccupants.length > 0;\n\n  if (sourceHandleAlreadyConnected || targetHandleAlreadyConnected) {\n    if (!fiberConnection) {\n      if (sourceHandleAlreadyConnected) {\n        return { valid: false, code: 'occupied', reason: `Source handle ${params.sourceHandle} on node ${params.source} is already connected` };\n      }\n      return { valid: false, code: 'occupied', reason: `Target handle ${params.targetHandle} on node ${params.target} is already connected` };\n    }\n  }\n\n  if (!fiberConnection) {\n    return { valid: true };\n  }\n\n  return evaluateFibreConnectionRule(\n    sourceNode,\n    params.sourceHandle,\n    targetNode,\n    params.targetHandle,\n  );\n}\n\nexport function validateNetworkConnection(\n  params: ValidateConnectionParams,\n  ctx: NetworkDiagramState,\n  options: NetworkDiagramOptions = {},\n): ValidationResult {\n  const fn = options.validateConnection ?? defaultValidateConnection;\n  if (fn === defaultValidateConnection) {\n    return defaultValidateConnection(params, ctx);\n  }\n  return fn(params, ctx);\n}\n\n// Determine which face a given handle belongs to, based on node data\nexport function getHandleSide(handleId: string, node: NetworkNode | undefined): ViewFace | undefined {\n  if (!node) {\n    return undefined;\n  }\n  const face = getEndpointMetadata(node, handleId).face;\n  return face === 'front' || face === 'rear' ? face : undefined;\n}\n\n/** Compute edge z-index from rack face visibility (pure; reads nodesById only). */\nexport function computeEdgeZIndex(\n  edge: NetworkEdge,\n  nodesById: Map<string, NetworkNode>,\n): number {\n  const getVisibleFaces = (node: NetworkNode): ViewFace[] => {\n    const parentId = node.parentId;\n    const parentRack = parentId ? nodesById.get(parentId) : undefined;\n    if (parentRack?.type !== 'rack') {\n      return [(node.data?.face as ViewFace) || getDefaultDeviceFace(node.data)];\n    }\n    if (parentRack && isSplitFaceRackLayout(parentRack.data)) {\n      return getVisibleFacesForMountMode(getRackMountMode(node.data));\n    }\n\n    const deviceView: ViewFace =\n      (node.data?.face as ViewFace) || getDefaultDeviceFace(node.data);\n    return [deviceView];\n  };\n\n  const resolveEndpointVisibility = (nodeId: string, handle: string | null | undefined): number | undefined => {\n    const node = nodesById.get(nodeId);\n    const isRackDeviceNode =\n      !!node &&\n      (node.type === 'device' || node.type === 'server' || node.type === 'switch' || node.type === 'router');\n    if (!isRackDeviceNode || !node) {\n      return undefined;\n    }\n\n    const normalizedDeviceType = node.data?.deviceType?.toLowerCase().replace(/_/g, '-');\n    if (normalizedDeviceType === 'patch-panel') {\n      return undefined;\n    }\n\n    const visibleFaces = getVisibleFaces(node);\n    const side = getHandleSide(handle || '', node);\n    if (side) {\n      return visibleFaces.includes(side) ? 1000 : 500;\n    }\n\n    const normalizedHandle = normalizeDirectionalHandleId(handle);\n    const handleLc = normalizedHandle.toLowerCase();\n    const isPowerHandle = handleLc.startsWith('pwr') || handleLc.startsWith('power');\n    const isStandardNetworkHandle =\n      handleLc.startsWith('net-') ||\n      handleLc.startsWith('port-') ||\n      handleLc.startsWith('uplink-') ||\n      handleLc.includes('fiber') ||\n      handleLc.includes('ports');\n    const isCustomPortName =\n      /^[A-Z0-9]+-\\d+$/i.test(normalizedHandle) &&\n      !handleLc.startsWith('pwr') &&\n      !handleLc.startsWith('power');\n    const isNetworkHandle = isStandardNetworkHandle || isCustomPortName;\n\n    if (isPowerHandle) {\n      return visibleFaces.includes('rear') ? 1000 : 500;\n    }\n    if (isNetworkHandle) {\n      return visibleFaces.includes('front') ? 1000 : 500;\n    }\n\n    return undefined;\n  };\n\n  const targetVisibility = resolveEndpointVisibility(edge.target, edge.targetHandle);\n  const sourceVisibility = resolveEndpointVisibility(edge.source, edge.sourceHandle);\n  // A connection is visible only when both face-scoped endpoints are visible.\n  return Math.min(targetVisibility ?? 1000, sourceVisibility ?? 1000);\n}\n","/**\n * Helpers shared by the standard rule preset. Not part of the engine — a\n * consumer writing custom rules may use them, but nothing here is required.\n */\n\nimport type { BreakerWay } from \"../../types\";\nimport type {\n  PowerElementKey,\n  PowerRuleOptions,\n  PowerValidationContext,\n  PowerWayRef,\n} from \"../types\";\n\nexport function num(value: unknown): number | undefined {\n  return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nexport function opt(\n  options: PowerRuleOptions,\n  key: string,\n  fallback: number,\n): number {\n  return num(options[key]) ?? fallback;\n}\n\n/** √3, the three-phase factor (NetBox's 1.732, copied verbatim). */\nexport const THREE_PHASE_FACTOR = 1.732;\n\n/** Whether a way is a three-phase (3-pole / `3P`) circuit. */\nfunction isThreePhaseWay(way: BreakerWay): boolean {\n  return way.phase === \"3P\" || way.poles === 3;\n}\n\n/**\n * The √3 factor for a three-phase way, else 1. Pair it with the way's\n * **line-to-line** voltage (see {@link wayCircuitVoltage}).\n */\nexport function phaseFactor(way: BreakerWay): number {\n  return isThreePhaseWay(way) ? THREE_PHASE_FACTOR : 1;\n}\n\n/**\n * Watts → amps: `I = P / (V × factor × PF)`. `voltageV` must be the voltage\n * that matches `factor` — line-to-neutral (or line-to-line for a 2-pole\n * circuit) with factor 1, line-to-line with factor √3 for a three-phase way.\n * `powerFactor` defaults to 1 (watts treated as volt-amperes, NetBox's\n * convention); a lower power factor raises the current.\n */\nexport function wattsToAmps(\n  watts: number,\n  voltageV: number,\n  factor: number,\n  powerFactor = 1,\n): number {\n  const pf = powerFactor > 0 && powerFactor <= 1 ? powerFactor : 1;\n  if (voltageV <= 0 || factor <= 0) return 0;\n  return watts / (voltageV * factor * pf);\n}\n\n/** Region defaults for the nominal line-to-neutral (phase) voltage. */\nexport const DEFAULT_PHASE_VOLTAGE_V: Readonly<Record<\"NEC\" | \"IEC\", number>> = {\n  NEC: 120,\n  IEC: 230,\n};\n\n/**\n * Where a board's circuit voltages came from:\n * - `explicit` — `phaseVoltageV` and/or `lineToLineVoltageV` were declared;\n * - `legacy` — only the single `voltageV` was declared; it is used as-is for\n *   every way (line-to-neutral reading for single-pole ways, line-to-line for\n *   2-pole/three-phase ways), which is only right when the board's ways are all\n *   one kind — PWR-B03 flags the ambiguous cases;\n * - `default` — nothing declared; the code-mode default is assumed.\n */\nexport type VoltageBasis = \"explicit\" | \"legacy\" | \"default\";\n\nexport interface ResolvedDerating {\n  codeMode: \"NEC\" | \"IEC\";\n  /** Continuous-load ceiling as a percentage of the breaker rating. */\n  maxUtilization: number;\n  /** Kept available but defaulted to 1: continuous IT load has no diversity. */\n  diversityFactor: number;\n  /**\n   * The board's declared `voltageV` (option → board), or the line-to-neutral\n   * default when none is declared. Kept for compatibility; the load rules read\n   * {@link ResolvedDerating.phaseVoltageV} / {@link ResolvedDerating.lineToLineVoltageV}.\n   */\n  voltageV: number;\n  /** Voltage a single-pole way supplies (line-to-neutral). */\n  phaseVoltageV: number;\n  /** Voltage a 2-pole or three-phase way supplies (line-to-line). */\n  lineToLineVoltageV: number;\n  /** Power factor applied to watt → amp conversion (default 1). */\n  powerFactor: number;\n  /** How the voltages were resolved — see {@link VoltageBasis}. */\n  voltageBasis: VoltageBasis;\n}\n\n/**\n * Resolve the derating regime for one way (PWR-B03).\n *\n * NEC 210.19/210.20 require 125% sizing for continuous load, which is 80%\n * loading on a standard breaker. BS 7671 has no equivalent rule (433.1.1 is\n * `Ib ≤ In ≤ Iz`), so `IEC` mode defaults to 100%. Precedence: explicit rule\n * option → board field → code default.\n *\n * Voltages (rule option → board field):\n * - `phaseVoltageV` / `lineToLineVoltageV` — the explicit pair; either one\n *   derives the other by √3.\n * - otherwise `voltageV` — the legacy single board voltage, used unchanged for\n *   every way kind.\n * - otherwise the code-mode default: 120/208 V (`NEC`), 230/400 V (`IEC`).\n */\nexport function resolveDerating(\n  ref: PowerWayRef,\n  options: PowerRuleOptions,\n): ResolvedDerating {\n  const boardData = ref.board.data ?? {};\n  const codeMode =\n    (options.codeMode as \"NEC\" | \"IEC\" | undefined) ??\n    (boardData.codeMode as \"NEC\" | \"IEC\" | undefined) ??\n    \"NEC\";\n  const codeDefault = codeMode === \"IEC\" ? 100 : 80;\n  const maxUtilization =\n    num(options.maxUtilization) ?? num(boardData.maxUtilization) ?? codeDefault;\n\n  const explicitPhase = positive(options.phaseVoltageV) ?? positive(boardData.phaseVoltageV);\n  const explicitLine =\n    positive(options.lineToLineVoltageV) ?? positive(boardData.lineToLineVoltageV);\n  const legacy = positive(options.voltageV) ?? positive(boardData.voltageV);\n\n  let phaseVoltageV: number;\n  let lineToLineVoltageV: number;\n  let voltageBasis: VoltageBasis;\n  if (explicitPhase !== undefined || explicitLine !== undefined) {\n    phaseVoltageV = explicitPhase ?? Math.round((explicitLine as number) / THREE_PHASE_FACTOR);\n    lineToLineVoltageV = explicitLine ?? Math.round((explicitPhase as number) * THREE_PHASE_FACTOR);\n    voltageBasis = \"explicit\";\n  } else if (legacy !== undefined) {\n    phaseVoltageV = legacy;\n    lineToLineVoltageV = legacy;\n    voltageBasis = \"legacy\";\n  } else {\n    phaseVoltageV = DEFAULT_PHASE_VOLTAGE_V[codeMode];\n    lineToLineVoltageV = Math.round(phaseVoltageV * THREE_PHASE_FACTOR);\n    voltageBasis = \"default\";\n  }\n\n  const powerFactor =\n    unitInterval(options.powerFactor) ?? unitInterval(boardData.powerFactor) ?? 1;\n  return {\n    codeMode,\n    maxUtilization,\n    diversityFactor: opt(options, \"diversityFactor\", 1),\n    voltageV: legacy ?? phaseVoltageV,\n    phaseVoltageV,\n    lineToLineVoltageV,\n    powerFactor,\n    voltageBasis,\n  };\n}\n\n/** The kind of circuit a way supplies, for its voltage basis. */\nexport function wayCircuitKind(way: BreakerWay): \"three-phase\" | \"two-pole\" | \"single-pole\" {\n  if (isThreePhaseWay(way)) return \"three-phase\";\n  if (way.poles === 2) return \"two-pole\";\n  return \"single-pole\";\n}\n\n/**\n * The voltage basis of one way's circuit, for watt → amp conversion:\n *\n * - a way's own `voltageV` wins (the circuit voltage as wired; line-to-line\n *   for a three-phase way);\n * - three-phase (`phase: \"3P\"` or `poles: 3`): line-to-line voltage, factor √3\n *   — `I = P / (√3 · V_LL)`, i.e. `P / (3 · V_LN)`;\n * - two-pole (`poles: 2`, e.g. a US 208 V circuit): line-to-line, factor 1;\n * - otherwise (single-pole `L1`/`L2`/`L3`): line-to-neutral, factor 1.\n */\nexport function wayCircuitVoltage(\n  way: BreakerWay,\n  derating: Pick<ResolvedDerating, \"phaseVoltageV\" | \"lineToLineVoltageV\">,\n): { voltageV: number; factor: number; basis: \"way\" | \"three-phase\" | \"line-to-line\" | \"line-to-neutral\" } {\n  const factor = phaseFactor(way);\n  const wayVoltage = positive((way as { voltageV?: unknown }).voltageV);\n  if (wayVoltage !== undefined) return { voltageV: wayVoltage, factor, basis: \"way\" };\n  const kind = wayCircuitKind(way);\n  if (kind === \"three-phase\") {\n    return { voltageV: derating.lineToLineVoltageV, factor, basis: \"three-phase\" };\n  }\n  if (kind === \"two-pole\") {\n    return { voltageV: derating.lineToLineVoltageV, factor: 1, basis: \"line-to-line\" };\n  }\n  return { voltageV: derating.phaseVoltageV, factor: 1, basis: \"line-to-neutral\" };\n}\n\nfunction positive(value: unknown): number | undefined {\n  const n = num(value);\n  return n !== undefined && n > 0 ? n : undefined;\n}\n\nfunction unitInterval(value: unknown): number | undefined {\n  const n = num(value);\n  return n !== undefined && n > 0 && n <= 1 ? n : undefined;\n}\n\n/** Ways carrying a usable rating, with their board context. */\nexport function ratedWays(ctx: PowerValidationContext): PowerWayRef[] {\n  return ctx.getWays().filter((ref) => (num(ref.way.ratingA) ?? 0) > 0);\n}\n\n/** Lowest known connector amperage on one link (the cord's weakest end). */\nexport function linkMinAmperage(\n  ctx: PowerValidationContext,\n  edgeId: string,\n): number | undefined {\n  const { supply, load } = ctx.getLinkConnectors(edgeId);\n  const values = [supply?.amperage, load?.amperage].filter(\n    (value): value is number => typeof value === \"number\",\n  );\n  return values.length > 0 ? Math.min(...values) : undefined;\n}\n\nexport function round(value: number, places = 1): number {\n  const factor = 10 ** places;\n  return Math.round(value * factor) / factor;\n}\n\n/**\n * Edge ids of every link carried by a way — the way's own outgoing links plus\n * every link downstream of them. Rules that charge load to a breaker must\n * restrict themselves to this set: a dual-corded device is downstream of both\n * of its ways, but each way only carries one of its cords.\n */\nexport function downstreamLinkEdgeIds(\n  ctx: PowerValidationContext,\n  wayKey: PowerElementKey,\n): Set<string> {\n  const out = new Set<string>();\n  const seenNodes = new Set<string>();\n  for (const link of ctx.getWayLinks(wayKey)) {\n    out.add(link.edgeId);\n    for (const nodeId of ctx.getDownstreamNodeIdsOfLink(link.edgeId)) {\n      if (seenNodes.has(nodeId)) continue;\n      seenNodes.add(nodeId);\n      for (const down of ctx.getDownstreamLinks(nodeId)) out.add(down.edgeId);\n    }\n  }\n  return out;\n}\n","/**\n * Tier A of the standards catalogue — every rule computable from the C1/C2\n * model with no new fields.\n *\n * Implements `docs/electrical-validation-rules-2026-08-14.md` Tier A:\n * PWR-A01…A07 and A09…A12. A08 (\"breaker state must not alter topology\") is an\n * invariant test over the library, not a graph rule, so it is deliberately\n * absent here.\n *\n * Every rule is a graph sum or comparison. None of them simulate electricity.\n */\n\nimport {\n  CIRCUIT_TERMINATION_REASON,\n  OUTLET_TO_OUTLET_REASON,\n  PDU_INLET_FEED_REASON,\n  WAY_TO_WAY_REASON,\n} from \"../../core/connection-validation\";\nimport type { PowerEndpointRole } from \"../../utils/endpoint-metadata\";\nimport { getPowerEndpointRole } from \"../../utils/endpoint-metadata\";\nimport { parseHandleId } from \"../../utils/handle-ids\";\nimport { hasPduInlet } from \"../../utils/power-connectors\";\nimport { isValidIecMate } from \"../connectors\";\nimport type { PowerIssueDraft, PowerRule, PowerValidationContext } from \"../types\";\nimport {\n  downstreamLinkEdgeIds,\n  linkMinAmperage,\n  num,\n  opt,\n  ratedWays,\n  resolveDerating,\n  round,\n} from \"./shared\";\n\n/** Consumers: leaf nodes that draw power. */\nfunction consumers(ctx: PowerValidationContext): string[] {\n  return ctx.nodes.filter((node) => ctx.isConsumer(node.id)).map((node) => node.id);\n}\n\nfunction label(ctx: PowerValidationContext, nodeId: string): string {\n  return ctx.describeElement(nodeId);\n}\n\n// ---------------------------------------------------------------------------\n// PWR-A01 — Unprotected tap / breaker-less feed\n// ---------------------------------------------------------------------------\n\n/**\n * NEC 210.20(A) / BS 7671 433.1.1: every path from a device back to a source\n * must pass through overcurrent protection — here, a distribution-board\n * breaker way.\n */\nexport const pwrA01UnprotectedTap: PowerRule = {\n  id: \"PWR-A01\",\n  name: \"Unprotected tap / breaker-less feed\",\n  basis: \"standard\",\n  defaultSeverity: \"error\",\n  description:\n    \"Every path from a device to a source must pass through a breaker way (NEC 210.20(A); BS 7671 433.1.1).\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const nodeId of consumers(ctx)) {\n      const unprotected = findUnprotectedPath(ctx, nodeId);\n      if (!unprotected) continue;\n      issues.push({\n        id: `PWR-A01:${nodeId}`,\n        message: `${label(ctx, nodeId)} has a supply path with no breaker between it and ${label(\n          ctx,\n          unprotected.originId,\n        )} — feed it from a protected way.`,\n        nodeIds: [nodeId, unprotected.originId],\n        edgeIds: unprotected.edgeIds,\n      });\n    }\n    return issues;\n  },\n};\n\n/**\n * Find a supply path from `nodeId` back to an origin (a node with no feeds)\n * that crosses no breaker way. Only way-less feeds can extend such a path, so\n * the walk follows those alone, depth-first in feed order, and remembers nodes\n * already proven to have no unprotected path. That keeps it linear in the size\n * of the graph — enumerating every simple path (the previous approach) grew\n * exponentially on meshed distribution.\n */\nfunction findUnprotectedPath(\n  ctx: PowerValidationContext,\n  nodeId: string,\n): { originId: string; edgeIds: string[] } | undefined {\n  const exhausted = new Set<string>();\n  const onPath = new Set<string>();\n  const walk = (\n    currentId: string,\n    edgeIds: string[],\n  ): { originId: string; edgeIds: string[] } | undefined => {\n    const feeds = ctx.getFeeds(currentId);\n    if (feeds.length === 0) {\n      // Reached an origin without crossing a breaker.\n      return { originId: currentId, edgeIds };\n    }\n    onPath.add(currentId);\n    for (const feed of feeds) {\n      // A feed through a way protects everything beyond it.\n      if (feed.wayKey !== undefined) continue;\n      const next = feed.supplyNodeId;\n      if (onPath.has(next) || exhausted.has(next)) continue;\n      const found = walk(next, [...edgeIds, feed.edgeId]);\n      if (found) return found;\n    }\n    onPath.delete(currentId);\n    exhausted.add(currentId);\n    return undefined;\n  };\n  return walk(nodeId, []);\n}\n\n// ---------------------------------------------------------------------------\n// PWR-A02 — Shared upstream on A/B feeds (paper 2N)\n// ---------------------------------------------------------------------------\n\n/**\n * Uptime Tier III/IV redundancy: the A and B feeds of a dual-corded device must\n * not converge upstream. Error when they share a way or an intermediate\n * board/PDU; warning when the only shared element is the incomer itself.\n */\nexport const pwrA02SharedUpstream: PowerRule = {\n  id: \"PWR-A02\",\n  name: \"Shared upstream on A/B feeds\",\n  basis: \"convention\",\n  defaultSeverity: \"error\",\n  description:\n    \"Redundant feeds that converge on a shared upstream way, board, or PDU are a single point of failure (Uptime Tier III/IV).\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const sources = new Set(ctx.sourceNodeIds);\n    for (const nodeId of consumers(ctx)) {\n      const feeds = ctx.getFeeds(nodeId);\n      if (feeds.length < 2) continue;\n      for (let i = 0; i < feeds.length; i += 1) {\n        for (let j = i + 1; j < feeds.length; j += 1) {\n          const a = ctx.getAncestorsOfLink(feeds[i].edgeId);\n          const b = ctx.getAncestorsOfLink(feeds[j].edgeId);\n          const shared = ctx.getDeepestShared(a, b);\n          if (!shared) continue;\n          const { nodeId: sharedNodeId, wayId } = ctx.parseElementKey(shared);\n          const onlyIncomer = !wayId && sources.has(sharedNodeId);\n          issues.push({\n            id: `PWR-A02:${nodeId}:${feeds[i].edgeId}:${feeds[j].edgeId}`,\n            severity: onlyIncomer ? \"warning\" : \"error\",\n            message: onlyIncomer\n              ? `${label(ctx, nodeId)}'s redundant feeds both originate at ${ctx.describeElement(\n                  shared,\n                )} — the incomer is a single point of failure.`\n              : `${label(ctx, nodeId)}'s redundant feeds both pass through ${ctx.describeElement(\n                  shared,\n                )} — split them onto independent upstream paths.`,\n            nodeIds: [nodeId, sharedNodeId],\n            edgeIds: [feeds[i].edgeId, feeds[j].edgeId],\n            wayIds: wayId ? [shared] : undefined,\n            details: { sharedElement: shared },\n          });\n        }\n      }\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A03 — Both cords on the same PDU\n// ---------------------------------------------------------------------------\n\n/** Uptime dual-corded practice: A and B must land on different PDUs. */\nexport const pwrA03SamePdu: PowerRule = {\n  id: \"PWR-A03\",\n  name: \"Both cords on the same PDU\",\n  basis: \"convention\",\n  defaultSeverity: \"error\",\n  description: \"A dual-corded device must not take both feeds from one PDU.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const nodeId of consumers(ctx)) {\n      const feeds = ctx.getFeeds(nodeId);\n      if (feeds.length < 2) continue;\n      const byPdu = new Map<string, string[]>();\n      for (const feed of feeds) {\n        if (ctx.getNode(feed.supplyNodeId)?.type !== \"vertical-pdu\") continue;\n        const list = byPdu.get(feed.supplyNodeId);\n        if (list) list.push(feed.edgeId);\n        else byPdu.set(feed.supplyNodeId, [feed.edgeId]);\n      }\n      for (const [pduId, edgeIds] of byPdu) {\n        if (edgeIds.length < 2) continue;\n        issues.push({\n          id: `PWR-A03:${nodeId}:${pduId}`,\n          message: `${label(ctx, nodeId)} takes ${edgeIds.length} cords from ${label(\n            ctx,\n            pduId,\n          )} — move one cord to the redundant PDU.`,\n          nodeIds: [nodeId, pduId],\n          edgeIds,\n        });\n      }\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A04 — Single-corded device in a dual-path design\n// ---------------------------------------------------------------------------\n\n/**\n * Uptime Tier III: a device with fewer feeds than `expectedFeeds` (default 2)\n * cannot survive a maintenance outage on its single path. A device that\n * declares `expectedFeeds: 1` downgrades the finding to info.\n */\nexport const pwrA04SingleCorded: PowerRule = {\n  id: \"PWR-A04\",\n  name: \"Single-corded device in dual-path design\",\n  basis: \"convention\",\n  defaultSeverity: \"warning\",\n  defaultOptions: { expectedFeeds: 2 },\n  description: \"Devices should have as many feeds as the design expects.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const fallbackExpected = opt(ctx.options, \"expectedFeeds\", 2);\n    for (const node of ctx.nodes) {\n      if (ctx.isSourceCapable(node.id)) continue;\n      const declared = num(node.data?.expectedFeeds);\n      const feeds = ctx.getFeeds(node.id).length;\n      // Only judge nodes that are actually in the power graph (or that opted in\n      // by declaring expectedFeeds) — silent nodes are A01's business, not ours.\n      if (feeds === 0 && declared === undefined) continue;\n      const expected = declared ?? fallbackExpected;\n      if (feeds >= expected) continue;\n      issues.push({\n        id: `PWR-A04:${node.id}`,\n        severity: declared === 1 ? \"info\" : undefined,\n        message: `${label(ctx, node.id)} has ${feeds} power feed${\n          feeds === 1 ? \"\" : \"s\"\n        } but the design expects ${expected} — add the missing feed or set expectedFeeds.`,\n        nodeIds: [node.id],\n        details: { feeds, expected },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A05 — Connector coupler mismatch\n// ---------------------------------------------------------------------------\n\n/**\n * IEC 60320 directed mate table. Only pairings where one end resolves to an\n * inlet and the other to a cord connector are judged — an unmodelled or\n * one-sided pairing is skipped rather than guessed at.\n */\nexport const pwrA05ConnectorMismatch: PowerRule = {\n  id: \"PWR-A05\",\n  name: \"Connector coupler mismatch\",\n  basis: \"standard\",\n  defaultSeverity: \"error\",\n  description: \"Power cord connectors must mate with the inlet they land on (IEC 60320).\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const link of ctx.links) {\n      const { supply, load } = ctx.getLinkConnectors(link.edgeId);\n      if (!supply || !load) continue;\n      let inlet;\n      let connector;\n      if (supply.role === \"inlet\" && load.role === \"connector\") {\n        inlet = supply;\n        connector = load;\n      } else if (load.role === \"inlet\" && supply.role === \"connector\") {\n        inlet = load;\n        connector = supply;\n      } else {\n        continue;\n      }\n      if (isValidIecMate(inlet.type, connector.type)) continue;\n      issues.push({\n        id: `PWR-A05:${link.edgeId}`,\n        message: `${connector.type} cord will not mate with a ${inlet.type} inlet — fit the correct IEC 60320 cord.`,\n        nodeIds: [link.supplyNodeId, link.loadNodeId],\n        edgeIds: [link.edgeId],\n        details: { inlet: inlet.type, connector: connector.type },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A06 — Connector rating below breaker rating\n// ---------------------------------------------------------------------------\n\n/** A strip's own outlet/bank protection rating, when it declares one. */\nfunction outletProtectionRating(\n  ctx: PowerValidationContext,\n  nodeId: string,\n): number | undefined {\n  const node = ctx.getNode(nodeId);\n  if (node?.type !== \"vertical-pdu\") return undefined;\n  const rating = num(node.data?.outletProtectionA);\n  return rating !== undefined && rating > 0 ? rating : undefined;\n}\n\n/**\n * Edge ids of the cords a protective device guards: its own `direct` links,\n * plus (scope `downstream`) everything below them — but the walk stops at the\n * next protective device: a distribution board (its ways are judged on their\n * own) or a PDU that declares `outletProtectionA` (judged against that).\n */\nfunction protectedLinkEdgeIds(\n  ctx: PowerValidationContext,\n  direct: readonly { edgeId: string; loadNodeId: string }[],\n  scope: \"direct\" | \"downstream\",\n): Set<string> {\n  const linkIds = new Set(direct.map((link) => link.edgeId));\n  if (scope === \"direct\") return linkIds;\n  const seen = new Set<string>();\n  const stack = direct.map((link) => link.loadNodeId);\n  while (stack.length > 0) {\n    const nodeId = stack.pop() as string;\n    if (seen.has(nodeId)) continue;\n    seen.add(nodeId);\n    if (ctx.getNode(nodeId)?.type === \"distribution-board\") continue;\n    if (outletProtectionRating(ctx, nodeId) !== undefined) continue;\n    for (const next of ctx.getDownstreamLinks(nodeId)) {\n      linkIds.add(next.edgeId);\n      stack.push(next.loadNodeId);\n    }\n  }\n  return linkIds;\n}\n\n/**\n * IEC 60320 + NEC 240.4: a cord rated below the breaker protecting it is the\n * weakest link on the circuit. `scope` defaults to `downstream` (the whole\n * path below the way, as the catalogue defines it); set it to `direct` to\n * judge only the cords landing on the way itself.\n *\n * The downstream walk stops at the next protective device. A sub-board's ways\n * are judged on their own, and a PDU that declares its outlet/bank protection\n * (`outletProtectionA`) has the cords below it judged against that rating —\n * so a 32 A way feeding a strip with 16 A banks and C13 cords is not reported\n * against the 32 A breaker. A strip that declares no protection is still\n * judged against the upstream way (the conservative catalogue reading).\n */\nexport const pwrA06ConnectorUnderBreaker: PowerRule = {\n  id: \"PWR-A06\",\n  name: \"Connector rating below breaker rating\",\n  basis: \"standard\",\n  defaultSeverity: \"error\",\n  defaultOptions: { scope: \"downstream\" },\n  description: \"The cord on a circuit must be rated at least as high as its breaker.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const scope = ctx.options.scope === \"direct\" ? \"direct\" : \"downstream\";\n    for (const ref of ratedWays(ctx)) {\n      const rating = num(ref.way.ratingA) as number;\n      const linkIds = protectedLinkEdgeIds(ctx, ctx.getWayLinks(ref.wayKey), scope);\n      for (const edgeId of linkIds) {\n        const amperage = linkMinAmperage(ctx, edgeId);\n        if (amperage === undefined || amperage >= rating) continue;\n        issues.push({\n          id: `PWR-A06:${ref.wayKey}:${edgeId}`,\n          message: `A ${amperage} A cord sits under the ${rating} A breaker on ${ctx.describeElement(\n            ref.wayKey,\n          )} — the cord, not the breaker, limits this circuit.`,\n          nodeIds: [ref.boardId],\n          edgeIds: [edgeId],\n          wayIds: [ref.wayKey],\n          details: { cordAmperage: amperage, ratingA: rating },\n        });\n      }\n    }\n    for (const node of ctx.nodes) {\n      const rating = outletProtectionRating(ctx, node.id);\n      if (rating === undefined) continue;\n      const linkIds = protectedLinkEdgeIds(ctx, ctx.getDownstreamLinks(node.id), scope);\n      for (const edgeId of linkIds) {\n        const amperage = linkMinAmperage(ctx, edgeId);\n        if (amperage === undefined || amperage >= rating) continue;\n        issues.push({\n          id: `PWR-A06:${ctx.elementKey(node.id)}:${edgeId}`,\n          message: `A ${amperage} A cord sits under the ${rating} A outlet protection of ${ctx.describeElement(\n            node.id,\n          )} — the cord, not the protection, limits this circuit.`,\n          nodeIds: [node.id],\n          edgeIds: [edgeId],\n          details: { cordAmperage: amperage, ratingA: rating, protection: \"pdu-outlet\" },\n        });\n      }\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A07 — Open/tripped breaker leaves a device unpowered\n// ---------------------------------------------------------------------------\n\n/**\n * Operational rule driven by the energisation overlay (PRD C4). Returns no\n * issues when the consumer supplied no `config.energization` — this rule never\n * recomputes energisation itself.\n */\nexport const pwrA07UnpoweredByBreaker: PowerRule = {\n  id: \"PWR-A07\",\n  name: \"Open or tripped breaker leaves device unpowered\",\n  basis: \"convention\",\n  defaultSeverity: \"error\",\n  description:\n    \"Reports devices left unpowered or degraded by the current breaker states.\",\n  evaluate(ctx) {\n    const nodeState = ctx.energization?.devices;\n    if (!nodeState) return [];\n    const issues: PowerIssueDraft[] = [];\n    const wayByKey = new Map(ctx.getWays().map((ref) => [ref.wayKey, ref]));\n    for (const [nodeId, state] of Object.entries(nodeState)) {\n      if (state !== \"unpowered\" && state !== \"degraded\") continue;\n      const ancestors = ctx.getAncestors(nodeId);\n      const tripped: string[] = [];\n      const open: string[] = [];\n      for (const key of ancestors) {\n        const ref = wayByKey.get(key);\n        if (!ref) continue;\n        if (ref.state?.switchState === \"tripped\") tripped.push(key);\n        else if (ref.state?.switchState === \"open\") open.push(key);\n      }\n      if (state === \"degraded\") {\n        if (tripped.length === 0 && open.length === 0) continue;\n        issues.push({\n          id: `PWR-A07:${nodeId}`,\n          severity: \"warning\",\n          message: `${label(ctx, nodeId)} has lost a redundant feed to ${\n            tripped.length > 0 ? \"a tripped\" : \"an open\"\n          } breaker — it is running without redundancy.`,\n          nodeIds: [nodeId],\n          wayIds: [...tripped, ...open],\n        });\n        continue;\n      }\n      issues.push({\n        id: `PWR-A07:${nodeId}`,\n        severity: tripped.length > 0 ? \"error\" : \"warning\",\n        message:\n          tripped.length > 0\n            ? `${label(ctx, nodeId)} is unpowered — ${ctx.describeElement(\n                tripped[0],\n              )} has tripped.`\n            : `${label(ctx, nodeId)} is unpowered — ${\n                open.length > 0\n                  ? `${ctx.describeElement(open[0])} is open`\n                  : \"no live supply path reaches it\"\n              }.`,\n        nodeIds: [nodeId],\n        wayIds: [...tripped, ...open],\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A09 — Non-standard breaker rating\n// ---------------------------------------------------------------------------\n\n/**\n * IEC 60898-1 preferred currents / NEC 240.6(A) standard ratings.\n *\n * Info only, and deliberately so: NEC 240.6(B)/(C) explicitly permit\n * non-standard ratings, and the IEC list below is secondary-sourced (the 8 A\n * and 125 A entries in particular) pending verification against a purchased\n * copy of the standard. Both ladders are configurable.\n */\nexport const IEC_60898_RATING_LADDER: readonly number[] = [\n  6, 8, 10, 13, 16, 20, 25, 32, 40, 50, 63, 80, 100, 125,\n];\n\nexport const NEC_240_6_RATING_LADDER: readonly number[] = [\n  15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 110, 125, 150, 175, 200,\n  225, 250, 300, 350, 400, 450, 500, 600, 700, 800, 1000, 1200, 1600, 2000, 2500,\n  3000, 4000, 5000, 6000,\n];\n\nexport const pwrA09NonStandardRating: PowerRule = {\n  id: \"PWR-A09\",\n  name: \"Non-standard breaker rating\",\n  basis: \"standard\",\n  defaultSeverity: \"info\",\n  defaultOptions: {\n    iecLadder: IEC_60898_RATING_LADDER,\n    necLadder: NEC_240_6_RATING_LADDER,\n  },\n  description:\n    \"Flags breaker ratings outside the preferred-current ladder. Informational — non-standard ratings are permitted.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const ref of ratedWays(ctx)) {\n      const rating = num(ref.way.ratingA) as number;\n      const { codeMode } = resolveDerating(ref, ctx.options);\n      const ladder = (\n        codeMode === \"NEC\" ? ctx.options.necLadder : ctx.options.iecLadder\n      ) as number[] | undefined;\n      const list = Array.isArray(ladder)\n        ? ladder\n        : codeMode === \"NEC\"\n          ? NEC_240_6_RATING_LADDER\n          : IEC_60898_RATING_LADDER;\n      if (list.includes(rating)) continue;\n      issues.push({\n        id: `PWR-A09:${ref.wayKey}`,\n        message: `${ctx.describeElement(\n          ref.wayKey,\n        )} is rated ${rating} A, which is not a ${codeMode} preferred rating — confirm the device exists in the range you buy from.`,\n        nodeIds: [ref.boardId],\n        wayIds: [ref.wayKey],\n        details: { ratingA: rating, codeMode },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A10 — Loop / ring in the power graph\n// ---------------------------------------------------------------------------\n\n/**\n * Topology sanity. Dual-feed convergence at a consuming device is legitimate\n * and forms a cycle only *through* that device, so consumers are excluded from\n * the graph before cycle detection; what survives is a genuine ring between\n * distribution elements.\n */\nexport const pwrA10PowerLoop: PowerRule = {\n  id: \"PWR-A10\",\n  name: \"Loop or ring in the power graph\",\n  basis: \"heuristic\",\n  defaultSeverity: \"warning\",\n  description: \"Detects rings between distribution elements that a radial design should not have.\",\n  evaluate(ctx) {\n    const parent = new Map<string, string>();\n    const find = (id: string): string => {\n      let root = id;\n      while (parent.get(root) && parent.get(root) !== root) {\n        root = parent.get(root) as string;\n      }\n      let cursor = id;\n      while (parent.get(cursor) && parent.get(cursor) !== cursor) {\n        const next = parent.get(cursor) as string;\n        parent.set(cursor, root);\n        cursor = next;\n      }\n      return root;\n    };\n    const issues: PowerIssueDraft[] = [];\n    for (const link of ctx.links) {\n      if (ctx.isConsumer(link.supplyNodeId) || ctx.isConsumer(link.loadNodeId)) {\n        continue;\n      }\n      if (!parent.has(link.supplyNodeId)) parent.set(link.supplyNodeId, link.supplyNodeId);\n      if (!parent.has(link.loadNodeId)) parent.set(link.loadNodeId, link.loadNodeId);\n      const a = find(link.supplyNodeId);\n      const b = find(link.loadNodeId);\n      if (a === b) {\n        issues.push({\n          id: `PWR-A10:${link.edgeId}`,\n          message: `This feed closes a ring between ${label(\n            ctx,\n            link.supplyNodeId,\n          )} and ${label(ctx, link.loadNodeId)} — power distribution here is expected to be radial.`,\n          nodeIds: [link.supplyNodeId, link.loadNodeId],\n          edgeIds: [link.edgeId],\n        });\n        continue;\n      }\n      parent.set(a, b);\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A11 — Way over-subscribed by connector count\n// ---------------------------------------------------------------------------\n\n/**\n * A practical bound, not a load figure: if every cord downstream of a way drew\n * its full plate rating at once, would the breaker hold? Messages say so\n * explicitly — this is not measured utilisation.\n */\nexport const pwrA11WayConnectorBound: PowerRule = {\n  id: \"PWR-A11\",\n  name: \"Way over-subscribed by connector count\",\n  basis: \"heuristic\",\n  defaultSeverity: \"warning\",\n  description: \"Sums downstream cord plate ratings against the breaker rating as a worst-case bound.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const ref of ratedWays(ctx)) {\n      const rating = num(ref.way.ratingA) as number;\n      const { maxUtilization } = resolveDerating(ref, ctx.options);\n      const limit = (rating * maxUtilization) / 100;\n      let total = 0;\n      const edgeIds: string[] = [];\n      // Only cords carried by *this* way count. A dual-corded device also has\n      // a feed from the redundant way; charging it here would double-count the\n      // load and highlight a cord that is not on the flagged breaker.\n      const ownLinkEdgeIds = downstreamLinkEdgeIds(ctx, ref.wayKey);\n      const seenFeeds = new Set<string>();\n      for (const link of ctx.getWayLinks(ref.wayKey)) {\n        for (const nodeId of ctx.getDownstreamNodeIdsOfLink(link.edgeId)) {\n          if (!ctx.isConsumer(nodeId)) continue;\n          for (const feed of ctx.getFeeds(nodeId)) {\n            if (!ownLinkEdgeIds.has(feed.edgeId)) continue;\n            if (seenFeeds.has(feed.edgeId)) continue;\n            seenFeeds.add(feed.edgeId);\n            const amperage = linkMinAmperage(ctx, feed.edgeId);\n            if (amperage === undefined) continue;\n            total += amperage;\n            edgeIds.push(feed.edgeId);\n          }\n        }\n      }\n      if (total === 0 || total <= limit) continue;\n      issues.push({\n        id: `PWR-A11:${ref.wayKey}`,\n        message: `Cords downstream of ${ctx.describeElement(\n          ref.wayKey,\n        )} total ${round(total)} A of plate rating against a ${round(\n          limit,\n        )} A limit — a worst-case bound, not measured load.`,\n        nodeIds: [ref.boardId],\n        edgeIds,\n        wayIds: [ref.wayKey],\n        details: { plateTotalA: round(total), limitA: round(limit) },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A12 — Power edge length advisory\n// ---------------------------------------------------------------------------\n\n/**\n * Cable-management advisory only. This is **our** heuristic: it must never be\n * presented as a code requirement and must not cite BS 7671 (a real voltage-drop\n * check needs conductor csa, which the edge model does not carry — see B07).\n */\nexport const pwrA12LengthAdvisory: PowerRule = {\n  id: \"PWR-A12\",\n  name: \"Power cord length advisory\",\n  basis: \"heuristic\",\n  defaultSeverity: \"info\",\n  defaultOptions: { maxLengthCm: 300 },\n  description: \"Flags long power cords as a cable-management advisory.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const maxLengthCm = opt(ctx.options, \"maxLengthCm\", 300);\n    for (const link of ctx.links) {\n      const length =\n        num(link.edge.data?.lengthCm) ?? num(link.edge.data?.length);\n      if (length === undefined || length <= maxLengthCm) continue;\n      issues.push({\n        id: `PWR-A12:${link.edgeId}`,\n        message: `This cord is ${length} cm, over the ${maxLengthCm} cm cable-management advisory — consider a shorter route or a closer outlet.`,\n        nodeIds: [link.supplyNodeId, link.loadNodeId],\n        edgeIds: [link.edgeId],\n        details: { lengthCm: length, maxLengthCm },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-A13 — Circuit termination\n// ---------------------------------------------------------------------------\n\n/**\n * Circuit/cord semantics over a materialised edge list — the graph-side twin of\n * the interactive `defaultValidateConnection`. Every pairing the canvas refuses\n * is judged here, citing the same reason constant, so a diagram assembled by\n * import or by script is graded exactly as one drawn by hand:\n *\n * - a way must land on a PDU/board **inlet** — never on a device IEC inlet or a\n *   PDU outlet, which are the two ends of a *cord*\n *   (`CIRCUIT_TERMINATION_REASON`);\n * - two ways are never wired together (`WAY_TO_WAY_REASON`);\n * - nothing but a way feeds an inlet, so an outlet-to-inlet daisy chain is a\n *   finding (`PDU_INLET_FEED_REASON`);\n * - a cord runs outlet → inlet, never outlet → outlet\n *   (`OUTLET_TO_OUTLET_REASON`).\n *\n * **Grading, and the pre-inlet-model migration path.** A way landing on an\n * outlet of a strip that declares *no* inlet is not a wiring error the author\n * can fix by re-routing: the strip has nowhere else for the circuit to land.\n * That grades `warning` and the message names the migration. As soon as the\n * strip declares an inlet — or the landing is a device inlet — there is a\n * correct destination, and it is an `error`.\n *\n * Scope hedge, matching PWR-A05: only endpoints the model can actually place\n * are judged. An unmodelled handle resolves to `unknown` and is skipped rather\n * than guessed at, so graphs authored before the inlet model do not light up\n * with findings the author cannot act on.\n *\n * Energisation is deliberately untouched: a daisy chain physically conducts, so\n * `computeEnergization` still propagates through it. This rule flags it; it\n * does not pretend the cable is dead.\n */\nexport const pwrA13CircuitTermination: PowerRule = {\n  id: \"PWR-A13\",\n  name: \"Circuit termination\",\n  basis: \"convention\",\n  defaultSeverity: \"error\",\n  description:\n    \"A breaker way terminates a branch circuit on a PDU or board inlet — never on a device inlet or a PDU outlet.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n\n    /** `<boardId>#<wayId>` when the end is a breaker way, else the node id. */\n    const elementOf = (nodeId: string, handle: string | undefined): string => {\n      if (typeof handle !== \"string\") return ctx.elementKey(nodeId);\n      const shape = parseHandleId(handle).shape;\n      return shape.kind === \"breaker-way\"\n        ? ctx.elementKey(nodeId, shape.wayId)\n        : ctx.elementKey(nodeId);\n    };\n\n    for (const link of ctx.links) {\n      const ends = [\n        { nodeId: link.supplyNodeId, handle: link.supplyHandle },\n        { nodeId: link.loadNodeId, handle: link.loadHandle },\n      ].map((end) => ({\n        ...end,\n        role: getPowerEndpointRole(ctx.getNode(end.nodeId), end.handle),\n      }));\n\n      // An unplaceable end is never judged (the A05 scope hedge).\n      if (ends.some((end) => end.role === \"unknown\")) continue;\n\n      const indexOfRole = (role: PowerEndpointRole): number =>\n        ends.findIndex((end) => end.role === role);\n\n      const push = (draft: {\n        near: (typeof ends)[number];\n        far: (typeof ends)[number];\n        message: string;\n        severity?: \"error\" | \"warning\";\n        details: Record<string, unknown>;\n      }): void => {\n        // Only a breaker-way end classifies as `circuit-source`, so every key\n        // collected here is a `<boardId>#<wayId>` way element.\n        const wayIds = [draft.near, draft.far]\n          .filter((end) => end.role === \"circuit-source\")\n          .map((end) => elementOf(end.nodeId, end.handle));\n        issues.push({\n          id: `PWR-A13:${link.edgeId}`,\n          message: draft.message,\n          nodeIds: [draft.near.nodeId, draft.far.nodeId],\n          edgeIds: [link.edgeId],\n          ...(wayIds.length > 0 ? { wayIds } : {}),\n          ...(draft.severity ? { severity: draft.severity } : {}),\n          details: draft.details,\n        });\n      };\n\n      const wayIndex = indexOfRole(\"circuit-source\");\n      if (wayIndex >= 0) {\n        const near = ends[wayIndex];\n        const far = ends[1 - wayIndex];\n        if (far.role === \"circuit-sink\") continue; // way → PDU/board inlet ✓\n\n        if (far.role === \"circuit-source\") {\n          push({\n            near,\n            far,\n            message: `${ctx.describeElement(\n              elementOf(near.nodeId, near.handle),\n            )} is wired to ${ctx.describeElement(\n              elementOf(far.nodeId, far.handle),\n            )}: ${WAY_TO_WAY_REASON}.`,\n            details: { landingRole: far.role, pairing: \"way-to-way\" },\n          });\n          continue;\n        }\n\n        // A strip with no declared inlet has nowhere else to take the circuit:\n        // that is a migration, not a mis-wiring.\n        const farNode = ctx.getNode(far.nodeId);\n        const preInletStrip =\n          far.role === \"cord-source\" &&\n          farNode?.type === \"vertical-pdu\" &&\n          !hasPduInlet(farNode.data);\n        const landing = far.role === \"cord-sink\" ? \"a device power inlet\" : \"a PDU outlet\";\n        const wayLabel = ctx.describeElement(elementOf(near.nodeId, near.handle));\n        push({\n          near,\n          far,\n          severity: preInletStrip ? \"warning\" : \"error\",\n          message: preInletStrip\n            ? `${wayLabel} lands on ${landing} of a strip that declares no inlet: declare the PDU's inlet and move this circuit to it.`\n            : `${wayLabel} lands on ${landing}: ${CIRCUIT_TERMINATION_REASON}.`,\n          details: preInletStrip\n            ? { landingRole: far.role, pduInletDeclared: false }\n            : { landingRole: far.role },\n        });\n        continue;\n      }\n\n      const inletIndex = indexOfRole(\"circuit-sink\");\n      if (inletIndex >= 0) {\n        // Nothing but a way may feed an inlet; the way case already returned.\n        const near = ends[inletIndex];\n        const far = ends[1 - inletIndex];\n        push({\n          near,\n          far,\n          message: `${ctx.describeElement(\n            near.nodeId,\n          )} is fed from ${ctx.describeElement(far.nodeId)}: ${PDU_INLET_FEED_REASON}.`,\n          details: { landingRole: far.role, pairing: \"feed-into-inlet\" },\n        });\n        continue;\n      }\n\n      if (ends[0].role === \"cord-source\" && ends[1].role === \"cord-source\") {\n        push({\n          near: ends[0],\n          far: ends[1],\n          message: `${ctx.describeElement(\n            ends[0].nodeId,\n          )} and ${ctx.describeElement(\n            ends[1].nodeId,\n          )} are joined outlet to outlet: ${OUTLET_TO_OUTLET_REASON}.`,\n          details: { landingRole: \"cord-source\", pairing: \"outlet-to-outlet\" },\n        });\n      }\n    }\n    return issues;\n  },\n};\n\n/** Tier A of the catalogue, in catalogue order. @public */\nexport const tierAPowerRules: readonly PowerRule[] = [\n  pwrA01UnprotectedTap,\n  pwrA02SharedUpstream,\n  pwrA03SamePdu,\n  pwrA04SingleCorded,\n  pwrA05ConnectorMismatch,\n  pwrA06ConnectorUnderBreaker,\n  pwrA07UnpoweredByBreaker,\n  pwrA09NonStandardRating,\n  pwrA10PowerLoop,\n  pwrA11WayConnectorBound,\n  pwrA12LengthAdvisory,\n  pwrA13CircuitTermination,\n];\n","/**\n * Tier B core of the standards catalogue — the load-vs-breaker rules that read\n * the NetBox-compatible draw fields (`maximumDrawW`, `allocatedDrawW`) and the\n * board's derating configuration.\n *\n * Implemented here: PWR-B01, PWR-B02, PWR-B03 (the catalogue's \"load core\"),\n * plus the two nesting rules PWR-B04 / PWR-B05, which need only the board\n * relation the graph already provides.\n *\n * Units follow NetBox verbatim so a sync is a straight field copy: draw is in\n * **watts**. Current is `P / (V · PF)` at the way's circuit voltage — the\n * board's line-to-neutral `voltageV` for a single-pole way, its line-to-line\n * voltage for a 2-pole way, and `P / (√3 · V_LL · PF)` for a three-phase way\n * (see `wayCircuitVoltage`).\n */\n\nimport type { BreakerWay } from \"../../types\";\nimport { getPduOutletPosition } from \"../../utils/endpoint-metadata\";\nimport { toOutletPhaseIndex } from \"../../utils/power-connectors\";\nimport type { PowerIssueDraft, PowerRule, PowerWayRef } from \"../types\";\nimport {\n  num,\n  opt,\n  ratedWays,\n  resolveDerating,\n  round,\n  wattsToAmps,\n  wayCircuitKind,\n  wayCircuitVoltage,\n} from \"./shared\";\n\ninterface WayLoad {\n  amps: number;\n  limitA: number;\n  watts: number;\n}\n\nfunction computeWayLoad(\n  ctx: Parameters<PowerRule[\"evaluate\"]>[0],\n  ref: PowerWayRef,\n  field: \"maximumDrawW\" | \"allocatedDrawW\",\n  fallback?: \"maximumDrawW\" | \"allocatedDrawW\",\n): WayLoad | undefined {\n  const rating = num(ref.way.ratingA);\n  if (!rating) return undefined;\n  const watts = ctx.sumDownstreamDrawW(ref.wayKey, field, fallback);\n  if (watts <= 0) return undefined;\n  const derating = resolveDerating(ref, ctx.options);\n  const { maxUtilization, diversityFactor, powerFactor } = derating;\n  const circuit = wayCircuitVoltage(ref.way, derating);\n  const amps =\n    wattsToAmps(watts, circuit.voltageV, circuit.factor, powerFactor) * diversityFactor;\n  return { amps, watts, limitA: (rating * maxUtilization) / 100 };\n}\n\n// ---------------------------------------------------------------------------\n// PWR-B01 — Breaker over-subscribed (derated nameplate)\n// ---------------------------------------------------------------------------\n\n/**\n * NEC 210.19/210.20 continuous-load sizing, expressed the way NetBox expresses\n * it: Σ nameplate watts, converted to amps at the board voltage and phase\n * factor, against `ratingA × maxUtilization`. Nameplate sums are an upper\n * bound, not measured utilisation — the message says so.\n */\nexport const pwrB01BreakerOverSubscribed: PowerRule = {\n  id: \"PWR-B01\",\n  name: \"Breaker over-subscribed (nameplate)\",\n  basis: \"standard\",\n  defaultSeverity: \"error\",\n  defaultOptions: { diversityFactor: 1 },\n  description:\n    \"Sums downstream nameplate draw against the derated breaker rating (NEC 210.19/210.20).\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const ref of ratedWays(ctx)) {\n      const load = computeWayLoad(ctx, ref, \"maximumDrawW\");\n      if (!load || load.amps <= load.limitA) continue;\n      issues.push({\n        id: `PWR-B01:${ref.wayKey}`,\n        message: `${ctx.describeElement(ref.wayKey)} carries ${round(\n          load.amps,\n        )} A of nameplate load against a ${round(\n          load.limitA,\n        )} A derated limit — move load off this way or raise the breaker.`,\n        nodeIds: [ref.boardId],\n        wayIds: [ref.wayKey],\n        details: {\n          watts: load.watts,\n          amps: round(load.amps),\n          limitA: round(load.limitA),\n        },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-B02 — Breaker over-allocated (budget)\n// ---------------------------------------------------------------------------\n\n/**\n * NetBox's `allocated_draw` budget view: the same sum over the design budget,\n * falling back to nameplate per device where no budget is set. A warning, not\n * an error — the budget is a planning figure.\n */\nexport const pwrB02BreakerOverAllocated: PowerRule = {\n  id: \"PWR-B02\",\n  name: \"Breaker over-allocated (budget)\",\n  basis: \"convention\",\n  defaultSeverity: \"warning\",\n  defaultOptions: { diversityFactor: 1 },\n  description: \"Sums downstream allocated draw against the derated breaker rating.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const ref of ratedWays(ctx)) {\n      const load = computeWayLoad(ctx, ref, \"allocatedDrawW\", \"maximumDrawW\");\n      if (!load || load.amps <= load.limitA) continue;\n      issues.push({\n        id: `PWR-B02:${ref.wayKey}`,\n        message: `${ctx.describeElement(ref.wayKey)} has ${round(\n          load.amps,\n        )} A allocated against a ${round(\n          load.limitA,\n        )} A derated limit — the budget on this way is over-committed.`,\n        nodeIds: [ref.boardId],\n        wayIds: [ref.wayKey],\n        details: {\n          watts: load.watts,\n          amps: round(load.amps),\n          limitA: round(load.limitA),\n        },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-B03 — Regional derating mode\n// ---------------------------------------------------------------------------\n\n/**\n * The derating regime itself is configuration, so this rule audits that\n * configuration rather than the graph: it reports boards whose `codeMode` and\n * `maxUtilization` disagree, boards that never declared a mode (and so are\n * being judged under the default), and any diversity factor other than 1.\n *\n * NEC 210.20(A) requires 125% sizing for continuous load (≡ 80% loading), with\n * 100% permitted only where the assembly is listed for it. BS 7671 433.1.1 has\n * no equivalent rule, so IEC mode runs at 100%. The BS 7671 / On-Site-Guide\n * diversity tables are domestic and retail; continuous IT load has diversity\n * ≈ 1.0, which is why `diversityFactor` defaults to 1.\n */\nexport const pwrB03DeratingMode: PowerRule = {\n  id: \"PWR-B03\",\n  name: \"Regional derating mode\",\n  basis: \"standard\",\n  defaultSeverity: \"info\",\n  defaultOptions: { diversityFactor: 1 },\n  description: \"Audits each board's codeMode / maxUtilization / diversityFactor combination.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const boards = new Map<string, PowerWayRef>();\n    for (const ref of ctx.getWays()) {\n      if (!boards.has(ref.boardId)) boards.set(ref.boardId, ref);\n    }\n    for (const [boardId, ref] of boards) {\n      const data = ref.board.data ?? {};\n      const { codeMode, maxUtilization, diversityFactor } = resolveDerating(\n        ref,\n        ctx.options,\n      );\n      const declaredMode = data.codeMode ?? ctx.options.codeMode;\n      if (!declaredMode) {\n        issues.push({\n          id: `PWR-B03:${boardId}:mode`,\n          message: `${ctx.describeElement(\n            boardId,\n          )} declares no codeMode — load rules are running under the ${codeMode} default (${maxUtilization}% derate).`,\n          nodeIds: [boardId],\n          details: { codeMode, maxUtilization },\n        });\n      }\n      const derating = resolveDerating(ref, ctx.options);\n      if (derating.voltageBasis === \"default\") {\n        issues.push({\n          id: `PWR-B03:${boardId}:voltage`,\n          message: `${ctx.describeElement(\n            boardId,\n          )} declares no voltage — load rules assume ${derating.phaseVoltageV} V line-to-neutral / ${derating.lineToLineVoltageV} V line-to-line, the ${codeMode} default.`,\n          nodeIds: [boardId],\n          details: {\n            phaseVoltageV: derating.phaseVoltageV,\n            lineToLineVoltageV: derating.lineToLineVoltageV,\n          },\n        });\n      } else if (derating.voltageBasis === \"legacy\") {\n        // A single `voltageV` is read as line-to-neutral by single-pole ways and\n        // line-to-line by 2-pole/three-phase ways. Flag the boards where that\n        // cannot be right for every way.\n        const kinds = new Set(\n          ctx\n            .getWays()\n            .filter((way) => way.boardId === boardId && !way.way.voltageV)\n            .map((way) => (wayCircuitKind(way.way) === \"single-pole\" ? \"phase\" : \"line\")),\n        );\n        const volts = derating.voltageV;\n        const reason = kinds.size > 1\n          ? `serves both single-pole and multi-pole ways with one voltageV (${volts} V)`\n          : kinds.has(\"phase\") && volts >= 380\n            ? `feeds single-pole ways at voltageV ${volts} V, which looks like a line-to-line voltage`\n            : kinds.has(\"line\") && codeMode === \"IEC\" && volts <= 250\n              ? `feeds three-phase/2-pole ways at voltageV ${volts} V, which looks like a line-to-neutral voltage`\n              : undefined;\n        if (reason) {\n          issues.push({\n            id: `PWR-B03:${boardId}:voltage-basis`,\n            basis: \"heuristic\",\n            message: `${ctx.describeElement(\n              boardId,\n            )} ${reason} — declare phaseVoltageV and lineToLineVoltageV (e.g. 230 / 400) so each way is converted at its own circuit voltage.`,\n            nodeIds: [boardId],\n            details: { voltageV: volts },\n          });\n        }\n      }\n      const hundredPercentRated = data.hundredPercentRated === true;\n      if (codeMode === \"NEC\" && maxUtilization >= 100 && !hundredPercentRated) {\n        issues.push({\n          id: `PWR-B03:${boardId}:nec`,\n          message: `${ctx.describeElement(\n            boardId,\n          )} runs at ${maxUtilization}% under NEC — 210.20(A) allows that only where the assembly is listed for 100% continuous duty.`,\n          nodeIds: [boardId],\n          details: { codeMode, maxUtilization },\n        });\n      }\n      if (codeMode === \"IEC\" && maxUtilization < 100) {\n        issues.push({\n          id: `PWR-B03:${boardId}:iec`,\n          basis: \"convention\",\n          message: `${ctx.describeElement(\n            boardId,\n          )} applies a ${maxUtilization}% derate under IEC — BS 7671 433.1.1 has no 80% rule, so this is a house convention.`,\n          nodeIds: [boardId],\n          details: { codeMode, maxUtilization },\n        });\n      }\n      if (diversityFactor !== 1) {\n        issues.push({\n          id: `PWR-B03:${boardId}:diversity`,\n          basis: \"heuristic\",\n          message: `${ctx.describeElement(\n            boardId,\n          )} applies a diversity factor of ${diversityFactor} — continuous IT load has diversity of about 1.0, so this understates the circuit load.`,\n          nodeIds: [boardId],\n          details: { diversityFactor },\n        });\n      }\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-B04 / PWR-B05 — nesting relation between an upstream way and a board\n// ---------------------------------------------------------------------------\n\ninterface NestedPair {\n  upstream: PowerWayRef;\n  downstreamBoardId: string;\n  downstreamWay: BreakerWay;\n  downstreamKey: string;\n}\n\nfunction nestedWayPairs(\n  ctx: Parameters<PowerRule[\"evaluate\"]>[0],\n): NestedPair[] {\n  const pairs: NestedPair[] = [];\n  const byKey = new Map(ctx.getWays().map((ref) => [ref.wayKey, ref]));\n  // A sub-board fed by two links from the same upstream way (or simply\n  // enumerated twice) yields the same upstream/downstream relation more than\n  // once. Issue ids are `<upstreamKey>#<downstreamKey>`, so an un-deduped pair\n  // would emit duplicate B04/B05 issues sharing one id.\n  const seen = new Set<string>();\n  for (const link of ctx.links) {\n    if (!link.wayKey) continue;\n    const upstream = byKey.get(link.wayKey);\n    if (!upstream) continue;\n    const board = ctx.getNode(link.loadNodeId);\n    if (board?.type !== \"distribution-board\") continue;\n    for (const ref of ctx.getWays()) {\n      if (ref.boardId !== link.loadNodeId) continue;\n      const pairKey = `${upstream.wayKey}|${ref.wayKey}`;\n      if (seen.has(pairKey)) continue;\n      seen.add(pairKey);\n      pairs.push({\n        upstream,\n        downstreamBoardId: ref.boardId,\n        downstreamWay: ref.way,\n        downstreamKey: ref.wayKey,\n      });\n    }\n  }\n  return pairs;\n}\n\n/**\n * IEC 60947-2 Annex A defines selectivity as a *tested* limit current published\n * per manufacturer, so a rating ratio can only ever be a screen. Between two\n * MCBs there is essentially no discrimination in the short-circuit region at\n * all — both magnetic elements trip. This rule therefore never emits a positive\n * \"selective\" verdict and always hedges its wording.\n */\nexport const pwrB04SelectivityScreen: PowerRule = {\n  id: \"PWR-B04\",\n  name: \"Rating-ratio selectivity screen\",\n  basis: \"heuristic\",\n  defaultSeverity: \"warning\",\n  defaultOptions: { mcbRatio: 2, fuseRatio: 1.6 },\n  description:\n    \"Advisory screen on the upstream/downstream rating ratio. Overload region only; never a selectivity guarantee.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const mcbRatio = opt(ctx.options, \"mcbRatio\", 2);\n    const fuseRatio = opt(ctx.options, \"fuseRatio\", 1.6);\n    for (const pair of nestedWayPairs(ctx)) {\n      const up = num(pair.upstream.way.ratingA);\n      const down = num(pair.downstreamWay.ratingA);\n      if (!up || !down) continue;\n      const ratio = up / down;\n      // ratio < 1 is PWR-B05's unambiguous error, not an advisory.\n      if (ratio < 1) continue;\n      const isFuse =\n        (pair.upstream.way as unknown as Record<string, unknown>).protectionType ===\n        \"fuse\";\n      const threshold = isFuse ? fuseRatio : mcbRatio;\n      if (ratio >= threshold) continue;\n      issues.push({\n        id: `PWR-B04:${pair.upstream.wayKey}:${pair.downstreamKey}`,\n        message: `${ctx.describeElement(\n          pair.upstream.wayKey,\n        )} (${up} A) over ${ctx.describeElement(\n          pair.downstreamKey,\n        )} (${down} A) is a ${round(\n          ratio,\n          2,\n        )}:1 ratio and may not discriminate — verify against the manufacturer's coordination table and the prospective fault current.`,\n        nodeIds: [pair.upstream.boardId, pair.downstreamBoardId],\n        wayIds: [pair.upstream.wayKey, pair.downstreamKey],\n        details: { upstreamA: up, downstreamA: down, ratio: round(ratio, 2) },\n      });\n    }\n    return issues;\n  },\n};\n\n/** Corollary of B04, and unambiguous: an upstream breaker must not be smaller. */\nexport const pwrB05UpstreamBelowDownstream: PowerRule = {\n  id: \"PWR-B05\",\n  name: \"Upstream rating below downstream\",\n  basis: \"standard\",\n  defaultSeverity: \"error\",\n  description: \"An upstream breaker rated below the one it feeds will trip first.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    for (const pair of nestedWayPairs(ctx)) {\n      const up = num(pair.upstream.way.ratingA);\n      const down = num(pair.downstreamWay.ratingA);\n      if (!up || !down || up >= down) continue;\n      issues.push({\n        id: `PWR-B05:${pair.upstream.wayKey}:${pair.downstreamKey}`,\n        message: `${ctx.describeElement(\n          pair.upstream.wayKey,\n        )} (${up} A) feeds ${ctx.describeElement(\n          pair.downstreamKey,\n        )} (${down} A) — the upstream breaker is the smaller device and will trip first.`,\n        nodeIds: [pair.upstream.boardId, pair.downstreamBoardId],\n        wayIds: [pair.upstream.wayKey, pair.downstreamKey],\n        details: { upstreamA: up, downstreamA: down },\n      });\n    }\n    return issues;\n  },\n};\n\n// ---------------------------------------------------------------------------\n// PWR-B08 — Three-phase imbalance across a PDU's outlets\n// ---------------------------------------------------------------------------\n\n/**\n * 1-based outlet position a link's PDU end sits at, resolved through the shared\n * endpoint classifier rather than the handle string.\n *\n * A bare-numeric handle is not the only shape a strip's outlets take: a PDU\n * authored with explicit port ids (`rear-power-1`) names them however it likes,\n * and reading positions off the id alone silently dropped those strips from the\n * phase attribution. The classifier resolves both forms from the strip's own\n * declared ports, in render order.\n */\nfunction outletPosition(\n  ctx: Parameters<PowerRule[\"evaluate\"]>[0],\n  nodeId: string,\n  handle: string | null | undefined,\n): number | undefined {\n  return getPduOutletPosition(ctx.getNode(nodeId), handle);\n}\n\n/** Σ nameplate watts of everything hanging off one cord. */\nfunction linkDownstreamWatts(\n  ctx: Parameters<PowerRule[\"evaluate\"]>[0],\n  edgeId: string,\n): number {\n  let total = 0;\n  for (const nodeId of ctx.getDownstreamNodeIdsOfLink(edgeId)) {\n    const data = ctx.getNode(nodeId)?.data;\n    const watts = num(data?.maximumDrawW) ?? num(data?.allocatedDrawW);\n    if (typeof watts === \"number\") total += watts;\n  }\n  return total;\n}\n\n/**\n * Load (current) imbalance across the phases of a multi-phase PDU strip.\n *\n * **Phase attribution convention.** Outlets are attributed round-robin from the\n * top — outlet `p` on an `n`-phase strip sits on phase `(p - 1) % n`, via\n * `toOutletPhaseIndex`. Real strips vary (some bank outlets per phase),\n * but the number is only meaningful if the diagram and the consuming\n * application attribute outlets identically, so the convention is fixed and\n * shared rather than inferred. Positions come from the endpoint classifier, so\n * a strip whose outlets carry explicit ids (`rear-power-1`) is attributed the\n * same as one using bare numbers; `attributedCount`/`totalLinks` in the issue\n * details report how many cords the number covers.\n *\n * Convention, not standard: **voltage** unbalance is the standardized quantity\n * (NEMA MG-1, EN 50160) and is measured, not diagrammable. This checks\n * load imbalance, whose real justification is per-phase breaker headroom — the\n * message must never cite NEMA MG-1. Nameplate sums are an upper bound, not\n * measured draw, and only strips where **every** phase carries load are judged.\n */\nexport const pwrB08PhaseImbalance: PowerRule = {\n  id: \"PWR-B08\",\n  name: \"Three-phase imbalance\",\n  basis: \"convention\",\n  defaultSeverity: \"warning\",\n  defaultOptions: { maxImbalancePct: 10 },\n  description:\n    \"Flags a multi-phase PDU whose per-phase nameplate load differs by more than the configured percentage.\",\n  evaluate(ctx) {\n    const issues: PowerIssueDraft[] = [];\n    const maxImbalancePct = opt(ctx.options, \"maxImbalancePct\", 10);\n    for (const node of ctx.nodes) {\n      if (node.type !== \"vertical-pdu\") continue;\n      const phases = num(node.data?.phases);\n      if (phases === undefined || phases < 2) continue;\n\n      const perPhaseW = new Array<number>(Math.floor(phases)).fill(0);\n      const downstreamLinks = ctx.getDownstreamLinks(node.id);\n      let attributed = 0;\n      for (const link of downstreamLinks) {\n        // A cord whose outlet position cannot be resolved is skipped rather\n        // than guessed at — `attributedCount` vs `totalLinks` in the details\n        // below says how much of the strip the number actually covers.\n        const position = outletPosition(ctx, node.id, link.supplyHandle);\n        if (position === undefined) continue;\n        perPhaseW[toOutletPhaseIndex(position, phases)] += linkDownstreamWatts(\n          ctx,\n          link.edgeId,\n        );\n        attributed += 1;\n      }\n      // Only judge a strip whose every phase carries load: a partially wired\n      // strip is unbalanced by construction, not by design error.\n      if (attributed === 0 || perPhaseW.some((watts) => watts <= 0)) continue;\n\n      const max = Math.max(...perPhaseW);\n      const min = Math.min(...perPhaseW);\n      const imbalancePct = ((max - min) / max) * 100;\n      if (imbalancePct <= maxImbalancePct) continue;\n\n      issues.push({\n        id: `PWR-B08:${node.id}`,\n        message: `${ctx.describeElement(node.id)} carries ${round(\n          imbalancePct,\n          1,\n        )}% nameplate load imbalance across its ${Math.floor(\n          phases,\n        )} phases (over the ${maxImbalancePct}% convention) — rebalance the outlets. Nameplate sums are an upper bound, not measured draw.`,\n        nodeIds: [node.id],\n        details: {\n          phases: Math.floor(phases),\n          phaseLoadsW: perPhaseW.map((watts) => round(watts, 1)),\n          imbalancePct: round(imbalancePct, 1),\n          maxImbalancePct,\n          /** How many of the strip's cords the attribution could place. */\n          attributedCount: attributed,\n          totalLinks: downstreamLinks.length,\n        },\n      });\n    }\n    return issues;\n  },\n};\n\n/** Tier B core (B01–B03), the nesting pair (B04/B05), and B08. @public */\nexport const tierBPowerRules: readonly PowerRule[] = [\n  pwrB01BreakerOverSubscribed,\n  pwrB02BreakerOverAllocated,\n  pwrB03DeratingMode,\n  pwrB04SelectivityScreen,\n  pwrB05UpstreamBelowDownstream,\n  pwrB08PhaseImbalance,\n];\n","/**\n * `standardPowerRules` — the OPT-IN standards preset.\n *\n * The engine ships no rules. This preset is the catalogue in\n * `docs/electrical-validation-rules-2026-08-14.md` expressed as rule objects,\n * and it is only in your bundle if you import it. Take it wholesale, filter it,\n * reconfigure it through `config.rules`, or ignore it and pass your own rules.\n *\n * Not included, deliberately:\n * - **PWR-A08** — \"breaker state must not alter topology\" is an invariant over\n *   the library itself, verified by a unit test, not a graph rule.\n * - **Tier C** (selectivity guarantees, trip curves, arc-flash, earth-loop\n *   impedance, voltage drop without conductor csa, measured utilisation) —\n *   not computable from a diagram, and a confident wrong answer there is a\n *   safety hazard.\n */\n\nimport type { PowerRule } from \"../types\";\nimport { tierAPowerRules } from \"./tier-a\";\nimport { tierBPowerRules } from \"./tier-b\";\n\nexport * from \"./tier-a\";\nexport * from \"./tier-b\";\nexport {\n  resolveDerating,\n  wattsToAmps,\n  phaseFactor,\n  wayCircuitVoltage,\n  wayCircuitKind,\n  DEFAULT_PHASE_VOLTAGE_V,\n  THREE_PHASE_FACTOR,\n} from \"./shared\";\nexport type { ResolvedDerating, VoltageBasis } from \"./shared\";\n\n/**\n * The full preset: Tier A (A01–A07, A09–A12) + Tier B core (B01–B03) and the\n * nesting pair (B04/B05).\n * @public\n */\nexport const standardPowerRules: readonly PowerRule[] = [\n  ...tierAPowerRules,\n  ...tierBPowerRules,\n];\n\n/** Look up one preset rule by catalogue id. @public */\nexport function getStandardPowerRule(id: string): PowerRule | undefined {\n  return standardPowerRules.find((rule) => rule.id === id);\n}\n"]}