package d2isometricimg

import (
	
	
	

	
	
)

// An endpoint owns only its own solid, never its containing platforms or
// descendants. Ranges refer to completed geometry before routes are appended.
type nativeArrowOwner struct {
	first, last int
	node        d2isometric.Node
	board       *d2isometric.Board
	caps        []Triangle
	prepared    bool
	obstacles   map[float64][]nativeArrowObstacle
}

type nativeArrowMarkerKey struct {
	kind  d2target.Arrowhead
	width int
}

// Query caches are local to mesh construction and bounded independently of
// output geometry. Work counts projection-axis comparisons, including misses.
const maxNativeArrowWork = 100_000_000
const maxNativeArrowCachePoints = 1_000_000

func ( *meshBuilder) ( int) bool {
	if .err != nil {
		return false
	}
	if .ctx != nil && .ctx.Err() != nil {
		.err = .ctx.Err()
		return false
	}
	.arrowWork += int64()
	if .arrowWork > maxNativeArrowWork {
		.err = fmt.Errorf("isometric endpoint visibility exceeds bounded query work")
		return false
	}
	return true
}

type nativeArrowMarker struct {
	vertices    []Vec
	minY, reach float64
}

func ( *meshBuilder) ( d2isometric.Node,  *d2isometric.Board,  int) {
	if .SequenceRole != "" || .Opacity <= 0 {
		return
	}
	if .arrowOwners == nil {
		.arrowOwners = make(map[string]*nativeArrowOwner)
	}
	.arrowOwners[.ID] = &nativeArrowOwner{first: , last: len(.triangles), node: , board: }
}

func ( *meshBuilder) ( d2target.Arrowhead,  int) nativeArrowMarker {
	 := nativeArrowMarkerKey{, }
	if ,  := .arrowMarkers[];  {
		return 
	}
	 := &meshBuilder{ctx: .ctx, scale: .scale, arrowBackground: .arrowBackground}
	.arrowWithOpacity(string(), Vec{}, nv(1, 0, 0), nativeMaterial("black", 0, 0, 1), , 1)
	if .err != nil {
		.err = .err
	}
	 := nativeArrowMarker{minY: math.Inf(1)}
	for ,  := range .triangles {
		for ,  := range .V {
			.vertices = append(.vertices, .Position)
			.minY = min(.minY, .Position.Y+.DepthBias*nativeViewDirection().Y)
			.reach = max(.reach, -.Position.X)
		}
	}
	if len(.arrowMarkers) < 64 {
		if .arrowMarkers == nil {
			.arrowMarkers = make(map[nativeArrowMarkerKey]nativeArrowMarker)
		}
		.arrowMarkers[] = 
	}
	return 
}

// Printed rectangular viewports are not shape silhouettes. Recreate only the
// exact filled source caps for the visibility query; they are never painted.
// This also makes the query identical in the raster and vector backends.
func ( *meshBuilder) ( *nativeArrowOwner) []Triangle {
	if .prepared {
		return .caps
	}
	.prepared = true
	 := nativeClassicNode(.node)
	if nativeMarkdownCard() {
		 = nativeMarkdownCardPaint()
	}
	// Only standalone solids have real textured cap geometry; source
	// containers of the same shape use their ordinary extruded profiles.
	if .board == nil && nativeSolidNode() {
		return nil
	}
	 := .scale
	if  <= 0 {
		 = .01
	}
	 := &meshBuilder{ctx: .ctx}
	 := func( d2target.Shape, , , , ,  float64,  string) {
		if nativePaint(, "#dce5eb").A == 0 ||  <= 0 ||  <= 0 {
			return
		}
		,  := nativeShapeProfiles()
		if  != nil {
			.err = 
			return
		}
		 := nativeMaterial(, 0, 0, .Opacity)
		for ,  := range  {
			 := make([]Vec, len())
			for ,  := range  {
				[] = nv(+.X*/float64(.Width), , +.Z*/float64(.Height))
			}
			.extrudedProfile(, , , nil)
		}
	}
	 := .Position.Y - .Size.Y/2
	 := nativeCanonicalHeight(, )
	for  := 1;  >= 0; -- {
		if  == 1 && !.Metadata.Original.Multiple {
			continue
		}
		, ,  := float64()*d2target.MULTIPLE_OFFSET*, -float64()*d2target.MULTIPLE_OFFSET*, -float64()*min(.045, *.25)
		if .board != nil {
			 := *.board
			.Size = .Size
			 := nativeFaceSource(, .Fill)
			(, .Position.X-.Size.X/2+, .Position.Z-.Size.Z/2+, .Size.X, .Size.Z, hierarchySurfaceY()-float64()*.00001, .Fill)
		} else if nativeStructuredNode() {
			 := .Metadata.Original
			,  := .Size.X/float64(.Width), .Size.Z/float64(.Height)
			,  = float64()*d2target.MULTIPLE_OFFSET*, -float64()*d2target.MULTIPLE_OFFSET*
			 := func(, ,  float64,  string) {
				if  <= 0 {
					return
				}
				 := nativeFaceSource(, )
				.Type = d2target.ShapeRectangle
				.Width, .Height = .Width, max(1, int(math.Round()))
				.BorderRadius = min(.BorderRadius, .Height/2)
				(, .Position.X-.Size.X/2+, .Position.Z-.Size.Z/2++*, .Size.X, *, +, )
			}
			(0, float64(.Height), +.01, .Fill)
			for ,  := range nativeStructuredRows() {
				,  := +*.32/.46, .Stroke
				if  == 0 {
					,  = +, .Fill
				}
				(.z+.back, .depth-.back-.front, , )
			}
		} else {
			 := nativeFaceSource(, .Fill)
			 :=  + (+)*hierarchyNodeRelief()
			(, .Position.X-.Size.X/2+, .Position.Z-.Size.Z/2+, .Size.X, .Size.Z, , .Fill)
		}
	}
	if .err != nil {
		.err = .err
	}
	.caps = .triangles
	return .caps
}

// At an equal projected point, greater world Y is closer to this camera.
// Clipping here excludes lower platforms and the backs of solids below the
// marker plane instead of treating every silhouette overlap as occlusion.
func nativeArrowOccluder( Triangle,  float64) []routeCaptionPoint {
	// DepthBias is a camera-depth offset, equivalent to this Y difference at
	// equal projection. The footprint itself must remain in its physical place.
	 -= .DepthBias * nativeViewDirection().Y
	 := []Vec{.V[0].Position, .V[1].Position, .V[2].Position}
	 := make([]Vec, 0, 4)
	for ,  := range  {
		 := [(+1)%len()]
		,  := .Y > +1e-6, .Y > +1e-6
		if  {
			 = append(, )
		}
		if  !=  {
			 = append(, nlerp(, , (+1e-6-.Y)/(.Y-.Y)))
		}
	}
	if len() < 3 {
		return nil
	}
	 := make([]routeCaptionPoint, len())
	for ,  := range  {
		[] = captionProjection()
	}
	return captionConvexHull()
}

type nativeArrowObstacle struct {
	points []routeCaptionPoint
	lo, hi routeCaptionPoint
}

func nativeArrowBounds( []routeCaptionPoint) (routeCaptionPoint, routeCaptionPoint) {
	,  := routeCaptionPoint{math.Inf(1), math.Inf(1)}, routeCaptionPoint{math.Inf(-1), math.Inf(-1)}
	for ,  := range  {
		.x = min(.x, .x)
		.z = min(.z, .z)
		.x = max(.x, .x)
		.z = max(.z, .z)
	}
	return , 
}

func ( *meshBuilder) ( string,  float64) []nativeArrowObstacle {
	 := .arrowOwners[]
	if  == nil {
		return nil
	}
	if ,  := .obstacles[];  {
		return 
	}
	 := .arrowOwnerCaps()
	var  []nativeArrowObstacle
	 := 0
	 := func( Triangle) {
		if !.arrowBudget(1) {
			return
		}
		if .Material == nil || .Material.Color.A == 0 || .NoDepthWrite || .svgCoverageOnly {
			return
		}
		// Source-texture viewports and labels include transparent margins. Exact
		// filled cap proxies replace them; solid ellipses have no viewport margins.
		if .Material.Texture != nil && (.board != nil || !nativeSolidNode(.node) || .Material.Unlit || nativePaint(.node.Fill, "#dce5eb").A == 0) {
			return
		}
		 := nativeArrowOccluder(, )
		if len() > 2 {
			,  := nativeArrowBounds()
			 = append(, nativeArrowObstacle{, , })
			 += len()
		}
	}
	for ,  := range .triangles[.first:.last] {
		()
	}
	for ,  := range  {
		()
	}
	if len(.obstacles) < 8 &&  <= maxNativeArrowCachePoints-.arrowCachePoints {
		if .obstacles == nil {
			.obstacles = make(map[float64][]nativeArrowObstacle)
		}
		.obstacles[] = 
		.arrowCachePoints += 
	}
	return 
}

func ( nativeArrowMarker) (,  Vec) []routeCaptionPoint {
	 := nmul(, -1)
	 := nv(-.Z, 0, .X)
	 := make([]routeCaptionPoint, 0, len(.vertices))
	for ,  := range .vertices {
		 := nadd(, nadd(nmul(, .X), nmul(, .Z)))
		.Y += .Y
		 = append(, captionProjection())
	}
	return captionConvexHull()
}

type nativeArrowInterval struct{ first, last float64 }

// Swept SAT gives exact overlap intervals for a translating marker footprint
// and each convex piece of the endpoint. Their union preserves concave gaps.
func nativeArrowOverlap(,  []routeCaptionPoint,  routeCaptionPoint,  float64) (nativeArrowInterval, bool) {
	 := nativeArrowInterval{0, }
	for ,  := range [][]routeCaptionPoint{, } {
		for ,  := range  {
			 := [(+1)%len()]
			 := routeCaptionPoint{-(.z - .z), .x - .x}
			if math.Abs(.x)+math.Abs(.z) < 1e-12 {
				continue
			}
			 := func( []routeCaptionPoint) (float64, float64) {
				,  := math.Inf(1), math.Inf(-1)
				for ,  := range  {
					 := .x*.x + .z*.z
					 = min(, )
					 = max(, )
				}
				return , 
			}
			,  := ()
			,  := ()
			 := .x*.x + .z*.z
			if math.Abs() < 1e-12 {
				if  <  ||  <  {
					return , false
				}
				continue
			}
			,  := (-)/, (-)/
			if  >  {
				,  = , 
			}
			.first = max(.first, )
			.last = min(.last, )
			if .first > .last {
				return , false
			}
		}
	}
	return , true
}

// Walk only the original flat path, including its short bends. A crossing
// bridge or exhausted path bounds the retreat; markers never extrapolate.
func ( *meshBuilder) ( string,  nativeArrowMarker,  []Vec,  float64) float64 {
	if len(.vertices) == 0 || len() < 2 ||  <= 0 {
		return 0
	}
	 := .arrowObstacles(, [0].Y+.minY)
	if len() == 0 {
		return 0
	}
	 := 0.
	const  = .0008
	for  := 1;  < len(); ++ {
		if .ctx != nil && .ctx.Err() != nil {
			.err = .ctx.Err()
			return 0
		}
		 := nsub([], [-1])
		 := nlen()
		if  < 1e-9 {
			continue
		}
		if math.Abs(.Y) > 1e-7 || math.Abs([].Y-[0].Y) > 1e-7 {
			return 
		}
		 := min(, -)
		if  <= 0 {
			return 
		}
		 := nmul(, 1/)
		 := .footprint([-1], )
		var  []nativeArrowInterval
		,  := nativeArrowBounds()
		.x += min(0, .X*)
		.z += min(0, .Z*)
		.x += max(0, .X*)
		.z += max(0, .Z*)
		for ,  := range  {
			if !.arrowBudget(1) {
				return 0
			}
			if .x > .hi.x || .x < .lo.x || .z > .hi.z || .z < .lo.z {
				continue
			}
			 := len() + len(.points)
			if !.arrowBudget( * ) {
				return 0
			}
			if ,  := nativeArrowOverlap(, .points, routeCaptionPoint{.X, .Z}, );  {
				 = append(, )
			}
		}
		sort.Slice(, func(,  int) bool { return [].first < [].first })
		 := 0.
		for ,  := range  {
			if .first > + {
				break
			}
			 = max(, .last+)
		}
		if  <=  {
			return  + 
		}
		 += 
		if  <  ||  >=  {
			return 
		}
	}
	return 
}

func ( *meshBuilder) ( d2isometric.Edge,  []Vec) (float64, float64) {
	if len(.arrowOwners) == 0 || len() < 2 || nativeSequenceEdge() {
		return 0, 1
	}
	if !.arrowBudget(1) {
		return 0, 1
	}
	,  := .arrowMarker(.SourceArrow, .StrokeWidth), .arrowMarker(.TargetArrow, .StrokeWidth)
	if len(.vertices)+len(.vertices) == 0 {
		return 0, 1
	}
	 := routeLengths()
	 := [len()-1]
	// Leave enough wire length for both marker bodies when the route permits it.
	 := max(0, -.reach-.reach-.002)
	 := .arrowRetreat(.Source, , , )
	 := make([]Vec, len())
	for  := range  {
		[] = [len()-1-]
	}
	 := .arrowRetreat(.Target, , , )
	if + >  && + > 0 {
		 :=  / ( + )
		 *= 
		 *= 
	}
	if  <= 1e-9 &&  <= 1e-9 {
		return 0, 1
	}
	return  / , 1 - /
}