package jdenticon

import (
	"fmt"
	"reflect"
	"strconv"

	"github.com/kevin/go-jdenticon/internal/engine"
	"github.com/kevin/go-jdenticon/internal/renderer"
)

// iconRenderer defines the common interface for rendering identicons to different formats
type iconRenderer interface {
	SetBackground(fillColor string, opacity float64)
	BeginShape(color string)
	AddPolygon(points []engine.Point)
	AddCircle(topLeft engine.Point, size float64, invert bool)
	EndShape()
}

// Icon represents a generated identicon that can be rendered in various formats.
type Icon struct {
	icon *engine.Icon
}

// renderTo renders the icon to the given renderer, handling all common rendering logic
func (i *Icon) renderTo(r iconRenderer) {
	if i.icon == nil {
		return
	}
	
	// Set background color if configured
	if i.icon.Config.BackColor != nil {
		r.SetBackground(i.icon.Config.BackColor.String(), 1.0)
	}
	
	// Render each shape group
	for _, group := range i.icon.Shapes {
		r.BeginShape(group.Color.String())
		
		for _, shape := range group.Shapes {
			// Skip empty shapes
			if shape.Type == "empty" {
				continue
			}
			
			switch shape.Type {
			case "polygon":
				// Transform points
				transformedPoints := make([]engine.Point, len(shape.Points))
				for j, point := range shape.Points {
					transformedPoints[j] = shape.Transform.TransformIconPoint(point.X, point.Y, 0, 0)
				}
				r.AddPolygon(transformedPoints)
				
			case "circle":
				// Use dedicated circle fields - CircleX, CircleY represent top-left corner
				topLeft := shape.Transform.TransformIconPoint(shape.CircleX, shape.CircleY, 0, 0)
				r.AddCircle(topLeft, shape.CircleSize, shape.Invert)
			}
		}
		
		r.EndShape()
	}
}

// Generate creates an identicon for the given input value and size.
// The input value is typically an email address, username, or any string
// that should produce a consistent visual representation.
func Generate(value string, size int) (*Icon, error) {
	// Compute hash from the input value
	hash := ComputeHash(value)
	
	// Create generator with default configuration
	generator := engine.NewDefaultGenerator()
	
	// Generate the icon
	engineIcon, err := generator.Generate(hash, float64(size))
	if err != nil {
		return nil, err
	}
	
	return &Icon{icon: engineIcon}, nil
}

// ToSVG renders the icon as an SVG string.
func (i *Icon) ToSVG() (string, error) {
	if i.icon == nil {
		return "", nil
	}
	
	svgRenderer := renderer.NewSVGRenderer(int(i.icon.Size))
	i.renderTo(svgRenderer)
	return svgRenderer.ToSVG(), nil
}

// ToPNG renders the icon as PNG image data.
func (i *Icon) ToPNG() ([]byte, error) {
	if i.icon == nil {
		return nil, nil
	}
	
	pngRenderer := renderer.NewPNGRenderer(int(i.icon.Size))
	i.renderTo(pngRenderer)
	return pngRenderer.ToPNG(), nil
}

// ToSVG generates an identicon as an SVG string for the given input value.
// The value can be any type - it will be converted to a string and hashed.
// Size specifies the icon size in pixels.
// Optional config parameters can be provided to customize the appearance.
func ToSVG(value interface{}, size int, config ...Config) (string, error) {
	if size <= 0 {
		return "", fmt.Errorf("size must be positive, got %d", size)
	}

	// Generate icon with the provided configuration
	icon, err := generateWithConfig(value, size, config...)
	if err != nil {
		return "", fmt.Errorf("failed to generate icon: %w", err)
	}

	// Render as SVG
	svg, err := icon.ToSVG()
	if err != nil {
		return "", fmt.Errorf("failed to render SVG: %w", err)
	}

	return svg, nil
}

// ToPNG generates an identicon as PNG image data for the given input value.
// The value can be any type - it will be converted to a string and hashed.
// Size specifies the icon size in pixels.
// Optional config parameters can be provided to customize the appearance.
func ToPNG(value interface{}, size int, config ...Config) ([]byte, error) {
	if size <= 0 {
		return nil, fmt.Errorf("size must be positive, got %d", size)
	}

	// Generate icon with the provided configuration
	icon, err := generateWithConfig(value, size, config...)
	if err != nil {
		return nil, fmt.Errorf("failed to generate icon: %w", err)
	}

	// Render as PNG
	png, err := icon.ToPNG()
	if err != nil {
		return nil, fmt.Errorf("failed to render PNG: %w", err)
	}

	return png, nil
}

// ToHash generates a hash string for the given input value.
// This is a convenience function that wraps ComputeHash with better type handling.
// The hash can be used with other functions or stored for consistent icon generation.
func ToHash(value interface{}) string {
	return ComputeHash(value)
}

// generateWithConfig is a helper function that creates an icon with optional configuration.
func generateWithConfig(value interface{}, size int, configs ...Config) (*Icon, error) {
	// Convert value to string representation
	stringValue := convertToString(value)
	
	// Compute hash from the input value
	hash := ComputeHash(stringValue)
	
	// Create generator with configuration
	var generator *engine.Generator
	if len(configs) > 0 {
		// Use the provided configuration
		engineConfig := convertToEngineConfig(configs[0])
		generator = engine.NewGenerator(engineConfig)
	} else {
		// Use default configuration
		generator = engine.NewDefaultGenerator()
	}
	
	// Generate the icon
	engineIcon, err := generator.Generate(hash, float64(size))
	if err != nil {
		return nil, err
	}
	
	return &Icon{icon: engineIcon}, nil
}

// convertToString converts any value to its string representation using reflection.
// This handles various types similar to how JavaScript would convert them.
func convertToString(value interface{}) string {
	if value == nil {
		return ""
	}

	// Use reflection to handle different types
	v := reflect.ValueOf(value)
	
	switch v.Kind() {
	case reflect.String:
		return v.String()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return fmt.Sprintf("%d", v.Int())
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return fmt.Sprintf("%d", v.Uint())
	case reflect.Float32, reflect.Float64:
		return fmt.Sprintf("%g", v.Float())
	case reflect.Bool:
		if v.Bool() {
			return "true"
		}
		return "false"
	case reflect.Slice:
		// Handle byte slices specially
		if v.Type().Elem().Kind() == reflect.Uint8 {
			return string(v.Bytes())
		}
		fallthrough
	default:
		// For all other types, use fmt.Sprintf
		return fmt.Sprintf("%v", value)
	}
}

// convertToEngineConfig converts a public Config to an internal engine.ColorConfig.
func convertToEngineConfig(config Config) engine.ColorConfig {
	engineConfig := engine.DefaultColorConfig()
	
	// Convert hue restrictions
	if len(config.HueRestrictions) > 0 {
		engineConfig.Hues = make([]float64, len(config.HueRestrictions))
		copy(engineConfig.Hues, config.HueRestrictions)
	}
	
	// Convert lightness ranges
	engineConfig.ColorLightness = engine.LightnessRange{
		Min: config.ColorLightnessRange[0], 
		Max: config.ColorLightnessRange[1],
	}
	engineConfig.GrayscaleLightness = engine.LightnessRange{
		Min: config.GrayscaleLightnessRange[0], 
		Max: config.GrayscaleLightnessRange[1],
	}
	
	// Convert saturation values
	engineConfig.ColorSaturation = config.ColorSaturation
	engineConfig.GrayscaleSaturation = config.GrayscaleSaturation
	
	// Convert background color
	if config.BackgroundColor != "" {
		normalizedColor, err := ParseColor(config.BackgroundColor)
		if err == nil {
			// Parse the normalized hex color into an engine.Color
			color, parseErr := parseHexToEngineColor(normalizedColor)
			if parseErr == nil {
				engineConfig.BackColor = &color
			}
		}
	}
	
	// Convert padding
	engineConfig.IconPadding = config.Padding
	
	return engineConfig
}

// parseHexToEngineColor converts a hex color string to an engine.Color.
func parseHexToEngineColor(hexColor string) (engine.Color, error) {
	if hexColor == "" {
		return engine.Color{}, fmt.Errorf("empty color string")
	}
	
	// Remove # if present
	if len(hexColor) > 0 && hexColor[0] == '#' {
		hexColor = hexColor[1:]
	}
	
	var r, g, b, a uint8 = 0, 0, 0, 255
	
	switch len(hexColor) {
	case 6: // RRGGBB
		rgb, err := parseHexComponent(hexColor[0:2])
		if err != nil {
			return engine.Color{}, err
		}
		r = rgb
		
		rgb, err = parseHexComponent(hexColor[2:4])
		if err != nil {
			return engine.Color{}, err
		}
		g = rgb
		
		rgb, err = parseHexComponent(hexColor[4:6])
		if err != nil {
			return engine.Color{}, err
		}
		b = rgb
		
	case 8: // RRGGBBAA
		rgb, err := parseHexComponent(hexColor[0:2])
		if err != nil {
			return engine.Color{}, err
		}
		r = rgb
		
		rgb, err = parseHexComponent(hexColor[2:4])
		if err != nil {
			return engine.Color{}, err
		}
		g = rgb
		
		rgb, err = parseHexComponent(hexColor[4:6])
		if err != nil {
			return engine.Color{}, err
		}
		b = rgb
		
		rgb, err = parseHexComponent(hexColor[6:8])
		if err != nil {
			return engine.Color{}, err
		}
		a = rgb
		
	default:
		return engine.Color{}, fmt.Errorf("invalid hex color length: %d", len(hexColor))
	}
	
	return engine.NewColorRGBA(r, g, b, a), nil
}

// parseHexComponent parses a 2-character hex string to a uint8 value.
func parseHexComponent(hex string) (uint8, error) {
	if len(hex) != 2 {
		return 0, fmt.Errorf("hex component must be 2 characters, got %d", len(hex))
	}
	value, err := strconv.ParseUint(hex, 16, 8)
	if err != nil {
		return 0, fmt.Errorf("invalid hex component '%s': %w", hex, err)
	}
	return uint8(value), nil
}