// Copyright (c) 2013, 2015 Kiel University and others.
// Copyright (c) 2013, 2020 Kiel University and others.
// Copyright (c) 2014, 2015 Kiel University and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

package layered

import (
	
	
	

	
	
	basegraph 
)

// CrossHierarchyEdge is one per-hierarchy-level replacement segment of an
// original edge. The declaration mirrors CompoundGraphPreprocessor's private
// CrossHierarchyEdge value.
type CrossHierarchyEdge struct {
	Edge     LEdgeID
	Graph    LGraphID
	PortType PortType
}

// CrossHierarchyEntry retains Java LinkedHashMap insertion order.
type CrossHierarchyEntry struct {
	Original LEdgeID
	Segments []CrossHierarchyEdge
}

// CrossHierarchyMap is the exact non-cloneable value stored on the top graph.
// Entries, rather than a Go map, are authoritative because insertion order is
// observable while labels and dummy edges are restored.
type CrossHierarchyMap struct {
	Entries []CrossHierarchyEntry
	index   map[LEdgeID]int
}

func ( *CrossHierarchyMap) ( LEdgeID,  CrossHierarchyEdge) {
	if .index == nil {
		.index = make(map[LEdgeID]int)
	}
	,  := .index[]
	if ! {
		 = len(.Entries)
		.index[] = 
		.Entries = append(.Entries, CrossHierarchyEntry{Original: })
	}
	.Entries[].Segments = append(.Entries[].Segments, )
}

// CompoundGraphPreprocessor splits every cross-hierarchy edge into one edge
// per hierarchy level, connected through external-port dummy nodes.
type CompoundGraphPreprocessor struct{}

func (*CompoundGraphPreprocessor) ( *LGraphArena,  LGraphID) error {
	if  == nil || .Index() >= len(.Graphs) {
		return fmt.Errorf("layered: invalid graph passed to compound preprocessor")
	}
	 := &CrossHierarchyMap{}
	 := len(.Edges)
	for  := 0;  < ; ++ {
		 := LEdgeID()
		 := .Edge()
		if .Source == nil || .Target == nil {
			continue
		}
		 := .NodeGraph(.EdgeSourceNode())
		 := .NodeGraph(.EdgeTargetNode())
		if  ==  {
			continue
		}
		if  := splitCrossHierarchyEdge(, , , );  != nil {
			return 
		}
	}
	// Once one port of a compound node crosses its hierarchy boundary, ELK
	// represents every port of that node inside the child graph. The additional
	// unconnected dummies are observable: phase 3 orders them together and the
	// hierarchical node resizer transfers all of their positions back.
	ensureCompoundPortDummies(, )
	// The Java implementation creates hierarchy dummies during a recursive,
	// post-order walk. Their insertion order becomes the initial order used by
	// crossing minimization, so recover that source-shaped order after the
	// per-edge splitting above.
	reorderCompoundPortDummies(, )
	basegraph.Set(&.Graph().Properties, CrossHierarchyEdges, )
	return nil
}

type compoundPortDummyOrder struct {
	node       LNodeID
	connected  bool
	path       []int
	portIndex  int
	edgeOrder  int
	stableRank int
}

// reorderCompoundPortDummies reproduces transformHierarchyEdges' observable
// post-order insertion order. Descendant boundary ports precede ports on the
// compound node itself; siblings retain node, port, and edge model order.
func reorderCompoundPortDummies( *LGraphArena,  LGraphID) {
	for ,  := range append([]LNodeID(nil), .Graph().LayerlessNodes...) {
		if  := .Node().NestedGraph;  != nil {
			(, *)
		}
	}

	 := append([]LNodeID(nil), .Graph().LayerlessNodes...)
	 := make([]LNodeID, 0, len())
	 := make([]compoundPortDummyOrder, 0)
	for ,  := range  {
		if .Node().Type != ExternalPortNode {
			 = append(, )
			continue
		}
		 := compoundPortDummyOrder{
			node: , portIndex: math.MaxInt, edgeOrder: math.MaxInt,
			stableRank: ,
		}
		if , ,  := compoundDummyInsideEndpoint(, );  {
			.connected = true
			.path, .portIndex = compoundInsideTraversalKey(, , )
			if ,  := basegraph.TryGet(&.Edge().Properties, ModelOrder);  {
				.edgeOrder = 
			} else {
				.edgeOrder = .Index()
			}
		} else if ,  := basegraph.TryGet(&.Node().Properties, OriginalElement);  &&
			.Kind == OriginLPort && .Port(.LPort).Node != nil {
			.portIndex = nodePortIndex(, *.Port(.LPort).Node, .LPort)
		}
		 = append(, )
	}
	if len() < 2 {
		return
	}
	sort.SliceStable(, func(,  int) bool {
		,  := [], []
		if .connected != .connected {
			return .connected
		}
		if  := compareCompoundTraversalPaths(.path, .path);  != 0 {
			return  < 0
		}
		if .portIndex != .portIndex {
			return .portIndex < .portIndex
		}
		if .edgeOrder != .edgeOrder {
			return .edgeOrder < .edgeOrder
		}
		return .stableRank < .stableRank
	})
	 := make([]LNodeID, 0, len())
	 = append(, ...)
	for ,  := range  {
		 = append(, .node)
	}
	.Graph().LayerlessNodes = 
}

// compoundDummyInsideEndpoint returns the endpoint of the dummy's segment
// that lies inside its graph. External input dummies have an outgoing segment;
// external output dummies have an incoming segment.
func compoundDummyInsideEndpoint(
	 *LGraphArena,
	 LNodeID,
) (LPortID, LEdgeID, bool) {
	for ,  := range .Node().Ports {
		for ,  := range .Port().OutgoingEdges {
			 := .Edge()
			if .Target != nil {
				return *.Target, , true
			}
		}
		for ,  := range .Port().IncomingEdges {
			 := .Edge()
			if .Source != nil {
				return *.Source, , true
			}
		}
	}
	return 0, 0, false
}

func compoundInsideTraversalKey(
	 *LGraphArena,
	 LGraphID,
	 LPortID,
) ([]int, int) {
	 := .Port()
	if .Node == nil {
		return nil, math.MaxInt
	}
	 := *.Node
	 := []int{graphNodeIndex(, , )}
	 := .Node()
	if .NestedGraph != nil {
		if ,  := basegraph.TryGet(&.Properties, PortDummy);  {
			if , ,  := compoundDummyInsideEndpoint(, );  {
				,  := (, *.NestedGraph, )
				return append(, ...), 
			}
		}
	}
	return , nodePortIndex(, , )
}

func graphNodeIndex( *LGraphArena,  LGraphID,  LNodeID) int {
	 := 0
	for ,  := range .Graph().LayerlessNodes {
		if .Node().Type == ExternalPortNode {
			continue
		}
		if  ==  {
			return 
		}
		++
	}
	return math.MaxInt
}

func nodePortIndex( *LGraphArena,  LNodeID,  LPortID) int {
	for ,  := range .Node().Ports {
		if  ==  {
			return 
		}
	}
	return math.MaxInt
}

func compareCompoundTraversalPaths(,  []int) int {
	 := len()
	if len() <  {
		 = len()
	}
	for  := 0;  < ; ++ {
		if [] < [] {
			return -1
		}
		if [] > [] {
			return 1
		}
	}
	// transformHierarchyEdges recurses before processing ports directly on a
	// compound child, so the descendant path sorts before its prefix.
	if len() > len() {
		return -1
	}
	if len() < len() {
		return 1
	}
	return 0
}

func ensureCompoundPortDummies( *LGraphArena,  LGraphID) {
	// CompoundGraphPreprocessor.transformHierarchyEdges is post-order. Copy the
	// node list because the recursive call may append external-port dummies to a
	// nested graph's layerless nodes.
	 := append([]LNodeID(nil), .Graph().LayerlessNodes...)
	for ,  := range  {
		 := .Node().NestedGraph
		if  == nil {
			continue
		}
		(, *)
		 := basegraph.Get(&.Graph(*).Properties, GraphProperties)
		if &GraphPropertyExternalPorts == 0 {
			continue
		}

		 := basegraph.Get(&.Node().Properties, PortConstraintsOption)
		 := append([]LPortID(nil), .Node().Ports...)
		for ,  := range  {
			if ,  := basegraph.TryGet(&.Port().Properties, PortDummy);  {
				continue
			}
			createUnconnectedExternalPortDummy(, *, , , )
		}
	}
}

func createUnconnectedExternalPortDummy(
	 *LGraphArena,
	 LGraphID,
	 LNodeID,
	 LPortID,
	 PortConstraints,
) LNodeID {
	 := .Port()
	 := .Properties.Clone()
	 := append([]LLabelID(nil), .Labels...)
	 := geometry.Point{}
	 := len(.OutgoingEdges) - len(.IncomingEdges)
	 := basegraph.Get(&.Graph().Properties, DirectionOption)
	 := .CreateExternalPortDummy(
		&,
		,
		.Side,
		,
		nil,
		&,
		.Size,
		,
		,
	)
	basegraph.Set(&.Node().Properties, OriginalElement, LPortOrigin())
	.Graph().LayerlessNodes = append(.Graph().LayerlessNodes, )

	// Dummy port labels reserve the same space as the compound node's ports.
	// They deliberately carry only dimensions; they are not exported labels.
	 := .Node().Ports[0]
	for ,  := range  {
		 := .AddLabel("")
		.Label().Size = .Label().Size
		.Port().Labels = append(.Port().Labels, )
	}

	 = .Port()
	basegraph.Set(&.Properties, PortDummy, )
	basegraph.Set(&.Properties, InsideConnections, true)
	 := basegraph.Get(&.Node().Properties, ExternalPortSide)
	.SetPortSide(, )
	basegraph.Set(&.Properties, PortSideOption, )
	basegraph.Set(&.Node().Properties, PortConstraintsOption, PortConstraintsFixedSide)
	 := .NodeGraph()
	 := basegraph.Get(&.Graph().Properties, GraphProperties)
	basegraph.Set(&.Graph().Properties, GraphProperties,
		|GraphPropertyNonFreePorts)
	return 
}

func splitCrossHierarchyEdge(
	 *LGraphArena,
	 LGraphID,
	 LEdgeID,
	 *CrossHierarchyMap,
) error {
	 := .Edge()
	,  := *.Source, *.Target
	 := .NodeGraph(*.Port().Node)
	 := .NodeGraph(*.Port().Node)
	,  := lowestCommonGraph(, , , )
	if ! {
		return fmt.Errorf("layered: edge %d endpoints are outside graph %d's hierarchy", , )
	}

	 := 
	 := 
	 := make([]CrossHierarchyEdge, 0)
	for  !=  {
		, ,  := hierarchyParent(, )
		if ! {
			return fmt.Errorf("layered: graph %d has no parent on edge %d's source path", , )
		}
		 := boundaryPort(, , , ,
			PortTypeOutput, , )
		 := externalDummyForBoundary(, , , ,
			PortTypeOutput, )
		 := .Node().Ports[0]
		 := createHierarchyDummyEdge(, , , )
		 = append(, CrossHierarchyEdge{
			Edge: , Graph: , PortType: PortTypeOutput,
		})
		,  = , 
	}

	 := 
	 = 
	 := make([]CrossHierarchyEdge, 0)
	for  !=  {
		, ,  := hierarchyParent(, )
		if ! {
			return fmt.Errorf("layered: graph %d has no parent on edge %d's target path", , )
		}
		 := boundaryPort(, , , ,
			PortTypeInput, , )
		 := externalDummyForBoundary(, , , ,
			PortTypeInput, )
		 := .Node().Ports[0]
		 := createHierarchyDummyEdge(, , , )
		 = append(, CrossHierarchyEdge{
			Edge: , Graph: , PortType: PortTypeInput,
		})
		,  = , 
	}

	 := append([]CrossHierarchyEdge(nil), ...)
	if  !=  {
		 := PortTypeOutput
		if len() == 0 {
			 = PortTypeInput
		}
		 := createHierarchyDummyEdge(, , , )
		 = append(, CrossHierarchyEdge{Edge: , Graph: , PortType: })
	}
	for  := len() - 1;  >= 0; -- {
		 = append(, [])
	}
	if len() == 0 {
		return fmt.Errorf("layered: cross-hierarchy edge %d produced no segments", )
	}
	for ,  := range  {
		.add(, )
	}
	moveCrossHierarchyLabels(, , , )
	.SetEdgeSource(, nil)
	.SetEdgeTarget(, nil)
	return nil
}

func hierarchyParent( *LGraphArena,  LGraphID) (LNodeID, LGraphID, bool) {
	 := .Graph().ParentNode
	if  == nil {
		return 0, 0, false
	}
	return *, .NodeGraph(*), true
}

func lowestCommonGraph(
	 *LGraphArena,
	, ,  LGraphID,
) (LGraphID, bool) {
	 := make(map[LGraphID]bool)
	 := 
	for {
		[] = true
		if  ==  {
			break
		}
		, ,  := hierarchyParent(, )
		if ! {
			return 0, false
		}
		 = 
	}
	 = 
	for {
		if [] {
			return , true
		}
		if  ==  {
			return 0, false
		}
		, ,  := hierarchyParent(, )
		if ! {
			return 0, false
		}
		 = 
	}
}

// boundaryPort returns an existing port when the edge ends at the compound
// node itself. Otherwise it introduces the external port exposed by that
// compound node to its parent graph.
func boundaryPort(
	 *LGraphArena,
	,  LGraphID,
	 LNodeID,
	 PortType,
	 LEdgeID,
	 LPortID,
) LPortID {
	if .Port().Node != nil && *.Port().Node ==  {
		return 
	}
	 := .AddPort()
	.SetPortNode(, &)
	 := basegraph.Get(&.Graph().Properties, DirectionOption)
	 := DirectionPortSide()
	if  == PortTypeInput {
		 = .Opposed()
	}
	.SetPortSide(, )
	basegraph.Set(&.Port().Properties, PortSideOption, )
	basegraph.Set(&.Port().Properties, PortBorderOffset,
		basegraph.Get(&.Graph().Properties, SpacingEdgeEdge)/2)
	basegraph.Set(&.Port().Properties, OriginalElement, LEdgeOrigin())
	return 
}

func externalDummyForBoundary(
	 *LGraphArena,
	 LGraphID,
	 LNodeID,
	 LPortID,
	 PortType,
	 LEdgeID,
) LNodeID {
	 := .Port()
	// CreateExternalPortDummy adds the dummy's own port. Keep its property
	// holder detached from arena.Ports so a backing-slice reallocation cannot
	// leave the helper (or the writes below) pointing at stale storage.
	 := .Properties.Clone()
	 := basegraph.Get(&.Node().Properties, PortConstraintsOption)
	if  == PortConstraintsUndefined {
		 = PortConstraintsFree
	}
	 := 1
	if  == PortTypeInput {
		 = -1
	}
	// A segment that ends directly at the compound parent reuses the original
	// endpoint port. ELK derives that port's side from all of its connections,
	// rather than from the hierarchy segment's INPUT/OUTPUT classification.
	// The distinction matters for an edge from an ancestor to a non-direct
	// descendant: the segment points into deeper hierarchy, but the ancestor
	// port's net flow still makes it an output port.
	 := .Edge()
	 := .Target
	if  == PortTypeInput {
		 = .Source
	}
	if  != nil && .Port(*).Node != nil &&
		*.Port(*).Node ==  {
		 = compoundPortNetFlow(, *)
	}
	 := .Position
	 := .Node().Size
	 := basegraph.Get(&.Graph().Properties, DirectionOption)
	 := .CreateExternalPortDummy(&, , .Side,
		, &, &, .Size, , )
	basegraph.Set(&.Node().Properties, OriginalElement, LPortOrigin())
	.Graph().LayerlessNodes = append(.Graph().LayerlessNodes, )
	 = .Port()
	basegraph.Set(&.Properties, PortDummy, )
	basegraph.Set(&.Properties, InsideConnections, true)
	 := basegraph.Get(&.Node().Properties, ExternalPortSide)
	.SetPortSide(, )
	basegraph.Set(&.Properties, PortSideOption, )
	basegraph.Set(&.Node().Properties, PortConstraintsOption, PortConstraintsFixedSide)

	 := basegraph.Get(&.Graph().Properties, GraphProperties)
	basegraph.Set(&.Graph().Properties, GraphProperties,
		|GraphPropertyExternalPorts)
	if basegraph.Get(&.Graph().Properties, PortConstraintsOption).IsSideFixed() {
		basegraph.Set(&.Graph().Properties, PortConstraintsOption, PortConstraintsFixedSide)
	} else {
		basegraph.Set(&.Graph().Properties, PortConstraintsOption, PortConstraintsFree)
	}
	 := basegraph.Get(&.Graph(.NodeGraph()).Properties, GraphProperties)
	basegraph.Set(&.Graph(.NodeGraph()).Properties, GraphProperties,
		|GraphPropertyNonFreePorts)
	return 
}

// compoundPortNetFlow mirrors CompoundGraphPreprocessor.calculateNetFlow.
// Positive values make a port an output of its compound parent; negative
// values make it an input.
func compoundPortNetFlow( *LGraphArena,  LPortID) int {
	 := .Port()
	if .Node == nil {
		return 0
	}
	 := *.Node
	 := basegraph.Get(&.Node().Properties, core.InsideSelfLoopsActivate)
	,  := 0, 0

	for ,  := range .OutgoingEdges {
		 := .Edge()
		if .Source == nil || .Target == nil {
			continue
		}
		 := .EdgeIsSelfLoop()
		 :=  &&  &&
			basegraph.Get(&.Properties, core.InsideSelfLoopsEdge)
		 := .EdgeTargetNode()
		 := .Graph(.NodeGraph())
		switch {
		case  && :
			++
		case :
			++
		case .ParentNode != nil && *.ParentNode == :
			++
		default:
			++
		}
	}

	for ,  := range .IncomingEdges {
		 := .Edge()
		if .Source == nil || .Target == nil {
			continue
		}
		 := .EdgeIsSelfLoop()
		 :=  &&  &&
			basegraph.Get(&.Properties, core.InsideSelfLoopsEdge)
		 := .EdgeSourceNode()
		 := .Graph(.NodeGraph())
		switch {
		case  && :
			++
		case :
			++
		case .ParentNode != nil && *.ParentNode == :
			++
		default:
			++
		}
	}

	return  - 
}

func createHierarchyDummyEdge(
	 *LGraphArena,
	 LEdgeID,
	,  LPortID,
) LEdgeID {
	 := .AddEdge()
	.Edge().Properties.CopyFrom(&.Edge().Properties)
	basegraph.Unset(&.Edge().Properties, JunctionPoints)
	.SetEdgeSource(, &)
	.SetEdgeTarget(, &)
	return 
}

func moveCrossHierarchyLabels(
	 *LGraphArena,
	 LGraphID,
	 LEdgeID,
	 []CrossHierarchyEdge,
) {
	 := append([]LLabelID(nil), .Edge().Labels...)
	if len() == 0 {
		return
	}
	 := append([]CrossHierarchyEdge(nil), ...)
	sortHierarchySegments(, , )
	 := make([]LLabelID, 0)
	for ,  := range  {
		 := -1
		switch basegraph.Get(&.Label().Properties, EdgeLabelsPlacement) {
		case EdgeLabelPlacementTail:
			 = 0
		case EdgeLabelPlacementHead:
			 = len() - 1
		case EdgeLabelPlacementCenter:
			 = shallowestHierarchySegment()
		}
		if  < 0 {
			 = append(, )
			continue
		}
		 := []
		.Edge(.Edge).Labels = append(.Edge(.Edge).Labels, )
		 := basegraph.Get(&.Graph(.Graph).Properties, GraphProperties)
		basegraph.Set(&.Graph(.Graph).Properties, GraphProperties,
			|GraphPropertyEndLabels|GraphPropertyCenterLabels)
		basegraph.Set(&.Label().Properties, OriginalLabelEdge, )
	}
	.Edge().Labels = 
}

func shallowestHierarchySegment( []CrossHierarchyEdge) int {
	 := -1
	for ,  := range  {
		if .PortType == PortTypeInput {
			if  == 0 {
				return 0
			}
			return  - 1
		}
		if  == len()-1 {
			 = 
		}
	}
	return 
}

func sortHierarchySegments( *LGraphArena,  []CrossHierarchyEdge,  LGraphID) {
	sort.SliceStable(, func(,  int) bool {
		,  := [], []
		if .PortType == PortTypeOutput && .PortType == PortTypeInput {
			return true
		}
		if .PortType == PortTypeInput && .PortType == PortTypeOutput {
			return false
		}
		 := hierarchyLevel(, .Graph, )
		 := hierarchyLevel(, .Graph, )
		if .PortType == PortTypeOutput {
			return  > 
		}
		return  < 
	})
}

func hierarchyLevel( *LGraphArena, ,  LGraphID) int {
	 := 0
	for  !=  {
		, ,  := hierarchyParent(, )
		if ! {
			return int(^uint(0) >> 1)
		}
		 = 
		++
	}
	return 
}

// CompoundGraphPostprocessor joins the laid-out hierarchy segments back into
// their original edge while translating bend points, junction points, labels,
// and the target anchor into a single reference coordinate system.
type CompoundGraphPostprocessor struct{}

func (*CompoundGraphPostprocessor) ( *LGraphArena,  LGraphID) error {
	if  == nil || .Index() >= len(.Graphs) {
		return fmt.Errorf("layered: invalid graph passed to compound postprocessor")
	}
	,  := basegraph.TryGet(&.Graph().Properties, CrossHierarchyEdges)
	if ! ||  == nil {
		return nil
	}
	 := basegraph.Get(&.Graph().Properties, UnnecessaryBendpoints)
	 := make([]LEdgeID, 0)
	for ,  := range .Entries {
		if len(.Segments) == 0 {
			continue
		}
		 := append([]CrossHierarchyEdge(nil), .Segments...)
		sortHierarchySegments(, , )
		 := segmentActualSource(, [0])
		 := segmentActualTarget(, [len()-1])
		,  := *.Port().Node, *.Port().Node
		 := .NodeGraph()
		if .IsDescendant(, ) && .Node().NestedGraph != nil {
			 = *.Node().NestedGraph
		}

		 := prepareJunctionPoints(, .Original, )
		.Edge(.Original).BendPoints = nil
		var  *geometry.Point
		for ,  := range  {
			 := graphCoordinateOffset(, .Graph, )
			 := .Edge(.Edge)
			 := make(geometry.Chain, len(.BendPoints))
			for ,  := range .BendPoints {
				[] = .Add()
			}
			 := portAbsoluteAnchor(, *.Source).Add()
			 := portAbsoluteAnchor(, *.Target).Add()
			if  != nil {
				 := 
				if len() > 0 {
					 = [0]
				}
				 := math.Abs(.X-.X) > .00001
				 := math.Abs(.Y-.Y) > .00001
				if (! &&  && ) ||
					( && ( || )) {
					.Edge(.Original).BendPoints = append(
						.Edge(.Original).BendPoints, )
				}
			}
			.Edge(.Original).BendPoints = append(
				.Edge(.Original).BendPoints, ...)
			if len() == 0 {
				 := 
				 = &
			} else {
				 := [len()-1]
				 = &
			}
			if ,  := basegraph.TryGet(&.Properties, JunctionPoints);  &&  != nil {
				for ,  := range  {
					* = append(*, .Add())
				}
			}
			if segmentActualTarget(, ) ==  {
				 := 
				 := .NodeGraph(*.Port().Node)
				if  != .Graph {
					 = graphCoordinateOffset(, , )
				}
				basegraph.Set(&.Edge(.Original).Properties, TargetOffset, )
			}
			copyHierarchyLabelsBack(, .Edge, .Original, )
			 = append(, .Edge)
		}
		if  != nil {
			basegraph.Set(&.Edge(.Original).Properties, JunctionPoints, *)
		}
		.SetEdgeSource(.Original, &)
		.SetEdgeTarget(.Original, &)
	}
	 := make(map[LEdgeID]bool)
	for ,  := range  {
		if [] {
			continue
		}
		[] = true
		.SetEdgeSource(, nil)
		.SetEdgeTarget(, nil)
	}
	return nil
}

func segmentActualSource( *LGraphArena,  CrossHierarchyEdge) LPortID {
	return segmentActualPort(, *.Edge(.Edge).Source)
}

func segmentActualTarget( *LGraphArena,  CrossHierarchyEdge) LPortID {
	return segmentActualPort(, *.Edge(.Edge).Target)
}

func segmentActualPort( *LGraphArena,  LPortID) LPortID {
	 := .Port()
	if .Node == nil || .Node(*.Node).Type != ExternalPortNode {
		return 
	}
	if ,  := basegraph.TryGet(&.Node(*.Node).Properties, OriginalElement);  && .Kind == OriginLPort {
		return .LPort
	}
	return 
}

func prepareJunctionPoints(
	 *LGraphArena,
	 LEdgeID,
	 []CrossHierarchyEdge,
) *geometry.Chain {
	 := false
	for ,  := range  {
		if ,  := basegraph.TryGet(&.Edge(.Edge).Properties, JunctionPoints);  && len() > 0 {
			 = true
			break
		}
	}
	if ! {
		basegraph.Unset(&.Edge().Properties, JunctionPoints)
		return nil
	}
	 := geometry.Chain{}
	return &
}

func copyHierarchyLabelsBack(
	 *LGraphArena,
	,  LEdgeID,
	 LGraphID,
) {
	 := append([]LLabelID(nil), .Edge().Labels...)
	 := make([]LLabelID, 0, len())
	for ,  := range  {
		,  := basegraph.TryGet(&.Label().Properties, OriginalLabelEdge)
		if ! ||  !=  {
			 = append(, )
			continue
		}
		 := .NodeGraph(.EdgeSourceNode())
		.Label().Position = changeGraphCoordinates(
			, .Label().Position, , )
		.Edge().Labels = append(.Edge().Labels, )
	}
	.Edge().Labels = 
}

func portAbsoluteAnchor( *LGraphArena,  LPortID) geometry.Point {
	 := .Port()
	if .Node == nil {
		return .Position.Add(.Anchor)
	}
	return .Node(*.Node).Position.Add(.Position).Add(.Anchor)
}

func graphCoordinateOffset( *LGraphArena, ,  LGraphID) geometry.Point {
	return changeGraphCoordinates(, geometry.Point{}, , )
}

func changeGraphCoordinates(
	 *LGraphArena,
	 geometry.Point,
	,  LGraphID,
) geometry.Point {
	// LGraphUtil.changeCoordSystem returns before doing any arithmetic when
	// both coordinate systems are identical. Apart from avoiding needless
	// work, that preserves the input's exact floating-point representation.
	if  ==  {
		return 
	}
	// LGraphUtil.changeCoordSystem mutates the supplied vector while it walks
	// the old hierarchy, then walks the new hierarchy and subtracts each term.
	// Reconstructing two absolute origins and subtracting them is algebraically
	// equivalent but changes the final ULP for fractional graph offsets, so
	// preserve the Java/GWT evaluation order here.
	 := 
	for  := ; ; {
		 := .Graph()
		 = .Add(.Offset)
		if .ParentNode == nil {
			break
		}
		 = .Add(geometry.Point{X: .Padding.Left, Y: .Padding.Top})
		 := *.ParentNode
		 = .Add(.Node().Position)
		 = .NodeGraph()
	}
	for  := ; ; {
		 := .Graph()
		 = .Sub(.Offset)
		if .ParentNode == nil {
			break
		}
		 = .Sub(geometry.Point{X: .Padding.Left, Y: .Padding.Top})
		 := *.ParentNode
		 = .Sub(.Node().Position)
		 = .NodeGraph()
	}
	return 
}

func graphContentOrigin( *LGraphArena,  LGraphID) geometry.Point {
	 := .Graph()
	 := .Offset.Add(geometry.Point{X: .Padding.Left, Y: .Padding.Top})
	if .ParentNode == nil {
		return 
	}
	 := *.ParentNode
	return (, .NodeGraph()).Add(
		.Node().Position).Add()
}