package i18n

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

	"golang.org/x/text/language"
	"golang.org/x/text/message"
)

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

var printer *message.Printer
var activeTag language.Tag
var initOnce sync.Once

// rawByTag holds the untemplated catalog strings per language. T consults it
// for ARGLESS lookups: x/text's printer always formats the message, so asking
// it for a string that contains verbs (%s, %d, %w error templates, a literal
// "90%") without arguments stamps %!x(MISSING) into user-facing text and
// corrupts fmt.Errorf wrapping templates. Argless reads must be verbatim.
var rawByTag = map[language.Tag]map[string]string{}

// rawTag is the registered tag whose raw catalog matches the active printer.
var rawTag = defaultLang

// defaultLang é o idioma padrão caso a detecção falhe.
var defaultLang = language.English

// Init inicializa o sistema de internacionalização (thread-safe, idempotent).
// Ele detecta o idioma com a seguinte prioridade:
// 1. Variável de ambiente CHATCLI_LANG (pode ser definida no .env)
// 2. Variáveis de ambiente do sistema (LC_ALL, LANG)
// 3. Fallback para o idioma padrão (Inglês)
func Init() {
	initOnce.Do(initI18n)
}

func initI18n() {
	// 1. Detectar o idioma do usuário com prioridade para CHATCLI_LANG
	langStr := os.Getenv("CHATCLI_LANG") // Maior prioridade
	if langStr == "" {
		langStr = os.Getenv("LC_ALL")
		if langStr == "" {
			langStr = os.Getenv("LANG")
		}
	}

	// Normaliza strings como "pt_BR.UTF-8" para "pt-BR"
	if idx := strings.Index(langStr, "."); idx != -1 {
		langStr = langStr[:idx]
	}
	langStr = strings.Replace(langStr, "_", "-", 1)

	userLang, err := language.Parse(langStr)
	if err != nil {
		userLang = defaultLang
	}

	// 2. Carregar e registrar todas as traduções dos arquivos JSON embutidos.
	files, err := localesFS.ReadDir("locales")
	if err != nil {
		printer = message.NewPrinter(defaultLang)
		return
	}

	registeredTags := []language.Tag{defaultLang}

	for _, file := range files {
		fileName := file.Name()
		if !strings.HasSuffix(fileName, ".json") {
			continue
		}

		tagStr := strings.TrimSuffix(fileName, ".json")
		tag, err := language.Parse(tagStr)
		if err != nil {
			continue
		}

		registeredTags = append(registeredTags, tag)

		content, err := localesFS.ReadFile("locales/" + fileName)
		if err != nil {
			continue
		}

		var translations map[string]string
		if err := json.Unmarshal(content, &translations); err != nil {
			continue
		}

		rawByTag[tag] = translations
		for key, value := range translations {
			if err := message.SetString(tag, key, value); err != nil {
				fmt.Printf("aviso i18n: falha ao definir a string para a chave '%s': %v\n", key, err)
			}
		}
	}

	// 3. Criar um matcher para encontrar o melhor idioma disponível.
	matcher := language.NewMatcher(registeredTags)
	bestTag, idx, _ := matcher.Match(userLang)

	// 4. Configurar o printer global para o melhor idioma encontrado. O raw
	// catalog é indexado pela tag REGISTRADA (o matcher pode devolver uma tag
	// com extensões que não é chave do mapa).
	activeTag = bestTag
	if idx >= 0 && idx < len(registeredTags) {
		rawTag = registeredTags[idx]
	}
	printer = message.NewPrinter(bestTag)
}

// ActiveTag returns the resolved language tag (e.g. "pt-BR", "en-US", "en").
func ActiveTag() language.Tag {
	Init() // ensure initialized
	return activeTag
}

// T é a função principal para obter uma string traduzida.
// Com args, usa o printer global para formatar a mensagem. SEM args, devolve
// a string crua do catálogo (com %% desescapado): o Sprintf do x/text
// formataria mesmo assim e estamparia %!s(MISSING) em qualquer valor com
// verbos — incluindo os templates de erro com %w, que precisam chegar
// intactos ao fmt.Errorf do chamador.
// Se o sistema i18n não for inicializado, retorna a chave como fallback.
func T(key string, args ...interface{}) string {
	if printer == nil {
		// Fallback caso Init() não tenha sido chamado ou falhou.
		// Retorna a chave para que o desenvolvedor veja que algo está errado.
		if len(args) > 0 {
			return key + " " + fmt.Sprint(args...)
		}
		return key
	}
	if len(args) == 0 {
		if v, ok := rawLookup(key); ok {
			return strings.ReplaceAll(v, "%%", "%")
		}
	}
	return printer.Sprintf(key, args...)
}

// rawLookup resolves key in the active language's raw catalog, falling back
// to the default language (mirroring the matcher's fallback chain).
func rawLookup(key string) (string, bool) {
	if m, ok := rawByTag[rawTag]; ok {
		if v, ok := m[key]; ok {
			return v, true
		}
	}
	if m, ok := rawByTag[defaultLang]; ok {
		if v, ok := m[key]; ok {
			return v, true
		}
	}
	return "", false
}
