package d2scenebuild

import (
	
	
	

	
	
	
	
)

type connectionPathProvenance uint8

const (
	connectionPathLine connectionPathProvenance = iota
	connectionPathCubic
	connectionPathSmoothCubic
)

type connectionPathSegment struct {
	kind    connectionPathProvenance
	start   d2scene.Point
	command d2scene.PathCommand
}

func ( *builder) ( d2target.Connection) (*d2scene.Node, error) {
	,  := .paint(.Stroke, fmt.Sprintf("connection %q stroke", .ID))
	if  != nil {
		return nil, 
	}
	 := .StrokeDash
	if .Animated &&  == 0 {
		// Preserve Connection.CSSStyle's implicit animated dash pattern.
		 = 5
	}
	,  := .stroke(.Stroke, .StrokeWidth, , d2scene.CapRound, d2scene.JoinRound, fmt.Sprintf("connection %q stroke", .ID))
	if  != nil {
		return nil, 
	}
	,  := arrowheadAdjustments(, .idToShape)
	, , , ,  := connectionPath(, , )
	if  != nil {
		return nil, fmt.Errorf("scene: connection %q: %w", .ID, )
	}
	.Stroke = 
	var ,  *d2scene.Track
	if .Animated &&  != nil {
		, ,  := animatedConnectionTiming()
		if  != nil {
			return nil, fmt.Errorf("scene: connection %q animation: %w", .ID, )
		}
		.DashOffset = 
		 := animatedConnectionTrack(, , false)
		 := animatedConnectionTrack(, , true)
		 = &
		 = &
	}
	if .options.Sketch && !.Animated {
		,  = .sketchConnectionPath(, )
		if  != nil {
			return nil, 
		}
	}

	 := d2scene.NewNode(nil)
	.ID = .ID
	.Classes = append([]string(nil), .Classes...)
	.Opacity = .Opacity
	,  := .buildConnectionIcon()
	if  != nil {
		return nil, 
	}
	if  != nil {
		.Children = append(.Children, )
	}
	var ,  *d2scene.Node
	if .SrcArrow != d2target.NoArrowhead {
		,  := .arrowhead(, .SrcArrow, false, , .Route[0].VectorTo(.Route[1]), )
		if  != nil {
			return nil, 
		}
		 = 
	}
	if .DstArrow != d2target.NoArrowhead {
		 := len(.Route) - 1
		,  := .arrowhead(, .DstArrow, true, , .Route[-1].VectorTo(.Route[]), )
		if  != nil {
			return nil, 
		}
		 = 
	}

	var  []*d2scene.Node
	 := .Animated && (.SrcArrow == d2target.NoArrowhead) == (.DstArrow == d2target.NoArrowhead)
	if  {
		, ,  := .splitAnimatedConnectionPath(, )
		if  != nil {
			return nil, fmt.Errorf("scene: connection %q animation split: %w", .ID, )
		}
		 := animatedConnectionPathNode(.ID+":path:reverse", , )
		 := animatedConnectionPathNode(.ID+":path:forward", , )
		// SVG paints each path's markers after that path's stroke. Interleave
		// the explicit source arrow with the split paths so a two-arrow
		// connection retains path1/marker-start/path2/marker-end z-order.
		 = append(, )
		if  != nil {
			 = append(, )
		}
		 = append(, )
		if  != nil {
			 = append(, )
		}
	} else {
		 := animatedConnectionPathNode(.ID+":path", , )
		 = append(, )
		if  != nil {
			 = append(, )
		}
		if  != nil {
			 = append(, )
		}
	}
	if .connectionMask == nil {
		.Children = append(.Children, ...)
	} else {
		 := d2scene.NewNode(nil)
		.ID = .ID + ":geometry"
		.Mask = .connectionMask
		.Children = 
		.Children = append(.Children, )
	}
	,  := .buildConnectionLabels()
	if  != nil {
		return nil, 
	}
	.Children = append(.Children, ...)
	return , nil
}

func animatedConnectionPathNode( string,  d2scene.Path,  *d2scene.Track) *d2scene.Node {
	 := d2scene.NewNode()
	.ID = 
	if  != nil {
		.Animations = []d2scene.Track{*}
	}
	return 
}

func validateAnimatedConnection( string,  d2target.Connection) error {
	 := .StrokeDash
	if  == 0 {
		 = 5
	}
	if  := validateDash(, , .StrokeWidth);  != nil {
		return 
	}
	if .StrokeWidth == 0 {
		return nil
	}
	if , ,  := animatedConnectionTiming();  != nil {
		return invalidField(, "strokeDash", .StrokeDash, .Error())
	}
	return nil
}

func animatedConnectionTiming( d2target.Connection) (float64, time.Duration, error) {
	 := .StrokeDash
	if  == 0 {
		 = 5
	}
	,  := svg.GetStrokeDashAttributes(float64(.StrokeWidth), )
	 :=  + 
	if !finite() ||  < 0 {
		return 0, 0, fmt.Errorf("must produce a finite non-negative dash period")
	}
	 := -10.0
	if .SrcArrow != d2target.NoArrowhead && .DstArrow == d2target.NoArrowhead {
		 = 10
	}
	 :=  * 
	if !finite() {
		return 0, 0, fmt.Errorf("must produce a finite dash offset")
	}
	 :=  * .5 * float64(time.Second)
	if !finite() ||  < 1 ||  > float64(math.MaxInt64) {
		return 0, 0, fmt.Errorf("must produce an animation duration within [1ns,%s]", time.Duration(math.MaxInt64))
	}
	 := time.Duration()
	if  <= 0 {
		return 0, 0, fmt.Errorf("must produce a positive animation duration")
	}
	return , , nil
}

func animatedConnectionTrack( float64,  time.Duration,  bool) d2scene.Track {
	,  := 0.0, 
	if  {
		,  = , 
	}
	return d2scene.Track{
		Property: d2scene.AnimateStrokeDashOffset,
		Duration: ,
		Repeat:   true,
		Keyframes: []d2scene.Keyframe{
			{Offset: 0, Value: d2scene.NumberValue(), Easing: d2scene.Easing{Kind: d2scene.EaseLinear}},
			{Offset: 1, Value: d2scene.NumberValue()},
		},
	}
}

// splitAnimatedConnectionPath is the typed equivalent of svg.SplitPath at
// 50%. That svg helper intentionally uses endpoint chord lengths, always
// bisects a C command at t=.5, and does not bisect an S command. Retaining the
// C/S provenance here is therefore required even though both serialize into a
// d2scene cubic command.
func ( *builder) ( d2scene.Path,  []connectionPathSegment) (d2scene.Path, d2scene.Path, error) {
	if len(.Commands) == 0 || .Commands[0].Kind != d2scene.MoveCommand || len(.Commands) != len()+1 {
		return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("invalid typed connection path")
	}
	 := 0.0
	 := make([]float64, len())
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return d2scene.Path{}, d2scene.Path{}, 
		}
		,  := connectionSegmentEndpoint(.command)
		if  != nil {
			return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("segment %d: %w", , )
		}
		 := math.Hypot(.X-.start.X, .Y-.start.Y)
		if !finite() {
			return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("segment %d has non-finite chord length", )
		}
		[] = 
		 += 
		if !finite() {
			return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("cumulative chord length is non-finite")
		}
	}
	if  <= 0 {
		return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("path has zero painted length")
	}

	 := d2scene.Path{FillRule: .FillRule, Fill: .Fill, Stroke: .Stroke}
	 := d2scene.Path{FillRule: .FillRule, Fill: .Fill, Stroke: .Stroke}
	.Commands = append(.Commands, .Commands[0])
	 :=  * .5
	 := 0.0
	 := false
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return d2scene.Path{}, d2scene.Path{}, 
		}
		 := []
		 += 
		if  {
			.Commands = append(.Commands, .command)
			continue
		}
		if  <  {
			.Commands = append(.Commands, .command)
			continue
		}

		,  := connectionSegmentEndpoint(.command)
		if  != nil {
			return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("segment %d: %w", , )
		}
		switch .kind {
		case connectionPathLine:
			if  == 0 {
				return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("line split has zero chord length")
			}
			 := ( -  + ) / 
			if !finite() ||  < 0 ||  > 1 {
				return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("line split parameter %v is outside [0,1]", )
			}
			 := d2scene.Point{
				X: .start.X + (.X-.start.X)*,
				Y: .start.Y + (.Y-.start.Y)*,
			}
			.Commands = append(.Commands, d2scene.LineTo(.X, .Y))
			.Commands = append(.Commands, d2scene.MoveTo(.X, .Y), d2scene.LineTo(.X, .Y))
		case connectionPathCubic:
			if .command.Kind != d2scene.CubicCommand {
				return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("C provenance has non-cubic command")
			}
			 := geo.Point{X: .start.X, Y: .start.Y}
			 := geo.Point{X: .command.P1.X, Y: .command.P1.Y}
			 := geo.Point{X: .command.P2.X, Y: .command.P2.Y}
			 := geo.Point{X: .X, Y: .Y}
			, , ,  := svg.BezierCurveSegment(&, &, &, &, 0, .5)
			.Commands = append(.Commands, d2scene.CubicTo(.X, .Y, .X, .Y, .X, .Y))
			, , ,  := svg.BezierCurveSegment(&, &, &, &, .5, 1)
			.Commands = append(.Commands,
				d2scene.MoveTo(.X, .Y),
				d2scene.CubicTo(.X, .Y, .X, .Y, .X, .Y),
			)
		case connectionPathSmoothCubic:
			// svg.SplitPath keeps the complete S command in the first path
			// and starts the second path at its endpoint.
			.Commands = append(.Commands, .command)
			.Commands = append(.Commands, d2scene.MoveTo(.X, .Y))
		default:
			return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("segment %d has unknown provenance %d", , .kind)
		}
		 = true
	}
	if ! || len(.Commands) == 0 {
		return d2scene.Path{}, d2scene.Path{}, fmt.Errorf("path did not cross its halfway point")
	}
	return , , nil
}

func connectionSegmentEndpoint( d2scene.PathCommand) (d2scene.Point, error) {
	switch .Kind {
	case d2scene.LineCommand:
		return .P1, nil
	case d2scene.CubicCommand:
		return .P3, nil
	default:
		return d2scene.Point{}, fmt.Errorf("unsupported path command %d", .Kind)
	}
}

func arrowheadAdjustment(,  *geo.Point,  d2target.Arrowhead, ,  int) *geo.Point {
	 := (float64() + float64()) / 2
	if  != d2target.NoArrowhead {
		 += float64()
	}
	 := geo.NewVector(.X-.X, .Y-.Y)
	if .Length() == 0 {
		return geo.NewPoint(0, 0)
	}
	return .Unit().Multiply(-).ToPoint()
}

func arrowheadAdjustments( d2target.Connection,  map[string]d2target.Shape) (,  *geo.Point) {
	 := [.Src]
	 := [.Dst]
	 = arrowheadAdjustment(.Route[1], .Route[0], .SrcArrow, .StrokeWidth, .StrokeWidth)
	 := len(.Route) - 1
	 = arrowheadAdjustment(.Route[-1], .Route[], .DstArrow, .StrokeWidth, .StrokeWidth)
	return , 
}

func connectionPath( d2target.Connection, ,  *geo.Point) (d2scene.Path, []connectionPathSegment, d2scene.Point, d2scene.Point, error) {
	 := .Route
	 := d2scene.Point{X: [0].X + .X, Y: [0].Y + .Y}
	 := d2scene.Point{X: [len()-1].X + .X, Y: [len()-1].Y + .Y}
	 := d2scene.Path{Commands: []d2scene.PathCommand{d2scene.MoveTo(.X, .Y)}}
	 := make([]connectionPathSegment, 0, len())
	 := func( connectionPathProvenance,  d2scene.Point,  d2scene.PathCommand) {
		.Commands = append(.Commands, )
		 = append(, connectionPathSegment{kind: , start: , command: })
	}
	if .IsCurve {
		 := 
		for  := 1;  < len();  += 3 {
			 := [+2]
			if +2 == len()-1 {
				 = geo.NewPoint(.X+.X, .Y+.Y)
			}
			 := d2scene.CubicTo(
				[].X, [].Y,
				[+1].X, [+1].Y,
				.X, .Y,
			)
			(connectionPathCubic, , )
			 = .P3
		}
		return , , , , nil
	}

	 := 
	for  := 1;  < len()-1; ++ {
		 := [-1]
		 := []
		 := [+1]
		 := .VectorTo()
		 := .VectorTo()
		if .Length() == 0 || .Length() == 0 {
			(connectionPathLine, , d2scene.LineTo(.X, .Y))
			 = d2scene.Point{X: .X, Y: .Y}
			continue
		}
		 := geo.EuclideanDistance(.X, .Y, .X, .Y)
		 := math.Min(.BorderRadius, /2)
		 := .Unit().Multiply().ToPoint()
		 := .Unit().Multiply().ToPoint()
		 := d2scene.Point{X: .X - .X, Y: .Y - .Y}
		(connectionPathLine, , d2scene.LineTo(.X, .Y))
		 = 

		if  < .BorderRadius &&  < len()-2 {
			 := [+2]
			 := .VectorTo()
			if .Length() == 0 {
				continue
			}
			++
			 := .Unit().Multiply().ToPoint()
			 := d2scene.Point{X: .X + .X, Y: .Y + .Y}
			 := d2scene.CubicTo(
				.X+.X, .Y+.Y,
				.X-.X, .Y-.Y,
				.X, .Y,
			)
			(connectionPathCubic, , )
			 = 
		} else {
			 := d2scene.Point{X: .X + .X, Y: .Y + .Y}
			// SVG's first smooth-cubic control is the current point because the
			// preceding command is a line.
			 := d2scene.CubicTo(
				.X, .Y,
				.X, .Y,
				.X, .Y,
			)
			(connectionPathSmoothCubic, , )
			 = 
		}
	}
	(connectionPathLine, , d2scene.LineTo(.X, .Y))
	return , , , , nil
}

func ( *builder) ( d2target.Connection,  d2target.Arrowhead,  bool,  d2scene.Point,  geo.Vector,  d2scene.Paint) (*d2scene.Node, error) {
	if .Length() == 0 {
		return nil, fmt.Errorf("scene: connection %q has zero-length arrowhead tangent", .ID)
	}
	if .options.Sketch {
		return .sketchArrowhead(, , , , )
	}
	 := float64(.StrokeWidth)
	,  := .Dimensions()
	 := 1.5 * 
	if  {
		 =  - 1.5*
	}
	,  := .paint(d2target.BG_COLOR, fmt.Sprintf("connection %q arrowhead background", .ID))
	if  != nil {
		return nil, 
	}
	 := "src"
	if  {
		 = "dst"
	}
	 := fmt.Sprintf("%s:%s-arrowhead", .ID, )
	var  d2scene.Primitive
	var  []*d2scene.Node
	 := func( ...d2scene.Point) d2scene.Path {
		 := []d2scene.PathCommand{d2scene.MoveTo([0].X, [0].Y)}
		for ,  := range [1:] {
			 = append(, d2scene.LineTo(.X, .Y))
		}
		 = append(, d2scene.ClosePath())
		return d2scene.Path{Commands: , Fill: }
	}
	 := func( ...d2scene.Point) d2scene.Path {
		 := []d2scene.PathCommand{d2scene.MoveTo([0].X, [0].Y)}
		for ,  := range [1:] {
			 = append(, d2scene.LineTo(.X, .Y))
		}
		return d2scene.Path{Commands: }
	}
	 := func() *d2scene.Stroke {
		return &d2scene.Stroke{Paint: , Width: , Cap: d2scene.CapButt, Join: d2scene.JoinMiter, MiterLimit: 4}
	}
	 := func( string,  d2scene.Primitive,  d2scene.Matrix) *d2scene.Node {
		 := d2scene.NewNode()
		.ID =  + ":" + 
		.Transform = 
		return 
	}
	switch  {
	case d2target.ArrowArrowhead:
		if  {
			 = (d2scene.Point{}, d2scene.Point{X: , Y:  / 2}, d2scene.Point{Y: }, d2scene.Point{X:  / 4, Y:  / 2})
		} else {
			 = (d2scene.Point{Y:  / 2}, d2scene.Point{X: }, d2scene.Point{X:  * .75, Y:  / 2}, d2scene.Point{X: , Y: })
		}
	case d2target.TriangleArrowhead:
		if  {
			 = (d2scene.Point{}, d2scene.Point{X: , Y:  / 2}, d2scene.Point{Y: })
		} else {
			 = (d2scene.Point{X: }, d2scene.Point{Y:  / 2}, d2scene.Point{X: , Y: })
		}
	case d2target.UnfilledTriangleArrowhead:
		 :=  / 2
		 := (d2scene.Point{X: , Y: }, d2scene.Point{X:  - , Y:  / 2}, d2scene.Point{X: , Y:  - })
		if ! {
			 = (d2scene.Point{X:  - , Y: }, d2scene.Point{X: , Y:  / 2}, d2scene.Point{X:  - , Y:  - })
		}
		.Fill = 
		.Stroke = &d2scene.Stroke{Paint: , Width: , Join: d2scene.JoinRound}
		 = 
	case d2target.LineArrowhead:
		 :=  / 2
		 := []d2scene.Point{
			{X: , Y: },
			{X:  - , Y:  / 2},
			{X: , Y:  - },
		}
		if ! {
			 = []d2scene.Point{
				{X:  - , Y: },
				{X: , Y:  / 2},
				{X:  - , Y:  - },
			}
		}
		 := (...)
		.Stroke = ()
		 = 
	case d2target.FilledDiamondArrowhead:
		// Filled diamonds use the full height; inset outline geometry belongs only
		// to the unfilled diamond below.
		 = (
			d2scene.Point{Y:  / 2},
			d2scene.Point{X:  / 2},
			d2scene.Point{X: , Y:  / 2},
			d2scene.Point{X:  / 2, Y: },
		)
	case d2target.DiamondArrowhead:
		 := (d2scene.Point{Y:  / 2}, d2scene.Point{X:  / 2, Y:  / 8}, d2scene.Point{X: , Y:  / 2}, d2scene.Point{X:  / 2, Y:  * .9})
		if ! {
			 = (
				d2scene.Point{X:  / 8, Y:  / 2},
				d2scene.Point{X:  * .6, Y:  / 8},
				d2scene.Point{X:  * 1.1, Y:  / 2},
				d2scene.Point{X:  * .6, Y:  * 7 / 8},
			)
		}
		.Fill = 
		.Stroke = &d2scene.Stroke{Paint: , Width: , Join: d2scene.JoinRound}
		if  {
			 =  - .6*
		} else {
			 = /8 + .6*
		}
		 = 
	case d2target.FilledCircleArrowhead, d2target.CircleArrowhead:
		 :=  / 2
		 :=  - /2
		if  {
			 =  + /2
		}
		 := d2scene.Ellipse{Center: d2scene.Point{X: , Y: }, RadiusX:  - /2, RadiusY:  - /2, Fill: }
		if  == d2target.CircleArrowhead {
			.RadiusX =  - 
			.RadiusY =  - 
			.Fill = 
			.Stroke = &d2scene.Stroke{Paint: , Width: , Join: d2scene.JoinRound}
		}
		 = 
	case d2target.CrossArrowhead:
		 :=  / 8
		 := (
			d2scene.Point{Y: /2 + },
			d2scene.Point{X: /2 - , Y: /2 + },
			d2scene.Point{X: /2 - , Y: },
			d2scene.Point{X: /2 + , Y: },
			d2scene.Point{X: /2 + , Y: /2 + },
			d2scene.Point{X: , Y: /2 + },
			d2scene.Point{X: , Y: /2 - },
			d2scene.Point{X: /2 + , Y: /2 - },
			d2scene.Point{X: /2 + },
			d2scene.Point{X: /2 - },
			d2scene.Point{X: /2 - , Y: /2 - },
			d2scene.Point{Y: /2 - },
		)
		.Fill = 
		.Stroke = ()
		 := d2scene.Point{X:  / 2, Y:  / 2}
		 := d2scene.Rotate(math.Pi / 4).Point()
		 := d2scene.Translate(-.X+/2, -.Y+/2).Mul(d2scene.Rotate(math.Pi / 4))
		 = append(, ("cross", , ))

		 := 
		if ! {
			 = 0
		}
		 := (d2scene.Point{X:  / 2, Y:  / 2}, d2scene.Point{X: , Y:  / 2})
		.Fill = 
		.Stroke = ()
		 = append(, ("stem", , d2scene.Identity()))
	case d2target.FilledBoxArrowhead, d2target.BoxArrowhead:
		 := 0.0
		if  == d2target.BoxArrowhead {
			 =  / 2
		}
		 := (d2scene.Point{X: , Y: }, d2scene.Point{X: , Y:  - }, d2scene.Point{X:  - , Y:  - }, d2scene.Point{X:  - , Y: })
		if  == d2target.BoxArrowhead {
			.Fill = 
			.Stroke = &d2scene.Stroke{Paint: , Width: , Join: d2scene.JoinMiter, MiterLimit: 4}
		}
		 = 
	case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
		 := 3 + *1.8
		 := d2scene.Identity()
		if ! {
			 = d2scene.Scale(-1, -1).Mul(d2scene.Translate(-, -))
		}

		if  == d2target.CfOneRequired ||  == d2target.CfManyRequired {
			 := (d2scene.Point{X: }, d2scene.Point{X: , Y: })
			.Fill = 
			.Stroke = ()
			 = append(, ("modifier", , ))
		} else {
			 := d2scene.Ellipse{
				Center:  d2scene.Point{X: /2 + 2, Y:  / 2},
				RadiusX:  / 2,
				RadiusY:  / 2,
				Fill:    ,
				Stroke:  (),
			}
			 = append(, ("modifier", , ))
		}

		var  d2scene.Path
		if  == d2target.CfMany ||  == d2target.CfManyRequired {
			 = d2scene.Path{Commands: []d2scene.PathCommand{
				d2scene.MoveTo(-3, /2), d2scene.LineTo(+, /2),
				d2scene.MoveTo(+3, /2), d2scene.LineTo(+, 0),
				d2scene.MoveTo(+3, /2), d2scene.LineTo(+, ),
			}}
		} else {
			 = d2scene.Path{Commands: []d2scene.PathCommand{
				d2scene.MoveTo(-3, /2), d2scene.LineTo(+, /2),
				d2scene.MoveTo(*2, 0), d2scene.LineTo(*2, ),
			}}
		}
		.Fill = 
		.Stroke = ()
		 = append(, ("marks", , ))
	default:
		return nil, unsupported(fmt.Sprintf("connection %q", .ID), "arrowhead "+string())
	}
	 := math.Atan2([1], [0])
	 := d2scene.NewNode()
	.ID = 
	.Children = 
	.Transform = d2scene.Translate(.X, .Y).Mul(d2scene.Rotate()).Mul(d2scene.Translate(-, -/2))
	return , nil
}