package engine

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

// Default configuration constants matching JavaScript implementation
const (
	// Default saturation values
	defaultColorSaturation     = 0.5 // Default saturation for colored shapes
	defaultGrayscaleSaturation = 0.0 // Default saturation for grayscale shapes

	// Default lightness range boundaries
	defaultColorLightnessMin     = 0.4 // Default minimum lightness for colors
	defaultColorLightnessMax     = 0.8 // Default maximum lightness for colors
	defaultGrayscaleLightnessMin = 0.3 // Default minimum lightness for grayscale
	defaultGrayscaleLightnessMax = 0.9 // Default maximum lightness for grayscale

	// Default padding
	defaultIconPadding = 0.08 // Default padding as percentage of icon size

	// Hue calculation constants
	hueIndexNormalizationFactor = 0.999 // Factor to normalize hue to [0,1) range for indexing
	degreesToTurns              = 360.0 // Conversion factor from degrees to turns
)

// ColorConfig represents the configuration for color generation
type ColorConfig struct {
	// Saturation settings
	ColorSaturation     float64 // Saturation for normal colors [0, 1]
	GrayscaleSaturation float64 // Saturation for grayscale colors [0, 1]

	// Lightness ranges
	ColorLightness     LightnessRange // Lightness range for normal colors
	GrayscaleLightness LightnessRange // Lightness range for grayscale colors

	// Hue restrictions
	Hues []float64 // Allowed hues in degrees [0, 360] or range [0, 1]. Empty means no restriction

	// Background color
	BackColor *Color // Background color (nil for transparent)

	// Icon padding
	IconPadding float64 // Padding as percentage of icon size [0, 1]
}

// LightnessRange represents a range of lightness values
type LightnessRange struct {
	Min float64 // Minimum lightness [0, 1]
	Max float64 // Maximum lightness [0, 1]
}

// GetLightness returns a lightness value for the given position in range [0, 1]
// where 0 returns Min and 1 returns Max
func (lr LightnessRange) GetLightness(value float64) float64 {
	// Clamp value to [0, 1] range
	value = clamp(value, 0, 1)

	// Linear interpolation between min and max
	result := lr.Min + value*(lr.Max-lr.Min)

	// Clamp result to valid lightness range
	return clamp(result, 0, 1)
}

// DefaultColorConfig returns the default configuration matching the JavaScript implementation
func DefaultColorConfig() ColorConfig {
	return ColorConfig{
		ColorSaturation:     defaultColorSaturation,
		GrayscaleSaturation: defaultGrayscaleSaturation,
		ColorLightness:      LightnessRange{Min: defaultColorLightnessMin, Max: defaultColorLightnessMax},
		GrayscaleLightness:  LightnessRange{Min: defaultGrayscaleLightnessMin, Max: defaultGrayscaleLightnessMax},
		Hues:                nil, // No hue restriction
		BackColor:           nil, // Transparent background
		IconPadding:         defaultIconPadding,
	}
}

// RestrictHue applies hue restrictions to the given hue value
// Returns the restricted hue in range [0, 1]
func (c ColorConfig) RestrictHue(originalHue float64) float64 {
	// Normalize hue to [0, 1) range
	hue := math.Mod(originalHue, 1.0)
	if hue < 0 {
		hue += 1.0
	}

	// If no hue restrictions, return original
	if len(c.Hues) == 0 {
		return hue
	}

	// Find the closest allowed hue
	// originalHue is in range [0, 1], multiply by 0.999 to get range [0, 1)
	// then truncate to get index
	index := int((hueIndexNormalizationFactor * hue * float64(len(c.Hues))))
	if index >= len(c.Hues) {
		index = len(c.Hues) - 1
	}

	restrictedHue := c.Hues[index]

	// Convert from degrees to turns in range [0, 1)
	// Handle any turn - e.g. 746° is valid
	result := math.Mod(restrictedHue/degreesToTurns, 1.0)
	if result < 0 {
		result += 1.0
	}

	return result
}

// Validate validates a ColorConfig to ensure all values are within valid ranges
// Returns an error if any validation issues are found without correcting the values
func (c *ColorConfig) Validate() error {
	var validationErrors []string

	// Validate saturation values
	if c.ColorSaturation < 0 || c.ColorSaturation > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color saturation out of range: value %f not in [0, 1]", c.ColorSaturation))
	}

	if c.GrayscaleSaturation < 0 || c.GrayscaleSaturation > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale saturation out of range: value %f not in [0, 1]", c.GrayscaleSaturation))
	}

	// Validate lightness ranges
	if c.ColorLightness.Min < 0 || c.ColorLightness.Min > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness minimum out of range: value %f not in [0, 1]", c.ColorLightness.Min))
	}
	if c.ColorLightness.Max < 0 || c.ColorLightness.Max > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness maximum out of range: value %f not in [0, 1]", c.ColorLightness.Max))
	}
	if c.ColorLightness.Min > c.ColorLightness.Max {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness range invalid: minimum %f greater than maximum %f", c.ColorLightness.Min, c.ColorLightness.Max))
	}

	if c.GrayscaleLightness.Min < 0 || c.GrayscaleLightness.Min > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness minimum out of range: value %f not in [0, 1]", c.GrayscaleLightness.Min))
	}
	if c.GrayscaleLightness.Max < 0 || c.GrayscaleLightness.Max > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness maximum out of range: value %f not in [0, 1]", c.GrayscaleLightness.Max))
	}
	if c.GrayscaleLightness.Min > c.GrayscaleLightness.Max {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness range invalid: minimum %f greater than maximum %f", c.GrayscaleLightness.Min, c.GrayscaleLightness.Max))
	}

	// Validate icon padding
	if c.IconPadding < 0 || c.IconPadding > 1 {
		validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: icon padding out of range: value %f not in [0, 1]", c.IconPadding))
	}

	if len(validationErrors) > 0 {
		return fmt.Errorf("jdenticon: engine: validation failed: configuration invalid: %s", strings.Join(validationErrors, "; "))
	}

	return nil
}

// Normalize validates and corrects a ColorConfig to ensure all values are within valid ranges
// This method provides backward compatibility by applying corrections for invalid values
func (c *ColorConfig) Normalize() {
	// Clamp saturation values
	c.ColorSaturation = clamp(c.ColorSaturation, 0, 1)
	c.GrayscaleSaturation = clamp(c.GrayscaleSaturation, 0, 1)

	// Validate and fix lightness ranges
	c.ColorLightness.Min = clamp(c.ColorLightness.Min, 0, 1)
	c.ColorLightness.Max = clamp(c.ColorLightness.Max, 0, 1)
	if c.ColorLightness.Min > c.ColorLightness.Max {
		c.ColorLightness.Min, c.ColorLightness.Max = c.ColorLightness.Max, c.ColorLightness.Min
	}

	c.GrayscaleLightness.Min = clamp(c.GrayscaleLightness.Min, 0, 1)
	c.GrayscaleLightness.Max = clamp(c.GrayscaleLightness.Max, 0, 1)
	if c.GrayscaleLightness.Min > c.GrayscaleLightness.Max {
		c.GrayscaleLightness.Min, c.GrayscaleLightness.Max = c.GrayscaleLightness.Max, c.GrayscaleLightness.Min
	}

	// Clamp icon padding
	c.IconPadding = clamp(c.IconPadding, 0, 1)
}
