package d2isometricimg

import (
	
	
	

	
	
)

type packetRoute struct {
	points           []Vec
	lengths          []float64
	material         *Material
	forward, reverse bool
}

func routeLengths( []Vec) []float64 {
	 := make([]float64, len())
	for  := 1;  < len(); ++ {
		[] = [-1] + nlen(nsub([], [-1]))
	}
	return 
}
func pathPoint( []Vec,  []float64,  float64) Vec {
	if len() == 0 {
		return Vec{}
	}
	 := max(0, min(1, )) * [len()-1]
	for  := 1;  < len(); ++ {
		if [] >=  {
			if  := [] - [-1];  > 1e-12 {
				return nlerp([-1], [], (-[-1])/)
			}
		}
	}
	return [len()-1]
}

// Keep every original bend/bridge vertex inside a dash. Uniform resampling can
// flatten a small bridge while traffic still follows its full curve.
func nativeRouteSection( []Vec,  []float64, ,  float64) []Vec {
	if len() < 2 || len() != len() ||  <=  {
		return nil
	}
	,  = max(0, min(1, )), max(0, min(1, ))
	 := [len()-1]
	 := []Vec{pathPoint(, , )}
	 := sort.Search(len(), func( int) bool { return [] > * })
	for  := ;  < len() && [] < *; ++ {
		 = append(, [])
	}
	return append(, pathPoint(, , ))
}

// Clip original dash intervals to a visible portion of the same path. Marker
// placement and stem length must never retime the rest of a dashed connection.
func nativeRouteDashes( []Vec,  []float64, , ,  float64,  int) [][]Vec {
	if len() < 2 || len() != len() ||  <=  {
		return nil
	}
	 := max(.18, *.07)
	 := max(1, min(max(1, ), int(math.Ceil([len()-1]/(*1.65)))))
	var  [][]Vec
	for  := 0;  < ; ++ {
		,  := max(, float64()/float64()), min(, min(1, (float64()+.62)/float64()))
		if  >  {
			 = append(, nativeRouteSection(, , , ))
		}
	}
	return 
}

// Corners are quadratic curves restricted to the original routed corridor.
// The finite subdivision count bounds both geometry and traffic interpolation.
func nativeRoundedRoute( []Vec) []Vec {
	 := make([]Vec, 0, len())
	for ,  := range  {
		if len() == 0 || nlen(nsub(, [len()-1])) > 1e-6 {
			 = append(, )
		}
	}
	 = 
	if len() < 2 {
		return append([]Vec(nil), ...)
	}
	 := []Vec{[0]}
	for  := 1;  < len()-1; ++ {
		, ,  := [], [-1], [+1]
		 := min(.18, min(nlen(nsub(, ))/4, nlen(nsub(, ))/4))
		 := nadd(, nmul(nunit(nsub(, )), ))
		 := nadd(, nmul(nunit(nsub(, )), ))
		 = append(, )
		for  := 1;  <= 12; ++ {
			 := float64() / 12
			 := nadd(nadd(nmul(, (1-)*(1-)), nmul(, 2*(1-)*)), nmul(, *))
			if .Y == .Y && .Y == .Y {
				.Y = .Y
			}
			 = append(, )
		}
	}
	return append(, [len()-1])
}

func ( *meshBuilder) ( []Vec,  float64,  *Material) {
	if len() < 2 ||  <= 0 {
		return
	}
	// Shared perpendicular rings keep the tube watertight through flat corners
	// and the small slopes at crossing bridges.
	 := make([][6]Vertex, len())
	for ,  := range  {
		,  := [max(0, -1)], [min(len()-1, +1)]
		 := nunit(nsub(, ))
		 := nunit(ncross(, nv(0, 1, 0)))
		 := nunit(ncross(, ))
		for  := 0;  < 6; ++ {
			 := 2 * math.Pi * float64() / 6
			 := nadd(nmul(, math.Cos()), nmul(, math.Sin()))
			[][] = Vertex{Position: nadd(, nmul(, )), Normal: }
		}
	}
	for  := 1;  < len(); ++ {
		for  := 0;  < 6; ++ {
			 := ( + 1) % 6
			, , ,  := [-1][], [-1][], [][], [][]
			.triangle(, , , , true)
			.triangle(, , , , true)
		}
	}
}

// nativeArrowClearance excludes this connection's own core/casing from hollow
// arrow interiors or pointed tapers. Packet paths retain the original port.
func nativeArrowClearance( d2target.Arrowhead,  int) float64 {
	switch  {
	case d2target.ArrowArrowhead:
		,  := .Dimensions(float64())
		return  * .0075 // Concave rear-center vertex is one quarter of the width.
	case d2target.TriangleArrowhead:
		,  := .Dimensions(float64())
		return  * .01 // The broad rear base receives the wire.
	case d2target.FilledDiamondArrowhead, d2target.FilledCircleArrowhead, d2target.FilledBoxArrowhead:
		,  := .Dimensions(float64())
		return  * .005 // Join at the broad center, before the taper or curved rim.
	case d2target.UnfilledTriangleArrowhead, d2target.DiamondArrowhead, d2target.CircleArrowhead, d2target.BoxArrowhead, d2target.CrossArrowhead:
		,  := .Dimensions(float64())
		return  * .01
	case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
		,  := .Dimensions(float64())
		return ( + 3 + float64()*1.8) * .01
	default:
		return 0
	}
}

func ( *meshBuilder) ( string, ,  Vec,  *Material,  ...int) {
	 := 2
	if len() > 0 {
		 = [0]
	}
	 := 1.
	if  != nil {
		 = float64(.Color.A) / 255
	}
	.arrowWithOpacity(, , , , , )
}

func ( *meshBuilder) ( string, ,  Vec,  *Material,  int,  float64) {
	if  == "" ||  == "none" ||  == nil {
		return
	}
	 := float64(max(0, ))
	 := .scale
	if  <= 0 {
		 = .01
	}
	,  := d2target.Arrowhead().Dimensions()
	if  <= 0 ||  <= 0 {
		return
	}
	 := nunit(nv(.X, 0, .Z))
	 := nv(-.Z, 0, .X)
	// D2 marker coordinates point along +X. Its tip becomes the exact port.
	 := func(,  float64) Vec { return nadd(, nadd(nmul(, (-)*), nmul(, (-/2)*))) }
	 := func( [][2]float64) {
		if  <= 0 {
			return
		}
		 := make([]Vec, len())
		for ,  := range  {
			[] = ([0], [1])
		}
		.tube(, */2, )
	}
	 := func( [][2]float64,  *Material) {
		 := make([]Vec, len())
		for ,  := range  {
			[] = ([0], [1])
		}
		.extrudedProfile(, .Y, , nil)
	}
	 := func( [][2]float64) { (, ) }
	 := nativeMaterial(.arrowBackground, .8, 0, )
	.Color = nativePaint(.arrowBackground, "#ffffff")
	.Color.A = uint8(math.Round(float64(.Color.A) * math.Max(0, math.Min(1, ))))
	.Unlit = true
	 := func( [][2]float64) { (, ) }
	 := len(.triangles)
	defer func() {
		// The marker is a printed knockout layer, not a raised physical
		// plate. Depth bias excludes crossing wire ink while leaving all
		// source coordinates and endpoints on their original plane.
		 := math.Max(.012, math.Min(.075, *.012)) * 1.1
		for  := ;  < len(.triangles); ++ {
			.triangles[].DepthBias = 
			.triangles[].CastShadow = false
			if .triangles[].Material !=  {
				.triangles[].DepthBias += .002
			}
		}
	}()
	 := func(, ,  float64,  bool) {
		 := make([][2]float64, 49)
		for  := range  {
			 := 2 * math.Pi * float64() / 48
			[] = [2]float64{ + *math.Cos(),  + *math.Sin()}
		}
		if  {
			([:48])
		} else {
			([:48])
			()
		}
	}
	 :=  / 2
	switch d2target.Arrowhead() {
	case d2target.ArrowArrowhead:
		([][2]float64{{0, 0}, {,  / 2}, {0, }, { / 4,  / 2}})
	case d2target.TriangleArrowhead:
		([][2]float64{{0, 0}, {,  / 2}, {0, }})
	case d2target.LineArrowhead:
		([][2]float64{{, }, { - ,  / 2}, {,  - }})
	case d2target.UnfilledTriangleArrowhead:
		([][2]float64{{, }, { - ,  / 2}, {,  - }})
		([][2]float64{{, }, { - ,  / 2}, {,  - }, {, }})
	case d2target.DiamondArrowhead:
		([][2]float64{{0,  / 2}, { / 2,  / 8}, {,  / 2}, { / 2,  * .9}})
		([][2]float64{{0,  / 2}, { / 2,  / 8}, {,  / 2}, { / 2,  * .9}, {0,  / 2}})
	case d2target.FilledDiamondArrowhead:
		([][2]float64{{0,  / 2}, { / 2, 0}, {,  / 2}, { / 2, }})
	case d2target.CircleArrowhead:
		(/2+, /2, max(0, /2-), false)
	case d2target.FilledCircleArrowhead:
		(/2+, /2, max(0, /2-), true)
	case d2target.BoxArrowhead:
		([][2]float64{{, }, { - , }, { - ,  - }, {,  - }})
		([][2]float64{{, }, { - , }, { - ,  - }, {,  - }, {, }})
	case d2target.FilledBoxArrowhead:
		([][2]float64{{0, 0}, {, 0}, {, }, {0, }})
	case d2target.CrossArrowhead:
		 :=  / 8
		 := [][2]float64{{0, /2 + }, {/2 - , /2 + }, {/2 - , }, {/2 + , }, {/2 + , /2 + }, {, /2 + }, {, /2 - }, {/2 + , /2 - }, {/2 + , 0}, {/2 - , 0}, {/2 - , /2 - }, {0, /2 - }}
		for ,  := range  {
			,  := [0]-/2, [1]-/2
			[] = [2]float64{/2 + (-)/math.Sqrt2, /2 + (+)/math.Sqrt2}
		}
		 = append(, [0])
		([:len()-1])
		()
		([][2]float64{{ / 2,  / 2}, {,  / 2}})
	case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
		 := 3 + *1.8
		// Crow's-foot's actual marker tip includes its offset beyond nominal width.
		 = nsub(, nmul(, *))
		if  == string(d2target.CfOneRequired) ||  == string(d2target.CfManyRequired) {
			([][2]float64{{, 0}, {, }})
		} else {
			(/2+2, /2, /2, false)
		}
		([][2]float64{{ - 3,  / 2}, { + ,  / 2}})
		if  == string(d2target.CfMany) ||  == string(d2target.CfManyRequired) {
			([][2]float64{{ + , 0}, { + 3,  / 2}, { + , }})
		} else {
			([][2]float64{{ * 2, 0}, { * 2, }})
		}
	}
}

func captionPosition( []Vec,  float64) ( Vec, ,  float64) {
	 := routeLengths()
	if len() < 2 {
		return Vec{}, 0, 1
	}
	 := [len()-1]
	 :=  * 
	 := -math.MaxFloat64
	 := 1
	for  := 1;  < len(); ++ {
		 := [] - [-1]
		if  < 1e-6 {
			continue
		}
		 := 3.
		if  == .5 {
			 = .35
		}
		 :=  - math.Abs([-1]+/2-)*
		if  >  {
			,  = , 
		}
	}
	,  := [-1], []
	 = max(1e-6, []-[-1])
	 := nunit(nsub(, ))
	if .X < -1e-6 || math.Abs(.X) < 1e-6 && .Z < 0 {
		 = nmul(, -1)
	}
	 := .5
	if  != .5 {
		 = max(.15, min(.85, (-[-1])/))
	}
	 = nlerp(, , )
	.Y += .006
	.X += .Z * .25
	.Z -= .X * .25
	return , math.Atan2(.Z, .X), 
}

func ( *meshBuilder) ( []d2isometric.Edge,  *routeCaptionPlacer,  ...*d2isometric.Scene) []packetRoute {
	 := []packetRoute{}
	var  []d2isometric.Node
	var  []d2isometric.Board
	var  *d2isometric.Scene
	if len() > 0 && [0] != nil {
		 = [0]
		,  = [0].Nodes, [0].Boards
	}
	 := nativeCaptionPaint()
	, ,  := nativeEdgeRoutes(.ctx, , , , .hierarchySupports)
	if  != nil {
		.err = 
		return 
	}
	// Reserve fixed source captions and all visible wires before placing any
	// automatic caption. Edge order must not let an earlier automatic label
	// occupy a later authored anchor, or cover a route drawn later in the pass.
	for ,  := range  {
		if .Opacity <= 0 || len([]) < 2 {
			continue
		}
		if nativeSequenceEdge() {
			.reserveSequenceEdge(, [], )
			continue
		}
		if .StrokeWidth > 0 {
			.AvoidRoute([], nativeRouteRadius()*1.6)
		}
		 := .Icon != "" && .Metadata.Original.Icon != nil
		if .Label == "" && ! {
			continue
		}
		,  := nativeRouteCaptionSize(.Metadata.Original.Text, , .scale)
		if ,  := nativeConnectionCaptionSurface([], .Metadata.Original, , , .scale);  {
			if nativeCaptionOnEdge(.Metadata.Original) {
				.center.Y += nativeRouteRadius()
			}
			.reserve(captionRect(, true))
		}
	}
	for ,  := range  {
		if .err != nil {
			return 
		}
		if len(.Points) < 2 || .Opacity <= 0 {
			continue
		}
		// BuildScene already bounds routes; additionally cap native tessellation.
		if len(.Points) > 10000 {
			.err = fmt.Errorf("isometric route exceeds 10000 control points")
			return 
		}
		if nativeSequenceEdge() {
			 = append(, .sequenceEdge(, [], )...)
			continue
		}
		 := nativeRouteTint()
		 := nativeMaterial(, .6, .05, .Opacity)
		.Unlit = true
		 := float64(.Color.A) / 255
		 := []
		if len() < 2 {
			continue
		}
		 := routeLengths()
		 := [len()-1]
		,  := .visibleArrowRange(, )
		 := 
		if  > 0 ||  < 1 {
			 = nativeRouteSection(, , , )
		}
		,  := , 
		if  > 1e-9 {
			 += nativeArrowClearance(.SourceArrow, .StrokeWidth) / 
			 -= nativeArrowClearance(.TargetArrow, .StrokeWidth) / 
		}
		 := 
		if  > 0 ||  < 1 {
			 = nativeRouteSection(, , , )
		}
		 := len(.triangles)
		if .StrokeWidth > 0 && len() > 1 {
			 := nativeRouteRadius()
			if .StrokeDash <= 0 {
				.routeCasing(, , )
				.routeInk(, , )
			} else {
				// Dash positions belong to the complete routed connection.
				// Endpoint visibility clips them without shifting their phase.
				for ,  := range nativeRouteDashes(, , , , .StrokeDash, max(4, 8000/max(1, len()))) {
					.routeCasing(, , )
					.routeInk(, , )
				}
			}
		}
		 := .Icon != "" && .Metadata.Original.Icon != nil
		if (.Label != "" || ) && nativeCaptionOnEdge(.Metadata.Original) {
			,  := nativeRouteCaptionSize(.Metadata.Original.Text, , .scale)
			if ,  := nativeConnectionCaptionSurface([], .Metadata.Original, , , .scale);  {
				.center.Y += nativeRouteRadius()
				// The print area can be wider than its text. Leave a close gap
				// around the compiled label dimensions, preserving the markers.
				if ! && .Metadata.Original.LabelWidth > 0 {
					.width = min(.width, float64(.Metadata.Original.LabelWidth)*.scale+.1)
				}
				.routeCaptionKnockout(, )
			}
		}
		 := nsub([0], [1])
		 := nsub([len()-1], [len()-2])
		.arrowWithOpacity(string(.SourceArrow), [0], , , .StrokeWidth, .Opacity)
		.arrowWithOpacity(string(.TargetArrow), [len()-1], , , .StrokeWidth, .Opacity)
		if .Metadata.Original.Animated && .StrokeWidth > 0 &&  > 0 {
			 := nativeMaterial("#e9f8ff", .2, .15, )
			.Emissive = nativePaint(, "#657e9e")
			,  := .TargetArrow != "" && .TargetArrow != "none", .SourceArrow != "" && .SourceArrow != "none"
			if ! && ! {
				 = true
			}
			 = append(, packetRoute{points: , lengths: , material: , forward: , reverse: })
		}
		 := []struct {
			     string
			 float64
			    d2target.Text
			      string
		}{{.Label, .5, .Metadata.Original.Text, }}
		// Connection.Fill is the compiled caption backing, separate from the
		// wire stroke and the Text's optional label-fill value.
		if .Metadata.Original.Fill != "" {
			[0]..LabelFill = (.Metadata.Original.Fill)
		}
		if .FontExplicit {
			[0]. = .FontColor
		}
		if .SourceLabel != nil {
			 = append(, struct {
				     string
				 float64
				    d2target.Text
				      string
			}{.SourceLabel.Label, .08, *.SourceLabel, .SourceLabel.Color})
		}
		if .TargetLabel != nil {
			 = append(, struct {
				     string
				 float64
				    d2target.Text
				      string
			}{.TargetLabel.Label, .92, *.TargetLabel, .TargetLabel.Color})
		}
		for ,  := range  {
			..LabelFill = (..LabelFill)
			. = (.)
			 :=  == 0 && .Icon != "" && .Metadata.Original.Icon != nil
			if . == "" && ! {
				continue
			}
			,  := nativeRouteCaptionSize(., , .scale)
			var  labelSurface
			 := false
			if  == 0 {
				,  = nativeConnectionCaptionSurface([], .Metadata.Original, , , .scale)
			}
			if  {
				if nativeCaptionOnEdge(.Metadata.Original) {
					// Keep the printed text and any authored fill above
					// the cable crown.
					.center.Y += nativeRouteRadius()
				}
			} else {
				 := []
				if  > 0 {
					// Endpoint captions follow the visible solid contact. Main
					// captions keep their original authored route anchors.
					 = 
				}
				 = .Place(, ., , )
			}
			if  == 0 {
				.addSurfaceLink(.Link, .Tooltip, )
			}
			if  {
				 := d2target.Shape{ID: .ID, Text: .Metadata.Original.Text, Icon: .Metadata.Original.Icon, IconPosition: .Metadata.Original.IconPosition}
				 = .shapeIcon(, , .Opacity, "edge", .Metadata.Original.IconBorderRadius)
			}
			.label(., , ., ., .Opacity, "edge")
		}
	}
	return 
}