// Package i18n provides internationalization and localization services.
//
// It is designed to be a simple, straightforward i18n solution that should be
// well-suited to most applications.
//
// # Getting Started
//
// To use the i18n service, you first need to create a new instance:
//
//	i18nService, err := i18n.New()
//	if err != nil {
//		log.Fatal(err)
//	}
//
// Once you have a service instance, you can set the language and translate
// messages.
//
// # Locales
//
// The i18n service loads locales from the `locales` directory. Locales are JSON
// files with the language code as the filename (e.g., `en.json`, `es.json`).
// The service uses the `embed` package to bundle the locales into the binary,
// so you don't need to worry about distributing the locale files with your
// application.
package i18n

import (
	"embed"
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/nicksnyder/go-i18n/v2/i18n"
	"golang.org/x/text/language"
)

//go:embed locales/*.json
var localeFS embed.FS

// Options holds configuration for the i18n service.
// This is a placeholder for future configuration options.
type Options struct{}

// Service provides internationalization and localization.
// It is the primary entrypoint for the i18n package.
type Service struct {
	bundle         *i18n.Bundle
	localizer      *i18n.Localizer
	availableLangs []language.Tag
}

// newI18nService contains the common logic for initializing a Service struct.
func newI18nService() (*Service, error) {
	bundle := i18n.NewBundle(language.English)
	bundle.RegisterUnmarshalFunc("json", json.Unmarshal)

	availableLangs, err := getAvailableLanguages()
	if err != nil {
		return nil, err
	}

	for _, lang := range availableLangs {
		filePath := fmt.Sprintf("locales/%s.json", lang.String())
		if _, err := bundle.LoadMessageFileFS(localeFS, filePath); err != nil {
			return nil, fmt.Errorf("failed to load message file %s: %w", filePath, err)
		}
	}

	s := &Service{
		bundle:         bundle,
		availableLangs: availableLangs,
	}
	// Language will be set during ServiceStartup after config is available.
	return s, nil
}

// New creates a new i18n service.
// The service is initialized with the English language as the default.
func New() (*Service, error) {
	s, err := newI18nService()
	if err != nil {
		return nil, err
	}
	err = s.SetLanguage("en")
	if err != nil {
		return nil, err
	}
	return s, nil
}

// --- Language Management ---

func getAvailableLanguages() ([]language.Tag, error) {
	files, err := localeFS.ReadDir("locales")
	if err != nil {
		return nil, fmt.Errorf("failed to read embedded locales directory: %w", err)
	}

	var availableLangs []language.Tag
	for _, file := range files {
		lang := strings.TrimSuffix(file.Name(), ".json")
		tag := language.Make(lang)
		availableLangs = append(availableLangs, tag)
	}
	return availableLangs, nil
}

func detectLanguage(supported []language.Tag) (string, error) {
	langEnv := os.Getenv("LANG")
	if langEnv == "" {
		return "", nil
	}

	baseLang := strings.Split(langEnv, ".")[0]
	parsedLang, err := language.Parse(baseLang)
	if err != nil {
		return "", fmt.Errorf("failed to parse language tag '%s': %w", baseLang, err)
	}

	if len(supported) == 0 {
		return "", nil
	}

	matcher := language.NewMatcher(supported)
	_, index, confidence := matcher.Match(parsedLang)

	if confidence >= language.Low {
		return supported[index].String(), nil
	}
	return "", nil
}

// --- Public Service Methods ---

// SetLanguage sets the language for the i18n service.
// The language tag should be a valid BCP 47 language tag (e.g., "en", "en-US").
// If the language is not supported, an error is returned.
func (s *Service) SetLanguage(lang string) error {
	requestedLang, err := language.Parse(lang)
	if err != nil {
		return fmt.Errorf("i18n: failed to parse language tag \"%s\": %w", lang, err)
	}

	if len(s.availableLangs) == 0 {
		return fmt.Errorf("i18n: no available languages loaded in the bundle")
	}

	matcher := language.NewMatcher(s.availableLangs)
	bestMatch, _, confidence := matcher.Match(requestedLang)

	if confidence == language.No {
		return fmt.Errorf("i18n: unsupported language: %s", lang)
	}

	s.localizer = i18n.NewLocalizer(s.bundle, bestMatch.String())
	return nil
}

// Translate translates a message by its ID.
// It accepts an optional template data argument to interpolate into the translation.
// If the message is not found, the message ID is returned.
func (s *Service) Translate(messageID string, args ...interface{}) string {
	config := &i18n.LocalizeConfig{MessageID: messageID}
	if len(args) > 0 {
		config.TemplateData = args[0]
		if len(args) > 1 {
			fmt.Fprintf(os.Stderr, "i18n: Translate called with %d arguments, expected at most 1 (template data)\n", len(args))
		}
	}

	translation, err := s.localizer.Localize(config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "i18n: translation for key \"%s\" not found\n", messageID)
		return messageID
	}
	return translation
}

// SetBundle is a test helper to inject a bundle.
func (s *Service) SetBundle(bundle *i18n.Bundle) {
	s.bundle = bundle
}

// AvailableLanguages returns a list of available language codes.
func (s *Service) AvailableLanguages() []string {
	langs := make([]string, len(s.availableLangs))
	for i, tag := range s.availableLangs {
		langs[i] = tag.String()
	}
	return langs
}

// GetAllMessages returns all translation messages for the specified language.
// The keys are message IDs and values are the translated strings.
// If lang is empty, it uses the current language.
func (s *Service) GetAllMessages(lang string) (map[string]string, error) {
	messages := make(map[string]string)

	// Default to English if no language specified
	if lang == "" {
		lang = "en"
	}

	// Try to find the locale file for the specified language
	filePath := fmt.Sprintf("locales/%s.json", lang)
	data, err := localeFS.ReadFile(filePath)
	if err != nil {
		// Try without region code (e.g., "en-US" -> "en")
		if strings.Contains(lang, "-") {
			baseLang := strings.Split(lang, "-")[0]
			filePath = fmt.Sprintf("locales/%s.json", baseLang)
			data, err = localeFS.ReadFile(filePath)
		}
		if err != nil {
			return nil, fmt.Errorf("failed to read locale file for language %s: %w", lang, err)
		}
	}

	var rawMessages map[string]interface{}
	if err := json.Unmarshal(data, &rawMessages); err != nil {
		return nil, fmt.Errorf("failed to parse locale file: %w", err)
	}

	// Extract messages - handle both simple strings and complex message objects
	for key, value := range rawMessages {
		switch v := value.(type) {
		case string:
			messages[key] = v
		case map[string]interface{}:
			if other, ok := v["other"].(string); ok {
				messages[key] = other
			}
		}
	}

	return messages, nil
}
