;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL

(ns app.util.i18n
  "A i18n foundation."
  (:require
   [app.common.data :as d]
   [app.common.i18n]
   [app.common.logging :as log]
   [app.common.time :as ct]
   [app.config :as cf]
   [app.util.globals :as globals]
   [app.util.modules :as mod]
   [app.util.storage :as storage]
   [cuerdas.core :as str]
   [goog.object :as gobj]
   [okulary.core :as l]
   [promesa.core :as p]
   [rumext.v2 :as mf]))

(log/set-level! :info)

(def supported-locales
  [{:label "English" :value "en"}
   {:label "Español" :value "es"}
   {:label "Català" :value "ca"}
   {:label "Deutsch (community)" :value "de"}
   {:label "Dutch (community)" :value "nl"}
   {:label "Euskera (community)" :value "eu"}
   {:label "Français (community)" :value "fr"}
   {:label "Français - Canada (community)" :value "fr_ca"}
   {:label "Gallego (Community)" :value "gl"}
   {:label "Hausa (Community)" :value "ha"}
   {:label "Hrvatski (Community)" :value "hr"}
   {:label "Italiano (community)" :value "it"}
   {:label "Norsk - Bokmål (community)" :value "nb_no"}
   {:label "Polski (community)" :value "pl"}
   {:label "Portuguese - Brazil (community)" :value "pt_br"}
   {:label "Portuguese - Portugal (community)" :value "pt_pt"}
   {:label "Bahasa Indonesia (community)" :value "id"}
   {:label "Rumanian (community)" :value "ro"}
   {:label "Türkçe (community)" :value "tr"}
   {:label "Ελληνική γλώσσα (community)" :value "el"}
   {:label "Русский (community)" :value "ru"}
   {:label "Украї́нська мо́ва (community)" :value "uk"}
   {:label "Český jazyk (community)" :value "cs"}
   {:label "Latviešu valoda (community)" :value "lv"}
   {:label "Српски (community)" :value "sr"}
   {:label "Føroyskt mál (community)" :value "fo"}
   {:label "Korean (community)" :value "ko"}
   {:label "עִבְרִית (community)" :value "he"}
   {:label "आधुनिक मानक हिन्दी (community)" :value "hi"}
   {:label "عربي/عربى (community)" :value "ar"}
   {:label "فارسی (community)" :value "fa"}
   {:label "日本語 (Community)" :value "ja_jp"}
   {:label "简体中文 (community)" :value "zh_cn"}
   {:label "繁體中文 (community)" :value "zh_hant"}])

(defn- parse-locale
  [locale]
  (let [locale (-> (str/lower locale)
                   (str/replace "-" "_"))]
    (cond-> [locale]
      (str/includes? locale "_")
      (conj (subs locale 0 2)))))

(def ^:private browser-locales
  (delay
    (-> (.-language globals/navigator)
        (parse-locale))))

;; Set initial translation loading state as globaly stored variable;
;; this facilitates hot reloading
(when-not (exists? (unchecked-get globals/global "penpotTranslations"))
  (unchecked-set globals/global "penpotTranslations" #js {}))

(defn- autodetect
  []
  (let [supported (into #{} (map :value supported-locales))]
    (loop [locales (seq @browser-locales)]
      (if-let [locale (first locales)]
        (if (contains? supported locale)
          locale
          (recur (rest locales)))
        cf/default-language))))

(defn get-current
  "Get the currently memoized locale or execute the autodetection"
  []
  (or (get storage/global ::locale) (autodetect)))

(def ^:dynamic *current-locale*
  (get-current))

(defonce locale
  (l/atom *current-locale*))

(defn- get-translations
  "Get globaly stored mutable object with all loaded translations"
  []
  (unchecked-get globals/global "penpotTranslations"))

(defn set-translations
  "A helper for synchronously set translations data for specified locale"
  [locale data]
  (let [translations (get-translations)]
    (unchecked-set translations locale data)
    nil))

(defn set-default-translations
  [data]
  (set-translations cf/default-language data))

(defn- load
  [locale]
  (let [path (str "./translation." locale ".js?version=" cf/version-tag)]
    (->> (mod/import path)
         (p/fmap (fn [result] (unchecked-get result "default")))
         (p/fnly (fn [data cause]
                   (if cause
                     (js/console.error "unexpected error on fetching locale" cause)
                     (set-translations locale data)))))))

(defn init
  "Initialize the i18n module"
  []
  (load *current-locale*)
  (when-not (= *current-locale* cf/default-language)
    (load cf/default-language)))

(defn set-locale
  [lname]
  (let [lname (if (or (nil? lname)
                      (str/empty? lname))
                (autodetect)
                (let [supported (into #{} (map :value) supported-locales)]
                  (loop [locales (seq (parse-locale lname))]
                    (if-let [locale (first locales)]
                      (if (contains? supported locale)
                        locale
                        (recur (rest locales)))
                      cf/default-language))))]

    (->> (load lname)
         (p/fnly (fn [_r _c]
                   (set! *current-locale* lname)
                   (reset! locale lname))))))

(deftype C [val]
  IDeref
  (-deref [_] val))

(defn ^boolean c?
  [r]
  (instance? C r))

;; A main public api for translate strings.

;; A marker type that is used just for mark
;; a parameter that represented the counter.

(defn c
  [x]
  (C. x))

(defn empty-string?
  [v]
  (or (nil? v) (empty? v)))

(defn t
  ([locale code]
   (let [translations (get-translations)
         code  (d/name code)
         value (gobj/getValueByKeys translations locale code)]
     (if (empty-string? value)
       (if (= cf/default-language locale)
         code
         (t cf/default-language code))
       (if (array? value)
         (aget value 0)
         value))))
  ([locale code & args]
   (let [translations (get-translations)
         code   (d/name code)
         value  (gobj/getValueByKeys translations locale code)]
     (if (empty-string? value)
       (if (= cf/default-language locale)
         code
         (apply t cf/default-language code args))
       (let [plural (first (filter c? args))
             value  (if (array? value)
                      (if (= @plural 1) (aget value 0) (aget value 1))
                      value)]
         (apply str/fmt value (map #(if (c? %) @% %) args)))))))

(defn tr
  ([code] (t *current-locale* code))
  ([code & args] (apply t *current-locale* code args)))

(mf/defc tr-html*
  {::mf/props :obj}
  [{:keys [content class tag-name on-click]}]
  (let [tag-name (d/nilv tag-name "p")]
    [:> tag-name {:dangerouslySetInnerHTML #js {:__html content}
                  :className class
                  :on-click on-click}]))

(add-watch locale "common.time"
           (fn [_ _ pv cv]
             (when (not= pv cv)
               (ct/set-default-locale cv))))

;; Initialize date-fns locale on startup, the watch above only fires on changes
(ct/set-default-locale *current-locale*)

;; We set the real translation function in the common i18n namespace,
;; so that when common code calls (tr ...) it uses this function.
(set! app.common.i18n/tr tr)
(set! app.common.i18n/c c)
