package d2target

import (
	
	

	
	
	
)

const (
	// Bound raw render targets before they reach recursive hashing and
	// multi-board rendering. Validation itself remains iterative.
	maxRenderTargetBoardDepth = 1_024
	maxRenderTargetBoardCount = 4_096
)

// ValidateRenderTarget checks the invariants required by renderers which
// consume Diagram JSON directly. Compiler output already satisfies these
// invariants, but callers of the public render API can construct a Diagram
// without going through the compiler.
func ( *Diagram) error {
	if  == nil {
		return fmt.Errorf("render target is nil")
	}

	type  struct {
		 *Diagram
		    string
		   int
	}

	 := []{{: , : "root"}}
	 := map[*Diagram]string{: "root"}
	 := 1

	for len() > 0 {
		 := [len()-1]
		 = [:len()-1]

		if  := validateRenderBoard(., .);  != nil {
			return 
		}

		 := []struct {
			     string
			 []*Diagram
		}{
			{: "layers", : ..Layers},
			{: "scenarios", : ..Scenarios},
			{: "steps", : ..Steps},
		}
		// Push in reverse so errors follow the renderer's layers, scenarios,
		// steps traversal order without using recursive calls.
		for  := len() - 1;  >= 0; -- {
			 := []
			for  := len(.) - 1;  >= 0; -- {
				 := .[]
				 := fmt.Sprintf("%s.%s[%d]", ., ., )
				if  == nil {
					return fmt.Errorf("render target board %s is nil", )
				}
				 := . + 1
				if  > maxRenderTargetBoardDepth {
					return fmt.Errorf("render target board tree exceeds depth %d at %s", maxRenderTargetBoardDepth, )
				}
				if ,  := [];  {
					return fmt.Errorf("render target board %s reuses board %s", , )
				}
				++
				if  > maxRenderTargetBoardCount {
					return fmt.Errorf("render target board tree exceeds %d total boards", maxRenderTargetBoardCount)
				}
				[] = 
				 = append(, {: , : , : })
			}
		}
	}

	return nil
}

func validateRenderBoard( *Diagram,  string) error {
	if  := validateRenderFontFamily(.FontFamily, +".fontFamily");  != nil {
		return 
	}
	if  := validateRenderFontFamily(.MonoFontFamily, +".monoFontFamily");  != nil {
		return 
	}
	if  := validateRenderShapeNumbers(.Root, +".root");  != nil {
		return 
	}
	for ,  := range .Shapes {
		if  := validateRenderShape(, fmt.Sprintf("%s.shapes[%d]", , ));  != nil {
			return 
		}
	}

	for ,  := range .Connections {
		if  := validateRenderConnection(, fmt.Sprintf("%s.connections[%d]", , ));  != nil {
			return 
		}
	}

	if .Legend != nil {
		for ,  := range .Legend.Shapes {
			if  := validateRenderLegendShape(, fmt.Sprintf("%s.legend.shapes[%d]", , ));  != nil {
				return 
			}
		}
		for ,  := range .Legend.Connections {
			 := fmt.Sprintf("%s.legend.connections[%d]", , )
			if  := validateRenderLegendConnection(, );  != nil {
				return 
			}
		}
	}

	return nil
}

func validateRenderLegendShape( Shape,  string) error {
	// Legend rendering replaces the icon geometry and suppresses the shape's
	// own label. Validate only the numeric fields which survive that projection.
	.Pos = Point{}
	.Width = 1
	.Height = 1
	.Text.Label = ""
	.Text.FontSize = 0
	.Text.LabelWidth = 0
	.Text.LabelHeight = 0
	return validateRenderShape(, )
}

func validateRenderLegendConnection( Connection,  string) error {
	// Legend rendering synthesizes its own two-point route and suppresses the
	// connection label. Project the fields it copies before applying numeric
	// validation so harmless unused JSON fields remain compatible.
	 := *BaseConnection()
	.SrcArrow = .SrcArrow
	.DstArrow = .DstArrow
	.StrokeDash = .StrokeDash
	.StrokeWidth = .StrokeWidth
	.BorderRadius = .BorderRadius
	.Opacity = .Opacity
	if  := validateRenderConnectionNumbers(, );  != nil {
		return 
	}
	if  := validateRenderArrowhead(.SrcArrow, +".srcArrow");  != nil {
		return 
	}
	return validateRenderArrowhead(.DstArrow, +".dstArrow")
}

func validateRenderShape( Shape,  string) error {
	if  := validateRenderShapeNumbers(, );  != nil {
		return 
	}
	if .Type == ShapeImage && .Icon == nil {
		return fmt.Errorf("render target %s image is missing an icon", )
	}
	return nil
}

func validateRenderConnection( Connection,  string) error {
	if  := validateRenderConnectionNumbers(, );  != nil {
		return 
	}
	if  := validateRenderArrowhead(.SrcArrow, +".srcArrow");  != nil {
		return 
	}
	if  := validateRenderArrowhead(.DstArrow, +".dstArrow");  != nil {
		return 
	}
	if len(.Route) < 2 {
		return fmt.Errorf("render target %s.route must contain at least two points", )
	}
	if .IsCurve && (len(.Route)-1)%3 != 0 {
		return fmt.Errorf("render target %s.route must contain 1+3n points for a cubic connection", )
	}
	for ,  := range .Route {
		if  == nil {
			return fmt.Errorf("render target %s.route[%d] is nil", , )
		}
		if !finiteRenderNumber(.X) || !finiteRenderNumber(.Y) {
			return fmt.Errorf("render target %s.route[%d] must contain finite coordinates", , )
		}
		 := fmt.Sprintf("%s.route[%d]", , )
		if  := validateRenderRouteCoordinate(, "x", .X, .StrokeWidth);  != nil {
			return 
		}
		if  := validateRenderRouteCoordinate(, "y", .Y, .StrokeWidth);  != nil {
			return 
		}
		if  > 0 {
			 := .Route[-1]
			,  := .X-.X, .Y-.Y
			 := * + *
			if !finiteRenderNumber() ||  == 0 {
				return fmt.Errorf("render target %s.route[%d:%d] must have a finite, non-zero segment length", , -1, )
			}
		}
	}

	if .Label != "" {
		 := label.FromString(.LabelPosition)
		if !.IsEdgePosition() {
			return fmt.Errorf("render target %s.labelPosition %q is not a connection label position", , .LabelPosition)
		}
		if !finiteRenderNumber(.LabelPercentage) {
			return fmt.Errorf("render target %s.labelPercentage must be finite", )
		}
		 := .GetLabelTopLeft()
		if  == nil || !finiteRenderNumber(.X) || !finiteRenderNumber(.Y) {
			return fmt.Errorf("render target %s.labelPosition does not resolve to a finite point on the route", )
		}
	}

	for ,  := range []struct {
		  string
		 *Text
		 bool
	}{
		{: "srcLabel", : .SrcLabel},
		{: "dstLabel", : .DstLabel, : true},
	} {
		if . == nil || ..Label == "" {
			continue
		}
		 := .GetArrowheadLabelPosition(.)
		if  == nil || !finiteRenderNumber(.X) || !finiteRenderNumber(.Y) {
			return fmt.Errorf("render target %s.%s does not resolve to a finite point on the route", , .)
		}
	}

	return nil
}

func validateRenderArrowhead( Arrowhead,  string) error {
	switch  {
	case NoArrowhead,
		ArrowArrowhead,
		UnfilledTriangleArrowhead,
		TriangleArrowhead,
		LineArrowhead,
		FilledDiamondArrowhead,
		DiamondArrowhead,
		FilledCircleArrowhead,
		CircleArrowhead,
		CrossArrowhead,
		FilledBoxArrowhead,
		BoxArrowhead,
		CfOne,
		CfMany,
		CfOneRequired,
		CfManyRequired:
		return nil
	default:
		return fmt.Errorf("render target %s uses unsupported arrowhead %q", , )
	}
}

func validateRenderFontFamily( *d2fonts.FontFamily,  string) error {
	if  == nil || * == "" {
		return nil
	}
	for ,  := range d2fonts.FontStyles {
		 := d2fonts.Font{Family: *, Style: }
		,  := d2fonts.FontFaces.Lookup()
		if ! || len() == 0 {
			return fmt.Errorf("render target %s %q is not a registered font family", , *)
		}
	}
	return nil
}

func validateRenderShapeNumbers( Shape,  string) error {
	for ,  := range []struct {
		  string
		 int
	}{
		{: "width", : .Width},
		{: "height", : .Height},
		{: "strokeWidth", : .StrokeWidth},
		{: "borderRadius", : .BorderRadius},
		{: "iconBorderRadius", : .IconBorderRadius},
	} {
		if . < 0 {
			return invalidRenderField(, ., ., "must be non-negative")
		}
	}
	if  := validateRenderOpacity(, .Opacity);  != nil {
		return 
	}
	if  := validateRenderDash(, .StrokeDash, .StrokeWidth);  != nil {
		return 
	}
	if .ContentAspectRatio != nil && (!finiteRenderNumber(*.ContentAspectRatio) || *.ContentAspectRatio <= 0) {
		return invalidRenderField(, "contentAspectRatio", *.ContentAspectRatio, "must be finite and greater than zero")
	}
	if  := validateRenderTextNumbers(, "", .Text);  != nil {
		return 
	}
	return validateRenderShapeIntegerBounds(, )
}

func validateRenderConnectionNumbers( Connection,  string) error {
	if .StrokeWidth < 0 {
		return invalidRenderField(, "strokeWidth", .StrokeWidth, "must be non-negative")
	}
	if  := validateRenderOpacity(, .Opacity);  != nil {
		return 
	}
	if  := validateRenderDash(, .StrokeDash, .StrokeWidth);  != nil {
		return 
	}
	if !finiteRenderNumber(.BorderRadius) || .BorderRadius < 0 {
		return invalidRenderField(, "borderRadius", .BorderRadius, "must be finite and non-negative")
	}
	if !finiteRenderNumber(.IconBorderRadius) || .IconBorderRadius < 0 {
		return invalidRenderField(, "iconBorderRadius", .IconBorderRadius, "must be finite and non-negative")
	}
	if !finiteRenderNumber(.LabelPercentage) || .LabelPercentage < 0 || .LabelPercentage > 1 {
		return invalidRenderField(, "labelPercentage", .LabelPercentage, "must be finite and within [0,1]")
	}
	if  := validateRenderTextNumbers(, "", .Text);  != nil {
		return 
	}
	if .SrcLabel != nil {
		if  := validateRenderTextNumbers(, "srcLabel.", *.SrcLabel);  != nil {
			return 
		}
	}
	if .DstLabel != nil {
		if  := validateRenderTextNumbers(, "dstLabel.", *.DstLabel);  != nil {
			return 
		}
	}
	return nil
}

func validateRenderOpacity( string,  float64) error {
	if !finiteRenderNumber() ||  < 0 ||  > 1 {
		return invalidRenderField(, "opacity", , "must be finite and within [0,1]")
	}
	return nil
}

func validateRenderDash( string,  float64,  int) error {
	if !finiteRenderNumber() ||  < 0 {
		return invalidRenderField(, "strokeDash", , "must be finite and non-negative")
	}
	if  > 0 &&  > 0 {
		,  := svg.GetStrokeDashAttributes(float64(), )
		if !finiteRenderNumber() || !finiteRenderNumber() ||  < 0 ||  < 0 {
			return invalidRenderField(, "strokeDash", , "must produce finite non-negative dash lengths with strokeWidth")
		}
	}
	return nil
}

func validateRenderTextNumbers(,  string,  Text) error {
	for ,  := range []struct {
		  string
		 int
	}{
		{:  + "fontSize", : .FontSize},
		{:  + "labelWidth", : .LabelWidth},
		{:  + "labelHeight", : .LabelHeight},
	} {
		if . < 0 {
			return invalidRenderField(, ., ., "must be non-negative")
		}
	}
	return nil
}

func validateRenderShapeIntegerBounds( string,  Shape) error {
	 := int64(^uint(0) >> 1)
	 := - - 1
	 := int64(.StrokeWidth/2 + .StrokeWidth%2)
	for ,  := range []struct {
		      string
		  int
		 int
	}{
		{: "pos.x", : .Pos.X, : .Width},
		{: "pos.y", : .Pos.Y, : .Height},
	} {
		if ,  := checkedRenderSub(int64(.), , , ); ! {
			return invalidRenderField(, ., ., "must fit bounds arithmetic with strokeWidth")
		}
		,  := checkedRenderAdd(int64(.), int64(.), , )
		if ! {
			return invalidRenderField(, ., ., "must fit bounds arithmetic with its dimension")
		}
		if ,  := checkedRenderAdd(, , , ); ! {
			return invalidRenderField(, ., ., "must fit bounds arithmetic with strokeWidth")
		}
	}
	return nil
}

func validateRenderRouteCoordinate(,  string,  float64,  int) error {
	 := int64(^uint(0) >> 1)
	 := - - 1
	 := math.Ceil(float64() / 2)
	// BoundingBox floors/ceils route coordinates and converts them to int.
	// Strict comparisons avoid the rounded float64 representation of MaxInt.
	if math.Floor()- <= float64() || math.Ceil()+ >= float64() {
		return invalidRenderField(, , , "must fit integer bounds arithmetic")
	}
	return nil
}

func checkedRenderAdd(, , ,  int64) (int64, bool) {
	if  > 0 &&  > - ||  < 0 &&  < - {
		return 0, false
	}
	return  + , true
}

func checkedRenderSub(, , ,  int64) (int64, bool) {
	if  > 0 &&  < + ||  < 0 &&  > + {
		return 0, false
	}
	return  - , true
}

func invalidRenderField(,  string,  any,  string) error {
	return fmt.Errorf("render target %s.%s has invalid value %v: %s", , , , )
}

func finiteRenderNumber( float64) bool {
	return !math.IsNaN() && !math.IsInf(, 0)
}