"use strict";

/**
 * @class TooltipJS
 * @description Une librairie qui permet d'afficher un TooltipJS au survol d'un élément HTML. L'instance de la classe est créée automatiquement lorsque la librairie est chargée dans la page.
 * @author TIMMatane
 * @version 1.0.0 (2022)
 */
let TooltipJS = class {
  constructor() {
    document.addEventListener("DOMContentLoaded", function () {
      setTimeout(function () {
        _checkForInlineTooltips();
      }, api.initDelay);
    });

    const api = {};

    /**
     * @memberof TooltipJS
     * @alias TooltipJS.initDelay
     * @type {Number}
     * @static
     * @default 100
     * @description Le délai en milisecondes avant de commencer à vérifier, dans le DOM, les balises html necéssitant un TooltipJS
     * @example <caption>Attendre une demi-seconde avant de détecter, dans le DOM, les balise necéssitant un TooltipJS</caption>
     *  TooltipJS.initDelay = 500;
     */
    api.initDelay = 100;

    /**
     *@alias TooltipJS.tooltipStyle
     *@type {Object}
     *@static
     *@description Le style css affecté au conteneur HTML d'un TooltipJS. Il aussi est possible d'ajouter n'importe quelle propriété CSS n'étant pas définie par défaut.
     *<br><em><b>Note</b>: Aucun fichier CSS nécessaire puisque le style sera affecté par JavaScript. Il est aussi possible de spécifier un style CSS sur mesure pour un TooltipJS particulier voir {@link TooltipJS.addTooltip}</em>
     *@member {Object}
     *@property {Object} BASE Propriétés du style CSS de base pour tout TooltipJS
     *@property {String} BASE.display="none" Type d'affiche du TooltipJS
     *@property {String} BASE.position="fixed" Type de positionnement du TooltipJS
     *@property {String} BASE.top="0" Position verticale du TooltipJS
     *@property {String} BASE.left="0" Position horizontale du TooltipJS
     *@property {String} BASE.pointerEvents="none" Le TooltipJS ne bloque pas les événements de la souris
     *@property {String} BASE.opacity="0" Opacité de départ du TooltipJS
     *@property {String} BASE.zIndex="1000" Index de profondeur du TooltipJS
     *@property {Object} DEFAULT Propriétés du style CSS par défaut pour le TooltipJS par défaut
     *@property {String} [DEFAULT.width="auto"] Largeur par défaut du TooltipJS
     *@property {String} [DEFAULT.height="auto"] Hauteur par défaut du TooltipJS
     *@property {String} [DEFAULT.backgroundColor="#ffffcc"] par défaut Couleur de fond du TooltipJS
     *@property {String} [DEFAULT.color="#000"] Couleur par défaut du texte du TooltipJS
     *@property {String} [DEFAULT.margin="0"] Marges par défaut du TooltipJS
     *@property {String} [DEFAULT.padding="0.2rem"] Padding par défaut du TooltipJS
     *@property {String} [DEFAULT.fontFamily="Arial"] Police par défaut du TooltipJS
     *@property {String} [DEFAULT.fontSize="0.8rem"] Grosseur par défaut du texte du TooltipJS
     *@property {String} [DEFAULT.border="1px solid #000"] Le type de bordure par défaut du TooltipJS
     *@property {String} [DEFAULT.boxSizing="border-box"] Les bordures sont calculées comme étant intérieures au TooltipJS
     *@property {String} [DEFAULT.boxShadow="3px 3px 5px rgba(0,0,0,0.5)"] Le style d'ombrage par défaut du TooltipJS
     *@example <caption>Changer le style par defaut du conteneur du TooltipJS.</caption>
     *TooltipJS.tooltipStyle.DEFAULT.fontFamily = "Verdana";
     * //Ajout d'une propriété qui n'existe pas à la base'
     *TooltipJS.tooltipStyle.DEFAULT.borderBottom = "5px solid #fff";
     */

    api.tooltipStyle = {
      BASE: {
        display: "none",
        position: "fixed",
        top: "0",
        left: "0",
        pointerEvents: "none",
        opacity: "0",
        zIndex: "10000",
      },
      DEFAULT: {
        width: "auto",
        height: "auto",
        backgroundColor: "#ffffcc",
        color: "#000",
        margin: "0",
        padding: "0.2rem",
        fontFamily: "Arial",
        fontSize: "0.8rem",
        border: "1px solid #000",
        boxSizing: "border-box",
        boxShadow: "3px 3px 5px rgba(0,0,0,0.5)",
      },
    };

    /**
     * @description Lance la détection de balises HTML nécessitant un TooltipJS.
     * Exécuté automatiquement au chargement de la page, il est possible que les éléments ne soient pas encore créés.
     * Il suffit d'appeler cette fonction au moment opportun.
     * @alias TooltipJS.scanForInlineTooltips
     * @static
     * @returns {undefined} Ne retourne aucune valeur
     * @example <caption>Lancer la détection</caption>
     *  TooltipJS.scanForInlineTooltips();
     */
    api.scanForInlineTooltips = function () {
      _checkForInlineTooltips();
    };

    /**
     * @description Association d'un TooltipJS pour un élément HTML donné
     * <br><em><b>Note</b>: Il est aussi possible d'associer un TooltipJS à un élément sans appel JavaScript direct.</em>
     * @alias TooltipJS.addTooltip
     * @static
     * @param {HTMLElement} elem La référence à l'élément HTML pour lequel associer le TooltipJS
     * @param {String} toolTipText Le texte à afficher dans le TooltipJS
     * @param {Number} [fadeDuration = 250] La durée en millisecondes de l'animation d'apparition du TooltipJS
     * @param {String} [customStyle = ""] Le nom d'une classe CSS existante à utiliser pour affecter l'apparence du TooltipJS
     * @param {Object} [positionMargins = {}] Le postionnement du TooltipJS en pixels en fonction de la position du curseur de la souris
     * @param {number} [positionMargins.x = 15] Position en pixels du TooltipJS en x en rapport avec la position du curseur de la souris
     * @param {number} [positionMargins.y = -15] Position en pixels du TooltipJS en y en rapport avec la position du curseur de la souris
     * @param {Object} [boundsPadding = {}] Le padding à prendre en compte pour la fenêtre du navigateur et les rebords du TooltipJS
     * @param {Number} [boundsPadding.top = 15] Le padding en pixels à prendre en compte en haut de la fenêtre
     * @param {Number} [boundsPadding.right = 15] Le padding en pixels à prendre en compte à droite de la fenêtre
     * @param {Number} [boundsPadding.bottom = 15] Le padding en pixels à prendre en compte en bas de la fenêtre
     * @param {Number} [boundsPadding.left = 15] Le padding en pixels à prendre en compte à gauche de la fenêtre
     * @param {Boolean} [fixed = false] Si l'on souhaite que le TooltipJS soit positionné en fonction du coin supérieur droit de l'élément de survol plutôt que la position de la souris
     * @param {Boolean} [hideOnClick = false] Si l'on souhaite cacher le tooltip lors du clic sur l'élément
     * @param {Number} [hideDelay = 0] Délai avant de cacher la tooltip (0 = toujours visible)
     * @param {HTMLElement} [htmlTemplate = undefined] La référence à un conteneur HTML devant servir de tooltip. La classe "tooltip-active" est automatiquement ajoutée sur le conteneur une fois ce dernier actif et complètement visible.
     * @returns {undefined} Ne retourne aucune valeur
     * @example <caption>Associer un TooltipJS par code.</caption>
     * TooltipJS.addTooltip(document.querySelector("#monElementId"), "Ceci est le texte du TooltipJS");
     * //Utiliser les paramètres optionnels
     * TooltipJS.addTooltip(document.querySelector("#monAutreElementId"), "Ceci est le texte d'un autre TooltipJS", 1000, "maClasse", {x:25, y:-30}, {top:0, right:0, bottom:10, left:0});
     * @example <caption>Associer un TooltipJS directement dans le DOM.</caption>
     * <input type="text" data-toolTipText="Veuillez inscrire votre identifiant" data-fadeDuration="1000" data-customStyle="maClasse" data-positionMargins="25,-30" data-boundsPadding="0,0,10,0" data-fixed  name="login" size="100"  placeholder="Ceci est un champs de formulaire"/>
     * @example <caption>Associer un TooltipJS avec conteneur HTML directement dans le DOM. Comme il est impossible de passer une référence directe à un element HTML, il faut passer le selecteur qui permet de récupérer le conteneur HTML voulu.</caption>
     * <select type="text" data-toolTipText="Ceci est un texte dynamique"  data-htmlTemplate=".tip"></select>
     */
    api.addTooltip = function (elem, toolTipText, fadeDuration, customStyle, positionMargins, boundsPadding, fixed, hideOnClick, hideDelay, htmlTemplate) {
      if (elem == null) {
        console.error("L'élément fourni n'existe pas");
        return;
      }

      //not working on mobile for now
      if (navigator.userAgent.match(/Mobi/)) {
        return;
      }

      if (typeof _tipContainer === "undefined") {
        _createTooltip(api.tooltipStyle);
      }

      if (typeof toolTipText === "undefined") {
        throw new Error("Le texte du TooltipJS doit être défini pour l'élément");
      } else if (toolTipText === "") {
        throw new Error("Le texte du TooltipJS ne peut être vide pour l'élément");
      }

      if (typeof fixed != typeof true) {
        fixed = false;
      }

      let defaultValues = {
        fadeDuration: fadeDuration,
        customStyle: customStyle,
        positionMargins: positionMargins,
        boundsPadding: boundsPadding,
      };

      _applyDefaultValue(defaultValues);

      let tipO = {
        elem: elem,
        toolTipText: toolTipText,
        onmouseover: elem.onmouseover,
        onmousemove: elem.onmousemove,
        onmouseout: elem.onmouseout,
        fadeDuration: defaultValues.fadeDuration,
        customStyle: defaultValues.customStyle,
        positionMargins: defaultValues.positionMargins,
        boundsPadding: defaultValues.boundsPadding,
        fixed: fixed,
        hideOnClick: hideOnClick == undefined ? false : hideOnClick,
        hideDelay: hideDelay,
        htmlTemplate: htmlTemplate,
      };

      elem.onmouseover = function (e) {
        if (tipO.onmouseover !== null) {
          tipO.onmouseover(e);
        }

        if (tipO.fixed) {
          tipO.elemFixedRect = tipO.elem.getBoundingClientRect();
        }

        if (!_currentlyShowing) {
          _showTooltip(e);
        }
      };

      elem.onmousemove = function (e) {
        let tipO = _allTooltips.get(e.currentTarget);
        /*  if (tipO.onmouseout !== null) {
                                      tipO.onmouseout(e);
                                  }*/

        if (!tipO.fixed) {
          _moveTooltip(e);
        }
      };

      elem.onmouseout = function (e) {
        let tipO = _allTooltips.get(e.currentTarget);
        /*if (tipO.onmouseout !== null) {
                                    tipO.onmouseout(e);
                                }*/

        if (!e.currentTarget.contains(e.relatedTarget)) {
          _hideTooltip(e);
        }
      };

      if (tipO.hideOnClick) {
        //hide TooltipJS on elemtent click
        elem.addEventListener(
          "click",
          function (e) {
            _hideTooltip(e);
          },
          { capture: true }
        );
      }

      if (tipO.htmlTemplate) {
        const label = tipO.htmlTemplate.querySelector(".tooltip-label");
        if (label) {
          label.innerText = tipO.toolTipText;
        }
      }

      //setting global TooltipJS value for this element reference
      _allTooltips.set(elem, tipO);
    };

    /**
     * @type {Function}
     * @description Permet de mettre à jour le TooltipJS préalablement associé à un élément HTML dans la page
     * <br><em><b>Note</b>: Il n'est pas possible de mettre à jour un TooltipJS sans appel JavaScript direct.</em>
     * @alias TooltipJS.updateTooltip
     * @static
     * @param {HTMLElement} elem La référence à l'élément HTML pour lequel mettre à jour le TooltipJS
     * @param {String} toolTipText Le texte à afficher dans le TooltipJS
     * @param {Number} [fadeDuration = 250] La durée en millisecondes de l'animation d'apparition du TooltipJS
     * @param {String} [customStyle = ""] Le nom d'une classe CSS existante à utiliser pour affecter l'apparence du TooltipJS
     * @param {Object} [positionMargins = {}] Le postionnement du TooltipJS en pixels en fonction de la position du curseur de la souris
     * @param {number} [positionMargins.x = 15] Position en pixels du TooltipJS en x en rapport avec la position du curseur de la souris
     * @param {number} [positionMargins.y = -15] Position en pixels du TooltipJS en y en rapport avec la position du curseur de la souris
     * @param {Object} [boundsPadding = {}] Le padding à prendre en compte pour la fenêtre du navigateur et les rebords du TooltipJS
     * @param {Number} [boundsPadding.top = 15] Le padding en pixels à prendre en compte en haut de la fenêtre
     * @param {Number} [boundsPadding.right = 15] Le padding en pixels à prendre en compte à droite de la fenêtre
     * @param {Number} [boundsPadding.bottom = 15] Le padding en pixels à prendre en compte en bas de la fenêtre
     * @param {Number} [boundsPadding.left = 15] Le padding en pixels à prendre en compte à gauche de la fenêtre
     * @param {Boolean} [fixed = false] Si l'on souhaite que le TooltipJS soit positionné en fonction du coin supérieur droit de l'élément de survol plutôt que la position de la souris
     * @param {Boolean} [hideOnClick = false] Si l'on souhaite cacher le tooltip lors du clic sur l'élément
     * @param {Number} [hideDelay = 0] Délai avant de cacher la tooltip (0 = toujours visible)
     * @param {HTMLElement} [htmlTemplate = undefined] La référence à un conteneur HTML devant servir de tooltip
     * @returns {undefined} Ne retourne aucune valeur
     * @example <caption>Mettre à jour un TooltipJS par code.</caption>
     * TooltipJS.updateTooltip(document.querySelector("#monElementId"), "Ceci est le texte du TooltipJS mis à jour");
     * //Utiliser les paramètres optionnels
     * TooltipJS.updateTooltip(document.querySelector("#monAutreElementId"), "Ceci est le texte d'un autre TooltipJS mis à jour", 1000, "maClasse", {x:25, y:-30}, {top:0, right:0, bottom:10, left:0}, true);
     */
    api.updateTooltip = function (elem, toolTipText, fadeDuration, customStyle, positionMargins, boundsPadding, fixed, hideOnClick, hideDelay) {
      //not working on mobile for now
      if (navigator.userAgent.match(/Mobi/)) {
        return;
      }
      let tipO = _allTooltips.get(elem);
      if (typeof tipO !== "undefined") {
        if (_currentlyShowing && _currentlyShownElement === elem) {
          _hideTooltip(elem, true);
        }

        if (typeof fixed != typeof true) {
          fixed = false;
        }

        let defaultValues = {
          fadeDuration: fadeDuration || tipO.fadeDuration,
          customStyle: customStyle || tipO.customStyle,
          positionMargins: positionMargins || tipO.positionMargins,
          boundsPadding: boundsPadding || tipO.boundsPadding,
        };

        _applyDefaultValue(defaultValues);

        tipO.toolTipText = toolTipText;
        tipO.fadeDuration = defaultValues.fadeDuration;
        tipO.customStyle = defaultValues.customStyle;
        tipO.positionMargins = defaultValues.positionMargins;
        tipO.boundsPadding = defaultValues.boundsPadding;
        tipO.fixed = fixed || tipO.fixed;
        tipO.hideOnClick = hideOnClick || tipO.hideOnClick;
        tipO.hideDelay = hideDelay || tipO.hideDelay;

        if (fixed) {
          elem.onmousemove = tipO.mousemove;
          tipO.elemFixedRect = elem.getBoundingClientRect();
        } else {
          elem.onmousemove = function (e) {
            if (tipO.onmouseout !== null) {
              tipO.onmouseout(e);
            }
            if (!tipO.fixed) {
              _moveTooltip(e);
            }
          };
          tipO.onmousemove = elem.onmousemove;
          tipO.elemFixedRect = undefined;
        }

        _allTooltips.set(elem, tipO);
        if (_currentlyShowing && _currentlyShownElement === elem) {
          _showTooltip(tipO.elem, true, true);
        }
      } else {
        throw new Error("L'élément choisi ne fait partie des éléments associé à un TooltipJS");
      }
    };

    /**
     * @description Détruit toutes les associations de TooltipJS précédemment créées
     * @alias TooltipJS.removeAllTooltips
     * @static
     * @returns {undefined} Ne retourne aucune valeur
     * @example <caption>Enlever toutes les associations de TooltipJS</caption>
     *  TooltipJS.removeAllTooltips();
     */
    api.removeAllTooltips = function () {
      _allTooltips.forEach(function (value, key, map) {
        let tipO = _allTooltips.get(value.elem);

        if (tipO.onmouseover !== null) {
          value.elem.onmouseover = tipO.onmouseover;
        } else value.elem.onmouseover = null;

        if (tipO.onmouseout !== null) {
          value.elem.onmouseout = tipO.onmouseout;
        } else value.elem.onmouseout = null;

        if (tipO.onmousemove !== null) {
          value.elem.onmousemove = tipO.onmousemove;
        } else value.elem.onmousemove = null;
      });
      _allTooltips.clear();
    };

    /**
     * @description Prepares and show the current TooltipJS
     * @param e MouseOver event
     * @private
     */
    let _showTooltip = function (e, checkRect, fadeBypass) {
      clearTimeout(_hideDelayTimeoutID);

      checkRect = typeof checkRect === "undefined" ? true : checkRect;
      fadeBypass = typeof fadeBypass === "undefined" ? false : fadeBypass;

      let tipO = _allTooltips.get(e.currentTarget || e);

      if (tipO.htmlTemplate) {
        _switchToHTMLTemplate(tipO.htmlTemplate);
      } else _switchToDefaultTemplate();

      if (tipO.htmlTemplate == (null || undefined)) {
        _tipContainer.innerText = tipO.toolTipText;
      }

      _currentPositionMargins = tipO.positionMargins;
      _currentBoundsPadding = tipO.boundsPadding;

      if (tipO.customStyle !== null && tipO.htmlTemplate == (null || undefined)) {
        _tipContainer.style.cssText = ""; //ie11 et edge n'aime pas style = "" (read only)
        _applyStyle(api.tooltipStyle.BASE);
        _tipContainer.classList.toggle(tipO.customStyle);
      }

      _tipContainer.setAttribute("data-fadeduration", tipO.fadeDuration);

      _tipContainer.style.display = "block";
      _currentTooltipRect = _tipContainer.getBoundingClientRect();
      if (checkRect) {
        _checkRectBoundsBeforePositioning(e);
      }

      if (tipO.elemFixedRect) {
        _checkRectBoundsBeforeFixedPositioning(tipO.elemFixedRect);
      }

      _fadeInAnimation(fadeBypass ? 0 : tipO.fadeDuration, tipO.htmlTemplate);

      _currentlyShowing = true;
      _currentlyShownElement = e.currentTarget || e;

      if (tipO.hideDelay > 0) {
        _hideDelayTimeoutID = setTimeout(() => {
          _hideTooltip(_currentlyShownElement);
        }, tipO.hideDelay);
      }
    };

    /**
     * @description Moves the current TooltipJS
     * @param e MouseMove event
     * @private
     */
    let _moveTooltip = function (e) {
      _checkRectBoundsBeforePositioning(e);
    };

    /**
     * @description Prepares and hides the current TooltipJS
     * @param e MouseOut event
     * @param codeTriggered Was the call triggered by user or code?
     * @private
     */
    let _hideTooltip = function (e, codeTriggered) {
      codeTriggered = typeof codeTriggered === "undefined" ? false : codeTriggered;
      clearTimeout(_hideDelayTimeoutID);
      window.cancelAnimationFrame(_currentAnimationFrameID);

      let tipO = _allTooltips.get(e.currentTarget || e);
      if (tipO.customStyle !== null && tipO.htmlTemplate == (null || undefined)) {
        _tipContainer.classList.remove(tipO.customStyle);
        _applyStyle(api.tooltipStyle.DEFAULT);
      }

      if (tipO.htmlTemplate) {
        _tipContainer.classList.remove("tooltip-active");
      }

      _tipContainer.style.display = "none";
      _tipContainer.style.opacity = 0;

      if (!codeTriggered) {
        _currentlyShowing = false;
        _currentlyShownElement = undefined;
      }
    };

    /**
     * Will apply default values to the corresponding parameters
     * @param defaultValues
     * @private
     */
    let _applyDefaultValue = function (defaultValues) {
      defaultValues.fadeDuration = typeof defaultValues.fadeDuration === "undefined" ? _fadeDuration : defaultValues.fadeDuration;
      defaultValues.customStyle = typeof defaultValues.customStyle === "undefined" ? null : defaultValues.customStyle;

      defaultValues.positionMargins = typeof defaultValues.positionMargins === "undefined" ? _tipPositionMargins : defaultValues.positionMargins;
      defaultValues.positionMargins.x = typeof defaultValues.positionMargins.x === "undefined" ? _tipPositionMargins.x : defaultValues.positionMargins.x;
      defaultValues.positionMargins.y = typeof defaultValues.positionMargins.y === "undefined" ? _tipPositionMargins.y : defaultValues.positionMargins.y;

      defaultValues.boundsPadding = typeof defaultValues.boundsPadding === "undefined" ? _tipBoundsPadding : defaultValues.boundsPadding;
      defaultValues.boundsPadding.top = typeof defaultValues.boundsPadding.top === "undefined" ? _tipBoundsPadding.top : defaultValues.boundsPadding.top;
      defaultValues.boundsPadding.right = typeof defaultValues.boundsPadding.right === "undefined" ? _tipBoundsPadding.right : defaultValues.boundsPadding.right;
      defaultValues.boundsPadding.bottom = typeof defaultValues.boundsPadding.bottom === "undefined" ? _tipBoundsPadding.bottom : defaultValues.boundsPadding.bottom;
      defaultValues.boundsPadding.left = typeof defaultValues.boundsPadding.left === "undefined" ? _tipBoundsPadding.left : defaultValues.boundsPadding.left;
    };

    /**
     * @description Sets the top and left properties of the TooltipJS HTMLElement
     * @param x The position from the left
     * @param y The position from the top
     * @private
     */
    let _setTooltipPosition = function (x, y) {
      if (isNaN(x)) {
        throw new Error(234);
      }
      _tipContainer.style.left = x + _currentPositionMargins.x + "px";
      _tipContainer.style.top = y + _currentPositionMargins.y + "px";
    };

    /**
     * @description Creates the TooltipJS container HTMLElement that will be used for all tooltips
     * @param {Object} style The object from with to extract the style properties and values
     * @returns {undefined}
     * @private
     */
    let _createTooltip = function (style) {
      _tipContainer = document.createElement("div");
      _originalTipContainer = _tipContainer;

      //basic styling
      _applyStyle(style.BASE);

      //visual styling
      _applyStyle(style.DEFAULT);

      _tipContainer.setAttribute("id", "tipContainer");
      document.body.appendChild(_tipContainer);
    };

    /**
     * @description Switch the current tooltipContainer to user the htmlTemplate
     * @private
     * @param {HTMLElement} templateElement HTML container to use
     */
    let _switchToHTMLTemplate = function (templateElement) {
      _tipContainer = templateElement;
    };

    /**
     * @description Switch the current tooltipContainer to user the default container
     * @private
     */
    let _switchToDefaultTemplate = function () {
      _tipContainer = _originalTipContainer;
    };

    /**
     * @description Will check if any HTMLElement in the DOM is setting TooltipJS usage with the data- attributes. If so, a TooltipJS is prepared from each one found
     * @private
     * @return {undefined}
     */
    let _checkForInlineTooltips = function () {
      let tts = document.querySelectorAll("[data-tooltiptext]");

      for (let i = 0; i < tts.length; i++) {
        let posMarginsInline = tts[i].getAttribute("data-positionMargins") || _tipPositionMargins;
        let posMargins = {};
        posMargins.x = typeof posMarginsInline === "string" ? parseInt(tts[i].getAttribute("data-positionMargins").split(",")[0]) : _tipPositionMargins.x;
        posMargins.y = typeof posMarginsInline === "string" ? parseInt(tts[i].getAttribute("data-positionMargins").split(",")[1]) : _tipPositionMargins.y;
        let boundsPaddingInline = tts[i].getAttribute("data-boundsPadding") || _tipBoundsPadding;
        let boundsPadding = {};
        boundsPadding.top = typeof boundsPaddingInline === "string" ? parseInt(tts[i].getAttribute("data-boundsPadding").split(",")[0]) : _tipBoundsPadding.top;
        boundsPadding.right = typeof boundsPaddingInline === "string" ? parseInt(tts[i].getAttribute("data-boundsPadding").split(",")[1]) : _tipBoundsPadding.right;
        boundsPadding.bottom = typeof boundsPaddingInline === "string" ? parseInt(tts[i].getAttribute("data-boundsPadding").split(",")[2]) : _tipBoundsPadding.bottom;
        boundsPadding.left = typeof boundsPaddingInline === "string" ? parseInt(tts[i].getAttribute("data-boundsPadding").split(",")[3]) : _tipBoundsPadding.left;

        let fixed = tts[i].hasAttribute("data-fixed");
        let hideOnClick = tts[i].hasAttribute("data-hideOnClick");
        let hideDelay = tts[i].hasAttribute("data-hideOnClick") ? tts[i].getAttribute("data-hideDelay") : 0;
        let htmlTemplate = tts[i].hasAttribute("data-htmlTemplate") ? document.querySelector(tts[i].getAttribute("data-htmlTemplate")) : null;
        api.addTooltip(tts[i], tts[i].getAttribute("data-tooltiptext"), tts[i].getAttribute("data-fadeDuration") || _fadeDuration, tts[i].getAttribute("data-customStyle") || null, posMargins, boundsPadding, fixed, hideOnClick, hideDelay, htmlTemplate);
      }
    };

    /**
     * @description Will apply style from an object to the tip container HTMLElement
     * @param style The object from with to extract the style properties and values
     * @private
     * @return {undefined}
     */
    let _applyStyle = function (style) {
      //basic styling
      for (let prop in style) {
        _tipContainer.style[prop] = style[prop];
      }
    };

    /**
     * @description Will start a fade in animation on the tip container HTMLElement
     * @param duration The duration in ms of the fade in animation
     * @private
     * @return {undefined}
     */
    let _fadeInAnimation = function (duration, htmlTemplate) {
      let end = +new Date() + parseInt(duration);
      let fadeInStep = function () {
        let current = +new Date();
        let remaining = end - current;
        if (remaining < 60) {
          _tipContainer.style.opacity = 1;
          if (htmlTemplate) {
            _tipContainer.classList.add("tooltip-active");
          }
          return;
        } else {
          let rate = 1 - remaining / duration;
          _tipContainer.style.opacity = rate;
        }

        _currentAnimationFrameID = window.requestAnimationFrame(fadeInStep);
      };
      fadeInStep();
    };

    /**
     * @description Will try to guess if the TooltipJS will be hidden by the browser window's sides
     * @param e MouseMove event reference
     * @private
     */
    let _checkRectBoundsBeforePositioning = function (e) {
      let rect = {};
      let html = document.documentElement;
      rect.left = (e.clientX || _lastKnowMousePosition.clientX) + _currentPositionMargins.x;
      rect.top = (e.clientY || _lastKnowMousePosition.clientY) + _currentPositionMargins.y;
      rect.width = _currentTooltipRect.width;
      rect.height = _currentTooltipRect.height;
      rect.right = rect.left + _currentTooltipRect.width;
      rect.bottom = rect.top + _currentTooltipRect.height;
      let inViewResults = _isRectInView(rect, true, true, {
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
      });
      let buffer = { left: 0, top: 0 };
      if (!inViewResults.inview) {
        if (!inViewResults.leftIsVisible) {
          buffer.left = rect.left + _currentPositionMargins.x - _currentBoundsPadding.left;
        } else if (!inViewResults.rightIsVisible) {
          buffer.left = rect.right - (html.clientWidth || window.innerWidth) + _currentPositionMargins.x + _currentBoundsPadding.right;
        }
        if (!inViewResults.topIsVisible) {
          buffer.top = rect.top + _currentPositionMargins.y - _currentBoundsPadding.top;
        } else if (!inViewResults.bottomIsVisible) {
          buffer.top = rect.bottom - (html.clientHeight || window.innerHeight) + _currentPositionMargins.y + _currentBoundsPadding.bottom;
        }

        _setTooltipPosition(rect.left - buffer.left, rect.top - buffer.top);
      } else {
        _setTooltipPosition(rect.left, rect.top);
      }

      _lastKnowMousePosition.clientX = e.clientX || _lastKnowMousePosition.clientX;
      _lastKnowMousePosition.clientY = e.clientY || _lastKnowMousePosition.clientY;
    };

    /**
     * @description Will position the TooltipJS in relation the the fixed element it is tied to
     * @param rect The fixed element rect
     * @private
     */
    let _checkRectBoundsBeforeFixedPositioning = function (rect) {
      let buffer = {
        left: _currentPositionMargins.x,
        top: _currentPositionMargins.y,
      };
      _setTooltipPosition(rect.left + rect.width + buffer.left, rect.top + buffer.top);
    };

    /**
     * @description Checks if the TooltipJS rect is fully visible or partially visible
     * @param rect The TooltipJS rect
     * @param fullyVisibleX Should we check for full visibility on X axis?
     * @param fullVisibleY Should we check for full visibility on Y axis?
     * @param offset An object describing offsets to use for top, right, bottom and left of the TooltipJS
     * @return {{inview: boolean, leftIsVisible: boolean, topIsVisible: boolean, rightIsVisible: boolean, bottomIsVisible: boolean, isFullyVisibleX: boolean, isFullyVisibleY: boolean}}
     * @private
     */
    let _isRectInView = function (rect, fullyVisibleX, fullVisibleY, offset) {
      let html = document.documentElement;
      let isPartiallyVisibleX = rect.right >= offset.left && rect.left <= (window.innerWidth || html.clientWidth) - offset.right;
      let isPartiallyVisibleY = rect.bottom >= offset.top && rect.top <= (window.innerHeight || html.clientHeight) - offset.bottom;
      let leftIsVisible = rect.left + _currentPositionMargins.x - _currentBoundsPadding.left >= offset.left;
      let rightIsVisible = rect.right <= (html.clientWidth || window.innerWidth) - (offset.right + _currentPositionMargins.x + _currentBoundsPadding.right);
      let isFullyVisibleX = leftIsVisible && rightIsVisible && rect.left + rect.width <= (window.innerWidth || html.clientWidth) - offset.right;
      let topIsVisible = rect.top + _currentPositionMargins.y - _currentBoundsPadding.top >= offset.top;
      let bottomIsVisible = rect.bottom <= (html.clientHeight || window.innerHeight) - (offset.bottom + _currentPositionMargins.y + _currentBoundsPadding.bottom);
      let isFullyVisibleY = topIsVisible && bottomIsVisible && rect.top + rect.height <= (window.innerHeight || html.clientHeight) - offset.bottom;
      let result = fullyVisibleX && fullVisibleY ? isFullyVisibleX && isFullyVisibleY : fullyVisibleX ? isFullyVisibleX && isPartiallyVisibleY : fullVisibleY ? isFullyVisibleY && isPartiallyVisibleX : isPartiallyVisibleX && isPartiallyVisibleY;
      return {
        inview: result,
        leftIsVisible: leftIsVisible,
        topIsVisible: topIsVisible,
        rightIsVisible: rightIsVisible,
        bottomIsVisible: bottomIsVisible,
        isFullyVisibleX: isFullyVisibleX,
        isFullyVisibleY: isFullyVisibleY,
      };
    };

    /**
     * @description Reference to the TooltipJS container HTMLElement
     * @private
     */
    let _tipContainer;
    /**
     * @description Is the TooltipJS container currently showed?
     * @private
     */
    let _currentlyShowing = false;
    /**
     * @description The last known mouse position
     * @private
     */
    let _lastKnowMousePosition = { clientX: 0, clientY: 0 };

    let _currentlyShownElement;
    /**
     * @description Default values to use as position margins from the mouse cursor's position
     * @private
     */
    let _tipPositionMargins = { x: 15, y: -15 };
    /**
     * @description Default values to use as bounds padding from the browser window's rect
     * @private
     */
    let _tipBoundsPadding = { left: 15, right: 15, top: 15, bottom: 15 };
    /**
     * @description A dictionary that reference all tooltips currently existing inside the page
     * @private
     */
    let _allTooltips = new Map();
    /**
     * @description Default value to use for fade in animation duration
     * @private
     */
    let _fadeDuration = 250;
    /**
     * @description Reference to the TooltipJS container HTMLElement current rect
     * @private
     */
    let _currentTooltipRect;
    /**
     * @description Reference to the TooltipJS container HTMLElement current position margins
     * @private
     */
    let _currentPositionMargins;
    /**
     * @description Reference to the TooltipJS container HTMLElement current bounds padding
     * @private
     */
    let _currentBoundsPadding;
    /**
     * @description Reference to the current fadeIn animation frame
     * @private
     */
    let _currentAnimationFrameID = 0;
    /**
     * @description Reference to the current hide timeout
     * @private
     */
    let _hideDelayTimeoutID = 0;

    let _originalTipContainer;

    //set the api object to be public
    return api;
  }
};

export default new TooltipJS();