package d2sketch

import (
	
	
	
	
	

	_ 

	
	
	
	
	
	
	
	
)

//go:embed rough.js
var roughJS string

//go:embed setup.js
var setupJS string

//go:embed streaks.txt
var streaks string

var baseRoughProps = `fillWeight: 2.0,
hachureGap: 16,
fillStyle: "solid",
bowing: 2,
seed: 1,`

var floatRE = regexp.MustCompile(`(\d+)\.(\d+)`)

const (
	BG_COLOR = color.N7
	FG_COLOR = color.N1
)

func ( jsrunner.JSRunner) error {
	if ,  := .RunString(roughJS);  != nil {
		return 
	}
	if ,  := .RunString(setupJS);  != nil {
		return 
	}
	return nil
}

// DefineFillPatterns adds reusable patterns that are overlayed on shapes with
// fill. This gives it a subtle streaky effect that subtly looks hand-drawn but
// not distractingly so.
func ( *bytes.Buffer,  string) {
	 := .String()
	fmt.Fprint(, "<defs>")

	defineFillPattern(, , , "bright", "rgba(0, 0, 0, 0.1)")
	defineFillPattern(, , , "normal", "rgba(0, 0, 0, 0.16)")
	defineFillPattern(, , , "dark", "rgba(0, 0, 0, 0.32)")
	defineFillPattern(, , , "darker", "rgba(255, 255, 255, 0.24)")

	fmt.Fprint(, "</defs>")
}

func defineFillPattern( *bytes.Buffer, ,  string, ,  string) {
	 := fmt.Sprintf(`url(#streaks-%s-%s)`, , )
	if strings.Contains(, ) {
		fmt.Fprintf(, streaks, , , )
	}
}

func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width, .Height, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 := ""
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width, .Height, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width-d2target.INNER_BORDER_OFFSET*2, .Height-d2target.INNER_BORDER_OFFSET*2, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}

	 := ""

	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X+d2target.INNER_BORDER_OFFSET), float64(.Pos.Y+d2target.INNER_BORDER_OFFSET))
	.Fill, .Stroke = d2themes.ShapeTheme()
	// No need for inner to double paint
	.Fill = "transparent"
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width/2, .Height/2, .Width, .Height, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 := ""
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("ellipse", nil)
	.SetTranslate(float64(.Pos.X+.Width/2), float64(.Pos.Y+.Height/2))
	.Rx = float64(.Width / 2)
	.Ry = float64(.Height / 2)
	,  := d2themes.NewThemableSketchOverlay(
		,
		.Fill,
	).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width/2, .Height/2, .Width, .Height, .StrokeWidth, baseRoughProps)
	 := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width/2, .Height/2, .Width-d2target.INNER_BORDER_OFFSET*2, .Height-d2target.INNER_BORDER_OFFSET*2, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}

	 := ""

	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	// No need for inner to double paint
	.Fill = "transparent"
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}
	 := d2themes.NewThemableElement("ellipse", nil)
	.SetTranslate(float64(.Pos.X+.Width/2), float64(.Pos.Y+.Height/2))
	.Rx = float64(.Width / 2)
	.Ry = float64(.Height / 2)
	,  := d2themes.NewThemableSketchOverlay(
		,
		.Fill,
	).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

// TODO need to personalize this per shape like we do in Terrastruct app
func ( jsrunner.JSRunner,  d2target.Shape,  []string) (string, error) {
	 := ""
	for ,  := range  {
		 := fmt.Sprintf(`node = rc.path("%s", {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, , .StrokeWidth, baseRoughProps)
		,  := computeRoughPathData(, )
		if  != nil {
			return "", 
		}
		 := d2themes.NewThemableElement("path", nil)
		.Fill, .Stroke = d2themes.ShapeTheme()
		.FillPattern = .FillPattern
		.ClassName = "shape"
		.Style = .CSSStyle()
		for ,  := range  {
			.D = 
			 += .Render()
		}

		 := d2themes.NewThemableElement("path", nil)
		for ,  := range  {
			.D = 
			,  := d2themes.NewThemableSketchOverlay(
				,
				.Fill,
			).Render()
			if  != nil {
				return "", 
			}
			 += 
		}
	}
	return , nil
}

func ( jsrunner.JSRunner,  d2target.Connection, ,  string) (string, error) {
	 := ""
	if .Animated {
		 = " animated-connection"
	}

	if .Animated {
		// If connection is animated and bidirectional
		if (.DstArrow == d2target.NoArrowhead && .SrcArrow == d2target.NoArrowhead) || (.DstArrow != d2target.NoArrowhead && .SrcArrow != d2target.NoArrowhead) {
			// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
			, ,  := svg.SplitPath(, 0.5)

			if  != nil {
				return "", 
			}

			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.Style += "animation-direction: reverse;"
			.Attributes = 

			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.Attributes = 
			return .Render() + " " + .Render(), nil
		} else {
			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.Attributes = 
			return .Render(), nil
		}
	} else {
		 := 0.5
		 := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, , )
		,  := computeRoughPathData(, )
		if  != nil {
			return "", 
		}

		 := ""

		 := d2themes.NewThemableElement("path", nil)
		.Fill = color.None
		.Stroke = .Stroke
		.ClassName = fmt.Sprintf("connection%s", )
		.Style = .CSSStyle()
		.Attributes = 
		for ,  := range  {
			.D = 
			 += .Render()
		}
		return , nil
	}
}

// TODO cleanup
func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := ""
	 := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width, .Height, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := geo.NewBox(
		geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
		float64(.Width),
		float64(.Height),
	)
	 := .Height / float64(1+len(.SQLTable.Columns))
	 := geo.NewBox(.TopLeft, .Width, )

	 = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
		fill: "#000",
		%s
	});`, .Width, , baseRoughProps)
	,  = computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	if .Label != "" {
		 := label.InsideMiddleLeft.GetPointOnBox(
			,
			20,
			float64(.LabelWidth),
			float64(.LabelHeight),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X
		.Y = .Y + float64(.LabelHeight)*3/4
		.Fill = .GetFontColor()
		.ClassName = "text"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
			"start", 4+.FontSize,
		)
		.Content = svg.EscapeText(.Label)
		 += .Render()
	}

	var  int
	for ,  := range .Columns {
		 = go2.Max(, .Name.LabelWidth)
	}

	 := geo.NewBox(.TopLeft.Copy(), .Width, )
	.TopLeft.Y += .Height
	for ,  := range .Columns {
		 := label.InsideMiddleLeft.GetPointOnBox(
			,
			d2target.NamePadding,
			.Width,
			float64(.FontSize),
		)
		 := label.InsideMiddleRight.GetPointOnBox(
			,
			d2target.TypePadding,
			0,
			float64(.FontSize),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X
		.Y = .Y + float64(.FontSize)*3/4
		.Fill = .PrimaryAccentColor
		.ClassName = "text"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", float64(.FontSize))
		.Content = svg.EscapeText(.Name.Label)
		 += .Render()

		.X = .X + float64() + 2*d2target.NamePadding
		.Fill = .NeutralAccentColor
		.Content = svg.EscapeText(.Type.Label)
		 += .Render()

		.X = .X
		.Y = .Y + float64(.FontSize)*3/4
		.Fill = .SecondaryAccentColor
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", float64(.FontSize))
		.Content = .ConstraintAbbr()
		 += .Render()

		.TopLeft.Y += 

		 = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, {
		%s
	});`, .TopLeft.X, .TopLeft.Y, .TopLeft.X+.Width, .TopLeft.Y, baseRoughProps)
		,  = computeRoughPathData(, )
		if  != nil {
			return "", 
		}
		 := d2themes.NewThemableElement("path", nil)
		.Fill = .Fill
		.FillPattern = .FillPattern
		for ,  := range  {
			.D = 
			 += .Render()
		}
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( jsrunner.JSRunner,  d2target.Shape) (string, error) {
	 := ""
	 := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, {
		fill: "#000",
		stroke: "#000",
		strokeWidth: %d,
		%s
	});`, .Width, .Height, .StrokeWidth, baseRoughProps)
	,  := computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := geo.NewBox(
		geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
		float64(.Width),
		float64(.Height),
	)

	 := .Height / float64(2+len(.Class.Fields)+len(.Class.Methods))
	 := geo.NewBox(.TopLeft, .Width, 2*)

	 = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, {
		fill: "#000",
		%s
	});`, .Width, .Height, baseRoughProps)
	,  = computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = .Height
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	if .Label != "" {
		 := label.InsideMiddleCenter.GetPointOnBox(
			,
			0,
			float64(.LabelWidth),
			float64(.LabelHeight),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X + float64(.LabelWidth)/2
		.Y = .Y + float64(.LabelHeight)*3/4
		.Fill = .GetFontColor()
		.ClassName = "text-mono"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
			"middle",
			4+.FontSize,
		)
		.Content = svg.EscapeText(.Label)
		 += .Render()
	}

	 := geo.NewBox(.TopLeft.Copy(), .Width, )
	.TopLeft.Y += .Height
	for ,  := range .Fields {
		 += classRow(, , .VisibilityToken(), .Name, .Type, float64(.FontSize))
		.TopLeft.Y += 
	}

	 = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, {
%s
	});`, .TopLeft.X, .TopLeft.Y, .TopLeft.X+.Width, .TopLeft.Y, baseRoughProps)
	,  = computeRoughPathData(, )
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	for ,  := range .Methods {
		 += classRow(, , .VisibilityToken(), .Name, .Return, float64(.FontSize))
		.TopLeft.Y += 
	}

	return , nil
}

func classRow( d2target.Shape,  *geo.Box, , ,  string,  float64) string {
	 := ""
	 := label.InsideMiddleLeft.GetPointOnBox(
		,
		d2target.PrefixPadding,
		.Width,
		,
	)
	 := label.InsideMiddleRight.GetPointOnBox(
		,
		d2target.TypePadding,
		0,
		,
	)

	 := d2themes.NewThemableElement("text", nil)
	.X = .X
	.Y = .Y + *3/4
	.Fill = .PrimaryAccentColor
	.ClassName = "text-mono"
	.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", )
	.Content = 
	 += .Render()

	.X = .X + d2target.PrefixWidth
	.Fill = .Fill
	.Content = svg.EscapeText()
	 += .Render()

	.X = .X
	.Y = .Y + *3/4
	.Fill = .SecondaryAccentColor
	.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", )
	.Content = svg.EscapeText()
	 += .Render()

	return 
}

func computeRoughPathData( jsrunner.JSRunner,  string) ([]string, error) {
	if ,  := .RunString();  != nil {
		return nil, 
	}
	,  := extractRoughPaths()
	if  != nil {
		return nil, 
	}
	return extractPathData()
}

func computeRoughPaths( jsrunner.JSRunner,  string) ([]roughPath, error) {
	if ,  := .RunString();  != nil {
		return nil, 
	}
	return extractRoughPaths()
}

type attrs struct {
	D string `json:"d"`
}

type style struct {
	Stroke      string `json:"stroke,omitempty"`
	StrokeWidth string `json:"strokeWidth,omitempty"`
	Fill        string `json:"fill,omitempty"`
}

type roughPath struct {
	Attrs attrs `json:"attrs"`
	Style style `json:"style"`
}

func ( roughPath) () string {
	 := ""
	if .Style.StrokeWidth != "" {
		 += fmt.Sprintf("stroke-width:%s;", .Style.StrokeWidth)
	}
	return 
}

func extractRoughPaths( jsrunner.JSRunner) ([]roughPath, error) {
	,  := .RunString("JSON.stringify(node.children, null, '  ')")
	if  != nil {
		return nil, 
	}

	var  []roughPath
	 = json.Unmarshal([]byte(.String()), &)
	if  != nil {
		return nil, 
	}

	// we want to have a fixed precision to the decimals in the path data
	for  := range  {
		// truncate all floats in path to only use up to 6 decimal places
		[].Attrs.D = floatRE.ReplaceAllStringFunc([].Attrs.D, func( string) string {
			 := strings.Index(, ".")
			 := len() -  - 1
			 :=  + go2.Min(, 6)
			return [:+1]
		})
	}

	return , nil
}

func extractPathData( []roughPath) ([]string, error) {
	var  []string
	for ,  := range  {
		 = append(, .Attrs.D)
	}
	return , nil
}

func ( jsrunner.JSRunner,  d2target.Arrowhead,  string,  int) (,  string) {
	// Note: selected each seed that looks the good for consistent renders
	switch  {
	case d2target.ArrowArrowhead:
		 = fmt.Sprintf(
			`node = rc.linearPath(%s, { strokeWidth: %d, stroke: "%s", seed: 3 })`,
			`[[-10, -4], [0, 0], [-10, 4]]`,
			,
			,
		)
	case d2target.TriangleArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 2 })`,
			`[[-10, -4], [0, 0], [-10, 4]]`,
			,
			,
			,
		)
	case d2target.UnfilledTriangleArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 2 })`,
			`[[-10, -4], [0, 0], [-10, 4]]`,
			,
			,
			BG_COLOR,
		)
	case d2target.DiamondArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1 })`,
			`[[-20, 0], [-10, 5], [0, 0], [-10, -5], [-20, 0]]`,
			,
			,
			BG_COLOR,
		)
	case d2target.FilledDiamondArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "zigzag", fillWeight: 4, seed: 1 })`,
			`[[-20, 0], [-10, 5], [0, 0], [-10, -5], [-20, 0]]`,
			,
			,
			,
		)
	case d2target.CfManyRequired:
		 = fmt.Sprintf(
			// TODO why does fillStyle: "zigzag" error with path
			`node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 2 })`,
			`"M-15,-10 -15,10 M0,10 -15,0 M0,-10 -15,0"`,
			,
			,
			,
		)
	case d2target.CfMany:
		 = fmt.Sprintf(
			`node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 8 })`,
			`"M0,10 -15,0 M0,-10 -15,0"`,
			,
			,
			,
		)
		 = fmt.Sprintf(
			`node = rc.circle(-20, 0, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 4 })`,
			,
			,
			BG_COLOR,
		)
	case d2target.CfOneRequired:
		 = fmt.Sprintf(
			`node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 2 })`,
			`"M-15,-10 -15,10 M-10,-10 -10,10"`,
			,
			,
			,
		)
	case d2target.CfOne:
		 = fmt.Sprintf(
			`node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 3 })`,
			`"M-10,-10 -10,10"`,
			,
			,
			,
		)
		 = fmt.Sprintf(
			`node = rc.circle(-20, 0, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 5 })`,
			,
			,
			BG_COLOR,
		)
	case d2target.CircleArrowhead:
		 = fmt.Sprintf(
			`node = rc.circle(-2, -1, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 5 })`,
			,
			,
			BG_COLOR,
		)
	case d2target.BoxArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1})`,
			`[[0, -10], [0, 10], [-20, 10], [-20, -10]]`,
			,
			,
			BG_COLOR,
		)
	case d2target.FilledBoxArrowhead:
		 = fmt.Sprintf(
			`node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1})`,
			`[[0, -10], [0, 10], [-20, 10], [-20, -10]]`,
			,
			,
			,
		)
	}
	return
}

func ( jsrunner.JSRunner,  d2target.Connection, ,  *geo.Point) (string, error) {
	 := []string{}

	if .SrcArrow != d2target.NoArrowhead {
		,  := ArrowheadJS(, .SrcArrow, .Stroke, .StrokeWidth)
		if  == "" {
			return "", nil
		}

		 := geo.NewSegment(.Route[0], .Route[1])
		 := .ToVector().Reverse()
		 := .Degrees()

		 := fmt.Sprintf(`transform="translate(%f %f) rotate(%v)"`,
			.Start.X+.X, .Start.Y+.Y, ,
		)

		,  := computeRoughPaths(, )
		if  != nil {
			return "", 
		}
		if  != "" {
			,  := computeRoughPaths(, )
			if  != nil {
				return "", 
			}
			 = append(, ...)
		}

		 := d2themes.NewThemableElement("path", nil)
		.ClassName = "connection"
		.Attributes = 
		for ,  := range  {
			.D = .Attrs.D
			.Fill = .Style.Fill
			.Stroke = .Style.Stroke
			.Style = .StyleCSS()
			 = append(, .Render())
		}
	}

	if .DstArrow != d2target.NoArrowhead {
		,  := ArrowheadJS(, .DstArrow, .Stroke, .StrokeWidth)
		if  == "" {
			return "", nil
		}

		 := len(.Route)
		 := geo.NewSegment(.Route[-2], .Route[-1])
		 := .ToVector()
		 := .Degrees()

		 := fmt.Sprintf(`transform="translate(%f %f) rotate(%v)"`,
			.End.X+.X, .End.Y+.Y, ,
		)

		,  := computeRoughPaths(, )
		if  != nil {
			return "", 
		}
		if  != "" {
			,  := computeRoughPaths(, )
			if  != nil {
				return "", 
			}
			 = append(, ...)
		}

		 := d2themes.NewThemableElement("path", nil)
		.ClassName = "connection"
		.Attributes = 
		for ,  := range  {
			.D = .Attrs.D
			.Fill = .Style.Fill
			.Stroke = .Style.Stroke
			.Style = .StyleCSS()
			 = append(, .Render())
		}
	}

	return strings.Join(, " "), nil
}