package d2scenebuild

import (
	
	
	
	
	

	
	
	
	
	
	
)

const (
	legendPadding       = 20
	legendItemSpacing   = 15
	legendIconSize      = 24
	legendFontSize      = 14
	legendCornerPadding = 10

	legendShapeScaleFactor      = 5
	legendConnectionScaleFactor = 2
)

type legendItemDimensions struct {
	width  int
	height int
}

type legendLayout struct {
	x      int
	y      int
	width  int
	height int

	maxLabelWidth        int
	itemCount            int
	shapeDimensions      []legendItemDimensions
	connectionDimensions []legendItemDimensions
	titleDimensions      legendItemDimensions
}

func ( *builder) () error {
	 := .diagram.Legend
	if  == nil || len(.Shapes) == 0 && len(.Connections) == 0 {
		return nil
	}
	for ,  := range .Shapes {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Label == "" {
			continue
		}
		 := legendShapeObject(, .ID)
		if .Link != "" || .PrettyLink != "" || .Tooltip != "" || .TooltipPosition != "" {
			return unsupported(, "link/tooltip metadata on a legend icon")
		}
		if  := validateOpacity(, .Opacity);  != nil {
			return 
		}
		 := legendShapeIconTarget(, )
		// Validate the geometry that buildLegendShapeIcon actually emits. The
		// outer node carries the source opacity, while its geometry is always
		// built at opacity one so structured and embedded-image work cannot hide
		// behind an opacity-zero preflight shortcut. A generic icon is omitted at
		// source opacity zero, matching drawShape. Label language is irrelevant
		// because the icon clone has no label.
		.Opacity = 1
		.Language = ""
		.Shadow = false
		.Blend = false
		.Animated = false
		 := .Type == d2target.ShapeImage || .Type == d2target.ShapeClass || .Type == d2target.ShapeSQLTable
		if ! && .Opacity == 0 {
			.Icon = nil
		}
		if  := .preflightShape();  != nil {
			return fmt.Errorf("scene: %s icon: %w", , )
		}
	}
	for ,  := range .Connections {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Label == "" {
			continue
		}
		 := legendConnectionObject(, .ID)
		if .Icon != nil {
			return unsupported(, "connection icon asset (svg legend connections render only route and arrow styles)")
		}
		if (.SrcLabel != nil && .SrcLabel.Label != "") ||
			(.DstLabel != nil && .DstLabel.Label != "") {
			return unsupported(, "endpoint label (svg legend connections render only route and arrow styles)")
		}
		if .Link != "" || .PrettyLink != "" || .Tooltip != "" {
			return unsupported(, "link/tooltip metadata on a legend connection")
		}
		 := legendConnectionIconTarget(, )
		if  := .preflightConnection();  != nil {
			return fmt.Errorf("scene: %s icon: %w", , )
		}
	}
	return .ctx.Err()
}

func legendShapeObject( int,  string) string {
	return fmt.Sprintf("legend shape[%d] %q", , )
}

func legendConnectionObject( int,  string) string {
	return fmt.Sprintf("legend connection[%d] %q", , )
}

func legendShapeIconTarget( int,  d2target.Shape) d2target.Shape {
	 := 
	.ID = fmt.Sprintf("legend:shape:%d:icon", )
	.Pos = d2target.Point{}
	.Width = legendIconSize * legendShapeScaleFactor
	.Height = legendIconSize * legendShapeScaleFactor
	.Label = ""
	return 
}

func legendConnectionIconTarget( int,  d2target.Connection) d2target.Connection {
	 := *d2target.BaseConnection()
	.ID = fmt.Sprintf("legend:connection:%d:icon", )
	.SrcArrow = .SrcArrow
	.DstArrow = .DstArrow
	.StrokeDash = .StrokeDash
	.StrokeWidth = .StrokeWidth
	.Stroke = .Stroke
	.Fill = .Fill
	.BorderRadius = .BorderRadius
	.Opacity = .Opacity
	.Animated = .Animated
	 := float64(legendIconSize * legendConnectionScaleFactor)
	.Route = []*geo.Point{{}, {X: }}
	return 
}

func ( *builder) (,  d2target.Point) (*legendLayout, error) {
	 := .diagram.Legend
	if  == nil || len(.Shapes) == 0 && len(.Connections) == 0 {
		return nil, nil
	}
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	,  := textmeasure.NewRuler()
	if  != nil {
		return nil, fmt.Errorf("scene: legend text ruler: %w", )
	}
	if  == nil {
		return nil, fmt.Errorf("scene: legend text ruler is nil")
	}

	 := &legendLayout{
		shapeDimensions:      make([]legendItemDimensions, len(.Shapes)),
		connectionDimensions: make([]legendItemDimensions, len(.Connections)),
	}
	 := int64(legendPadding + legendFontSize + legendItemSpacing)
	 := 0
	 := func(, ,  string) (legendItemDimensions, error) {
		if  := .ctx.Err();  != nil {
			return legendItemDimensions{}, 
		}
		 := &d2target.MText{Text: , FontSize: legendFontSize}
		 := d2graph.GetTextDimensions(nil, , , legendFontToFamily())
		if  == nil || .Width < 0 || .Height < 0 {
			return legendItemDimensions{}, fmt.Errorf("scene: %s has invalid measured text dimensions", )
		}
		return legendItemDimensions{width: .Width, height: .Height}, nil
	}
	 := func( string,  legendItemDimensions) error {
		 := maxInt(.height, legendIconSize)
		 := int64( + legendItemSpacing)
		 := int64(^uint(0) >> 1)
		if  > - {
			return fmt.Errorf("scene: %s causes legend height to exceed the platform integer domain", )
		}
		 += 
		.maxLabelWidth = maxInt(.maxLabelWidth, .width)
		++
		return nil
	}
	for ,  := range .Shapes {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if .Label == "" {
			continue
		}
		,  := (legendShapeObject(, .ID), .Label, .FontFamily)
		if  != nil {
			return nil, 
		}
		.shapeDimensions[] = 
		if  := (legendShapeObject(, .ID), );  != nil {
			return nil, 
		}
	}
	for ,  := range .Connections {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if .Label == "" {
			continue
		}
		,  := (legendConnectionObject(, .ID), .Label, .FontFamily)
		if  != nil {
			return nil, 
		}
		.connectionDimensions[] = 
		if  := (legendConnectionObject(, .ID), );  != nil {
			return nil, 
		}
	}
	if  > 0 {
		 -= legendItemSpacing / 2
	}
	if  > 0 && len(.Connections) > 0 {
		 += legendPadding * 3 / 2
	} else {
		 += legendPadding * 6 / 5
	}
	 := int64(^uint(0) >> 1)
	if  <= 0 ||  >  {
		return nil, fmt.Errorf("scene: legend height exceeds the platform integer domain")
	}
	 := int64(legendPadding*2 + legendIconSize + legendPadding)
	if int64(.maxLabelWidth) > - {
		return nil, fmt.Errorf("scene: legend width exceeds the platform integer domain")
	}
	 += int64(.maxLabelWidth)
	,  := checkedAdd(int64(.X), legendCornerPadding, --1, )
	if ! {
		return nil, fmt.Errorf("scene: legend x position exceeds the platform integer domain")
	}
	,  := checkedSub(int64(.Y), , --1, )
	if ! {
		return nil, fmt.Errorf("scene: legend y position exceeds the platform integer domain")
	}
	if  < int64(.Y) {
		 = int64(.Y)
	}
	.x, .y = int(), int()
	.width, .height = int(), int()
	for ,  := range []struct {
		  string
		 int64
		 int64
	}{
		{: "right edge", : , : },
		{: "bottom edge", : , : },
		{: "title x position", : , : legendPadding},
		{: "title y position", : , : legendPadding + legendFontSize},
		{: "item y position", : , : legendPadding*2 + legendFontSize},
	} {
		if ,  := checkedAdd(., ., --1, ); ! {
			return nil, fmt.Errorf("scene: legend %s exceeds the platform integer domain", .)
		}
	}

	 := .Label
	if  == "" {
		 = "Legend"
	}
	 := .diagram.FontFamily
	 := &d2target.MText{Text: , FontSize: legendFontSize + 2, IsBold: true}
	 := d2graph.GetTextDimensions(nil, , , )
	if  == nil || .Width < 0 || .Height < 0 {
		return nil, fmt.Errorf("scene: legend title has invalid measured text dimensions")
	}
	.titleDimensions = legendItemDimensions{width: .Width, height: .Height}
	.itemCount = 
	return , .ctx.Err()
}

func legendFontToFamily( string) *d2fonts.FontFamily {
	,  := d2fonts.D2_FONT_TO_FAMILY[]
	if ! {
		return nil
	}
	return &
}

func ( *legendLayout) (, , ,  int,  int64) (int, int, int, int, error) {
	if  == nil || .maxLabelWidth <= 0 {
		return , , , , nil
	}
	 := int64(^uint(0) >> 1)
	 := - - 1
	,  := checkedAdd(int64(.x), int64(.width), , )
	if ! {
		return 0, 0, 0, 0, fmt.Errorf("scene: legend right edge exceeds the platform integer domain")
	}
	,  := checkedAdd(int64(), int64(), , )
	if ! {
		return 0, 0, 0, 0, fmt.Errorf("scene: legend viewbox right edge exceeds the platform integer domain")
	}
	, , ,  := int64(), int64(), int64(), int64()
	if  <  {
		,  = checkedSub(, , , )
		if  {
			,  = checkedAdd(, /2, , )
		}
		if ! {
			return 0, 0, 0, 0, fmt.Errorf("scene: legend width expansion exceeds the platform integer domain")
		}
	}
	if int64(.y) <  {
		,  := checkedSub(, int64(.y), , )
		if ! {
			return 0, 0, 0, 0, fmt.Errorf("scene: legend top expansion exceeds the platform integer domain")
		}
		 = int64(.y)
		,  = checkedAdd(, , , )
		if ! {
			return 0, 0, 0, 0, fmt.Errorf("scene: legend height expansion exceeds the platform integer domain")
		}
	}
	,  := checkedAdd(int64(.y), int64(.height), , )
	if ! {
		return 0, 0, 0, 0, fmt.Errorf("scene: legend bottom edge exceeds the platform integer domain")
	}
	,  := checkedAdd(, , , )
	if ! {
		return 0, 0, 0, 0, fmt.Errorf("scene: legend viewbox bottom edge exceeds the platform integer domain")
	}
	if  <  {
		,  = checkedSub(, , , )
		if  {
			,  = checkedAdd(, /2, , )
		}
		if ! {
			return 0, 0, 0, 0, fmt.Errorf("scene: legend bottom expansion exceeds the platform integer domain")
		}
	}
	if  < 0 ||  < 0 {
		return 0, 0, 0, 0, invalidPaddingError()
	}
	return int(), int(), int(), int(), nil
}

func ( *builder) ( *legendLayout) (*d2scene.Node, error) {
	if  == nil {
		return nil, nil
	}
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	,  := .paint("#F7F7FA", "legend shadow fill")
	if  != nil {
		return nil, 
	}
	,  := .paint("#ffffff", "legend panel fill")
	if  != nil {
		return nil, 
	}
	,  := .paint("#DEE1EB", "legend border")
	if  != nil {
		return nil, 
	}
	,  := .paint("#000000", "legend text")
	if  != nil {
		return nil, 
	}
	 := func() *d2scene.Stroke {
		return &d2scene.Stroke{Paint: , Width: 1, Cap: d2scene.CapButt, Join: d2scene.JoinMiter, MiterLimit: 4}
	}
	 := d2scene.Box{X: float64(.x), Y: float64(.y), Width: float64(.width), Height: float64(.height)}
	 := d2scene.NewNode(nil)
	.ID = "legend"
	 := d2scene.NewNode(d2scene.Rect{Box: , RadiusX: 4, RadiusY: 4, Fill: , Stroke: ()})
	.ID = "legend:shadow"
	.Filters = []d2scene.Filter{d2scene.DropShadow{
		OffsetY: 2, SigmaX: 3, SigmaY: 3, Color: color.NRGBA{A: 26},
	}}
	 := d2scene.NewNode(d2scene.Rect{Box: , RadiusX: 4, RadiusY: 4, Fill: , Stroke: ()})
	.ID = "legend:panel"
	.Children = append(.Children, , )

	 := .diagram.Legend.Label
	if  == "" {
		 = "Legend"
	}
	,  := .font(d2target.Text{FontSize: legendFontSize + 2, Bold: true})
	if  != nil {
		return nil, fmt.Errorf("scene: legend title: %w", )
	}
	 := float64(.x + legendPadding)
	 := float64(.y + legendPadding + legendFontSize)
	 := d2scene.NewNode(d2scene.TextRun{
		Text: legendRenderedText(), Origin: d2scene.Point{X: , Y: }, Anchor: d2scene.AnchorStart,
		Font: , Fill: ,
		Ink: legendTextInk(, , .titleDimensions),
	})
	.ID = "legend:title"
	.Children = append(.Children, )

	var  d2scene.Font
	if .itemCount > 0 {
		,  = .font(d2target.Text{FontSize: legendFontSize})
		if  != nil {
			return nil, fmt.Errorf("scene: legend item: %w", )
		}
	}
	 := .y + legendPadding*2 + legendFontSize
	 := 0
	for ,  := range .diagram.Legend.Shapes {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if .Label == "" {
			continue
		}
		,  := .x+legendPadding, 
		,  := .buildLegendShapeIcon(, , , )
		if  != nil {
			return nil, 
		}
		.Children = append(.Children, )
		 := .shapeDimensions[]
		 := maxInt(.height, legendIconSize)
		 := float64( + legendIconSize + legendPadding)
		 := float64( + /2 + int(float64(.height)*.3))
		 := d2scene.NewNode(d2scene.TextRun{
			Text: legendRenderedText(.Label), Origin: d2scene.Point{X: , Y: }, Anchor: d2scene.AnchorStart,
			Font: , Fill: , Ink: legendTextInk(, , ),
		})
		.ID = fmt.Sprintf("legend:shape:%d:label", )
		.Children = append(.Children, )
		 +=  + legendItemSpacing
		++
	}

	if  > 0 && len(.diagram.Legend.Connections) > 0 {
		 += legendItemSpacing / 2
		 := ()
		.Dashes = []float64{2, 2}
		 := d2scene.NewNode(d2scene.Path{
			Commands: []d2scene.PathCommand{
				d2scene.MoveTo(float64(.x+legendPadding), float64()),
				d2scene.LineTo(float64(.x+.width-legendPadding), float64()),
			},
			Stroke: ,
		})
		.ID = "legend:separator"
		.Children = append(.Children, )
		 += legendItemSpacing
	}

	for ,  := range .diagram.Legend.Connections {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if .Label == "" {
			continue
		}
		 := .x + legendPadding
		 :=  + legendIconSize/2
		,  := .buildLegendConnectionIcon(, , , )
		if  != nil {
			return nil, 
		}
		.Children = append(.Children, )
		 := .connectionDimensions[]
		 := maxInt(.height, legendIconSize)
		 := float64( + legendIconSize + legendPadding)
		 := float64( + /2 + int(float64(.height)*.2))
		 := d2scene.NewNode(d2scene.TextRun{
			Text: legendRenderedText(.Label), Origin: d2scene.Point{X: , Y: }, Anchor: d2scene.AnchorStart,
			Font: , Fill: , Ink: legendTextInk(, , ),
		})
		.ID = fmt.Sprintf("legend:connection:%d:label", )
		.Children = append(.Children, )
		 +=  + legendItemSpacing
	}
	return , .ctx.Err()
}

func legendTextInk(,  float64,  legendItemDimensions) d2scene.Bounds {
	 := math.Max(1, float64(.width))
	 := math.Max(1, float64(.height))
	return d2scene.NewBounds(, -, +, )
}

// SVG's default white-space handling collapses ASCII spaces, tabs, and line
// breaks in a text node. Keep measuring the raw target string, as RenderLegend
// does, but give raster renderers the same whitespace-collapsed text.
func legendRenderedText( string) string {
	var  strings.Builder
	 := false
	for ,  := range  {
		switch  {
		case ' ', '\t', '\n', '\r':
			if .Len() != 0 {
				 = true
			}
		default:
			if  {
				.WriteByte(' ')
				 = false
			}
			.WriteRune()
		}
	}
	return .String()
}

func ( *builder) ( int,  d2target.Shape, ,  int) (*d2scene.Node, error) {
	 := legendShapeIconTarget(, )
	 := 
	.Opacity = 1
	.Animated = false
	.Shadow = false
	.Blend = false
	 := .Type == d2target.ShapeImage || .Type == d2target.ShapeClass || .Type == d2target.ShapeSQLTable
	if ! {
		.Icon = nil
	}
	,  := .buildShape()
	if  != nil {
		return nil, fmt.Errorf("scene: %s: %w", legendShapeObject(, .ID), )
	}
	.ID = .ID + ":shape"
	.Classes = nil
	if .Shadow && legendShapePaintsShadow(.Type) {
		.Filters = append(.Filters, d2scene.DropShadow{
			OffsetX: 3, OffsetY: 5, SigmaX: 1.7, SigmaY: 1.7,
			Color: color.NRGBA{R: 0x3d, G: 0x45, B: 0x74, A: 102},
		})
	}
	if .Blend {
		.Blend = d2scene.BlendMultiply
		.Opacity *= .5
	}

	 := d2scene.NewNode(nil)
	.ID = .ID
	.Classes = append([]string(nil), .Classes...)
	.Opacity = .Opacity
	.Children = append(.Children, )
	if ! && .Icon != nil && .Opacity != 0 {
		 := 
		.Opacity = 1
		,  := .buildShapeIcon(, false)
		if  != nil {
			return nil, fmt.Errorf("scene: %s: %w", legendShapeObject(, .ID), )
		}
		if  != nil {
			.Children = append(.Children, )
		}
	}
	if .Animated {
		addLegendShapeAnimation()
	}

	 := d2scene.NewNode(nil)
	.ID = fmt.Sprintf("legend:shape:%d", )
	.Transform = d2scene.Translate(float64(), float64()).Mul(d2scene.Scale(1.0/legendShapeScaleFactor, 1.0/legendShapeScaleFactor))
	.Children = []*d2scene.Node{}
	return , nil
}

func legendShapePaintsShadow( string) bool {
	switch  {
	case d2target.ShapeText, d2target.ShapeCode, d2target.ShapeClass, d2target.ShapeSQLTable:
		return false
	default:
		return true
	}
}

func addLegendShapeAnimation( *d2scene.Node) {
	 := d2scene.DropShadow{Color: color.NRGBA{}}
	.Filters = append(.Filters, , )
	 := d2scene.Easing{Kind: d2scene.EaseLinear}
	.Animations = append(.Animations,
		d2scene.Track{
			Property: d2scene.AnimateTransform, Duration: time.Second, Repeat: true,
			Keyframes: []d2scene.Keyframe{
				{Offset: 0, Value: d2scene.TransformValue(d2scene.Identity()), Easing: },
				{Offset: .5, Value: d2scene.TransformValue(d2scene.Translate(0, -4)), Easing: },
				{Offset: 1, Value: d2scene.TransformValue(d2scene.Identity())},
			},
		},
		legendShapeShadowTrack(0, d2scene.DropShadow{
			OffsetY: 12.6, SigmaX: 25.2, SigmaY: 25.2,
			Color: color.NRGBA{R: 50, G: 50, B: 93, A: 64},
		}),
		legendShapeShadowTrack(1, d2scene.DropShadow{
			OffsetY: 7.56, SigmaX: 15.12, SigmaY: 15.12,
			Color: color.NRGBA{A: 26},
		}),
	)
}

func legendShapeShadowTrack( int,  d2scene.DropShadow) d2scene.Track {
	 := d2scene.DropShadow{Color: color.NRGBA{}}
	return d2scene.Track{
		Property: d2scene.AnimateDropShadow, TargetIndex: , Duration: time.Second, Repeat: true,
		Keyframes: []d2scene.Keyframe{
			{Offset: 0, Value: d2scene.ShadowValue(), Easing: d2scene.Easing{Kind: d2scene.EaseLinear}},
			{Offset: .5, Value: d2scene.ShadowValue(), Easing: d2scene.Easing{Kind: d2scene.EaseLinear}},
			{Offset: 1, Value: d2scene.ShadowValue()},
		},
	}
}

func ( *builder) ( int,  d2target.Connection, ,  int) (*d2scene.Node, error) {
	 := legendConnectionIconTarget(, )
	 := .connectionMask
	 := .idToShape
	.connectionMask = nil
	.idToShape = nil
	,  := func() (*d2scene.Node, error) {
		defer func() {
			.connectionMask = 
			.idToShape = 
		}()
		return .buildConnection()
	}()
	if  != nil {
		return nil, fmt.Errorf("scene: %s: %w", legendConnectionObject(, .ID), )
	}
	 := d2scene.NewNode(nil)
	.ID = fmt.Sprintf("legend:connection:%d", )
	.Transform = d2scene.Translate(float64(), float64()).Mul(d2scene.Scale(1.0/legendConnectionScaleFactor, 1.0/legendConnectionScaleFactor))
	.Children = []*d2scene.Node{}
	return , nil
}