package d2isometric

import (
	
	

	
)

// composeHierarchy is a projection of the compiled layout, not a second layout.
// Parent links are resolved by BuildScene's D2 key parser, including quoted IDs.
// An invisible root board provides membership for leaves without a container.
func composeHierarchy( *Scene,  map[string]int,  []int) error {
	 := make([]int, len(.Nodes))
	for  := range  {
		[] = -1
	}
	for  := range .Nodes {
		 := &.Nodes[]
		 := .Metadata.Original
		.Size = moduleSize()
		.Position = hierarchyCenter()
		.Position.Y = .07 + .Size.Y/2
		if !.Container {
			continue
		}
		 := 0
		for  := [];  >= 0;  = [] {
			if .Nodes[].Container {
				++
			}
		}
		 := "platform"
		if  > 0 {
			 = "group"
		}
		 := Board{
			ID: "@container:" + .ID, SourceID: .ID, Label: .Label,
			Kind: , Level: , Position: hierarchyCenter(),
			Size: Vec3{.Size.X, .14, .Size.Z}, NodeIDs: []string{},
		}
		if .Label != "" {
			,  := boardLabelMetrics(, .Nodes, )
			// Header metadata must never enlarge the source footprint or move a
			// child. The native renderer places text using source label anchors.
			.HeaderDepth = math.Min(.Size.Z, math.Max(.5, +.3))
		}
		[] = len(.Boards)
		.Boards = append(.Boards, )
		.Position, .Size, .BoardID = .Position, .Size, .ID
	}
	 := -1
	for  := range .Nodes {
		 := &.Nodes[]
		 := []
		// Sequence semantic parents (actors, spans and groups) can own
		// annotations without enclosing them. Keep ParentID, but attach the
		// render membership to the nearest actual source container board.
		for  >= 0 && [] < 0 {
			 = []
		}
		if  >= 0 {
			 := &.Boards[[]]
			.NodeIDs = append(.NodeIDs, .ID)
			if .Container {
				.Boards[[]].ParentID = .ID
			} else {
				.BoardID = .ID
			}
		} else if !.Container {
			if  < 0 {
				 = len(.Boards)
				.Boards = append(.Boards, Board{ID: "@ungrouped", Kind: "ungrouped", NodeIDs: []string{}})
			}
			 := &.Boards[]
			.NodeIDs = append(.NodeIDs, .ID)
			.BoardID = .ID
		}
	}
	if  >= 0 {
		// This bound is only membership metadata; no visible plate or invented
		// dependency caption is introduced around ungrouped source objects.
		 := &.Boards[]
		,  := math.Inf(1), math.Inf(1)
		,  := math.Inf(-1), math.Inf(-1)
		for ,  := range .NodeIDs {
			 := .Nodes[[]]
			 = math.Min(, .Position.X-.Size.X/2)
			 = math.Max(, .Position.X+.Size.X/2)
			 = math.Min(, .Position.Z-.Size.Z/2)
			 = math.Max(, .Position.Z+.Size.Z/2)
		}
		.Position = Vec3{( + ) / 2, 0, ( + ) / 2}
		.Size = Vec3{math.Max(SceneScale, -), .14, math.Max(SceneScale, -)}
	}
	 := 0
	 := false
	for  := range .Edges {
		 := &.Edges[]
		 := .Metadata.Original
		if len(.Route) >= 2 {
			var  error
			.Points,  = hierarchyRoute(, &)
			if  != nil {
				return fmt.Errorf("isometric: hierarchy route %q: %w", .ID, )
			}
		} else {
			// BuildScene has already rejected unknown endpoints without a route.
			// A caller can supply a typed target before routing; keep it usable
			// without claiming this simple fallback is a layout engine's route.
			,  := .Nodes[[.Source]], .Nodes[[.Target]]
			.Points = hierarchyFallback(, )
			 += len(.Points)
			 = true
		}
		if  > maxEntries {
			return fmt.Errorf("isometric: hierarchy route points exceed %d", maxEntries)
		}
	}
	if  {
		.Warnings = append(.Warnings, "Connections without a usable compiled route use a simple perimeter fallback; obstacle avoidance requires a routed D2 layout.")
	}
	return nil
}

func hierarchyCenter( d2target.Shape) Vec3 {
	// Convert before adding to avoid integer overflow on 32-bit targets. Zero
	// dimensions use the same one-source-pixel footprint as moduleSize.
	return Vec3{
		(float64(.Pos.X) + float64(max(1, .Width))/2) * SceneScale,
		0,
		(float64(.Pos.Y) + float64(max(1, .Height))/2) * SceneScale,
	}
}

// hierarchyRoute preserves polylines exactly. Cubic controls are metadata, not
// corners: flatten each cubic to a quarter-source-pixel geometric tolerance.
// The fixed recursion depth and shared output budget bound pathological curves.
func hierarchyRoute( d2target.Connection,  *int) ([]Vec3, error) {
	if .IsCurve && (len(.Route)-1)%3 != 0 {
		return nil, fmt.Errorf("invalid cubic control point count %d", len(.Route))
	}
	 := make([]Vec3, 0, len(.Route))
	 := func( Vec3) error {
		if * >= maxEntries {
			return fmt.Errorf("flattened route points exceed %d", maxEntries)
		}
		*++
		 = append(, )
		return nil
	}
	 := func( int) Vec3 {
		return Vec3{.Route[].X * SceneScale, surfaceHeight, .Route[].Y * SceneScale}
	}
	if !.IsCurve {
		for  := range .Route {
			if  := (());  != nil {
				return nil, 
			}
		}
		return , nil
	}
	if  := ((0));  != nil {
		return nil, 
	}
	var  func(Vec3, Vec3, Vec3, Vec3, int) error
	 = func(, , ,  Vec3,  int) error {
		if hierarchyControlDistance(, , ) <= SceneScale*.25 && hierarchyControlDistance(, , ) <= SceneScale*.25 {
			return ()
		}
		if  >= 24 {
			return fmt.Errorf("curve subdivision exceeds precision budget")
		}
		, ,  := hierarchyMidpoint(, ), hierarchyMidpoint(, ), hierarchyMidpoint(, )
		,  := hierarchyMidpoint(, ), hierarchyMidpoint(, )
		 := hierarchyMidpoint(, )
		if  := (, , , , +1);  != nil {
			return 
		}
		return (, , , , +1)
	}
	for  := 1;  < len(.Route);  += 3 {
		if  := ((-1), (), (+1), (+2), 0);  != nil {
			return nil, 
		}
	}
	return , nil
}

func hierarchyMidpoint(,  Vec3) Vec3 {
	return Vec3{(.X + .X) / 2, surfaceHeight, (.Z + .Z) / 2}
}

func hierarchyControlDistance(, ,  Vec3) float64 {
	,  := .X-.X, .Z-.Z
	 := 0.0
	if  := * + *;  > 0 {
		 = math.Max(0, math.Min(1, ((.X-.X)*+(.Z-.Z)*)/))
	}
	return math.Hypot(.X-.X-*, .Z-.Z-*)
}

func hierarchyFallback(,  Node) []Vec3 {
	if .ID == .ID {
		,  := .Size.X/2, .Size.Z/2
		 := .Position
		return []Vec3{{.X + , surfaceHeight, .Z}, {.X +  + .35, surfaceHeight, .Z}, {.X +  + .35, surfaceHeight, .Z +  + .35}, {.X, surfaceHeight, .Z +  + .35}, {.X, surfaceHeight, .Z + }}
	}
	 := func( Node,  Vec3,  float64) Vec3 {
		,  := .X-.Position.X, .Z-.Position.Z
		if  == 0 &&  == 0 {
			 = 
		}
		 := 1 / math.Max(math.Abs()/(.Size.X/2), math.Abs()/(.Size.Z/2))
		return Vec3{.Position.X + *, surfaceHeight, .Position.Z + *}
	}
	return []Vec3{(, .Position, 1), (, .Position, -1)}
}