package persona

import (
	"embed"
	"log"
	"sync"

	"gopkg.in/yaml.v3"
)

//go:embed data/*.yaml
var dataFS embed.FS

// DataStore holds loaded pool data.
type DataStore struct {
	BodyDescriptions      map[BodyBuildCategory][]string
	VoiceDescriptions     map[string][]string
	EthnicityDistribution map[EthnicityCode]EthnicityFeatureDistribution
	MorphologyFeatures    MorphologyFeaturePool
}

// EthnicityFeatureDistribution holds feature probabilities for an ethnicity.
type EthnicityFeatureDistribution struct {
	EyeColors    map[EyeColorCategory]float64    `yaml:"eye_colors"`
	HairColors   map[HairColorCategory]float64   `yaml:"hair_colors"`
	HairTextures map[HairTextureCategory]float64 `yaml:"hair_textures"`
	SkinTones    []SkinToneCategory              `yaml:"skin_tones"`
}

// MorphologyFeaturePool holds valid morphology feature values.
type MorphologyFeaturePool struct {
	EarTypes  []string `yaml:"ear_types"`
	TailTypes []string `yaml:"tail_types"`
	FangTypes []string `yaml:"fang_types"`
	WingTypes []string `yaml:"wing_types"`
	HornTypes []string `yaml:"horn_types"`
	ClawTypes []string `yaml:"claw_types"`
}

var (
	dataStore     *DataStore
	dataStoreOnce sync.Once
	dataStoreErr  error
)

// GetDataStore returns the singleton data store.
// Data is loaded lazily on first access.
func GetDataStore() (*DataStore, error) {
	dataStoreOnce.Do(func() {
		dataStore, dataStoreErr = loadDataStore()
	})
	return dataStore, dataStoreErr
}

// MustGetDataStore returns the data store or panics on error.
func MustGetDataStore() *DataStore {
	ds, err := GetDataStore()
	if err != nil {
		log.Fatalf("failed to load persona data: %v", err)
	}
	return ds
}

func loadDataStore() (*DataStore, error) {
	ds := &DataStore{
		BodyDescriptions:      make(map[BodyBuildCategory][]string),
		VoiceDescriptions:     make(map[string][]string),
		EthnicityDistribution: make(map[EthnicityCode]EthnicityFeatureDistribution),
	}

	// Load body descriptions
	if err := loadYAML("data/body_descriptions.yaml", &ds.BodyDescriptions); err != nil {
		// Use defaults if file doesn't exist
		ds.BodyDescriptions = defaultBodyDescriptions()
	}

	// Load voice descriptions
	if err := loadYAML("data/voice_descriptions.yaml", &ds.VoiceDescriptions); err != nil {
		ds.VoiceDescriptions = defaultVoiceDescriptions()
	}

	// Load ethnicity distributions
	if err := loadYAML("data/ethnicity_distributions.yaml", &ds.EthnicityDistribution); err != nil {
		ds.EthnicityDistribution = defaultEthnicityDistributions()
	}

	// Load morphology features
	if err := loadYAML("data/morphology_features.yaml", &ds.MorphologyFeatures); err != nil {
		ds.MorphologyFeatures = defaultMorphologyFeatures()
	}

	return ds, nil
}

func loadYAML[T any](path string, target *T) error {
	data, err := dataFS.ReadFile(path)
	if err != nil {
		return err
	}
	return yaml.Unmarshal(data, target)
}

// defaultBodyDescriptions provides fallback body descriptions.
func defaultBodyDescriptions() map[BodyBuildCategory][]string {
	return map[BodyBuildCategory][]string{
		BodyBuildSlender: {
			"slim and graceful figure",
			"lean, elegant frame",
			"willowy silhouette",
		},
		BodyBuildAthletic: {
			"toned, athletic physique",
			"fit and active build",
			"athletic frame with visible muscle tone",
		},
		BodyBuildCurvy: {
			"curvy, feminine figure",
			"shapely silhouette with soft curves",
			"hourglass proportions",
		},
		BodyBuildMuscular: {
			"muscular, powerful build",
			"strong, well-defined physique",
			"athletic frame with prominent muscles",
		},
		BodyBuildAverage: {
			"average build",
			"balanced proportions",
			"typical physique",
		},
		BodyBuildPlusCurvy: {
			"full-figured and curvy",
			"generous curves",
			"voluptuous silhouette",
		},
		BodyBuildPetite: {
			"petite and delicate",
			"small-framed",
			"compact, graceful build",
		},
	}
}

// defaultVoiceDescriptions provides fallback voice descriptions.
func defaultVoiceDescriptions() map[string][]string {
	return map[string][]string{
		"warm": {
			"warm and inviting",
			"comforting timbre",
			"friendly warmth",
		},
		"cool": {
			"cool and measured",
			"calm, collected tone",
			"composed delivery",
		},
		"bright": {
			"bright and energetic",
			"lively, upbeat quality",
			"animated expression",
		},
	}
}

// defaultEthnicityDistributions provides fallback distributions.
func defaultEthnicityDistributions() map[EthnicityCode]EthnicityFeatureDistribution {
	return map[EthnicityCode]EthnicityFeatureDistribution{
		EthnicityEastAsian: {
			EyeColors: map[EyeColorCategory]float64{
				EyeColorDarkBrown: 0.85,
				EyeColorBrown:     0.15,
			},
			HairColors: map[HairColorCategory]float64{
				HairColorBlack:     0.85,
				HairColorDarkBrown: 0.15,
			},
			HairTextures: map[HairTextureCategory]float64{
				HairTextureStraight: 0.85,
				HairTextureWavy:     0.15,
			},
			SkinTones: []SkinToneCategory{
				SkinToneFair, SkinToneLight, SkinToneMedium,
			},
		},
		EthnicityCaucasian: {
			EyeColors: map[EyeColorCategory]float64{
				EyeColorBlue:  0.30,
				EyeColorBrown: 0.25,
				EyeColorGreen: 0.15,
				EyeColorHazel: 0.15,
				EyeColorGray:  0.15,
			},
			HairColors: map[HairColorCategory]float64{
				HairColorBrown:      0.35,
				HairColorLightBrown: 0.20,
				HairColorBlonde:     0.20,
				HairColorDarkBrown:  0.15,
				HairColorRed:        0.10,
			},
			HairTextures: map[HairTextureCategory]float64{
				HairTextureStraight: 0.40,
				HairTextureWavy:     0.40,
				HairTextureCurly:    0.20,
			},
			SkinTones: []SkinToneCategory{
				SkinToneFair, SkinToneLight, SkinToneMedium,
			},
		},
	}
}

// defaultMorphologyFeatures provides fallback morphology pools.
func defaultMorphologyFeatures() MorphologyFeaturePool {
	return MorphologyFeaturePool{
		EarTypes:  ValidEarTypes,
		TailTypes: ValidTailTypes,
		FangTypes: ValidFangTypes,
		WingTypes: ValidWingTypes,
		HornTypes: ValidHornTypes,
		ClawTypes: ValidClawTypes,
	}
}

// GetBodyDescription returns a description for a body build type.
func (ds *DataStore) GetBodyDescription(build BodyBuildCategory, characterID string) string {
	pool, ok := ds.BodyDescriptions[build]
	if !ok || len(pool) == 0 {
		return string(build) + " build"
	}
	return SelectDescription(pool, characterID, "body_description")
}

// GetVoiceDescription returns a description for a voice tone.
func (ds *DataStore) GetVoiceDescription(tone string, characterID string) string {
	pool, ok := ds.VoiceDescriptions[tone]
	if !ok || len(pool) == 0 {
		return tone + " voice"
	}
	return SelectDescription(pool, characterID, "voice_description")
}
