;; 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.main.data.workspace.tokens.errors
  (:require
   [app.util.dom :as dom]
   [app.util.i18n :refer [tr]]
   [cuerdas.core :as str]))

(def error-codes
  {:error.import/json-parse-error
   {:error/code :error.import/json-parse-error
    :error/fn #(tr "errors.tokens.error-parse")}

   :error.import/no-token-files-found
   {:error/code :error.import/no-token-files-found
    :error/fn #(tr "errors.tokens.no-token-files-found")}

   :error.import/invalid-json-data
   {:error/code :error.import/invalid-json-data
    :error/fn #(tr "errors.tokens.invalid-json")}

   :error.import/invalid-token-name
   {:error/code :error.import/invalid-token-name
    :error/fn #(tr "errors.tokens.invalid-json-token-name")
    :error/detail #(tr "errors.tokens.invalid-json-token-name-detail" (dom/escape-html %))}

   :error.import/style-dictionary-reference-errors
   {:error/code :error.import/style-dictionary-reference-errors
    :error/fn #(str (tr "errors.tokens.import-error") "\n\n" (first %))
    :error/detail #(str/join "\n\n" (map dom/escape-html (rest %)))}

   :error.import/style-dictionary-unknown-error
   {:error/code :error.import/style-dictionary-reference-errors
    :error/fn #(tr "errors.tokens.import-error")}

   :error.token/empty-input
   {:error/code :error.token/empty-input
    :error/fn #(tr "errors.tokens.empty-input")}

   :error.token/direct-self-reference
   {:error/code :error.token/direct-self-reference
    :error/fn #(tr "errors.tokens.self-reference")}

   :error.token/circular-reference
   {:error/code :error.token/circular-reference
    :error/fn #(tr "errors.tokens.circular-reference")}

   :error.token/invalid-color
   {:error/code :error.token/invalid-color
    :error/fn #(str (tr "errors.tokens.invalid-color" %))}

   :error.token/number-too-large
   {:error/code :error.token/number-too-large
    :error/fn #(str (tr "errors.tokens.number-too-large" %))}

   ;; Surfaced when a token name conflicts with a token-group prefix
   ;; across the active sets (e.g. one set defines `a`, another set
   ;; defines `a.b`). DTCG/StyleDictionary cannot represent both as
   ;; resolvable leaves, so the colliding token is preserved with this
   ;; error rather than silently disappearing from the sidebar — see
   ;; #9584.
   :error.token/name-collision
   {:error/code :error.token/name-collision
    :error/fn #(str (tr "errors.tokens.name-collision" %))}

   :error.style-dictionary/missing-reference
   {:error/code :error.style-dictionary/missing-reference
    :error/fn #(str (tr "errors.tokens.missing-references") (str/join " " %))}

   :error.style-dictionary/invalid-token-value
   {:error/code :error.style-dictionary/invalid-token-value
    :error/fn #(str (tr "errors.tokens.invalid-value" %))}

   :error.style-dictionary/value-with-units
   {:error/code :error.style-dictionary/value-with-units
    :error/fn #(str (tr "errors.tokens.value-with-units"))}

   :error.style-dictionary/value-with-percent
   {:error/code :error.style-dictionary/value-with-percent
    :error/fn #(str (tr "errors.tokens.value-with-percent"))}

   :error.style-dictionary/invalid-token-value-opacity
   {:error/code :error.style-dictionary/invalid-token-value-opacity
    :error/fn #(str/join "\n" [(str (tr "errors.tokens.invalid-value" %) ".") (tr "errors.tokens.opacity-range")])}

   :error.style-dictionary/invalid-token-value-stroke-width
   {:error/code :error.style-dictionary/invalid-token-value-stroke-width
    :error/fn #(str/join "\n" [(str (tr "errors.tokens.invalid-value" %) ".") (tr "errors.tokens.stroke-width-range")])}

   :error.style-dictionary/invalid-token-value-text-case
   {:error/code :error.style-dictionary/invalid-token-value-text-case
    :error/fn #(tr "errors.tokens.invalid-text-case-token-value" %)}

   :error.style-dictionary/invalid-token-value-text-decoration
   {:error/code :error.style-dictionary/invalid-token-value-text-decoration
    :error/fn #(tr "errors.tokens.invalid-text-decoration-token-value" %)}

   :error.style-dictionary/invalid-token-value-font-weight
   {:error/code :error.style-dictionary/invalid-token-value-font-weight
    :error/fn #(tr "errors.tokens.invalid-font-weight-token-value" %)}

   :error.style-dictionary/invalid-token-value-font-family
   {:error/code :error.style-dictionary/invalid-token-value-font-family
    :error/fn #(tr "errors.tokens.invalid-font-family-token-value" %)}

   :error.style-dictionary/invalid-token-value-typography
   {:error/code :error.style-dictionary/invalid-token-value-typography
    :error/fn #(tr "errors.tokens.invalid-token-value-typography" %)}

   :error.style-dictionary/composite-line-height-needs-font-size
   {:error/code :error.style-dictionary/composite-line-height-needs-font-size
    :error/fn #(tr "errors.tokens.composite-line-height-needs-font-size" %)}

   :error.style-dictionary/invalid-token-value-shadow-type
   {:error/code :error.style-dictionary/invalid-token-value-shadow-type
    :error/fn #(tr "errors.tokens.invalid-shadow-type-token-value" %)}

   :error.style-dictionary/invalid-token-value-shadow-blur
   {:error/code :error.style-dictionary/invalid-token-value-shadow-blur
    :error/fn #(tr "errors.tokens.shadow-blur-range")}

   :error.style-dictionary/invalid-token-value-shadow-spread
   {:error/code :error.style-dictionary/invalid-token-value-shadow-spread
    :error/fn #(tr "errors.tokens.shadow-spread-range")}

   :error.style-dictionary/invalid-token-value-shadow
   {:error/code :error.style-dictionary/invalid-token-value-shadow
    :error/fn #(tr "errors.tokens.invalid-token-value-shadow" %)}

   :error/unknown
   {:error/code :error/unknown
    :error/fn #(tr "labels.unknown-error")}})

(defn get-error-code [error-key]
  (get error-codes error-key (:error/unknown error-codes)))

(defn error-with-value [error-key error-value]
  (-> (get-error-code error-key)
      (assoc :error/value error-value)))

(defn error-ex-info [error-key error-value exception]
  (let [err (-> (error-with-value error-key error-value)
                (assoc :error/exception exception))]
    (ex-info (:error/code err) err)))

(defn has-error-code? [error-key errors]
  (some #(= (:error/code %) error-key) errors))

(defn resolve-error-message
  "Returns the human-readable message string for a single error map.
  When the error carries an :error/fn key the function is called with
  :error/value to produce the message.  Falls back to :message for
  errors that originate from schema-validation (which have no :error/fn)."
  [error]
  (if error
    (if-let [f (:error/fn error)]
      (f (:error/value error))
      (:message error))
    (tr "labels.unknown-error")))

(defn resolve-error-assoc-message
  "Returns the error map with a :message key set to the resolved human-
  readable string.  When the error carries an :error/fn key the function
  is called with :error/value; otherwise the map is returned unchanged
  (it is expected to already carry a :message from schema-validation)."
  [error]
  (if error
    (if-let [f (:error/fn error)]
      (assoc error :message (f (:error/value error)))
      error)
    (assoc error :message (tr "labels.unknown-error"))))

(defn humanize-errors [errors]
  (->> errors
       (map (fn [err]
              (cond
                (:error/fn err) ((:error/fn err) (:error/value err))
                (:error/message err) (:error/message err)
                :else err)))))

(defn detail-errors [errors]
  (->> errors
       (map (fn [err]
              (when (:error/detail err)
                ((:error/detail err) (:error/value err)))))
       (filter some?)
       (seq)))
