package d2talalayout

import (
	
	
	
	
	
	
	
	

	
	

	
	
	
	
	
)

type translation struct {
	objectIDs        map[*d2graph.Object]layoutgraph.EntityID
	edgeIDs          map[*d2graph.Edge]layoutgraph.EntityID
	edgeDestinations map[*d2graph.Edge]*d2graph.Object
}

const firstD2SpillEntityID layoutgraph.EntityID = 1 << 32

func d2FNV32( string) uint32 {
	 := fnv.New32a()
	_, _ = .Write([]byte())
	return .Sum32()
}

type d2EntityIdentity[ comparable] struct {
	entity 
	absID  string
}

type hashedD2EntityIdentity[ comparable] struct {
	d2EntityIdentity[]
	hash uint32
}

// allocateD2EntityIDs preserves the unsigned FNV-1a ID for every nonzero
// singleton bucket. Only colliding or root-reserved zero hashes receive
// deterministic spill IDs above the uint32 range. Consequently, every bounded
// set of distinct D2 identities has a total, architecture-independent mapping
// without perturbing ordinary graph ordering.
func allocateD2EntityIDs[ comparable](
	 context.Context,
	 string,
	 []d2EntityIdentity[],
) (map[]layoutgraph.EntityID, error) {
	if  == nil {
		return nil, fmt.Errorf("allocate D2 %s IDs requires a context", )
	}
	 := make([]hashedD2EntityIdentity[], len())
	 := make(map[uint32]int, len())
	 := make(map[string]struct{}, len())
	for ,  := range  {
		if %256 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		if ,  := [.absID];  {
			return nil, fmt.Errorf("D2 %s ID %q is repeated", , .absID)
		}
		[.absID] = struct{}{}
		 := d2FNV32(.absID)
		[] = hashedD2EntityIdentity[]{d2EntityIdentity: , hash: }
		[]++
	}

	 := make(map[]layoutgraph.EntityID, len())
	 := make([]hashedD2EntityIdentity[], 0)
	for ,  := range  {
		if %256 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		if .hash != 0 && [.hash] == 1 {
			[.entity] = layoutgraph.EntityID(.hash)
			continue
		}
		 = append(, )
	}
	slices.SortFunc(, func(,  hashedD2EntityIdentity[]) int {
		if  := cmp.Compare(.hash, .hash);  != 0 {
			return 
		}
		return cmp.Compare(.absID, .absID)
	})
	if  := .Err();  != nil {
		return nil, 
	}
	for ,  := range  {
		[.entity] = firstD2SpillEntityID + layoutgraph.EntityID()
	}
	return , nil
}

func d2TALAEdgeAbsID( *d2graph.Edge) string {
	if !isSupportedLifelineEdge() {
		return .AbsID()
	}
	 := *
	.Dst = .Src
	return .AbsID()
}

func translateGraph( context.Context,  *d2graph.Graph,  *layoutgraph.Graph,  bool) (translation, error) {
	 := translation{
		objectIDs:        make(map[*d2graph.Object]layoutgraph.EntityID),
		edgeIDs:          make(map[*d2graph.Edge]layoutgraph.EntityID),
		edgeDestinations: make(map[*d2graph.Edge]*d2graph.Object),
	}
	if  := .Err();  != nil {
		return , 
	}
	if  == nil || .Root == nil {
		return , fmt.Errorf("tala requires a D2 graph with a root object")
	}
	if  == nil {
		return , fmt.Errorf("tala requires a destination graph")
	}
	, ,  := collectD2ObjectVisits(, .Root)
	if  != nil {
		return , 
	}
	if  := validateD2GraphStructure(, , );  != nil {
		return , 
	}
	if  := validateD2Objects(, , );  != nil {
		return , 
	}
	if  := validateD2Edges(, .Edges, );  != nil {
		return , 
	}
	 := make([]d2EntityIdentity[*d2graph.Object], len())
	for ,  := range  {
		[] = d2EntityIdentity[*d2graph.Object]{
			entity: .object,
			absID:  .object.AbsID(),
		}
	}
	,  := allocateD2EntityIDs(, "object", )
	if  != nil {
		return , 
	}
	 := make([]d2EntityIdentity[*d2graph.Edge], len(.Edges))
	 := make(map[*d2graph.Edge]string, len(.Edges))
	for ,  := range .Edges {
		 := d2TALAEdgeAbsID()
		[] = 
		[] = d2EntityIdentity[*d2graph.Edge]{entity: , absID: }
	}
	,  := allocateD2EntityIDs(, "edge", )
	if  != nil {
		return , 
	}

	 := make(map[*d2graph.Object]*layoutgraph.Node)
	if .Root.Direction.Value != "" {
		.Directions[nil] = parseDirection(.Root.Direction.Value)
	}
	if .Root.Shape.Value == d2target.ShapeHierarchy {
		.IsRootHierarchy = true
	}

	for ,  := range  {
		if  := .Err();  != nil {
			return , 
		}
		,  := .object, .parent
		 := []
		.objectIDs[] = 
		 := layoutgraph.NewNode(, .Width, .Height)
		if .Top != nil && .Left != nil {
			,  := parseD2IntegerAttribute(.ID, "top", .Top, -maxResultCoordinate, maxResultCoordinate)
			,  := parseD2IntegerAttribute(.ID, "left", .Left, -maxResultCoordinate, maxResultCoordinate)
			.FixedTopLeft = geo.NewPoint(float64(), float64())
		}
		[] = 
		if .WidthAttr != nil {
			,  := parseD2IntegerAttribute(.ID, "width", .WidthAttr, 1, maxResultCoordinate)
			.DesiredWidth = new(float64())
		}
		if .HeightAttr != nil {
			,  := parseD2IntegerAttribute(.ID, "height", .HeightAttr, 1, maxResultCoordinate)
			.DesiredHeight = new(float64())
		}

		if  {
			if .TopLeft != nil {
				.TopLeft = .TopLeft.Copy()
			}
		}

		if d2target.IsShape(.Shape.Value) {
			 := strings.ToLower(.Shape.Value)
			if  == d2target.ShapeHierarchy {
				.ForceHierarchy = true
			}
			if ,  := d2target.DSL_SHAPE_TO_SHAPE_TYPE[];  {
				.SetShape()
			}
			if .SQLTable != nil {
				.SetNumColumns(len(.SQLTable.Columns))
			}
		}
		if .Icon != nil {
			.InitIcon()
			if .IconPosition != nil {
				.Icon.Position = label.FromString(*.IconPosition)
			}
		}
		if .Direction.Value != "" {
			.Directions[] = parseDirection(.Direction.Value)
		}

		.FontSize = new(.Text().FontSize)
		if .Class != nil || .SQLTable != nil {
			*.FontSize -= d2target.HeaderFontAdd
		}

		if .HasLabel() {
			.Label = &layoutgraph.Label{
				Text:   .Label.Value,
				Width:  float64(.LabelDimensions.Width),
				Height: float64(.LabelDimensions.Height),
			}
			if .LabelPosition != nil {
				.Label.Position = label.FromString(*.LabelPosition)
			}
		}

		if .Is3D() {
			.Is3D = true
		}
		if .IsMultiple() {
			.IsMultiple = true
		}

		if .Style.Opacity != nil {
			,  := strconv.ParseFloat(.Style.Opacity.Value, 64)
			if  == nil &&  == 0 {
				.IsInvisible = true
			}
		}
		if !.IsInvisible {
			 := false
			 := false
			 := false
			 := false

			if .Style.Fill != nil && strings.EqualFold(.Style.Fill.Value, "transparent") {
				 = true
			}
			if .Style.StrokeWidth != nil {
				,  := strconv.Atoi(.Style.StrokeWidth.Value)
				if  == nil &&  == 0 {
					 = true
				}
			}
			if .Style.Stroke != nil && strings.EqualFold(.Style.Stroke.Value, "transparent") {
				 = true
			}
			if .Label.Value == "" {
				 = true
			}
			if .Icon == nil {
				 = true
			}

			if  &&  &&  &&  {
				.IsInvisible = true
			}
		}

		.D2ID = new(.AbsID())

		.AddNewNodeToContainer([], )
	}

	for ,  := range  {
		if  := .Err();  != nil {
			return , 
		}
		 := .object
		if .NearKey != nil {
			if len(.NearKey.Path) > maxInputTreeDepth {
				return , fmt.Errorf("D2 object %q near key depth exceeds limit %d", .AbsID(), maxInputTreeDepth)
			}
			for ,  := range .NearKey.Path {
				if  == nil || .Unbox() == nil {
					return , fmt.Errorf("D2 object %q has an invalid near key part at index %d", .AbsID(), )
				}
			}
			 := d2graph.Key(.NearKey)
			if len() == 0 {
				return , fmt.Errorf("D2 object %q has an empty near key", .AbsID())
			}
			, ,  := findD2Object(, .Root, )
			if  != nil {
				return , 
			}
			if  {
				,  := []
				if ! {
					return , fmt.Errorf("D2 object %q has a near key outside the object tree", .AbsID())
				}
				[].AddNear()
				continue
			}
			if len() != 1 {
				return , fmt.Errorf("D2 object %q has a near key outside the object tree", .AbsID())
			}
			if ,  := d2ast.NearConstants[[0]]; ! {
				return , fmt.Errorf("D2 object %q has a near key outside the object tree", .AbsID())
			}
		}
	}

	for ,  := range .Edges {
		if  := .Err();  != nil {
			return , 
		}
		if  == nil || .Src == nil || .Dst == nil {
			return , fmt.Errorf("tala cannot translate an edge with a nil endpoint")
		}

		// if connection is a <- b create it as src:b dst:a
		var  *layoutgraph.Edge
		 := []
		 := .Src
		 := .Dst
		 := .SrcTableColumnIndex
		 := .DstTableColumnIndex
		if .SrcArrow && !.DstArrow {
			,  = , 
			 = .Connect([], [])
			.SourceArrowhead = layoutgraph.Arrowhead(d2target.NoArrowhead)
			if  := .DstArrowhead;  != nil {
				if .Label.Value != "" {
					.SourceArrowheadLabel = &layoutgraph.Label{
						Text:   .Label.Value,
						Width:  float64(.LabelDimensions.Width),
						Height: float64(.LabelDimensions.Height),
					}
				}
			}
			if  := .SrcArrowhead;  != nil {
				var  *bool
				if .Style.Filled != nil {
					,  := strconv.ParseBool(.Style.Filled.Value)
					 = new()
				}
				.TargetArrowhead = layoutgraph.Arrowhead(d2target.ToArrowhead(.Shape.Value, ))
				if .Label.Value != "" {
					.TargetArrowheadLabel = &layoutgraph.Label{
						Text:   .Label.Value,
						Width:  float64(.LabelDimensions.Width),
						Height: float64(.LabelDimensions.Height),
					}
				}
			} else {
				.TargetArrowhead = layoutgraph.Arrowhead(d2target.TriangleArrowhead)
			}
			,  = , 

			if  {
				for ,  := range slices.Backward(.Route) {
					.Points = append(.Points, .Copy())
				}
			}
		} else {
			// Pretend it's a self-loop, since sequence diagram lifelines aren't connected on both ends
			if isSupportedLifelineEdge() {
				 = 
				.edgeDestinations[] = 
			}
			 = .Connect([], [])
			if  := .SrcArrowhead;  != nil {
				var  *bool
				if .Style.Filled != nil {
					,  := strconv.ParseBool(.Style.Filled.Value)
					 = new()
				}
				.SourceArrowhead = layoutgraph.Arrowhead(d2target.ToArrowhead(.Shape.Value, ))
				if .Label.Value != "" {
					.SourceArrowheadLabel = &layoutgraph.Label{
						Text:   .Label.Value,
						Width:  float64(.LabelDimensions.Width),
						Height: float64(.LabelDimensions.Height),
					}
				}
			} else {
				if .SrcArrow {
					.SourceArrowhead = layoutgraph.Arrowhead(d2target.TriangleArrowhead)
				} else {
					.SourceArrowhead = layoutgraph.Arrowhead(d2target.NoArrowhead)
				}
			}
			if  := .DstArrowhead;  != nil {
				var  *bool
				if .Style.Filled != nil {
					,  := strconv.ParseBool(.Style.Filled.Value)
					 = new()
				}
				.TargetArrowhead = layoutgraph.Arrowhead(d2target.ToArrowhead(.Shape.Value, ))
				if .Label.Value != "" {
					.TargetArrowheadLabel = &layoutgraph.Label{
						Text:   .Label.Value,
						Width:  float64(.LabelDimensions.Width),
						Height: float64(.LabelDimensions.Height),
					}
				}
			} else {
				if .DstArrow {
					.TargetArrowhead = layoutgraph.Arrowhead(d2target.TriangleArrowhead)
				} else {
					.TargetArrowhead = layoutgraph.Arrowhead(d2target.NoArrowhead)
				}
			}

			if  {
				for ,  := range .Route {
					.Points = append(.Points, .Copy())
				}
			}
		}

		.ID = []
		.edgeIDs[] = .ID
		.D2ID = new()
		.Style = cloneStyle(.Style)
		 := int64(.LabelDimensions.Width + 2*label.PADDING)
		 := int64(.LabelDimensions.Height + 2*label.PADDING)
		// TODO update minWidth/minHeight logic
		if  := .SourceArrowheadLabel;  != nil {
			 := math.Max(.Width, .Height) + 5
			 += int64()
			 += int64()
		}
		if  := .TargetArrowheadLabel;  != nil {
			 := math.Max(.Width, .Height) + 5
			 += int64()
			 += int64()
		}
		if  > maxResultCoordinate ||  > maxResultCoordinate {
			return , fmt.Errorf("D2 edge %q minimum dimensions exceed supported limit %d", .AbsID(), maxResultCoordinate)
		}
		.MinWidth = int()
		.MinHeight = int()

		if .Label.Value != "" {
			.Label = &layoutgraph.Label{
				Text:   .Label.Value,
				Width:  float64(.LabelDimensions.Width),
				Height: float64(.LabelDimensions.Height),
			}
			if  && .LabelPosition != nil {
				.Label.Position = label.FromString(*.LabelPosition)
				if .LabelPercentage != nil {
					.LabelPercentage = *.LabelPercentage
				}
				if .SrcArrow && !.DstArrow {
					// The route was reversed above. Preserve the label's
					// physical position so rerouting reserves its actual box.
					.Label.Position = .Label.Position.Mirrored()
					if .LabelPercentage != nil || .Label.Position.IsUnlocked() {
						.LabelPercentage = 1 - .LabelPercentage
					}
				}
			}
		}

		if .SQLTable != nil &&  != nil {
			.FromTableColumnIndex = new(*)
		}
		if .SQLTable != nil &&  != nil {
			.ToTableColumnIndex = new(*)
		}
		if .Style.Opacity != nil {
			,  := strconv.ParseFloat(.Style.Opacity.Value, 64)
			if  == nil &&  == 0 {
				.IsInvisible = true
				.MinHeight = 0
				.MinWidth = 0
			}
		}
		if .Style.Stroke != nil && strings.EqualFold(.Style.Stroke.Value, "transparent") {
			.IsInvisible = true
		}
	}

	return , nil
}

func cloneStyle( d2graph.Style) layoutgraph.EdgeStyle {
	 := func( *d2graph.Scalar) *layoutgraph.StyleScalar {
		if  == nil {
			return nil
		}
		// MapKey points back into the mutable D2 AST. TALA only consumes the
		// scalar value, so do not retain that provenance in the isolated graph.
		return &layoutgraph.StyleScalar{Value: .Value}
	}
	return layoutgraph.EdgeStyle{
		Opacity:       (.Opacity),
		Stroke:        (.Stroke),
		Fill:          (.Fill),
		FillPattern:   (.FillPattern),
		StrokeWidth:   (.StrokeWidth),
		StrokeDash:    (.StrokeDash),
		BorderRadius:  (.BorderRadius),
		Shadow:        (.Shadow),
		ThreeDee:      (.ThreeDee),
		Multiple:      (.Multiple),
		Font:          (.Font),
		FontSize:      (.FontSize),
		FontColor:     (.FontColor),
		Animated:      (.Animated),
		Bold:          (.Bold),
		Italic:        (.Italic),
		Underline:     (.Underline),
		Filled:        (.Filled),
		DoubleBorder:  (.DoubleBorder),
		TextTransform: (.TextTransform),
	}
}

func parseDirection( string) geo.Orientation {
	switch  {
	case "up":
		return geo.Top
	case "down":
		return geo.Bottom
	case "left":
		return geo.Left
	case "right":
		return geo.Right
	default:
		return geo.NONE
	}
}

// RouteEdges reroutes edges in g. It mutates g and must not run concurrently
// against the same graph.
func ( context.Context,  *d2graph.Graph,  []*d2graph.Edge) ( error) {
	defer recoverAsError("edge routing", &)
	if  == nil {
		return fmt.Errorf("TALA edge routing requires a context")
	}
	if  := validateRequestedD2Edges(, );  != nil {
		return 
	}

	 := layoutgraph.NewGraph()
	,  := translateGraph(, , , true)
	if  != nil {
		return 
	}

	 := make(map[layoutgraph.EntityID]*layoutgraph.Edge, len(.Edges))
	for ,  := range .Edges {
		[.ID] = 
	}
	 := make([]*layoutgraph.Edge, 0, len())
	for ,  := range  {
		if  == nil {
			return fmt.Errorf("cannot route a nil D2 edge")
		}
		,  := .edgeIDs[]
		,  := []
		if ! || ! {
			return fmt.Errorf("could not find edge %#v in graph", .AbsID())
		}
		 = append(, )
	}

	 := 
	 := func() {}
	if .Done() == nil {
		,  = context.WithCancel()
	}
	defer ()
	 = routing.RouteEdges(, , )
	if  != nil {
		return preferContextError(, "edge routing", )
	}

	,  := buildRoutePatch(, , , )
	if  != nil {
		return preferContextError(, "edge routing", )
	}
	return commitRoutePatch(, )
}

func recoverAsError( string,  *error) {
	if recover() != nil {
		* = fmt.Errorf("TALA %s failed due to an internal invariant", )
	}
}