/*
 * Copyright (c) 2010, 2024 BSI Business Systems Integration AG
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */
import {App, arrays, Session, TextMap} from '../index';
import $ from 'jquery';

export type TextMapType = Record<string, TextMap>;

export const texts = {

  /**
   * This default language is used whenever a new text is registered.
   */
  defaultLanguage: 'en',

  TEXT_KEY_REGEX: /\${textKey:([^}]*)}/,

  textsByLocale: {} as TextMapType,

  bootstrap(url: string | string[]): JQuery.Promise<any> {
    if (!url) {
      return $.resolvedPromise({});
    }
    let promises = [];
    let urls = arrays.ensure(url);
    urls.forEach(url => promises.push(
      $.ajaxJson(url).then(texts._handleBootstrapResponse.bind(this, url)))
    );
    return $.promiseAll(promises);
  },

  /** @internal */
  _setTextsByLocale(val: TextMapType) {
    texts.textsByLocale = val;
  },

  /** @internal */
  _handleBootstrapResponse(url: string, data: any) {
    App.handleJsonError(url, data);
    texts.init(data);
  },

  init(model: Record<string, Record<string, string>>) {
    Object.keys(model)
      .forEach(languageTag => texts.get(languageTag).addAll(model[languageTag]), this);
  },

  /**
   * Links the texts of the given languageTag to make parent lookup possible (e.g. look first in de-CH, then in de, then in default).
   */
  link(languageTag: string) {
    let tags = texts.createOrderedLanguageTags(languageTag);
    let child: TextMap;
    tags.forEach(tag => {
      let textMap: TextMap = texts._get(tag);
      if (!textMap) {
        // If there are no texts for the given tag, create an empty Texts object for linking purpose
        textMap = new TextMap();
        texts._put(tag, textMap);
      }
      if (child) {
        child.setParent(textMap);
      }
      child = textMap;
    });
  },

  /**
   * Creates an array containing all relevant tags.
   * <p>
   * Examples:<br>
   * - 'de-CH' generates the array: ['de-CH', 'de', 'default']
   * - 'de' generates the array: ['de', 'default']
   * - 'default' generates the array: ['default']
   */
  createOrderedLanguageTags(languageTag: string): string[] {
    let tags = [],
      i = languageTag.lastIndexOf('-');

    tags.push(languageTag);

    while (i >= 0) {
      languageTag = languageTag.substring(0, i);
      tags.push(languageTag);
      i = languageTag.lastIndexOf('-');
    }

    if (languageTag !== 'default') {
      tags.push('default');
    }
    return tags;
  },

  /**
   * Returns the (modifiable) TextMap for the given language tag.
   * If no TextMap exists for the languageTag given, a new empty map is created.
   * @returns the TextMap for the given languageTag. Never returns null or undefined.
   */
  get(languageTag: string): TextMap {
    let textMap: TextMap = texts._get(languageTag);
    if (textMap) {
      return textMap;
    }

    texts.link(languageTag);
    textMap = texts._get(languageTag);
    if (!textMap) {
      throw new Error('Texts missing for the language tag ' + languageTag);
    }
    return textMap;
  },

  /** @internal */
  _get(languageTag: string): TextMap {
    return texts.textsByLocale[languageTag];
  },

  /**
   * Registers the text map for the given locale.
   * If there already is a text map registered for that locale, it will be replaced, meaning existing texts for that locale are deleted.
   * @internal
   */
  _put(languageTag: string, textMap: TextMap) {
    texts.textsByLocale[languageTag] = textMap;
  },

  /**
   * Extracts NLS texts from the DOM tree. Texts are expected in the following format:
   *
   *   <scout-text data-key="..." data-value="..." />
   *
   * This method returns a map with all found texts. It must be called before scout.prepareDOM()
   * is called, as that method removes all <scout-text> tags.
   */
  readFromDOM(): Record<string, string> {
    let textMap = {};
    $('scout-text').each(function() {
      // No need to unescape strings (the browser did this already)
      let key = $(this).data('key');
      // noinspection UnnecessaryLocalVariableJS
      let value = $(this).data('value');
      textMap[key] = value;
    });
    return textMap;
  },

  /**
   * Converts a key to the form '${textKey:AKey}'.
   * @param key to convert (e.g. 'AKey')
   * @returns text containing the text key like '${textKey:AKey}'.
   */
  buildKey(key: string): string {
    return '${textKey:' + key + '}';
  },

  /**
   * @param value text which contains a text key like '${textKey:AKey}'.
   * @returns the resolved key or the unchanged value if the text key could not be extracted.
   */
  resolveKey(value: string): string {
    let result = texts.TEXT_KEY_REGEX.exec(value);
    if (result && result.length === 2) {
      return result[1];
    }
    return value;
  },

  /**
   * @param value text which contains a text key like '${textKey:AKey}'.
   * @param languageTag the languageTag to use for the text lookup with the resolved key.
   * @returns the resolved text in the given language or the unchanged text if the text key could not be extracted.
   */
  resolveText(value: string, languageTag: string): string {
    let key = texts.resolveKey(value);
    if (key !== value) {
      return texts.get(languageTag).get(key);
    }
    return value;
  },

  /**
   * Utility function to easily replace an object property which contains a text key like '${textKey:AKey}'.
   *
   * @param object object having a text property which contains a text-key
   * @param [textProperty] name of the property where a text-key should be replaced by a text. By default, 'text' is used as property name.
   * @param [session] can be undefined when given 'object' has a session property, otherwise mandatory
   */
  resolveTextProperty(object: any, textProperty?: string, session?: Session) {
    textProperty = textProperty || 'text';
    session = object.session || session;
    let value = object[textProperty];
    let text = texts.resolveText(value, session.locale.languageTag);
    if (text !== value) {
      object[textProperty] = text;
    }
  },

  /**
   * Registers a texts map for a text key.
   * The texts for the default language specified by {@link texts.defaultLanguage} are registered as default texts.
   *
   * @param key the text key under which the given textsArg map will be registered.
   * @param textsArg an object with the languageTag as key and the translated text as value
   */
  registerTexts(key: string, textsArg: Record<string, string>) {
    // In case of changed defaultLanguage clear the 'default' entry
    texts.get('default').remove(key);

    for (let languageTag in textsArg) {
      let text = textsArg[languageTag];
      // Use defaultLanguage as default, if specified (maybe changed or set to null by the app).
      if (languageTag && languageTag === texts.defaultLanguage) {
        languageTag = 'default';
      }
      texts.get(languageTag).add(key, text);
    }
  }
};
