package d2themes

import (
	"fmt"
	"math"

	"oss.terrastruct.com/d2/lib/color"
)

// ThemableElement is a helper class for creating new XML elements.
// This should be preferred over formatting and must be used
// whenever Fill, Stroke, BackgroundColor or Color contains a color from a theme.
// i.e. N[1-7] | B[1-6] | AA[245] | AB[45]
type ThemableElement struct {
	tag string

	X      float64
	X1     float64
	X2     float64
	Y      float64
	Y1     float64
	Y2     float64
	Width  float64
	Height float64
	R      float64
	Rx     float64
	Ry     float64
	Cx     float64
	Cy     float64

	D         string
	Mask      string
	Points    string
	Transform string
	Href      string
	Xmlns     string

	Fill            string
	Stroke          string
	StrokeDashArray string
	BackgroundColor string
	Color           string

	ClassName  string
	Style      string
	Attributes string

	Content  string
	ClipPath string

	FillPattern string

	inlineTheme *Theme
}

func NewThemableElement(tag string, inlineTheme *Theme) *ThemableElement {
	xmlns := ""
	if tag == "div" {
		xmlns = "http://www.w3.org/1999/xhtml"
	}

	return &ThemableElement{
		tag,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		math.MaxFloat64,
		"",
		"",
		"",
		"",
		"",
		xmlns,
		color.Empty,
		color.Empty,
		color.Empty,
		color.Empty,
		"",
		"",
		"",
		"",
		"",
		"",
		"",
		inlineTheme,
	}
}

func (el *ThemableElement) Copy() *ThemableElement {
	tmp := *el
	return &tmp
}

func (el *ThemableElement) SetTranslate(x, y float64) {
	el.Transform = fmt.Sprintf("translate(%f %f)", x, y)
}

func (el *ThemableElement) SetMaskUrl(url string) {
	el.Mask = fmt.Sprintf("url(#%s)", url)
}

func (el *ThemableElement) Render() string {
	out := "<" + el.tag

	// href has to be at the top for the img bundler to detect <image> tags correctly
	if len(el.Href) > 0 {
		out += fmt.Sprintf(` href="%s"`, el.Href)
	}
	if el.X != math.MaxFloat64 {
		out += fmt.Sprintf(` x="%f"`, el.X)
	}
	if el.X1 != math.MaxFloat64 {
		out += fmt.Sprintf(` x1="%f"`, el.X1)
	}
	if el.X2 != math.MaxFloat64 {
		out += fmt.Sprintf(` x2="%f"`, el.X2)
	}
	if el.Y != math.MaxFloat64 {
		out += fmt.Sprintf(` y="%f"`, el.Y)
	}
	if el.Y1 != math.MaxFloat64 {
		out += fmt.Sprintf(` y1="%f"`, el.Y1)
	}
	if el.Y2 != math.MaxFloat64 {
		out += fmt.Sprintf(` y2="%f"`, el.Y2)
	}
	if el.Width != math.MaxFloat64 {
		out += fmt.Sprintf(` width="%f"`, el.Width)
	}
	if el.Height != math.MaxFloat64 {
		out += fmt.Sprintf(` height="%f"`, el.Height)
	}
	if el.R != math.MaxFloat64 {
		out += fmt.Sprintf(` r="%f"`, el.R)
	}
	if el.Rx != math.MaxFloat64 {
		out += fmt.Sprintf(` rx="%f"`, calculateAxisRadius(el.Rx, el.Width, el.Height))
	}
	if el.Ry != math.MaxFloat64 {
		out += fmt.Sprintf(` ry="%f"`, calculateAxisRadius(el.Ry, el.Width, el.Height))
	}
	if el.Cx != math.MaxFloat64 {
		out += fmt.Sprintf(` cx="%f"`, el.Cx)
	}
	if el.Cy != math.MaxFloat64 {
		out += fmt.Sprintf(` cy="%f"`, el.Cy)
	}
	if el.StrokeDashArray != "" {
		out += fmt.Sprintf(` stroke-dasharray="%s"`, el.StrokeDashArray)
	}

	if len(el.D) > 0 {
		out += fmt.Sprintf(` d="%s"`, el.D)
	}
	if len(el.Mask) > 0 {
		out += fmt.Sprintf(` mask="%s"`, el.Mask)
	}
	if len(el.Points) > 0 {
		out += fmt.Sprintf(` points="%s"`, el.Points)
	}
	if len(el.Transform) > 0 {
		out += fmt.Sprintf(` transform="%s"`, el.Transform)
	}
	if len(el.Xmlns) > 0 {
		out += fmt.Sprintf(` xmlns="%s"`, el.Xmlns)
	}

	class := el.ClassName
	style := el.Style

	// Add class {property}-{theme color} if the color is from a theme, set the property otherwise
	if color.IsThemeColor(el.Stroke) {
		class += fmt.Sprintf(" stroke-%s", el.Stroke)
		if el.inlineTheme != nil {
			out += fmt.Sprintf(` stroke="%s"`, ResolveThemeColor(*el.inlineTheme, el.Stroke))
		}
	} else if len(el.Stroke) > 0 {
		if color.IsGradient(el.Stroke) {
			el.Stroke = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Stroke))
		}
		out += fmt.Sprintf(` stroke="%s"`, el.Stroke)
	}
	if color.IsThemeColor(el.Fill) {
		class += fmt.Sprintf(" fill-%s", el.Fill)
		if el.inlineTheme != nil {
			out += fmt.Sprintf(` fill="%s"`, ResolveThemeColor(*el.inlineTheme, el.Fill))
		}
	} else if len(el.Fill) > 0 {
		if color.IsGradient(el.Fill) {
			el.Fill = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Fill))
		}
		out += fmt.Sprintf(` fill="%s"`, el.Fill)
	}
	if color.IsThemeColor(el.BackgroundColor) {
		class += fmt.Sprintf(" background-color-%s", el.BackgroundColor)
		if el.inlineTheme != nil {
			out += fmt.Sprintf(` background-color="%s"`, ResolveThemeColor(*el.inlineTheme, el.BackgroundColor))
		}
	} else if len(el.BackgroundColor) > 0 {
		out += fmt.Sprintf(` background-color="%s"`, el.BackgroundColor)
	}
	if color.IsThemeColor(el.Color) {
		class += fmt.Sprintf(" color-%s", el.Color)
		if el.inlineTheme != nil {
			out += fmt.Sprintf(` color="%s"`, ResolveThemeColor(*el.inlineTheme, el.Color))
		}
	} else if len(el.Color) > 0 {
		out += fmt.Sprintf(` color="%s"`, el.Color)
	}

	if len(class) > 0 {
		out += fmt.Sprintf(` class="%s"`, class)
	}
	if len(style) > 0 {
		out += fmt.Sprintf(` style="%s"`, style)
	}
	if len(el.Attributes) > 0 {
		out += fmt.Sprintf(` %s`, el.Attributes)
	}

	if len(el.ClipPath) > 0 {
		out += fmt.Sprintf(` clip-path="url(#%s)"`, el.ClipPath)
	}

	if len(el.Content) > 0 {
		return fmt.Sprintf("%s>%s</%s>", out, el.Content, el.tag)
	}

	out += " />"
	if el.FillPattern != "" && el.FillPattern != "none" {
		patternEl := el.Copy()
		patternEl.Fill = ""
		patternEl.Stroke = ""
		patternEl.BackgroundColor = ""
		patternEl.Color = ""
		patternEl.ClassName = fmt.Sprintf("%s-overlay", el.FillPattern)
		patternEl.FillPattern = ""
		out += patternEl.Render()
	}
	return out
}

func calculateAxisRadius(borderRadius, width, height float64) float64 {
	minimumSideSize := math.Min(width, height)
	maximumBorderRadiusValue := minimumSideSize / 2.0
	return math.Min(borderRadius, maximumBorderRadiusValue)
}
