// Package i18n provides internationalization for the CLI.
package i18n

import (
	"encoding/json"
	"fmt"
	"io/fs"
	"path"
	"strings"
	"sync"
)

// FSLoader loads translations from a filesystem (embedded or disk).
type FSLoader struct {
	fsys fs.FS
	dir  string

	// Cache of available languages (populated on first Languages() call)
	languages []string
	langOnce  sync.Once
	langErr   error // Error from directory scan, if any
}

// NewFSLoader creates a loader for the given filesystem and directory.
func NewFSLoader(fsys fs.FS, dir string) *FSLoader {
	return &FSLoader{
		fsys: fsys,
		dir:  dir,
	}
}

// Load implements Loader.Load - loads messages and grammar for a language.
func (l *FSLoader) Load(lang string) (map[string]Message, *GrammarData, error) {
	// Try both hyphen and underscore variants
	variants := []string{
		lang + ".json",
		strings.ReplaceAll(lang, "-", "_") + ".json",
		strings.ReplaceAll(lang, "_", "-") + ".json",
	}

	var data []byte
	var err error
	for _, filename := range variants {
		filePath := path.Join(l.dir, filename) // Use path.Join for fs.FS (forward slashes)
		data, err = fs.ReadFile(l.fsys, filePath)
		if err == nil {
			break
		}
	}
	if err != nil {
		return nil, nil, fmt.Errorf("locale %q not found: %w", lang, err)
	}

	var raw map[string]any
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, nil, fmt.Errorf("invalid JSON in locale %q: %w", lang, err)
	}

	messages := make(map[string]Message)
	grammar := &GrammarData{
		Verbs: make(map[string]VerbForms),
		Nouns: make(map[string]NounForms),
		Words: make(map[string]string),
	}

	flattenWithGrammar("", raw, messages, grammar)

	return messages, grammar, nil
}

// Languages implements Loader.Languages - returns available language codes.
// Thread-safe: uses sync.Once to ensure the directory is scanned only once.
// Returns nil if the directory scan failed (check LanguagesErr for details).
func (l *FSLoader) Languages() []string {
	l.langOnce.Do(func() {
		entries, err := fs.ReadDir(l.fsys, l.dir)
		if err != nil {
			l.langErr = fmt.Errorf("failed to read locale directory %q: %w", l.dir, err)
			return
		}

		for _, entry := range entries {
			if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
				continue
			}
			lang := strings.TrimSuffix(entry.Name(), ".json")
			// Normalise underscore to hyphen (en_GB -> en-GB)
			lang = strings.ReplaceAll(lang, "_", "-")
			l.languages = append(l.languages, lang)
		}
	})

	return l.languages
}

// LanguagesErr returns any error that occurred during Languages() scan.
// Returns nil if the scan succeeded.
func (l *FSLoader) LanguagesErr() error {
	l.Languages() // Ensure scan has been attempted
	return l.langErr
}

// Ensure FSLoader implements Loader at compile time.
var _ Loader = (*FSLoader)(nil)

// --- Flatten helpers ---

// flatten recursively flattens nested maps into dot-notation keys.
func flatten(prefix string, data map[string]any, out map[string]Message) {
	flattenWithGrammar(prefix, data, out, nil)
}

// flattenWithGrammar recursively flattens nested maps and extracts grammar data.
func flattenWithGrammar(prefix string, data map[string]any, out map[string]Message, grammar *GrammarData) {
	for key, value := range data {
		fullKey := key
		if prefix != "" {
			fullKey = prefix + "." + key
		}

		switch v := value.(type) {
		case string:
			// Check if this is a word in gram.word.*
			if grammar != nil && strings.HasPrefix(fullKey, "gram.word.") {
				wordKey := strings.TrimPrefix(fullKey, "gram.word.")
				grammar.Words[strings.ToLower(wordKey)] = v
				continue
			}
			out[fullKey] = Message{Text: v}

		case map[string]any:
			// Check if this is a verb form object
			// Grammar data lives under "gram.*" (a nod to Gram - grandmother)
			if grammar != nil && isVerbFormObject(v) {
				verbName := key
				if strings.HasPrefix(fullKey, "gram.verb.") {
					verbName = strings.TrimPrefix(fullKey, "gram.verb.")
				}
				forms := VerbForms{}
				if past, ok := v["past"].(string); ok {
					forms.Past = past
				}
				if gerund, ok := v["gerund"].(string); ok {
					forms.Gerund = gerund
				}
				grammar.Verbs[strings.ToLower(verbName)] = forms
				continue
			}

			// Check if this is a noun form object (under gram.noun.* path, or has gender field)
			if grammar != nil && (strings.HasPrefix(fullKey, "gram.noun.") || isNounFormObject(v)) {
				nounName := key
				if strings.HasPrefix(fullKey, "gram.noun.") {
					nounName = strings.TrimPrefix(fullKey, "gram.noun.")
				}
				// Only process if it has one/other structure (noun pluralization)
				_, hasOne := v["one"]
				_, hasOther := v["other"]
				if hasOne && hasOther {
					forms := NounForms{}
					if one, ok := v["one"].(string); ok {
						forms.One = one
					}
					if other, ok := v["other"].(string); ok {
						forms.Other = other
					}
					if gender, ok := v["gender"].(string); ok {
						forms.Gender = gender
					}
					grammar.Nouns[strings.ToLower(nounName)] = forms
					continue
				}
			}

			// Check if this is an article object
			if grammar != nil && fullKey == "gram.article" {
				if indef, ok := v["indefinite"].(map[string]any); ok {
					if def, ok := indef["default"].(string); ok {
						grammar.Articles.IndefiniteDefault = def
					}
					if vowel, ok := indef["vowel"].(string); ok {
						grammar.Articles.IndefiniteVowel = vowel
					}
				}
				if def, ok := v["definite"].(string); ok {
					grammar.Articles.Definite = def
				}
				continue
			}

			// Check if this is a punctuation rules object
			if grammar != nil && fullKey == "gram.punct" {
				if label, ok := v["label"].(string); ok {
					grammar.Punct.LabelSuffix = label
				}
				if progress, ok := v["progress"].(string); ok {
					grammar.Punct.ProgressSuffix = progress
				}
				continue
			}

			// Check if this is a plural object (has CLDR plural category keys)
			if isPluralObject(v) {
				msg := Message{}
				if zero, ok := v["zero"].(string); ok {
					msg.Zero = zero
				}
				if one, ok := v["one"].(string); ok {
					msg.One = one
				}
				if two, ok := v["two"].(string); ok {
					msg.Two = two
				}
				if few, ok := v["few"].(string); ok {
					msg.Few = few
				}
				if many, ok := v["many"].(string); ok {
					msg.Many = many
				}
				if other, ok := v["other"].(string); ok {
					msg.Other = other
				}
				out[fullKey] = msg
			} else {
				// Recurse into nested object
				flattenWithGrammar(fullKey, v, out, grammar)
			}
		}
	}
}

// --- Check helpers ---

// isVerbFormObject checks if a map represents verb conjugation forms.
func isVerbFormObject(m map[string]any) bool {
	_, hasBase := m["base"]
	_, hasPast := m["past"]
	_, hasGerund := m["gerund"]
	return (hasBase || hasPast || hasGerund) && !isPluralObject(m)
}

// isNounFormObject checks if a map represents noun forms (with gender).
// Noun form objects have "gender" field, distinguishing them from CLDR plural objects.
func isNounFormObject(m map[string]any) bool {
	_, hasGender := m["gender"]
	return hasGender
}

// hasPluralCategories checks if a map has CLDR plural categories beyond one/other.
func hasPluralCategories(m map[string]any) bool {
	_, hasZero := m["zero"]
	_, hasTwo := m["two"]
	_, hasFew := m["few"]
	_, hasMany := m["many"]
	return hasZero || hasTwo || hasFew || hasMany
}

// isPluralObject checks if a map represents plural forms.
// Recognizes all CLDR plural categories: zero, one, two, few, many, other.
func isPluralObject(m map[string]any) bool {
	_, hasZero := m["zero"]
	_, hasOne := m["one"]
	_, hasTwo := m["two"]
	_, hasFew := m["few"]
	_, hasMany := m["many"]
	_, hasOther := m["other"]

	// It's a plural object if it has any plural category key
	if !hasZero && !hasOne && !hasTwo && !hasFew && !hasMany && !hasOther {
		return false
	}
	// But not if it contains nested objects (those are namespace containers)
	for _, v := range m {
		if _, isMap := v.(map[string]any); isMap {
			return false
		}
	}
	return true
}
