package engine

import (
	"fmt"
	"math"
	"strings"
)

// Color-related constants
const (
	// Alpha channel constants
	defaultAlphaValue = 255 // Default alpha value for opaque colors

	// RGB/HSL conversion constants
	rgbComponentMax = 255.0 // Maximum RGB component value
	rgbMaxValue     = 255   // Maximum RGB value as integer
	hueCycle        = 6.0   // Hue cycle length for HSL conversion
	hslMidpoint     = 0.5   // HSL lightness midpoint

	// Grayscale detection threshold
	grayscaleToleranceThreshold = 0.01 // Threshold for detecting grayscale colors

	// Hue calculation constants
	hueSegmentCount = 6   // Number of hue segments for correction
	hueRounding     = 0.5 // Rounding offset for hue indexing

	// Color theme lightness values (matches JavaScript implementation)
	colorThemeDarkLightness = 0.0 // Dark color lightness value
	colorThemeMidLightness  = 0.5 // Mid color lightness value
	colorThemeFullLightness = 1.0 // Full lightness value

	// Hex color string buffer sizes
	hexColorLength      = 7 // #rrggbb = 7 characters
	hexColorAlphaLength = 9 // #rrggbbaa = 9 characters
)

// Lightness correctors for each hue segment (based on JavaScript implementation)
// These values are carefully tuned to match the JavaScript reference implementation
var correctors = []float64{
	0.55, // Red hues (0°-60°)
	0.5,  // Yellow hues (60°-120°)
	0.5,  // Green hues (120°-180°)
	0.46, // Cyan hues (180°-240°)
	0.6,  // Blue hues (240°-300°)
	0.55, // Magenta hues (300°-360°)
	0.55, // Wrap-around for edge cases
}

// Color represents a color with HSL representation and on-demand RGB conversion
type Color struct {
	H, S, L   float64 // HSL values: H=[0,1], S=[0,1], L=[0,1]
	A         uint8   // Alpha channel: [0,255]
	corrected bool    // Whether to use corrected HSL to RGB conversion
}

// ToRGB returns the RGB values computed from HSL using appropriate conversion
func (c Color) ToRGB() (r, g, b uint8, err error) {
	if c.corrected {
		return CorrectedHSLToRGB(c.H, c.S, c.L)
	}
	return HSLToRGB(c.H, c.S, c.L)
}

// ToRGBA returns the RGBA values computed from HSL using appropriate conversion
func (c Color) ToRGBA() (r, g, b, a uint8, err error) {
	r, g, b, err = c.ToRGB()
	return r, g, b, c.A, err
}

// NewColorHSL creates a new Color from HSL values
func NewColorHSL(h, s, l float64) Color {
	return Color{
		H: h, S: s, L: l,
		A:         defaultAlphaValue,
		corrected: false,
	}
}

// NewColorCorrectedHSL creates a new Color from HSL values with lightness correction
func NewColorCorrectedHSL(h, s, l float64) Color {
	return Color{
		H: h, S: s, L: l,
		A:         defaultAlphaValue,
		corrected: true,
	}
}

// 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,
		A:         defaultAlphaValue,
		corrected: false,
	}
}

// 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,
		A:         a,
		corrected: false,
	}
}

// String returns the hex representation of the color
func (c Color) String() string {
	r, g, b, err := c.ToRGB()
	if err != nil {
		// Return a fallback color (black) if conversion fails
		// This maintains the string contract while indicating an error state
		r, g, b = 0, 0, 0
	}

	if c.A == defaultAlphaValue {
		return RGBToHex(r, g, b)
	}

	// Use strings.Builder for RGBA format
	var buf strings.Builder
	buf.Grow(hexColorAlphaLength)

	buf.WriteByte('#')
	writeHexByte(&buf, r)
	writeHexByte(&buf, g)
	writeHexByte(&buf, b)
	writeHexByte(&buf, c.A)

	return buf.String()
}

// Equals compares two colors for equality
func (c Color) Equals(other Color) bool {
	r1, g1, b1, err1 := c.ToRGB()
	r2, g2, b2, err2 := other.ToRGB()

	// If either color has a conversion error, they are not equal
	if err1 != nil || err2 != nil {
		return false
	}

	return r1 == r2 && g1 == g2 && b1 == b2 && 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,
		A:         alpha,
		corrected: c.corrected,
	}
}

// IsGrayscale returns true if the color is grayscale (saturation near zero)
func (c Color) IsGrayscale() bool {
	return c.S < grayscaleToleranceThreshold
}

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

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

// 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) / rgbComponentMax
	gf := float64(g) / rgbComponentMax
	bf := float64(b) / rgbComponentMax

	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 > hslMidpoint {
			s = delta / (2.0 - max - min)
		} else {
			s = delta / (max + min)
		}

		// Calculate hue
		switch max {
		case rf:
			h = (gf - bf) / delta
			if gf < bf {
				h += 6
			}
		case gf:
			h = (bf-rf)/delta + 2
		case bf:
			h = (rf-gf)/delta + 4
		}
		h /= hueCycle
	}

	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] and an error if conversion fails
func HSLToRGB(h, s, l float64) (r, g, b uint8, err error) {
	// 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*rgbComponentMax, 0, rgbComponentMax))
		return gray, gray, gray, nil
	}

	// Calculate intermediate values for HSL to RGB conversion
	var m2 float64
	if l <= hslMidpoint {
		m2 = l * (s + 1)
	} else {
		m2 = l + s - l*s
	}
	m1 := l*2 - m2

	// Convert each RGB component
	rf := hueToRGB(m1, m2, h*hueCycle+2) * rgbComponentMax
	gf := hueToRGB(m1, m2, h*hueCycle) * rgbComponentMax
	bf := hueToRGB(m1, m2, h*hueCycle-2) * rgbComponentMax

	// Validate floating point results before conversion to uint8
	if math.IsNaN(rf) || math.IsInf(rf, 0) ||
		math.IsNaN(gf) || math.IsInf(gf, 0) ||
		math.IsNaN(bf) || math.IsInf(bf, 0) {
		return 0, 0, 0, fmt.Errorf("jdenticon: engine: HSL to RGB conversion failed: non-finite value produced during conversion")
	}

	r = uint8(clamp(rf, 0, rgbComponentMax))
	g = uint8(clamp(gf, 0, rgbComponentMax))
	b = uint8(clamp(bf, 0, rgbComponentMax))

	return r, g, b, nil
}

// 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, err error) {
	// Defensive check: ensure correctors table is properly initialized
	if len(correctors) == 0 {
		return 0, 0, 0, fmt.Errorf("jdenticon: engine: corrected HSL to RGB conversion failed: color correctors table is empty or not initialized")
	}

	// Get the corrector for the current hue
	hueIndex := int((h*hueSegmentCount + hueRounding)) % len(correctors)
	corrector := correctors[hueIndex]

	// Adjust lightness relative to the corrector
	if l < hslMidpoint {
		l = l * corrector * 2
	} else {
		l = corrector + (l-hslMidpoint)*(1-corrector)*2
	}

	// Clamp the corrected lightness
	l = clamp(l, 0, 1)

	// Call HSLToRGB and propagate any error
	r, g, b, err = HSLToRGB(h, s, l)
	if err != nil {
		return 0, 0, 0, fmt.Errorf("jdenticon: engine: corrected HSL to RGB conversion failed: %w", err)
	}

	return r, g, b, nil
}

// 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 += hueCycle
	} else if h > hueCycle {
		h -= hueCycle
	}

	// 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
}

// writeHexByte writes a single byte as two hex characters to the builder
func writeHexByte(buf *strings.Builder, b uint8) {
	const hexChars = "0123456789abcdef"
	buf.WriteByte(hexChars[b>>4])
	buf.WriteByte(hexChars[b&0x0f])
}

// RGBToHex converts RGB values to a hexadecimal color string
func RGBToHex(r, g, b uint8) string {
	// Use a strings.Builder for more efficient hex formatting
	var buf strings.Builder
	buf.Grow(hexColorLength)

	buf.WriteByte('#')
	writeHexByte(&buf, r)
	writeHexByte(&buf, g)
	writeHexByte(&buf, b)

	return buf.String()
}

// 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 := colorThemeDarkLightness

	// 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(colorThemeDarkLightness)),

		// Mid color (normal color with lightness 0.5)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(colorThemeMidLightness)),

		// Light gray (grayscale with lightness 1)
		NewColorCorrectedHSL(restrictedHue, config.GrayscaleSaturation, config.GrayscaleLightness.GetLightness(colorThemeFullLightness)),

		// Light color (normal color with lightness 1)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(colorThemeFullLightness)),

		// Dark color (normal color with lightness 0)
		NewColorCorrectedHSL(restrictedHue, config.ColorSaturation, config.ColorLightness.GetLightness(colorThemeDarkLightness)),
	}
}
