package themer

import (
	"os"
	"strconv"
	"strings"

	"github.com/pelletier/go-toml/v2"
)

type Theme struct {
	Meta struct {
		Name    string
		Type    string
		Version string
	}
	Primitive map[string]string
	Colors    struct {
		Semantic struct {
			Background Color
			Surface    Color
			SurfaceAlt Color
			Disabled   Color
			Border     Color
			Text       Color
			Accent     Color
			Warn       Color
		}
	} `toml:"Colors"`
	Spacing struct {
		Padding      string
		PaddingSmall string
		PaddingLarge string
		Margin       string
		MarginSmall  string
		Radius       string
		RadiusSmall  string
		RadiusLarge  string
	} `toml:"Spacing"`
}

type Color struct {
	raw   string
	Value string
}

func (c Color) Alpha(bg Color, amount float64) string {
	r1, g1, b1, ok1 := parseHexRGB(c.Value)
	r2, g2, b2, ok2 := parseHexRGB(bg.Value)
	if !ok1 || !ok2 {
		return c.Value
	}
	r := blend8(r2, r1, amount)
	g := blend8(g2, g1, amount)
	b := blend8(b2, b1, amount)
	return formatHexRGB(r, g, b)
}

func (c Color) RGBA(alpha int) string {
	r, g, b, ok := parseHexRGB(c.Value)
	if !ok {
		return c.Value
	}
	if alpha < 100 {
		return "rgba ( " + strconv.Itoa(int(r)) + ", " + strconv.Itoa(int(g)) + ", " + strconv.Itoa(int(b)) + ", " + strconv.Itoa(alpha) + " % )"
	}
	return c.Value
}

func (c *Color) UnmarshalText(text []byte) error {
	c.raw = strings.TrimSpace(string(text))
	return nil
}

func LoadTheme(path string) (Theme, error) {
	theme := Theme{}
	if themeFile, err := os.ReadFile(path); err != nil {
		return theme, err
	} else {
		if err := toml.Unmarshal(themeFile, &theme); err != nil {
			return theme, err
		}
	}
	theme.resolveColor(&theme.Colors.Semantic.Background)
	theme.resolveColor(&theme.Colors.Semantic.Surface)
	theme.resolveColor(&theme.Colors.Semantic.SurfaceAlt)
	theme.resolveColor(&theme.Colors.Semantic.Disabled)
	theme.resolveColor(&theme.Colors.Semantic.Border)
	theme.resolveColor(&theme.Colors.Semantic.Text)
	theme.resolveColor(&theme.Colors.Semantic.Accent)
	theme.resolveColor(&theme.Colors.Semantic.Warn)

	return theme, nil
}

func (t Theme) resolveColor(color *Color) {
	raw, shadeRaw, hasShade := strings.Cut(color.raw, "|")
	base := strings.TrimSpace(raw)
	shade, err := strconv.Atoi(strings.TrimSpace(shadeRaw))
	if primitive, ok := t.Primitive[base]; ok {
		base = primitive
	} else {
		base = raw
	}
	base = normalizeHex(base)
	if !hasShade || err != nil {
		color.Value = base
		return
	}
	if shade < 0 {
		shade = 0
	}
	if shade > 1000 {
		shade = 1000
	}

	r, g, b, ok := parseHexRGB(base)
	if !ok {
		color.Value = base
		return
	}

	switch {
	case shade == 500:
		// unchanged
	case shade < 500:
		amount := float64(500-shade) / 500.0
		r = blend8(r, 255, amount)
		g = blend8(g, 255, amount)
		b = blend8(b, 255, amount)
	default:
		amount := float64(shade-500) / 500.0
		r = blend8(r, 0, amount)
		g = blend8(g, 0, amount)
		b = blend8(b, 0, amount)
	}
	color.Value = formatHexRGB(r, g, b)
}

func normalizeHex(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return s
	}
	if strings.HasPrefix(s, "#") {
		return s
	}
	return "#" + s
}

func parseHexRGB(s string) (uint8, uint8, uint8, bool) {
	s = strings.TrimSpace(s)
	if strings.HasPrefix(s, "#") {
		s = s[1:]
	}
	if len(s) != 6 {
		return 0, 0, 0, false
	}
	// Manual hex parsing to avoid extra deps; strconv handles base 16 fine.
	rv, err1 := strconv.ParseUint(s[0:2], 16, 8)
	gv, err2 := strconv.ParseUint(s[2:4], 16, 8)
	bv, err3 := strconv.ParseUint(s[4:6], 16, 8)
	if err1 != nil || err2 != nil || err3 != nil {
		return 0, 0, 0, false
	}
	return uint8(rv), uint8(gv), uint8(bv), true
}
func formatHexRGB(r, g, b uint8) string {
	const hex = "0123456789abcdef"
	out := make([]byte, 7)
	out[0] = '#'
	out[1] = hex[r>>4]
	out[2] = hex[r&0x0f]
	out[3] = hex[g>>4]
	out[4] = hex[g&0x0f]
	out[5] = hex[b>>4]
	out[6] = hex[b&0x0f]
	return string(out)
}

func blend8(a, b uint8, t float64) uint8 {
	// result = a*(1-t) + b*t, rounded
	v := float64(a)*(1.0-t) + float64(b)*t
	if v < 0 {
		v = 0
	}
	if v > 255 {
		v = 255
	}
	return uint8(v + 0.5)
}
