/**
 * @module MelTemplate
 * @local MelTemplate
 * @tutorial js-template
 */

export { MelTemplate };

/**
 * @class
 * @classdesc Permet de générer du html en javascript à partir d'un template.
 * @tutorial js-template
 */
class MelTemplate {

  /**
   * @typedef TemplateEvent
   * @property {string} target Selecteur de l'élément sur lequel ajouter l'event
   * @property {string} type Type d'événement à écouter
   * @property {function} listener Callback à appeler lors de l'événement
   */

  /**
   * Selecteur du template à traiter
   * 
   * @type {string}
   * @package
   */
  #template;

  /**
   * Liste de données à injecter dans le template sous la forme { key: value, key1: value1}
   * 
   * @type {Object}
   * @package
   */
  #data;

  /**
   * Tableau d'événements à injecter dans le template sous la forme [ { target: target, type: type, listener: listener }, { target: target1, type: type1, listener: listener1 } ] avec target = selecteur
   * 
   * @type {TemplateEvent[]}
   * @package
   */
  #events;

  /**
   * Liste de html à injecter dans le template sous la forme { selector: html, selector1: html1}
   * 
   * @type {Object}
   * @package
   */ 
  #htmlContents;

  /**
   * Regex à utiliser pour remplacer les données dans le template
   * 
   * @type {RegExp}
   * @package
   */
  #regex;
  
  /**
   * @param {Object}  [destructured={}]
   * @param {string}  [destructured.templateSelector=''] Selecteur du template à traiter
   * @param {Object}  [destructured.data={}] Liste de données à injecter dans le template sous la forme { key: value, key1: value1}
   * @param {TemplateEvent[]}   [destructured.events=[]] Tableau d'événements à injecter dans le template sous la forme [ { target: target, type: type, listener: listener }, { target: target1, type: type1, listener: listener1 } ] avec target = selecteur
   * @param {Object}  [destructured.htmlContents={}] Liste de html à injecter dans le template sous la forme { selector: html, selector1: html1}
   * @param {RegExp}  [destructured.regex=/%%(\w*)%%/g] Regex à utiliser pour remplacer les données dans le template
   * @frommoduleparam {MelTemplate} events {@linkto TemplateEvent}
   */
  constructor({ templateSelector = '', data = {}, events = [], htmlContents = {}, regex = /%%(\w*)%%/g } = {}) {
    this.#template = templateSelector;
    this.#data = data;
    this.#events = events;
    this.#htmlContents = htmlContents;
    this.#regex = regex;
  }

  /**
   * Ajoute un événement dans le template
   * 
   * @param {string} targetSelector Selecteur de l'élément sur lequel ajouter l'event
   * @param {string} type Type d'événement à écouter
   * @param {function} listener Callback à appeler lors de l'événement
   * @returns {MelTemplate}
   */
  addEvent(targetSelector, type, listener) {
    if (listener)
      this.#events.push({
        target: targetSelector,
        type: type,
        listener: listener
      });
    return this;
  }

  /**
   * Défini une liste d'événements dans le template
   * 
   * @param {TemplateEvent[]} events Tableau d'événements à injecter dans le template sous la forme [ { target: target, type: type, listener: listener }, { target: target1, type: type1, listener: listener1 } ] avec target = selecteur
   * @returns {MelTemplate}
   * @frommoduleparam {MelTemplate} events {@linkto TemplateEvent}
   */
  setEvents(events) {
    this.#events = events;
    return this;
  }

  /**
   * Ajoute une nouvelle donnée à remplacer
   * 
   * @param {string} key 
   * @param {string} value 
   * @returns {MelTemplate}
   */
  addData(key, value) {
    this.#data[key] = value;
    return this;
  }

  /**
   * Défini une liste de données à remplacer
   * 
   * @param {Object} data Liste de données à injecter dans le template sous la forme { key: value, key1: value1}
   * @returns {MelTemplate}
   */
  setData(data) {
    this.#data = data;
    return this;
  }

  /**
   * Défini le selecteur du template à traiter
   * 
   * @param {string} templateSelector 
   * @returns {MelTemplate}
   */
  setTemplateSelector(templateSelector) {
    this.#template = templateSelector;
    return this;
  }

  /**
   * Ajoute du html dans un élément du template
   * 
   * @param {string} selector Selecteur de l'élément dans lequel ajouter le html
   * @param {*} html Html à ajouter au format String, Node, NodeList, Object ou Array
   * @returns {MelTemplate}
   */
  addHtml(selector, html) {
    if (html && html !== null)
      this.#htmlContents[selector] = html;
    return this;
  }

  /**
   * Génération d'un Node à partir du template et des données
   * 
   * @package
   * @returns {HTMLElement} Element généré pour le template, encapsulé dans un div
   */
  #process() {
    let template = document.querySelector(this.#template).innerHTML;
    let html = template.replace(this.#regex, (match, p1) => {
      return this.#data[p1] || '';
    });

    let div = document.createElement('div');
    div.innerHTML = html;

    for (const selector in this.#htmlContents) {
      if (typeof this.#htmlContents[selector] === 'string' || this.#htmlContents[selector] instanceof String) {
        div.querySelector(selector)?.insertAdjacentHTML('beforeend', this.#htmlContents[selector]);
      }
      else if (typeof this.#htmlContents[selector][Symbol.iterator] === 'function') {
        div.querySelector(selector)?.append(...this.#htmlContents[selector]);
      }
      else {
        div.querySelector(selector)?.append(this.#htmlContents[selector]);
      }
    }

    this.#events.forEach(event => {
      div.querySelector(event.target)?.addEventListener(event.type, event.listener);
    });

    return div;
  }

  /**
   * Retourne le résultat du template traité au format NodeList
   * 
   * @returns {NodeListOf<ChildNode>} Liste des éléments générés
   */
  render() {
    return this.#process().childNodes;
  }

  /**
   * Retourne le résultat du template traité au format HTML
   * 
   * @returns {string} Résultat du template traité au format HTML
   */
  renderHtml() {
    return this.#process().innerHTML;
  }
}