package engine

import (
	"fmt"
	"math"
	"strconv"
)

// Lightness correctors for each hue segment (based on JavaScript implementation)
var correctors = []float64{0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55}

// Color represents a color with both HSL and RGB representations
type Color struct {
	H, S, L float64 // HSL values: H=[0,1], S=[0,1], L=[0,1]
	R, G, B uint8   // RGB values: [0,255]
	A       uint8   // Alpha channel: [0,255]
}

// NewColorHSL creates a new Color from HSL values
func NewColorHSL(h, s, l float64) Color {
	r, g, b := HSLToRGB(h, s, l)
	return Color{
		H: h, S: s, L: l,
		R: r, G: g, B: b,
		A: 255,
	}
}

// NewColorCorrectedHSL creates a new Color from HSL values with lightness correction
func NewColorCorrectedHSL(h, s, l float64) Color {
	r, g, b := CorrectedHSLToRGB(h, s, l)
	return Color{
		H: h, S: s, L: l,
		R: r, G: g, B: b,
		A: 255,
	}
}

// NewColorRGB creates a new Color from RGB values and calculates HSL
func NewColorRGB(r, g, b uint8) Color {
	h, s, l := RGBToHSL(r, g, b)
	return Color{
		H: h, S: s, L: l,
		R: r, G: g, B: b,
		A: 255,
	}
}

// NewColorRGBA creates a new Color from RGBA values and calculates HSL
func NewColorRGBA(r, g, b, a uint8) Color {
	h, s, l := RGBToHSL(r, g, b)
	return Color{
		H: h, S: s, L: l,
		R: r, G: g, B: b,
		A: a,
	}
}

// String returns the hex representation of the color
func (c Color) String() string {
	if c.A == 255 {
		return RGBToHex(c.R, c.G, c.B)
	}
	return fmt.Sprintf("#%02x%02x%02x%02x", c.R, c.G, c.B, c.A)
}

// Equals compares two colors for equality
func (c Color) Equals(other Color) bool {
	return c.R == other.R && c.G == other.G && c.B == other.B && c.A == other.A
}

// WithAlpha returns a new color with the specified alpha value
func (c Color) WithAlpha(alpha uint8) Color {
	return Color{
		H: c.H, S: c.S, L: c.L,
		R: c.R, G: c.G, B: c.B,
		A: alpha,
	}
}

// IsGrayscale returns true if the color is grayscale (saturation near zero)
func (c Color) IsGrayscale() bool {
	return c.S < 0.01 // Small tolerance for floating point comparison
}

// Darken returns a new color with reduced lightness
func (c Color) Darken(amount float64) Color {
	newL := clamp(c.L-amount, 0, 1)
	return NewColorCorrectedHSL(c.H, c.S, newL)
}

// Lighten returns a new color with increased lightness
func (c Color) Lighten(amount float64) Color {
	newL := clamp(c.L+amount, 0, 1)
	return NewColorCorrectedHSL(c.H, c.S, newL)
}

// RGBToHSL converts RGB values to HSL
// Returns H=[0,1], S=[0,1], L=[0,1]
func RGBToHSL(r, g, b uint8) (h, s, l float64) {
	rf := float64(r) / 255.0
	gf := float64(g) / 255.0
	bf := float64(b) / 255.0
	
	max := math.Max(rf, math.Max(gf, bf))
	min := math.Min(rf, math.Min(gf, bf))
	
	// Calculate lightness
	l = (max + min) / 2.0
	
	if max == min {
		// Achromatic (gray)
		h, s = 0, 0
	} else {
		delta := max - min
		
		// Calculate saturation
		if l > 0.5 {
			s = delta / (2.0 - max - min)
		} else {
			s = delta / (max + min)
		}
		
		// Calculate hue
		switch max {
		case rf:
			h = (gf-bf)/delta + (func() float64 {
				if gf < bf {
					return 6
				}
				return 0
			})()
		case gf:
			h = (bf-rf)/delta + 2
		case bf:
			h = (rf-gf)/delta + 4
		}
		h /= 6.0
	}
	
	return h, s, l
}

// HSLToRGB converts HSL color values to RGB.
// h: hue in range [0, 1]
// s: saturation in range [0, 1] 
// l: lightness in range [0, 1]
// Returns RGB values in range [0, 255]
func HSLToRGB(h, s, l float64) (r, g, b uint8) {
	// Clamp input values to valid ranges
	h = math.Mod(h, 1.0)
	if h < 0 {
		h += 1.0
	}
	s = clamp(s, 0, 1)
	l = clamp(l, 0, 1)
	
	// Handle grayscale case (saturation = 0)
	if s == 0 {
		// All RGB components are equal for grayscale
		gray := uint8(clamp(l*255, 0, 255))
		return gray, gray, gray
	}
	
	// Calculate intermediate values for HSL to RGB conversion
	var m2 float64
	if l <= 0.5 {
		m2 = l * (s + 1)
	} else {
		m2 = l + s - l*s
	}
	m1 := l*2 - m2
	
	// Convert each RGB component
	r = uint8(clamp(hueToRGB(m1, m2, h*6+2)*255, 0, 255))
	g = uint8(clamp(hueToRGB(m1, m2, h*6)*255, 0, 255))
	b = uint8(clamp(hueToRGB(m1, m2, h*6-2)*255, 0, 255))
	
	return r, g, b
}

// CorrectedHSLToRGB converts HSL to RGB with lightness correction for better visual perception.
// This function adjusts the lightness based on the hue to compensate for the human eye's
// different sensitivity to different colors.
func CorrectedHSLToRGB(h, s, l float64) (r, g, b uint8) {
	// Get the corrector for the current hue
	hueIndex := int((h*6 + 0.5)) % len(correctors)
	corrector := correctors[hueIndex]
	
	// Adjust lightness relative to the corrector
	if l < 0.5 {
		l = l * corrector * 2
	} else {
		l = corrector + (l-0.5)*(1-corrector)*2
	}
	
	// Clamp the corrected lightness
	l = clamp(l, 0, 1)
	
	return HSLToRGB(h, s, l)
}

// hueToRGB converts a hue value to an RGB component value
// Based on the W3C CSS3 color specification
func hueToRGB(m1, m2, h float64) float64 {
	// Normalize hue to [0, 6) range
	if h < 0 {
		h += 6
	} else if h > 6 {
		h -= 6
	}
	
	// Calculate RGB component based on hue position
	if h < 1 {
		return m1 + (m2-m1)*h
	} else if h < 3 {
		return m2
	} else if h < 4 {
		return m1 + (m2-m1)*(4-h)
	} else {
		return m1
	}
}

// clamp constrains a value to the specified range [min, max]
func clamp(value, min, max float64) float64 {
	if value < min {
		return min
	}
	if value > max {
		return max
	}
	return value
}

// RGBToHex converts RGB values to a hexadecimal color string
func RGBToHex(r, g, b uint8) string {
	return fmt.Sprintf("#%02x%02x%02x", r, g, b)
}

// ParseHexColor parses a hexadecimal color string and returns RGB values
// Supports formats: #RGB, #RRGGBB, #RRGGBBAA
// Returns error if the format is invalid
func ParseHexColor(color string) (r, g, b, a uint8, err error) {
	if len(color) == 0 || color[0] != '#' {
		return 0, 0, 0, 255, fmt.Errorf("invalid color format: %s", color)
	}
	
	hex := color[1:] // Remove '#' prefix
	a = 255          // Default alpha
	
	// Helper to parse a component and chain errors
	parse := func(target *uint8, hexStr string) {
		if err != nil {
			return // Don't parse if a previous component failed
		}
		*target, err = hexToByte(hexStr)
	}
	
	switch len(hex) {
	case 3: // #RGB
		parse(&r, hex[0:1]+hex[0:1])
		parse(&g, hex[1:2]+hex[1:2])
		parse(&b, hex[2:3]+hex[2:3])
	case 6: // #RRGGBB
		parse(&r, hex[0:2])
		parse(&g, hex[2:4])
		parse(&b, hex[4:6])
	case 8: // #RRGGBBAA
		parse(&r, hex[0:2])
		parse(&g, hex[2:4])
		parse(&b, hex[4:6])
		parse(&a, hex[6:8])
	default:
		return 0, 0, 0, 255, fmt.Errorf("invalid hex color length: %s", color)
	}
	
	if err != nil {
		// Return zero values for color components on error, but keep default alpha
		return 0, 0, 0, 255, fmt.Errorf("failed to parse color '%s': %w", color, err)
	}
	
	return r, g, b, a, nil
}

// hexToByte converts a 2-character hex string to a byte value
func hexToByte(hex string) (uint8, error) {
	if len(hex) != 2 {
		return 0, fmt.Errorf("invalid hex string length: expected 2 characters, got %d", len(hex))
	}
	
	n, err := strconv.ParseUint(hex, 16, 8)
	if err != nil {
		return 0, fmt.Errorf("invalid hex value '%s': %w", hex, err)
	}
	return uint8(n), nil
}

// GenerateColor creates a color with the specified hue and configuration-based saturation and lightness
func GenerateColor(hue float64, config ColorConfig, lightnessValue float64) Color {
	// Restrict hue according to configuration
	restrictedHue := config.RestrictHue(hue)
	
	// Get lightness from configuration range
	lightness := config.ColorLightness.GetLightness(lightnessValue)
	
	// Use corrected HSL to RGB conversion
	return NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, lightness)
}

// GenerateGrayscale creates a grayscale color with configuration-based saturation and lightness
func GenerateGrayscale(config ColorConfig, lightnessValue float64) Color {
	// For grayscale, hue doesn't matter, but we'll use 0
	hue := 0.0
	
	// Get lightness from grayscale configuration range
	lightness := config.GrayscaleLightness.GetLightness(lightnessValue)
	
	// Use grayscale saturation (typically 0)
	return NewColorCorrectedHSL(hue, config.GrayscaleSaturation, lightness)
}

// GenerateColorTheme generates a set of color candidates based on the JavaScript colorTheme function
// This matches the JavaScript implementation that creates 5 colors:
// 0: Dark gray, 1: Mid color, 2: Light gray, 3: Light color, 4: Dark color
func GenerateColorTheme(hue float64, config ColorConfig) []Color {
	// Restrict hue according to configuration
	restrictedHue := config.RestrictHue(hue)
	
	return []Color{
		// Dark gray (grayscale with lightness 0)
		NewColorCorrectedHSL(restrictedHue, config.GrayscaleSaturation, config.GrayscaleLightness.GetLightness(0)),
		
		// Mid color (normal color with lightness 0.5)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(0.5)),
		
		// Light gray (grayscale with lightness 1)
		NewColorCorrectedHSL(restrictedHue, config.GrayscaleSaturation, config.GrayscaleLightness.GetLightness(1)),
		
		// Light color (normal color with lightness 1)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(1)),
		
		// Dark color (normal color with lightness 0)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(0)),
	}
}