package domain

import (
	"encoding/json"
	"time"
	"unicode/utf8"
)

// UserID is a strongly-typed identifier for users.
type UserID string

// String returns the string representation of the ID.
func (id UserID) String() string {
	return string(id)
}

// ValidThemes lists the allowed theme values.
var ValidThemes = []string{"light", "dark", "system"}

// NotificationPreferences controls notification delivery channels.
type NotificationPreferences struct {
	Email bool `json:"email"`
	Push  bool `json:"push"`
	SMS   bool `json:"sms"`
}

// Preferences holds a user's preference settings.
// Known fields are typed; unknown keys are preserved in Extra for extensibility.
type Preferences struct {
	Theme         string                  `json:"theme"`
	Language      string                  `json:"language"`
	Notifications NotificationPreferences `json:"notifications"`
	Extra         map[string]any          `json:"-"`
}

// MarshalJSON merges known fields and Extra into a single JSON object.
func (p Preferences) MarshalJSON() ([]byte, error) {
	// Build a map with known fields
	m := make(map[string]any)

	// Copy extra fields first (so known fields override if there's overlap)
	for k, v := range p.Extra {
		m[k] = v
	}

	m["theme"] = p.Theme
	m["language"] = p.Language
	m["notifications"] = p.Notifications

	return json.Marshal(m)
}

// UnmarshalJSON decodes known fields into struct fields and captures unknown keys in Extra.
func (p *Preferences) UnmarshalJSON(data []byte) error {
	// Decode known fields via an alias to avoid recursion
	type Alias Preferences
	var alias Alias
	if err := json.Unmarshal(data, &alias); err != nil {
		return err
	}

	// Decode all fields into a map to find unknown keys
	var raw map[string]json.RawMessage
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

	// Copy known fields
	p.Theme = alias.Theme
	p.Language = alias.Language
	p.Notifications = alias.Notifications

	// Capture unknown keys
	knownKeys := map[string]bool{
		"theme":         true,
		"language":      true,
		"notifications": true,
	}

	p.Extra = nil
	for k, v := range raw {
		if knownKeys[k] {
			continue
		}
		if p.Extra == nil {
			p.Extra = make(map[string]any)
		}
		var val any
		if err := json.Unmarshal(v, &val); err != nil {
			return err
		}
		p.Extra[k] = val
	}

	return nil
}

// MaxLanguageLen is the maximum rune length for a language tag.
const MaxLanguageLen = 10

// Validate checks the known preference fields.
// Returns ErrInvalidTheme if theme is not a valid value.
// Returns ErrInvalidLanguage if language exceeds MaxLanguageLen runes.
func (p *Preferences) Validate() error {
	if p.Theme != "" {
		valid := false
		for _, t := range ValidThemes {
			if p.Theme == t {
				valid = true
				break
			}
		}
		if !valid {
			return ErrInvalidTheme
		}
	}

	if utf8.RuneCountInString(p.Language) > MaxLanguageLen {
		return ErrInvalidLanguage
	}

	return nil
}

// DefaultPreferences returns the default preference values.
func DefaultPreferences() Preferences {
	return Preferences{
		Theme:    "system",
		Language: "en",
		Notifications: NotificationPreferences{
			Email: true,
			Push:  true,
			SMS:   false,
		},
	}
}

// UserPreferences is the aggregate associating preferences with a user.
type UserPreferences struct {
	UserID      UserID
	Preferences Preferences
	UpdatedAt   time.Time
}
