using System;
using System.Collections.Generic;
using HarmonyLib;

namespace UXAssist.Common;

/// <summary>
/// Provides bilingual localization helpers that integrate with the game's <see cref="Localization"/> system.
/// </summary>
public static class I18N
{
    private static bool _initialized;
    private static bool _dirty;

    /// <summary>
    /// Invoked whenever the active language is applied or changed and all registered strings have been applied.
    /// </summary>
    public static Action OnInitialized;

    /// <summary>
    /// Registers the localization hooks with Harmony.
    /// </summary>
    public static void Init()
    {
        Harmony.CreateAndPatchAll(typeof(I18N));
    }

    /// <summary>
    /// Returns whether the localization system has finished its initial load.
    /// </summary>
    /// <returns><c>true</c> if localization is initialized; otherwise <c>false</c>.</returns>
    public static bool Initialized() => _initialized;

    /// <summary>
    /// Holds the English and Simplified Chinese localized values for a single entry.
    /// </summary>
    private sealed class LocalizedString
    {
        public string En { get; }
        public string Zh { get; }

        public LocalizedString(string en, string zh)
        {
            En = en;
            Zh = zh;
        }
    }

    private static readonly Dictionary<string, LocalizedString> Entries = new();

    /// <summary>
    /// Registers a localized string pair.
    /// </summary>
    /// <param name="key">The lookup key.</param>
    /// <param name="enus">The English text.</param>
    /// <param name="zhcn">The Simplified Chinese text.</param>
    public static void Add(string key, string enus, string zhcn = null)
    {
        // Skip identity fallback entries: if no Chinese translation is provided and the key equals the English text,
        // there is no need to register a separate entry because the key already serves as the fallback text.
        if (zhcn is null && key == enus) return;
        Entries[key] = new LocalizedString(enus, string.IsNullOrEmpty(zhcn) ? enus : zhcn);
        _dirty = true;
    }

    private static void ApplyIndexers()
    {
        var indexer = Localization.namesIndexer;
        var index = indexer.Count;
        foreach (var key in Entries.Keys)
        {
            if (indexer.TryGetValue(key, out _)) continue;
            indexer[key] = index;
            index++;
        }
        _dirty = false;
        var strings = Localization.strings;
        if (strings == null) return;
        var len = strings.Length;
        for (var i = 0; i < len; i++)
        {
            ApplyLanguage(i);
            if (i != Localization.currentLanguageIndex) continue;
            Localization.currentStrings = Localization.strings[i];
            Localization.currentFloats = Localization.floats[i];
        }
    }

    private static void ApplyLanguage(int index)
    {
        var indexerLength = Localization.namesIndexer.Count;
        var strs = Localization.strings[index];
        if (strs == null) return;
        if (strs.Length < indexerLength)
        {
            var newStrs = new string[indexerLength];
            Array.Copy(strs, newStrs, strs.Length);
            strs = newStrs;
            Localization.strings[index] = strs;
        }
        var floats = Localization.floats[index];
        if (floats != null)
        {
            if (floats.Length < indexerLength)
            {
                var newFloats = new float[indexerLength];
                Array.Copy(floats, newFloats, floats.Length);
                floats = newFloats;
                Localization.floats[index] = floats;
            }
        }

        var isChinese = Localization.Languages[index].lcId == 2052;
        foreach (var entry in Entries)
        {
            if (!Localization.namesIndexer.TryGetValue(entry.Key, out var idx)) continue;
            strs[idx] = isChinese ? entry.Value.Zh : entry.Value.En;
        }
    }

    /// <summary>
    /// Applies any pending localization changes to the game's localization tables.
    /// </summary>
    public static void Apply()
    {
        if (!_initialized) return;
        if (Entries.Count == 0) return;
        if (!_dirty) return;
        ApplyIndexers();
    }

    /// <summary>
    /// Returns the localized text for the given key in the currently active language,
    /// falling back to the key itself when no entry is registered.
    /// </summary>
    /// <param name="key">The lookup key.</param>
    /// <returns>The localized text, or <paramref name="key"/> if no entry exists.</returns>
    internal static string Translate(string key)
    {
        if (!Entries.TryGetValue(key, out var loc)) return key;
        var languageIndex = Localization.currentLanguageIndex;
        if (languageIndex >= 0 && languageIndex < Localization.Languages.Length && Localization.Languages[languageIndex].lcId == 2052)
            return loc.Zh;
        return loc.En;
    }

    [HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.LoadSettings))]
    private static void Localization_LoadSettings_Postfix()
    {
        if (_initialized) return;
        _initialized = true;
        Apply();
    }

    [HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.LoadLanguage))]
    private static void Localization_LoadLanguage_Postfix(int index)
    {
        if (!_initialized) return;
        ApplyLanguage(index);
    }

    [HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.NotifyLanguageChange))]
    private static void Localization_NotifyLanguageChange_Postfix()
    {
        if (!_initialized) return;
        OnInitialized?.Invoke();
    }
}
