package d2isometricimg

import (
	
	
	
	
	
)

// SVG visibility is solved in the final orthographic camera. Depth is affine
// over a projected face, so even intersecting faces can be cut exactly without
// a global BSP or a centroid painter approximation. Larger z is nearer. The
// caller includes Triangle.DepthBias in z; it never changes projected x/y.
type svgPoint struct{ x, y, z float64 }

// Canonicalize the camera boundary before topology-sensitive clipping. Fused
// multiply-add is available on some CPUs but not others; its last-bit changes
// must not change which coplanar polygons are split or their paint order. The
// binary grids preserve far more precision than the SVG's six decimal places
// (and the depth tie tolerance), without changing the source layout.
func svgProjectedPoint(, ,  float64) svgPoint {
	return svgPoint{
		math.Round(*(1<<24)) / (1 << 24),
		math.Round(*(1<<24)) / (1 << 24),
		math.Round(*(1<<36)) / (1 << 36),
	}
}

type svgVisibilityFace struct {
	points  []svgPoint // Convex, planar projected polygon, either winding.
	opaque  bool       // A fully opaque, depth-writing surface, not a decal.
	group   *nativeOpacityGroup
	owner   *nativePaintOwner
	contour bool
	order   int // Later source paint wins an exactly coplanar tie.
}

type svgVisibilityLimits struct {
	faces, vertices, fragments, work, gridReferences int
}

var svgDefaultVisibilityLimits = svgVisibilityLimits{
	faces: rasterMaxTriangles, vertices: 8_000_000, fragments: 2_000_000,
	work: 100_000_000, gridReferences: 8_000_000,
}

type svgVisibilityBudget struct {
	ctx                                context.Context
	limits                             svgVisibilityLimits
	work                               int
	face, faces, candidates, fragments int
}

func ( *svgVisibilityBudget) ( int) error {
	if  < 0 ||  > .limits.work-.work {
		return fmt.Errorf("isometric SVG visibility exceeds work limit (%d faces, face %d, %d candidates, %d fragments)", .faces, .face, .candidates, .fragments)
	}
	.work += 
	return .ctx.Err()
}

type svgBox struct{ minX, minY, maxX, maxY float64 }

func svgPolygonBox( []svgPoint) svgBox {
	 := svgBox{math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)}
	for ,  := range  {
		.minX, .minY = math.Min(.minX, .x), math.Min(.minY, .y)
		.maxX, .maxY = math.Max(.maxX, .x), math.Max(.maxY, .y)
	}
	return 
}

func ( svgBox) ( svgBox) bool {
	return .minX < .maxX && .minX < .maxX && .minY < .maxY && .minY < .maxY
}

// Depth planes use a nearby origin to avoid loss of precision after a large
// screen translation. The plane and every cut remain in the subject's depth.
type svgDepthPlane struct {
	origin svgPoint
	dx, dy float64
}

func ( svgDepthPlane) ( svgPoint) float64 {
	// Explicit conversions round each product, forbidding implicit FMA here
	// and in the clipping predicates below even after compiler inlining.
	return .origin.z + float64(.dx*(.x-.origin.x)) + float64(.dy*(.y-.origin.y))
}

func svgPolygonArea( []svgPoint) float64 {
	if len() < 3 {
		return 0
	}
	var  float64
	 := [0]
	for  := 1; +1 < len(); ++ {
		,  := [], [+1]
		 += float64((.x-.x)*(.y-.y)) - float64((.y-.y)*(.x-.x))
	}
	return  / 2
}

func svgPolygonPlane( []svgPoint) (svgDepthPlane, bool) {
	if len() < 3 {
		return svgDepthPlane{}, false
	}
	 := [0]
	,  := 0.0, 0
	for  := 1; +1 < len(); ++ {
		,  := [], [+1]
		 := float64((.x-.x)*(.y-.y)) - float64((.y-.y)*(.x-.x))
		if math.Abs() > math.Abs() {
			,  = , 
		}
	}
	if math.Abs() <= 1e-14 {
		return svgDepthPlane{}, false
	}
	,  := [], [+1]
	return svgDepthPlane{,
		(float64((.z-.z)*(.y-.y)) - float64((.z-.z)*(.y-.y))) / ,
		(float64((.x-.x)*(.z-.z)) - float64((.x-.x)*(.z-.z))) / ,
	}, true
}

type svgVisibilityPolygon struct {
	points []svgPoint
	plane  svgDepthPlane
	box    svgBox
	area   float64
}

// A uniform grid keeps ordinary diagram meshes local. Very large polygons use
// one overflow bucket instead of occupying thousands of cells. All candidate
// visits, including duplicates and overflow visits, consume the work budget.
type svgVisibilityGrid struct {
	bounds svgBox
	nx, ny int
	cells  [][]int
	large  []int
}

func ( *svgVisibilityGrid) ( svgBox) (, , ,  int,  bool) {
	if !.bounds.overlaps() {
		return 0, 0, 0, 0, false
	}
	 := func( float64) int {
		return max(0, min(.nx-1, int(math.Floor((-.bounds.minX)/(.bounds.maxX-.bounds.minX)*float64(.nx)))))
	}
	 := func( float64) int {
		return max(0, min(.ny-1, int(math.Floor((-.bounds.minY)/(.bounds.maxY-.bounds.minY)*float64(.ny)))))
	}
	return (.minX), (.minY), (.maxX), (.maxY), true
}

func svgNewVisibilityGrid( []svgVisibilityPolygon,  []svgVisibilityFace,  *svgVisibilityBudget) (*svgVisibilityGrid, error) {
	 := svgBox{math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)}
	 := 0
	for ,  := range  {
		if  := .spend(1);  != nil {
			return nil, 
		}
		if len(.points) == 0 || ![].opaque {
			continue
		}
		.minX, .minY = math.Min(.minX, .box.minX), math.Min(.minY, .box.minY)
		.maxX, .maxY = math.Max(.maxX, .box.maxX), math.Max(.maxY, .box.maxY)
		++
	}
	if  == 0 {
		return nil, nil
	}
	,  := .maxX-.minX, .maxY-.minY
	 := math.Max(1, math.Min(65536, float64()/4))
	 := max(1, min(256, int(math.Ceil(math.Sqrt(*/)))))
	 := max(1, min(256, int(math.Ceil(/float64()))))
	 := &svgVisibilityGrid{bounds: , nx: , ny: , cells: make([][]int, *)}
	 := 0
	for ,  := range  {
		if  := .spend(1);  != nil {
			return nil, 
		}
		if len(.points) == 0 || ![].opaque {
			continue
		}
		, , , ,  := .cellsFor(.box)
		 := ( -  + 1) * ( -  + 1)
		if  > 64 {
			.large = append(.large, )
			 = 1
		} else {
			for  := ;  <= ; ++ {
				for  := ;  <= ; ++ {
					.cells[*+] = append(.cells[*+], )
				}
			}
		}
		 += 
		if  > .limits.gridReferences {
			return nil, fmt.Errorf("isometric SVG visibility exceeds spatial index limit")
		}
	}
	return , nil
}

// svgSplitPolygon cuts a convex polygon into two convex pieces. New vertices
// interpolate all three coordinates, so the output remains on its own face.
func svgSplitPolygon( []svgPoint,  func(svgPoint) float64) (,  []svgPoint) {
	if len() == 0 {
		return nil, nil
	}
	 := make([]float64, len())
	,  := false, false
	for ,  := range  {
		 := ()
		[] = 
		,  =  ||  > 0,  ||  < 0
	}
	// An exactly coincident or zero-length clipping edge contains the whole
	// face. It must not also emit the whole face as an outside fragment: that
	// would resurrect previously hidden geometry and multiply later cuts.
	if ! {
		return , nil
	}
	if ! {
		return nil, 
	}
	 := [len()-1]
	 := [len()-1]
	for ,  := range  {
		 := []
		if ( < 0 &&  > 0) || ( > 0 &&  < 0) {
			 :=  / ( - )
			 := svgPoint{.x + float64(*(.x-.x)), .y + float64(*(.y-.y)), .z + float64(*(.z-.z))}
			,  = append(, ), append(, )
		}
		if  >= 0 {
			 = append(, )
		}
		if  <= 0 {
			 = append(, )
		}
		,  = , 
	}
	return svgValidFragment(), svgValidFragment()
}

func svgValidFragment( []svgPoint) []svgPoint {
	if len() < 3 {
		return nil
	}
	// A depth cut through a shared vertex can create two identical projected
	// points with a last-bit difference in z. Remove those zero-length edges
	// before they become half-plane constraints in a subsequent subtraction.
	 := func(,  svgPoint) bool {
		 := math.Max(1e-9, math.Max(math.Max(math.Abs(.x), math.Abs(.y)), math.Max(math.Abs(.x), math.Abs(.y)))*1e-15)
		return math.Abs(.x-.x) <=  && math.Abs(.y-.y) <= 
	}
	for ,  := range  {
		if !(, [(+1)%len()]) {
			continue
		}
		 := make([]svgPoint, 0, len())
		for ,  := range  {
			if len() == 0 || !(, [len()-1]) {
				 = append(, )
			}
		}
		if len() > 1 && ([0], [len()-1]) {
			 = [:len()-1]
		}
		 = 
		break
	}
	if len() < 3 {
		return nil
	}
	 := svgPolygonBox()
	 := math.Max(.maxX-.minX, .maxY-.minY)
	// Projection rounding can leave almost-collinear slivers at shared edges.
	// They are narrower than SVG serialization precision, but retaining them
	// would multiply subsequent cuts and exhaust the visibility work budget.
	if math.Abs(svgPolygonArea()) <= math.Max(*1e-8, **1e-13) {
		return nil
	}
	return 
}

func svgEdgeDistance(,  svgPoint) func(svgPoint) float64 {
	return func( svgPoint) float64 { return float64((.x-.x)*(.y-.y)) - float64((.y-.y)*(.x-.x)) }
}

// A convex subtraction yields disjoint convex fragments. Checking intersection
// first is essential: subtracting a remote polygon must not fragment a face
// along infinitely extended edges that never actually cover any of it.
func svgSubtractPolygon(,  []svgPoint,  *svgVisibilityBudget) ([][]svgPoint, error) {
	 := 
	for ,  := range  {
		if  := .spend(len());  != nil {
			return nil, 
		}
		, _ = svgSplitPolygon(, svgEdgeDistance(, [(+1)%len()]))
		if len() == 0 {
			return [][]svgPoint{}, nil
		}
	}
	 := 
	var  [][]svgPoint
	for ,  := range  {
		if  := .spend(len());  != nil {
			return nil, 
		}
		var  []svgPoint
		,  = svgSplitPolygon(, svgEdgeDistance(, [(+1)%len()]))
		if len() != 0 {
			 = append(, )
		}
		if len() == 0 {
			break
		}
	}
	return , nil
}

func svgVisibleFaces( context.Context,  []svgVisibilityFace) ([][][]svgPoint, error) {
	 := svgDefaultVisibilityLimits
	.work = svgVisibilityWorkLimit(len())
	return svgVisibleFacesWithLimits(, , )
}

// Preserve the ordinary diagram budget while giving large meshes a linear
// amount of work per input face for indexing, local depth tests and clipping.
// Fragment indexing controls combinatorial growth independently. The native
// raster work ceiling remains an absolute bound for every visibility pass.
func svgVisibilityWorkLimit( int) int {
	if  > rasterMaxWork/2304 {
		return rasterMaxWork
	}
	return max(svgDefaultVisibilityLimits.work, *2304)
}

func svgVisibleFacesWithLimits( context.Context,  []svgVisibilityFace,  svgVisibilityLimits) ([][][]svgPoint, error) {
	if  := .Err();  != nil {
		return nil, 
	}
	if len() > .faces {
		return nil, fmt.Errorf("isometric SVG visibility exceeds face limit")
	}
	 := &svgVisibilityBudget{ctx: , limits: , faces: len()}
	 := make([]svgVisibilityPolygon, len())
	 := 0
	for ,  := range  {
		if  := .spend(len(.points) + 1);  != nil {
			return nil, 
		}
		 += len(.points)
		if  > .vertices {
			return nil, fmt.Errorf("isometric SVG visibility exceeds vertex limit")
		}
		for ,  := range .points {
			if math.IsNaN(.x) || math.IsNaN(.y) || math.IsNaN(.z) || math.Abs(.x) > rasterCoordinateLimit || math.Abs(.y) > rasterCoordinateLimit || math.Abs(.z) > rasterCoordinateLimit {
				return nil, fmt.Errorf("isometric SVG visibility face %d has invalid coordinates", )
			}
		}
		 := svgValidFragment(.points)
		,  := svgPolygonPlane()
		if ! {
			continue
		}
		 := append([]svgPoint(nil), ...)
		if svgPolygonArea() < 0 {
			for ,  := 0, len()-1;  < ; ,  = +1, -1 {
				[], [] = [], []
			}
		}
		// Area is only a clipping-order heuristic. Group subpixel-equivalent
		// areas so symmetric faces use the stable source-order tie break.
		[] = svgVisibilityPolygon{, , svgPolygonBox(), math.Round(svgPolygonArea()*1e4) / 1e4}
	}
	var  *svgVisibilityGrid
	var  *svgVisibilityTree
	var  error
	if len() > 8192 {
		,  = svgNewVisibilityTree(, , )
	} else {
		,  = svgNewVisibilityGrid(, , )
	}
	if  != nil {
		return nil, 
	}
	 := make([][][]svgPoint, len())
	 := make([]int, len())
	,  := 0, 0
	for ,  := range  {
		.face, .candidates, .fragments = , 0, 1
		if  := .spend(1);  != nil {
			return nil, 
		}
		if len(.points) == 0 {
			continue
		}
		 := [][]svgPoint{.points}
		var  []int
		 := func( []int) error {
			if  := .spend(len());  != nil {
				return 
			}
			for ,  := range  {
				if  ==  || [] == +1 {
					continue
				}
				[] =  + 1
				if [].group != nil && [].group != [].group {
					continue
				}
				if [].contour && ![].contour && (([].owner != nil && [].owner == [].owner) || ([].group != nil && [].group == [].group)) {
					continue
				}
				if .box.overlaps([].box) {
					 = append(, )
				}
			}
			return nil
		}
		if  != nil {
			,  := .query(.box, )
			if  != nil {
				return nil, 
			}
			if  := ();  != nil {
				return nil, 
			}
		} else if  != nil {
			if , , , ,  := .cellsFor(.box);  {
				for  := ;  <= ; ++ {
					for  := ;  <= ; ++ {
						if  := (.cells[*.nx+]);  != nil {
							return nil, 
						}
					}
				}
				if  := (.large);  != nil {
					return nil, 
				}
			}
		}
		// Remove broad solid faces before the narrow ink ribbons around them.
		// Cutting thousands of ribbons first would unnecessarily partition a
		// large board into slivers that its few broad caps subsequently hide.
		.candidates = len()
		if  := .spend(len() * bits.Len(uint(len())));  != nil {
			return nil, 
		}
		sort.Slice(, func(,  int) bool {
			,  := [], []
			if [].area != [].area {
				return [].area > [].area
			}
			if [].order == [].order {
				return  > 
			}
			return [].order > [].order
		})
		var  *svgVisibleFragmentSet
		if len() > 64 {
			,  = svgNewVisibleFragmentSet(.points, , .fragments-, .vertices-)
			if  != nil {
				return nil, 
			}
		}
		for ,  := range  {
			 := []
			if  := .spend(len(.points) + 4);  != nil {
				return nil, 
			}
			 := .points
			 := func( svgPoint) float64 { return .plane.at() - .plane.at() }
			, ,  := math.Inf(1), math.Inf(-1), 1.0
			// The overlap bbox bounds both affine depth extrema without requiring
			// another polygon allocation merely to classify a coplanar pair.
			 := svgBox{math.Max(.box.minX, .box.minX), math.Max(.box.minY, .box.minY), math.Min(.box.maxX, .box.maxX), math.Min(.box.maxY, .box.maxY)}
			for ,  := range []svgPoint{{x: .minX, y: .minY}, {x: .maxX, y: .minY}, {x: .maxX, y: .maxY}, {x: .minX, y: .maxY}} {
				 := ()
				,  = math.Min(, ), math.Max(, )
				 = math.Max(, math.Max(math.Abs(.plane.at()), math.Abs(.plane.at())))
			}
			 := math.Max(1e-9, *2e-14)
			if  >= - &&  <=  {
				if [].order < [].order || ([].order == [].order &&  < ) {
					continue
				}
			} else if  <= 0 {
				continue
			} else if  < 0 {
				, _ = svgSplitPolygon(, )
				if len() == 0 {
					continue
				}
			}
			if  != nil {
				if  := .subtract();  != nil {
					return nil, 
				}
				.fragments = len(.fragments)
				if .fragments == 0 {
					break
				}
				continue
			}
			 := svgPolygonBox()
			var  [][]svgPoint
			 := 0
			for ,  := range  {
				if  := .spend(len() + 1);  != nil {
					return nil, 
				}
				 := [][]svgPoint{}
				if svgPolygonBox().overlaps() {
					,  = svgSubtractPolygon(, , )
					if  != nil {
						return nil, 
					}
				}
				for ,  := range  {
					 += len()
				}
				 = append(, ...)
				if len() > .fragments- ||  > .vertices- {
					return nil, fmt.Errorf("isometric SVG visibility exceeds fragment limit")
				}
			}
			 = 
			.fragments = len()
			if len() == 0 {
				break
			}
		}
		if  != nil {
			,  = .polygons()
			if  != nil {
				return nil, 
			}
		}
		 += len()
		for ,  := range  {
			 += len()
		}
		if  > .fragments ||  > .vertices {
			return nil, fmt.Errorf("isometric SVG visibility exceeds fragment limit")
		}
		[] = 
	}
	return , nil
}