#pragma once

#include <array>
#include <cstddef>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>

namespace i18n {

  // Transparent hash/eq so Catalog::find() can take string_view without
  // allocating a temporary std::string.
  struct StringHash {
    using is_transparent = void;
    std::size_t operator()(std::string_view s) const noexcept { return std::hash<std::string_view>{}(s); }
    std::size_t operator()(const std::string& s) const noexcept { return std::hash<std::string_view>{}(s); }
    std::size_t operator()(const char* s) const noexcept { return std::hash<std::string_view>{}(s); }
  };

  struct StringEq {
    using is_transparent = void;
    bool operator()(std::string_view a, std::string_view b) const noexcept { return a == b; }
  };

  using Catalog = std::unordered_map<std::string, std::string, StringHash, StringEq>;

  struct LanguageOption {
    std::string_view code;
    std::string_view displayName;
  };

  inline constexpr std::array<LanguageOption, 25> kSupportedLanguages = {{
      {"ar", "العربية"},
      {"be", "Беларуская"},
      {"be-Latn", "Biełaruskaja (Łacinka)"},
      {"ca", "Català"},
      {"cs", "Čeština"},
      {"de", "Deutsch"},
      {"en", "English"},
      {"es", "Español"},
      {"fr", "Français"},
      {"gl-ES", "Galego"},
      {"he", "עברית"},
      {"hu", "Magyar"},
      {"it", "Italiano"},
      {"ko", "한국어"},
      {"ku", "Kurdî"},
      {"nl", "Nederlands"},
      {"nn", "Norsk nynorsk"},
      {"pl", "Polski"},
      {"pt-BR", "Português (Brasil)"},
      {"ru", "Русский"},
      {"sv", "Svenska"},
      {"tr", "Türkçe"},
      {"uk-UA", "Українська"},
      {"vi", "Tiếng Việt"},
      {"zh-Hans", "简体中文"},
  }};

  // Loads translation catalogs and resolves dotted keys against them.
  // English is always loaded as a fallback alongside the active language.
  class Service {
  public:
    static Service& instance();

    // Loads the active catalog. Pass an empty view to auto-detect from the
    // standard locale environment ($LC_ALL, $LC_MESSAGES, $LANG). Always
    // loads English as a fallback first; if the detected language is also
    // English, both slots end up pointing at the same catalog contents.
    void init(std::string_view preferredLang = {});

    void setLanguage(std::string_view lang);
    [[nodiscard]] std::string_view language() const noexcept { return m_language; }
    [[nodiscard]] std::string_view requestedLanguage() const noexcept { return m_requestedLanguage; }
    [[nodiscard]] bool rtl() const noexcept { return m_rtl; }

    // Returns a view into the active or fallback catalog, or {} if the key
    // exists in neither.
    [[nodiscard]] std::string_view lookup(std::string_view dottedKey) const;

  private:
    bool loadCatalog(std::string_view lang, Catalog& out) const;

    Catalog m_active;
    Catalog m_fallback;
    std::string m_language;
    std::string m_requestedLanguage;
    bool m_rtl = false;
  };

} // namespace i18n
