// Package resolution provides a profile system for managing screen regions across different resolutions.
//
// Each game plugin can register resolution profiles that define named screen regions
// for different resolutions. This allows the engine to work with games at various
// resolutions without hardcoding pixel coordinates.
package resolution

import (
	"fmt"
	"image"
	"sort"
)

// Profile defines screen regions for a specific resolution.
type Profile struct {
	Width   int                            `yaml:"width"`   // Screen width in pixels
	Height  int                            `yaml:"height"`  // Screen height in pixels
	Label   string                         `yaml:"label"`   // Human-readable label (e.g., "1080p", "720p")
	Regions map[string]image.Rectangle     `yaml:"regions"` // Named screen regions
}

// Registry holds profiles per game per resolution.
type Registry struct {
	// profiles[gameID][resolution_key] = Profile
	profiles map[string]map[string]*Profile
}

// NewRegistry creates a new resolution profile registry.
func NewRegistry() *Registry {
	return &Registry{
		profiles: make(map[string]map[string]*Profile),
	}
}

// Register adds a resolution profile for a specific game.
func (r *Registry) Register(gameID string, profile *Profile) error {
	if profile == nil {
		return fmt.Errorf("profile cannot be nil")
	}

	if profile.Width <= 0 || profile.Height <= 0 {
		return fmt.Errorf("invalid resolution: %dx%d", profile.Width, profile.Height)
	}

	if r.profiles[gameID] == nil {
		r.profiles[gameID] = make(map[string]*Profile)
	}

	key := resolutionKey(profile.Width, profile.Height)
	r.profiles[gameID][key] = profile

	return nil
}

// Get returns the profile for a game and resolution, or error if unsupported.
func (r *Registry) Get(gameID string, width, height int) (*Profile, error) {
	gameProfiles, exists := r.profiles[gameID]
	if !exists {
		return nil, fmt.Errorf("no profiles registered for game %q", gameID)
	}

	key := resolutionKey(width, height)
	profile, exists := gameProfiles[key]
	if !exists {
		return nil, fmt.Errorf("no profile for game %q at resolution %dx%d", gameID, width, height)
	}

	return profile, nil
}

// GetRegion returns a named region for a game and resolution.
func (r *Registry) GetRegion(gameID string, width, height int, regionName string) (image.Rectangle, error) {
	profile, err := r.Get(gameID, width, height)
	if err != nil {
		return image.Rectangle{}, err
	}

	region, exists := profile.Regions[regionName]
	if !exists {
		return image.Rectangle{}, fmt.Errorf("region %q not found in profile for %s at %dx%d", regionName, gameID, width, height)
	}

	return region, nil
}

// SupportedResolutions returns resolutions available for a game.
func (r *Registry) SupportedResolutions(gameID string) []image.Point {
	gameProfiles, exists := r.profiles[gameID]
	if !exists {
		return nil
	}

	var resolutions []image.Point
	for _, profile := range gameProfiles {
		resolutions = append(resolutions, image.Point{X: profile.Width, Y: profile.Height})
	}

	// Sort by resolution (width first, then height)
	sort.Slice(resolutions, func(i, j int) bool {
		if resolutions[i].X != resolutions[j].X {
			return resolutions[i].X < resolutions[j].X
		}
		return resolutions[i].Y < resolutions[j].Y
	})

	return resolutions
}

// ListGames returns all game IDs that have registered profiles.
func (r *Registry) ListGames() []string {
	var games []string
	for gameID := range r.profiles {
		games = append(games, gameID)
	}
	sort.Strings(games)
	return games
}

// GetProfiles returns all profiles for a specific game.
func (r *Registry) GetProfiles(gameID string) []*Profile {
	gameProfiles, exists := r.profiles[gameID]
	if !exists {
		return nil
	}

	var profiles []*Profile
	for _, profile := range gameProfiles {
		profiles = append(profiles, profile)
	}

	// Sort by resolution
	sort.Slice(profiles, func(i, j int) bool {
		if profiles[i].Width != profiles[j].Width {
			return profiles[i].Width < profiles[j].Width
		}
		return profiles[i].Height < profiles[j].Height
	})

	return profiles
}

// ValidateProfile checks if a profile is valid and complete.
func (r *Registry) ValidateProfile(profile *Profile) error {
	if profile == nil {
		return fmt.Errorf("profile is nil")
	}

	if profile.Width <= 0 || profile.Height <= 0 {
		return fmt.Errorf("invalid resolution: %dx%d", profile.Width, profile.Height)
	}

	if profile.Label == "" {
		return fmt.Errorf("profile label is required")
	}

	if profile.Regions == nil {
		return fmt.Errorf("profile regions map is nil")
	}

	// Validate that all regions are within screen bounds
	screenBounds := image.Rect(0, 0, profile.Width, profile.Height)
	for regionName, region := range profile.Regions {
		if !region.In(screenBounds) {
			return fmt.Errorf("region %q is outside screen bounds: %v not in %v", regionName, region, screenBounds)
		}

		if region.Empty() {
			return fmt.Errorf("region %q is empty: %v", regionName, region)
		}
	}

	return nil
}

// RegisterMultiple registers multiple profiles for a game.
func (r *Registry) RegisterMultiple(gameID string, profiles []*Profile) error {
	for _, profile := range profiles {
		if err := r.ValidateProfile(profile); err != nil {
			return fmt.Errorf("invalid profile %s: %w", profile.Label, err)
		}

		if err := r.Register(gameID, profile); err != nil {
			return fmt.Errorf("failed to register profile %s: %w", profile.Label, err)
		}
	}

	return nil
}

// Clone creates a deep copy of a profile.
func (p *Profile) Clone() *Profile {
	clone := &Profile{
		Width:   p.Width,
		Height:  p.Height,
		Label:   p.Label,
		Regions: make(map[string]image.Rectangle),
	}

	for name, region := range p.Regions {
		clone.Regions[name] = region
	}

	return clone
}

// HasRegion checks if a profile contains a named region.
func (p *Profile) HasRegion(name string) bool {
	_, exists := p.Regions[name]
	return exists
}

// ListRegions returns all region names in the profile.
func (p *Profile) ListRegions() []string {
	var names []string
	for name := range p.Regions {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

// AspectRatio returns the aspect ratio of the profile resolution.
func (p *Profile) AspectRatio() float64 {
	return float64(p.Width) / float64(p.Height)
}

// IsWidescreen returns true if the profile is widescreen (16:9 or wider).
func (p *Profile) IsWidescreen() bool {
	return p.AspectRatio() >= 16.0/9.0
}

// ScaleFrom creates a new profile by scaling regions from another resolution.
func (p *Profile) ScaleFrom(source *Profile) *Profile {
	if source == nil {
		return nil
	}

	scaleX := float64(p.Width) / float64(source.Width)
	scaleY := float64(p.Height) / float64(source.Height)

	scaled := &Profile{
		Width:   p.Width,
		Height:  p.Height,
		Label:   p.Label,
		Regions: make(map[string]image.Rectangle),
	}

	for name, region := range source.Regions {
		scaled.Regions[name] = image.Rect(
			int(float64(region.Min.X)*scaleX),
			int(float64(region.Min.Y)*scaleY),
			int(float64(region.Max.X)*scaleX),
			int(float64(region.Max.Y)*scaleY),
		)
	}

	return scaled
}

// resolutionKey generates a unique key for a resolution.
func resolutionKey(width, height int) string {
	return fmt.Sprintf("%dx%d", width, height)
}

// ParseResolution parses a resolution string like "1920x1080".
func ParseResolution(s string) (width, height int, err error) {
	n, err := fmt.Sscanf(s, "%dx%d", &width, &height)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid resolution format %q: %w", s, err)
	}
	if n != 2 {
		return 0, 0, fmt.Errorf("invalid resolution format %q: expected WIDTHxHEIGHT", s)
	}
	if width <= 0 || height <= 0 {
		return 0, 0, fmt.Errorf("invalid resolution: %dx%d", width, height)
	}
	return width, height, nil
}

// FormatResolution formats width and height as a resolution string.
func FormatResolution(width, height int) string {
	return fmt.Sprintf("%dx%d", width, height)
}