package jdenticon

import (
	"fmt"
	"regexp"
)

// Config holds configuration options for identicon generation.
type Config struct {
	// HueRestrictions specifies the hues (in degrees) that are allowed for the identicon.
	// If empty, any hue can be used. Values should be between 0 and 360.
	HueRestrictions []float64
	
	// ColorLightnessRange specifies the lightness range for colored shapes [min, max].
	// Values should be between 0.0 and 1.0. Default: [0.4, 0.8]
	ColorLightnessRange [2]float64
	
	// GrayscaleLightnessRange specifies the lightness range for grayscale shapes [min, max].
	// Values should be between 0.0 and 1.0. Default: [0.3, 0.9]
	GrayscaleLightnessRange [2]float64
	
	// ColorSaturation controls the saturation of colored shapes.
	// Values should be between 0.0 and 1.0. Default: 0.5
	ColorSaturation float64
	
	// GrayscaleSaturation controls the saturation of grayscale shapes.
	// Values should be between 0.0 and 1.0. Default: 0.0
	GrayscaleSaturation float64
	
	// BackgroundColor sets the background color for the identicon.
	// Accepts hex colors like "#fff", "#ffffff", "#ffffff80" (with alpha).
	// If empty, the background will be transparent.
	BackgroundColor string
	
	// Padding controls the padding around the identicon as a percentage of the size.
	// Values should be between 0.0 and 0.5. Default: 0.08
	Padding float64
}

// DefaultConfig returns a default configuration that matches jdenticon-js defaults.
func DefaultConfig() Config {
	return Config{
		HueRestrictions:         nil, // No restrictions
		ColorLightnessRange:     [2]float64{0.4, 0.8},
		GrayscaleLightnessRange: [2]float64{0.3, 0.9},
		ColorSaturation:         0.5,
		GrayscaleSaturation:     0.0,
		BackgroundColor:         "", // Transparent
		Padding:                 0.08,
	}
}

// ConfigOption represents a configuration option function.
type ConfigOption func(*Config) error

// WithHueRestrictions sets the allowed hues in degrees (0-360).
func WithHueRestrictions(hues []float64) ConfigOption {
	return func(c *Config) error {
		for _, hue := range hues {
			if hue < 0 || hue >= 360 {
				return fmt.Errorf("hue must be between 0 and 360, got %f", hue)
			}
		}
		c.HueRestrictions = make([]float64, len(hues))
		copy(c.HueRestrictions, hues)
		return nil
	}
}

// WithColorLightnessRange sets the lightness range for colored shapes.
func WithColorLightnessRange(min, max float64) ConfigOption {
	return func(c *Config) error {
		if min < 0 || min > 1 || max < 0 || max > 1 {
			return fmt.Errorf("lightness values must be between 0 and 1, got min=%f max=%f", min, max)
		}
		if min > max {
			return fmt.Errorf("minimum lightness cannot be greater than maximum, got min=%f max=%f", min, max)
		}
		c.ColorLightnessRange = [2]float64{min, max}
		return nil
	}
}

// WithGrayscaleLightnessRange sets the lightness range for grayscale shapes.
func WithGrayscaleLightnessRange(min, max float64) ConfigOption {
	return func(c *Config) error {
		if min < 0 || min > 1 || max < 0 || max > 1 {
			return fmt.Errorf("lightness values must be between 0 and 1, got min=%f max=%f", min, max)
		}
		if min > max {
			return fmt.Errorf("minimum lightness cannot be greater than maximum, got min=%f max=%f", min, max)
		}
		c.GrayscaleLightnessRange = [2]float64{min, max}
		return nil
	}
}

// WithColorSaturation sets the saturation for colored shapes.
func WithColorSaturation(saturation float64) ConfigOption {
	return func(c *Config) error {
		if saturation < 0 || saturation > 1 {
			return fmt.Errorf("saturation must be between 0 and 1, got %f", saturation)
		}
		c.ColorSaturation = saturation
		return nil
	}
}

// WithGrayscaleSaturation sets the saturation for grayscale shapes.
func WithGrayscaleSaturation(saturation float64) ConfigOption {
	return func(c *Config) error {
		if saturation < 0 || saturation > 1 {
			return fmt.Errorf("saturation must be between 0 and 1, got %f", saturation)
		}
		c.GrayscaleSaturation = saturation
		return nil
	}
}

// WithBackgroundColor sets the background color.
func WithBackgroundColor(color string) ConfigOption {
	return func(c *Config) error {
		if color != "" {
			if err := validateHexColor(color); err != nil {
				return fmt.Errorf("invalid background color: %w", err)
			}
		}
		c.BackgroundColor = color
		return nil
	}
}

// WithPadding sets the padding around the identicon.
func WithPadding(padding float64) ConfigOption {
	return func(c *Config) error {
		if padding < 0 || padding > 0.5 {
			return fmt.Errorf("padding must be between 0 and 0.5, got %f", padding)
		}
		c.Padding = padding
		return nil
	}
}

// Configure creates a new configuration with the given options.
func Configure(options ...ConfigOption) (Config, error) {
	config := DefaultConfig()
	
	for _, option := range options {
		if err := option(&config); err != nil {
			return Config{}, err
		}
	}
	
	return config, nil
}

// validateHexColor validates that a color string is a valid hex color.
func validateHexColor(color string) error {
	hexColorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3,8}$`)
	if !hexColorPattern.MatchString(color) {
		return fmt.Errorf("color must be a hex color like #fff, #ffffff, or #ffffff80")
	}
	
	// Validate length - must be 3, 4, 6, or 8 characters after #
	length := len(color) - 1
	if length != 3 && length != 4 && length != 6 && length != 8 {
		return fmt.Errorf("hex color must be 3, 4, 6, or 8 characters after #")
	}
	
	return nil
}

// ParseColor normalizes a hex color string to the full #RRGGBB or #RRGGBBAA format.
func ParseColor(color string) (string, error) {
	if color == "" {
		return "", nil
	}
	
	if err := validateHexColor(color); err != nil {
		return "", err
	}
	
	// Normalize short forms to full forms
	switch len(color) {
	case 4: // #RGB -> #RRGGBB
		r, g, b := color[1], color[2], color[3]
		return fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b), nil
	case 5: // #RGBA -> #RRGGBBAA
		r, g, b, a := color[1], color[2], color[3], color[4]
		return fmt.Sprintf("#%c%c%c%c%c%c%c%c", r, r, g, g, b, b, a, a), nil
	case 7, 9: // #RRGGBB or #RRGGBBAA - already normalized
		return color, nil
	default:
		return "", fmt.Errorf("unsupported color format")
	}
}

// GetHue returns the hue for the given original hue value, taking into account hue restrictions.
func (c *Config) GetHue(originalHue float64) float64 {
	if len(c.HueRestrictions) == 0 {
		return originalHue
	}
	
	// Map originalHue [0,1] to one of the restricted hues
	index := int(originalHue * 0.999 * float64(len(c.HueRestrictions)))
	if index >= len(c.HueRestrictions) {
		index = len(c.HueRestrictions) - 1
	}
	
	hue := c.HueRestrictions[index]
	// Convert degrees to [0,1] range and normalize
	return ((hue/360.0)+1.0) - float64(int((hue/360.0)+1.0))
}

// GetColorLightness returns a lightness value within the color lightness range.
func (c *Config) GetColorLightness(value float64) float64 {
	return c.getLightness(value, c.ColorLightnessRange)
}

// GetGrayscaleLightness returns a lightness value within the grayscale lightness range.
func (c *Config) GetGrayscaleLightness(value float64) float64 {
	return c.getLightness(value, c.GrayscaleLightnessRange)
}

// getLightness maps a value [0,1] to the specified lightness range.
func (c *Config) getLightness(value float64, lightnessRange [2]float64) float64 {
	result := lightnessRange[0] + value*(lightnessRange[1]-lightnessRange[0])
	if result < 0 {
		return 0
	}
	if result > 1 {
		return 1
	}
	return result
}