import { EMPTY_STRING } from '../constants/constants.js';
import { MelHtml } from '../html/JsHtml/MelHtml.js';
import { BnumEvent } from '../mel_events.js';
import { MelObject } from '../mel_object.js';
import { Mel_Promise } from '../mel_promise.js';
import { NotifierObject } from './NotifierObject.js';
import { VisualiserObject } from './VisualiserObject.js';
import { BaseStorage } from './base_storage.js';
export {
  RcmailDialog,
  RcmailDialogButton,
  RcmailDialogChoiceButton,
  MelDialog,
  DialogPage,
  JQueryDialogPage,
};

/**
 * Contient les classes utiles pour la création d'une dialogue.
 * @module Modal
 * @local DialogButtonConfig
 * @local SwitchPageCallback
 * @local RcmailDialog
 * @local RcmailDialogButton
 * @local RcmailDialogChoiceButton
 * @local RcmailDialogChoiceButtonVisualiser
 * @local DialogPage
 * @local DialogPageManager
 * @local MelDialog
 * @local JQueryDialogPage
 */

/**
 * @typedef {Object} DialogButtonConfig
 * @property {?string} classes Classes css du bouton
 * @property {?string} text Texte du bouton
 * @property {?EventClickCallback} click exécuter lors du clique du bouton
 * @property {?EventHoverCallback} hover exécuter lors du survol du bouton
 * @property {?EventMouseEnterCallback} mouseenter exécuter lors de l'entrée de la souris dans le bouton
 * @property {?EventMouseLeaveCallback} mouseleave exécuter lors de la sortie de la souris du bouton
 */

/**
 * @callback SwitchPageCallback
 * @param {Object} params
 * @param {string} params.name Nom de la nouvele page courrante
 * @param {DialogPage} params.page Nouvelle page courrante
 * @param {DialogPageManager} params.manager Manager de la dialog
 * @return {void}
 *
 */

/**
 * @class
 * @classdesc Représente un bouton de dialog
 * @extends NotifierObject
 * @tutorial meldialog
 */
class RcmailDialogButton extends NotifierObject {
  /**
   * Constructeur de la classe
   * @param {string} text Label du bouton
   * @param {Object} param1 Configuration du bouton
   * @param {!string} param1.classes Classes css du bouton
   * @param {?EventClickCallback} param1.click Callback appelé lorsqu'on clique sur le bouton
   * @param {?EventHoverCallback} param1.hover Callback appelé lorsqu'on survole le bouton
   * @param {?EventMouseLeaveCallback} param1.mouseenter Callback appelé lorsqu'on entre dans le bouton
   * @param {?EventMouseLeaveCallback} param1.mouseleave Callback appelé lorsqu'on sort du bouton
   */
  constructor(
    text,
    {
      classes = EMPTY_STRING,
      click = null,
      hover = null,
      mouseenter = null,
      mouseleave = null,
      options = null,
    },
  ) {
    super();
    this._init()._setup(
      text,
      classes,
      click,
      hover,
      mouseenter,
      mouseleave,
      options,
    );
  }

  _init() {
    /**
     * text du bouton
     * @type {string}
     */
    this.text = EMPTY_STRING;
    /**
     * Classes css du bouton
     * @type {string}
     */
    this.classes = EMPTY_STRING;
    /**
     * Callback appelé lorsqu'on clique sur le bouton
     * @type {BnumEvent<EventClickCallback>}
     */
    this.click = new BnumEvent();
    /**
     * Callback appelé lorsqu'on survole le bouton
     * @type {BnumEvent<EventHoverCallback>}
     */
    this.hover = new BnumEvent();
    /**
     * Callback appelé lorsqu'on entre dans le bouton
     * @type {BnumEvent<EventMouseEnterCallback>}
     */
    this.mouseenter = new BnumEvent();
    /**
     * Callback appelé lorsqu'on sort du bouton
     * @type {BnumEvent<EventMouseLeaveCallback>}
     */
    this.mouseleave = new BnumEvent();

    /**
     * Options en plus du bouton
     * @type {?Object<string, *>}
     */
    this.options = null;

    return this;
  }

  _setup(text, classes, click, hover, mouseenter, mouseleave, options) {
    this._p_addProp('text', { value: text });

    if (click) this.click.push(click);
    if (hover) this.hover.push(hover);
    if (mouseenter) this.mouseenter.push(mouseenter);
    if (mouseleave) this.mouseleave.push(mouseleave);

    let _classes = classes;
    Object.defineProperty(this, 'classes', {
      get() {
        return `mel-button no-button-margin no-margin-button ${_classes}`;
      },
      set: (value) => {
        _classes = value;
        this.on_prop_update.call('classes', this.classes, this);
      },
    });

    this.options = options;

    return this;
  }

  /**
   * Convertit en objet lisible par la fonction dialog de rcmail
   * @returns {DialogButtonConfig}
   */
  generate() {
    let generated = {
      text: this.text,
      class: this.classes,
      click: this.click.call.bind(this.click),
      hover: this.hover.call.bind(this.hover),
      mouseenter: this.mouseenter.call.bind(this.mouseenter),
      mouseleave: this.mouseleave.call.bind(this.mouseleave),
    };

    if (this.options) {
      for (const key in this.options) {
        if (Object.prototype.hasOwnProperty.call(this.options, key)) {
          const element = this.options[key];
          generated[key] = element;
        }
      }
    }

    return generated;
  }

  /**
   * Génère un bouton qui permettra de sauvegarder les données
   * @static
   * @param {Object} param0
   * @param {string=} param0.text Texte du bouton
   * @param {?BnumEvent=} param0.click Callback appelé lorsqu'on clique sur le bouton
   * @param {?BnumEvent=} param0.mouseenter Callback appelé lorsqu'on entre dans le bouton
   * @param {?BnumEvent=} param0.mouseleave Callback appelé lorsqu'on sort du bouton
   * @returns {RcmailDialogButton}
   */
  static ButtonSave({
    text = 'Enregistrer',
    classes = EMPTY_STRING,
    click = null,
    mouseenter = null,
    mouseleave = null,
    options = null,
  } = {}) {
    return new RcmailDialogButton(text, {
      classes: `mel-button no-button-margin no-margin-button ${classes}`,
      click: click?.call?.bind?.(click),
      mouseenter: mouseenter?.call?.bind?.(mouseenter),
      mouseleave: mouseleave?.call?.bind?.(mouseleave),
      options,
    });
  }

  static ButtonCancel({
    text = 'Annuler',
    classes = EMPTY_STRING,
    click = null,
    mouseenter = null,
    mouseleave = null,
    options = null,
  } = {}) {
    return new RcmailDialogButton(text, {
      classes: `mel-button no-button-margin no-margin-button btn btn-danger ${classes}`,
      click: click?.call?.bind?.(click),
      mouseenter: mouseenter?.call?.bind?.(mouseenter),
      mouseleave: mouseleave?.call?.bind?.(mouseleave),
      options,
    });
  }
}

/**
 * @class
 * @classdesc Représente un "bouton de choix", il s'agit d'un bouton qui contient un texte et une icône
 * @extends RcmailDialogButton
 * @tutorial meldialog
 */
class RcmailDialogChoiceButton extends RcmailDialogButton {
  /**
   * Constructeur de la classe
   * @param {string} text Texte du bouton
   * @param {string} icon Icône du bouton (Material Symbol)
   * @param {Object} param2 Configuration du bouton
   * @param {!string} param2.classes Classes css du bouton
   * @param {?EventClickCallback} param2.click Callback appelé lorsqu'on clique sur le bouton
   * @param {?EventHoverCallback} param2.hover Callback appelé lorsqu'on survole le bouton
   * @param {?EventMouseEnterCallback} param2.mouseenter Callback appelé lorsque la souris entre dans le bouton
   * @param {?EventMouseLeaveCallback} param2.mouseleave Callback appelé lorsque la souris sort du bouton
   */
  constructor(
    text,
    icon,
    {
      classes = EMPTY_STRING,
      click = null,
      hover = null,
      mouseenter = null,
      mouseleave = null,
    },
  ) {
    super(text, {
      classes,
      click,
      hover,
      mouseenter,
      mouseleave,
    });

    /**
     * Icône du bouton (Material Symbol)
     * @type {string}
     */
    this.icon = EMPTY_STRING;
    this._p_addProp('icon', {
      value: icon,
    });
  }
}

/**
 *
 * @class
 * @classdesc Représentation du bouton. Si la classe du bouton change, le html change aussi.
 * @extends VisualiserObject
 * @package
 */
class RcmailDialogChoiceButtonVisualiser extends VisualiserObject {
  /**
   * Constructeur de la classe
   * @param {RcmailDialogButton} button Bouton à afficher en html
   */
  constructor(button) {
    super(button);
  }

  /**
   * Retourne l'élément en jshtml
   * @protected
   * @override
   * @returns {____JsHtml}
   * @frommodulereturn JsHtml
   */
  _p_draw() {
    return super
      ._p_draw()
      .button({
        class: this._get_button_classes(this.ref.classes),
        onclick: this.ref.click.call.bind(this.ref.click),
        onhover: this.ref.click.call.bind(this.ref.hover),
        onmouseenter: this.ref.click.call.bind(this.ref.mouseenter),
        onmouseleave: this.ref.click.call.bind(this.ref.mouseleave),
      })
      .icon(this.ref.icon, { class: 'block' })
      .end()
      .span({ class: 'btn-txt' })
      .text(this.ref.text)
      .end()
      .end();
  }

  /**
   * Classes de la bouton
   * @param {string} classes Classes additionnels
   * @private
   * @returns {string}
   */
  _get_button_classes(classes) {
    return `${classes} btn btn-block-mel btn-secondary btn-mel`;
  }

  /**
   * Met à jours le texte du bouton
   * @param {string} value Nouveau texte
   * @private
   */
  _prop_update_text(value) {
    this.$ref.find('.btn-txt').text(value);
  }

  /**
   * Met à jours les classes du bouton
   * @param {string} value Nouvelles classes
   * @private
   */
  _prop_update_classes(value) {
    this.$ref[0].classList = [];
    this.$ref.addClass(this._get_button_classes(value));
  }

  /**
   * Met à jours l'icône du bouton
   * @private
   * @param {string} value Nouveau bouton
   * @private
   */
  _prop_update_icon(value) {
    this.$ref.find('bnum-icon').text(value);
  }
}

/**
 * @class
 * @classdesc Affiche une dialog en utilisant Rcmail (déprécier, utilisez plutôt {@link MelDialog})
 * @extends MelObject
 * @deprecated
 * @tutorial modal
 *
 */
class RcmailDialog extends MelObject {
  /**
   * Constructeur de la classe
   * @deprecated
   * @param {(____JsHtml | external:jQuery)} contents html en jshtml ou jquery
   * @param {Object} param1 Configuration de la dialog
   * @param {string} param1.title Titre de la dialog
   * @param {RcmailDialogButton[]} param1.buttons Boutons de la dialog
   * @param {Object} param2.options Options de la boite de dialogue. Voir {@link https://api.jqueryui.com/dialog/}. `disable_show_on_start` désactive l'affichage à la création si vrai.
   * @frommoduleparam JsHtml contents {@linkto ____JsHtml}
   *
   */
  constructor(contents, { title = EMPTY_STRING, buttons = [], options = {} }) {
    super(contents, title, buttons, options);
  }

  _init() {
    /**
     * Contenu de la dialog
     * @type {____JsHtml}
     * @frommodule JsHtml
     */
    this.contents = MelHtml.start;
    /**
     * Titre de la dialog
     * @type {string}
     */
    this.title = EMPTY_STRING;
    /**
     * Boutons de la dialog
     * @type {RcmailDialogButton[]}
     */
    this.buttons = [];
    /**
     * Options de la dialog
     * @type {Object}
     */
    this.options = {};
    /**
     * Dialog
     * @type {external:jQuery}
     * @package
     */
    this._$dialog = $();

    return this;
  }

  _setup(contents, title, buttons, options) {
    Object.defineProperties(this, {
      contents: {
        get() {
          return contents;
        },
      },
      title: {
        get() {
          return title;
        },
      },
      buttons: {
        get() {
          return buttons;
        },
      },
      options: {
        get() {
          return options;
        },
      },
    });

    return this;
  }

  /**
   * Actions principales
   * @param  {...any} args Arguments envoyé par le constructeur
   */
  main(...args) {
    {
      super.main(...args);
      const [contents, title, buttons, options] = args;
      this._init()._setup(contents, title, buttons, options);
    }

    if (!this.options?.disable_show_on_start) this.show();
  }

  /**
   * Cache la dialog
   * @returns {RcmailDialog} Chaînage
   */
  hide() {
    return this.destroy();
  }

  /**
   * Affiche la dialog
   * @returns {RcmailDialog} Chaînage
   */
  show() {
    let $contents = this.contents.generate
      ? this.contents.generate()
      : this.contents;

    this._$dialog = this.rcmail().show_popup_dialog(
      $contents[0],
      this.title,
      this.buttons.map((x) => x.generate()),
      this.options,
    );
    return this;
  }

  /**
   * Supprime la dialog
   * @returns {RcmailDialog} Chaînage
   */
  destroy() {
    this._$dialog.dialog('destroy');
    return this;
  }

  /**
   * Affiche X boutons qui permettront de faire certaines actions
   * @param {string} title Titre de la modale
   * @param  {...RcmailDialogChoiceButton} buttons Bouttons qui seront affichés
   * @returns {RcmailDialog}
   */
  static DrawChoices(title, ...buttons) {
    let $html = MelHtml.start.flex_container().end().generate();

    for (const iterator of buttons) {
      $html.append(new RcmailDialogChoiceButtonVisualiser(iterator).$ref);
    }

    return new RcmailDialog($html, { title });
  }

  /**
   * Affiche 2 boutons qui permettront de faire certaines actions
   * @param {string} title Titre de la modale
   * @param {RcmailDialogChoiceButton} button1 Option 1
   * @param {RcmailDialogChoiceButton} button2 Option 2
   * @returns {RcmailDialog}
   */
  static DrawChoice(title, button1, button2) {
    return this.DrawChoices(title, button1, button2);
  }
}

/**
 * @class
 * @classdesc Données d'une page de dialog
 * @tutorial meldialog
 */
class DialogPage {
  /**
   * Constructeur de la classe
   * @param {string} name Nom de la page, lui sert d'id.
   * @param {object} params Contenu de la page
   * @param {?____JsHtml} params.content Contenu de la page
   * @param {!string} params.title Titre de la page
   * @param {RcmailDialogButton[]} params.buttons Boutons de la page
   * @frommoduleparam {JsHtml} params.content {@linkto ____JsHtml}
   */
  constructor(name, { content = null, title = EMPTY_STRING, buttons = [] }) {
    this._init()._setup(name, content, title, buttons);
  }

  _init() {
    /**
     * Contenu de la dialog
     * @type {?____JsHtml}
     * @frommodule JsHtml {@linkto ____JsHtml}
     */
    this.content = null;
    /**
     * Id de la dialog
     * @type {string}
     */
    this.name = EMPTY_STRING;
    /**
     * Titre de la dialog
     * @type {string}
     */
    this.title = EMPTY_STRING;
    /**
     * Boutons de la dialog
     * @type {RcmailDialogButton[]}
     */
    this.buttons = [];

    return this;
  }

  _setup(name, content, title, buttons) {
    this.name = name;
    this.content = content;
    this.title = title;
    this.buttons = buttons;
    return this;
  }

  /**
   * Permet de modifier le contenu de la dialog
   * @param {Object} param0
   * @param {!boolean} param0.force_restart Si on recommence l'écriture de la page ou non
   * @returns {____JsHtml}
   * @frommodulereturn JsHtml
   */
  start_update_content({ force_restart = false }) {
    if (force_restart) this.content = MelHtml.start;

    return this.content;
  }

  /**
   * Récupère sous format jQuery
   * @returns {external:jQuery}
   */
  get() {
    return (this.content || MelHtml.start.placeholder().end()).generate();
  }

  /**
   * Créer une page avec plusieurs choix de boutons
   * @param {string} title Titre de la modale
   * @param {Object} param1
   * @param {!string} param1.name Nom de la page
   * @param {RcmailDialogChoiceButton[]} param1.buttons Boutons de la page
   * @returns {JQueryDialogPage} Page créée
   */
  static DrawChoices(title, { name = 'choices', buttons = [] }) {
    let $html = MelHtml.start.flex_container().end().generate();

    for (const iterator of buttons) {
      $html.append(new RcmailDialogChoiceButtonVisualiser(iterator).$ref);
    }

    return new JQueryDialogPage(name, { title, content: $html });
  }

  /**
   * Créer une page avec 2 boutons
   * @param {string} title Titre de la modale
   * @param {RcmailDialogChoiceButton} button1 Premier bouton
   * @param {RcmailDialogChoiceButton} button2 Second bouton
   * @param {string} name Id de la page
   * @returns {JQueryDialogPage}
   */
  static DrawChoice(title, button1, button2, name = 'choice') {
    return this.DrawChoices(title, { name, buttons: [button1, button2] });
  }

  /**
   * Permet de modifier un objet 'RcmailDialogButton'.
   * @callback OnButtonCreatedCallback
   * @param {RcmailDialogButton} button
   * @returns {RcmailDialogButton}
   */

  /**
   * Permet de modifier un objet 'DialogPage'
   * @callback OnPageCreatedCallback
   * @param {DialogPage} page
   * @returns {DialogPage}
   */

  /**
   * Affiche une page de confirmation.
   *
   * Le bouton de confirmation peut ou pas s'activer au bout de X secondes.
   * @param {string} text Texte de la modale.
   * @param {EventClickCallback} callback Action à faire lorsque l'on clique sur la confirmation
   * @param {Object} optionnals Paramètres optionnels
   * @param {string} [optionnals.title=''] Titre de la modale
   * @param {string} [optionnals.button_confirm='Ok'] Texte du bouton de confirmation
   * @param {string} [optionnals.button_confirm='Annuler'] Texte du bouton d'annulation
   * @param {string} [optionnals.name='confirm'] Id de la page
   * @param {?EventClickCallback} [optionnals.oncancel=null] Action à l'annulation. Si null, ferme la modale.
   * @param {boolean} [optionnals.center=false] Centrer le texte ?
   * @param {?OnButtonCreatedCallback} [optionnals.on_button_ok_object_created=null] Est appelé après la création du bouton de confirmation. Permet de modifier le bouton.
   * @param {?OnButtonCreatedCallback} [optionnals.on_button_cancel_object_created=null] Est appelé après la création du bouton d'annulation. Permet de modifier le bouton.
   * @param {?OnPageCreatedCallback} [optionnals.on_page_object_created=null] Est appelé après la création de la page. Permet de modifier la page.
   * @returns {DialogPage}
   * @static
   */
  static DrawConfirm(
    text,
    callback,
    {
      title = EMPTY_STRING,
      button_confirm = 'Ok',
      button_cancel = 'Annuler',
      name = 'confirm',
      oncancel = null,
      center = false,
      on_button_ok_object_created = null,
      on_button_cancel_object_created = null,
      on_page_object_created = null,
    },
  ) {
    let rcbutton_ok = RcmailDialogButton.ButtonSave({
      text: button_confirm,
      click: callback,
    }); /*new RcmailDialogButton(button_confirm, {
      click: callback,
    });*/

    rcbutton_ok = on_button_ok_object_created?.(rcbutton_ok) ?? rcbutton_ok;

    let rcbutton_cancel = RcmailDialogButton.ButtonCancel({
      text: button_cancel,
      click: oncancel ? oncancel : () => {},
    });
    /*new RcmailDialogButton(button_cancel, {
      click: oncancel ? oncancel : () => {},
    });*/

    rcbutton_cancel =
      on_button_cancel_object_created?.(rcbutton_cancel) ?? rcbutton_cancel;

    let page = new DialogPage(name, {
      title: title,
      buttons: [rcbutton_cancel, rcbutton_ok],
    });

    page
      .start_update_content({ force_restart: true })
      .div({ style: `text-align:${center ? 'center' : 'left'}` })
      .css('width', '100%')
      .text(text)
      .end();

    return on_page_object_created?.(page) ?? page;
  }
}

/**
 * @class
 * @classdesc Données d'une page de dialog, le conteu est en jQuery.
 * @extends DialogPage
 */
class JQueryDialogPage extends DialogPage {
  /**
   * Constructeur de la classe
   * @param {string} name Nom de la page, lui sert d'id.
   * @param {object} params Contenu de la page
   * @param {?external:jQuery} params.content Contenu de la page
   * @param {!string} params.title Titre de la page
   * @param {RcmailDialogButton[]} params.buttons Boutons de la page
   */
  constructor(name, { content = null, title = EMPTY_STRING, buttons = [] }) {
    super(name, { content, title, buttons });
    /**
     * Contenu de la dialog
     * @type {external:jQuery}
     * @overload
     * @override
     */
    this.content = content;
  }

  /**
   * Permet de modifier le contenu de la dialog
   * @param {Object} param0
   * @param {?external:jQuery} param0.new_content Si on recommence l'écriture de la page ou non
   * @returns {?external:jQuery}
   * @override
   */
  start_update_content({ new_content = null }) {
    if (new_content) this.content = new_content;

    return this.content;
  }

  /**
   * Récupère sous format jQuery
   * @returns {external:jQuery}
   */
  get() {
    return this.content ?? $();
  }
}

/**
 * @class
 * @classdesc Gère les différentes pages de la dialog
 * @package
 */
class DialogPageManager {
  /**
   * Constructeur de la classe
   * @param  {...DialogPage} pages Pages par défaut
   */
  constructor(...pages) {
    this._init()._setup(...pages);
  }

  _init() {
    /**
     * Pages de la dialog
     * @type {module:BaseStorage~BaseStorage<DialogPage>}
     * @package
     * @frommodule Modal {@linkto DialogPage}
     */
    this._pages = new BaseStorage();
    /**
     * Page actuelle
     * @type {string}
     */
    this._current_page = null;

    /**
     * @type {BnumEvent<SwitchPageCallback>}
     * @frommodule Modal {@linkto SwitchPageCallback}
     */
    this.onswitchpage = new BnumEvent();

    return this;
  }

  _setup(...pages) {
    this.add_pages(...pages);
    return this;
  }

  _add_page(page) {
    this._pages.add(page.name, page);

    return this;
  }

  _add_no_create_page(name, content, { title = EMPTY_STRING, buttons = [] }) {
    const page = content?.before
      ? new JQueryDialogPage(name, { content, title, buttons })
      : new DialogPage(name, { content, title, buttons });

    return this._add_page(page);
  }

  /**
   * Ajoute une page. Si vous souhaitez ajouter une page déjà existante, donnez la en argument de `page_or_name`.
   *
   * Sinon, donnez le nom de la page (son id) et le contenu de la page.
   * @param {(string | DialogPage)} page_or_name
   * @param {Object} overloads
   * @param {(?external:jQuery | ?____JsHtml | undefined)} overloads.content Contenu de la page (jQuery, jsHtml ou undefined)
   * @param {!string} overloads.title Titre de la page
   * @param {RcmailDialogButton[]} overloads.buttons Boutons de la page
   * @returns {DialogPageManager}
   * @frommoduleparam JsHtml overloads.content {@linkto ____JsHtml}
   */
  add_page(
    page_or_name,
    { content = undefined, title = EMPTY_STRING, buttons = [] },
  ) {
    if (content !== undefined)
      return this._add_no_create_page(page_or_name, content, {
        title,
        buttons,
      });
    else return this._add_page(page_or_name);
  }

  /**
   * Ajoute plusieurs pages
   * @param  {...DialogPage} pages Pages à ajouter
   * @returns {DialogPageManager} Chaînage
   */
  add_pages(...pages) {
    for (const page of pages) {
      this.add_page(page);
    }

    return this;
  }

  /**
   * Si le manager contient des pages ou non
   * @returns {boolean}
   */
  has_pages() {
    return Object.keys(this._pages).length > 0;
  }

  /**
   * Change de page
   * @param {string} name Nom de la page à afficher
   * @throws {Error} Si la page n'existe pas
   */
  switch_page(name) {
    this._current_page = name;
    this.onswitchpage.call({
      name,
      page: this._pages.get(this._current_page),
      manager: this,
    });
  }
}

/**
 * @class
 * @classdesc Affiche une dialog en utilisant Rcmail
 * @tutorial meldialog
 * @tutorial meldialogex
 */
class MelDialog {
  /**
   * Si vous ne souhaitez pas créer un objet page, utilisez la fonction static `Create`
   * @param {DialogPage} page Contenu de la dialog
   * @param {Object} options Options de la boite de dialogue. Voir {@link https://api.jqueryui.com/dialog/}
   * @see {@link MelDialog.Create}
   * @see {@link https://api.jqueryui.com/dialog/|jQueryUI}
   */
  constructor(page, options = {}) {
    this._init()._setup(page, options)._main();
  }

  _init() {
    /**
     * Gestionnaire de page
     * @type {DialogPageManager}
     */
    this.page_manager = new DialogPageManager();
    /**
     * Options de la dialog
     * @type {Object}
     */
    this.options = {};
    /**
     * Dialog
     * @type {external:jQuery}
     * @package
     */
    this._$dialog = null;
    /**
     * Nom de la première page
     * @type {string}
     * @private
     */
    this._first_page_name = EMPTY_STRING;
    return this;
  }

  _setup(page, options) {
    this.options = options;
    this._first_page_name = page.name;
    this.page_manager.add_page(page, {});
    return this;
  }

  _main() {
    this.page_manager.onswitchpage.push(this._update_page.bind(this));
  }

  /**
   * Met à jours le contenu de la dialogue lors d'un changement de page.
   * @param {{name:string, page:DialogPage, manager:DialogPageManager}} args Arguments envoyer par l'évènement
   * @returns {external:jQuery}
   * @package
   */
  _update_page(args) {
    const { name, page } = args;
    let $querry = this._$dialog.find(`#${name}`);
    this._$dialog.find('.mel-dialog-page').hide();

    if ($querry.length) $querry.show();
    else {
      const $page = page.get();
      this._$dialog.append(
        $('<div>').attr('id', name).addClass('mel-dialog-page').append($page),
      );
    }

    this.update_option('title', page.title);
    this.update_option(
      'buttons',
      page.buttons.map((x) => x.generate()),
    );

    return this._$dialog.find(`#${name}`);
  }

  get dialog() {
    return this._$dialog;
  }

  get dialogContainer() {
    return this.dialog.parent();
  }

  /**
   * Affiche la dialogue
   */
  show({ context = window } = {}) {
    if (!this._$dialog) {
      if (!this.options.close) this.options.close = () => {};

      this._$dialog = context.rcmail.show_popup_dialog(
        $('<div>').attr('id', this.options?.id || 'mel-dialog')[0],
        EMPTY_STRING,
        [],
        this.options,
      );

      this.switch_page(this._first_page_name);
    } else this._$dialog.dialog('open');
  }

  /**
   * Cache la dialogue
   */
  hide() {
    this._$dialog.dialog('close');
  }

  /**
   * Supprime la dialogue
   */
  destroy() {
    this._$dialog.dialog('destroy');
  }

  /**
   * Met à jour une option de la dialogue
   * @param {string} name Nom de l'option
   * @param {*} value Valeur de l'option
   * @see {@link https://api.jqueryui.com/dialog/|jQueryUI}
   */
  update_option(name, value) {
    this._$dialog.dialog('option', name, value);
  }

  /**
   * Met à jours plusieurs options de la dialogue
   * @param {Object<string, *>} options  Options à ajouter
   */
  update_options(options) {
    for (const key in options) {
      if (Object.hasOwnProperty.call(options, key)) {
        const element = options[key];
        this.update_option(key, element);
      }
    }
  }

  /**
   * Ajoute une page à la dialogue
   * @param {DialogPage | string} page_or_name Page à ajouter, si vous ne souhaitez pas passer par un objet `DialogPage`, donnez le nom de la page, puis définissez les variables du paramètre déstructuré.
   * @param {Object} param1
   * @param {?____JsHtml | ?external:jQuery} param1.content Contenu de la page
   * @param {!string} param1.title Titre de la page
   * @param {RcmailDialogButton[]} param1.buttons Boutons de la page
   * @frommoduleparam JsHtml param1.content {@linkto ____JsHtml}
   */
  add_page(
    page_or_name,
    { content = undefined, title = EMPTY_STRING, buttons = [] },
  ) {
    this.page_manager.add_page(page_or_name, { content, title, buttons });
  }

  /**
   * Change la page de la dialogue
   * @param {string} name Nom de la page
   * @returns {void}
   * @throws {Error} Si la page n'existe pas
   */
  switch_page(name) {
    return this.page_manager.switch_page(name);
  }

  /**
   * Ajoute une page de choix à la modale.
   * @param {string} title Titre de la page
   * @param {Object} param1
   * @param {!string} param1.name Nom de la page
   * @param {RcmailDialogChoiceButton[]} param1.buttons Boutons de la page
   * @returns {void}
   */
  draw_choices(title, { name = 'choices', buttons = [] }) {
    return this.add_page(DialogPage.DrawChoices(title, { name, buttons }));
  }

  /**
   * Ajoute une page à 2 choix à la modale.
   * @param {string} title Titre de la page
   * @param {RcmailDialogChoiceButton} button1 Bouton de gauche
   * @param {RcmailDialogChoiceButton} button2 Bouton de droite
   * @param {string} name Id de la page
   * @returns {void}
   */
  draw_choice(title, button1, button2, name = 'choice') {
    return this.add_page(DialogPage.DrawChoice(title, button1, button2, name));
  }

  /**
   * Créer une dialog sans passer parge la création d'un objet `DialogPage`
   * @param {string} name Nom (id) de la page
   * @param {external:jQuery | ____JsHtml} content Contenu de la page
   * @param {Object} param2
   * @param {string} param2.title Titre de la page
   * @param {RcmailDialogButton[]} param2.buttons Boutons de la page
   * @param {Object} options Options de la boite de dialogue. Voir {@link https://api.jqueryui.com/dialog/}
   * @returns {MelDialog}
   * @frommoduleparam JsHtml content {@linkto ____JsHtml}
   */
  static Create(
    name,
    content,
    { title = EMPTY_STRING, options = {}, buttons = [] },
  ) {
    const page = content?.before
      ? new JQueryDialogPage(name, { content, title, buttons })
      : new DialogPage(name, { content, title, buttons });
    return new MelDialog(page, options);
  }

  /**
   *
   * @param {*} text
   * @param {*} callback
   * @param {*} param2
   * @returns
   */
  static CreateConfirmDialog(
    text,
    callback,
    {
      title = EMPTY_STRING,
      button_confirm = 'Ok',
      button_cancel = 'Annuler',
      name = 'confirm',
      oncancel = null,
      center = false,
      on_button_ok_object_created = null,
      on_button_cancel_object_created = null,
      on_page_object_created = null,
      options = {},
    },
  ) {
    const page = DialogPage.DrawConfirm(text, callback, {
      title,
      button_confirm,
      button_cancel,
      name,
      oncancel,
      center,
      on_button_cancel_object_created,
      on_button_ok_object_created,
      on_page_object_created,
    });

    return new MelDialog(page, options);
  }

  /**
   * Affiche une page de confirmation.
   *
   * Le bouton de confirmation peut ou pas s'activer au bout de X secondes.
   * @param {string} text Texte de la modale.
   * @param {Object} optionnals Paramètres optionnels
   * @param {EventClickCallback} [optionnals.onok=(()=>{})] Action à faire lors de la validation
   * @param {string} [optionnals.title=''] Titre de la modale
   * @param {string} [optionnals.button_confirm='Ok'] Texte du bouton de confirmation
   * @param {string} [optionnals.button_confirm='Annuler'] Texte du bouton d'annulation
   * @param {?EventClickCallback} [optionnals.oncancel=null] Action à l'annulation. Si null, ferme la modale.
   * @param {boolean} [optionnals.center=false] Centrer le texte ?
   * @param {boolean} [optionnals.waiting_button_enabled=0] En seconde, au bout de combien de temps le bouton de confirmation est actif.
   * @param {Object<string, any>} [optionnals.options={}]  Options de la boite de dialogue. Voir {@link https://api.jqueryui.com/dialog/}
   * @returns {DialogPage}
   * @example var can = await MelDialog.Confirm('Veux-tu supprimer cet espace ?', {waiting_button_enabled:5, title:'Confirmation'});
   * @static
   */
  static async Confirm(
    text,
    {
      onok = () => {},
      title = EMPTY_STRING,
      button_confirm = 'Ok',
      button_cancel = 'Annuler',
      oncancel = null,
      center = false,
      waiting_button_enabled = 0,
      options = {},
    },
  ) {
    return await new Mel_Promise((current_promise) => {
      current_promise.start_resolving();
      let function_ok = null;

      if (waiting_button_enabled > 0) {
        //On désactive le bouton de confirmation
        function_ok = (button) => {
          button.classes += ' disabled needToBeEnabled';
          return button;
        };
      }

      let dialog = this.CreateConfirmDialog(
        text,
        (...args) => {
          onok(...args);
          dialog.hide();
          //On détruit la modale après appuie du bouton
          setTimeout(
            (_dialog) => {
              _dialog.destroy();
            },
            1000,
            dialog,
          );
          current_promise.resolve(true);
        },
        {
          title,
          button_confirm,
          button_cancel,
          center,
          options,
          on_button_ok_object_created: function_ok,
          oncancel: (...args) => {
            if (oncancel) oncancel(...args);
            dialog.hide();
            //On détruit la modale après appuie du bouton
            setTimeout(
              (_dialog) => {
                _dialog.destroy();
              },
              1000,
              dialog,
            );
            current_promise.resolve(false);
          },
        },
      );

      dialog.show();

      //On vire le bouton "close"
      dialog._$dialog.parent().find('button.ui-dialog-titlebar-close').remove();

      if (waiting_button_enabled > 0) {
        dialog._$dialog
          .parent()
          .find('.needToBeEnabled')
          .append(
            $('<span>')
              .css('margin-left', '5px')
              .attr('id', 'ntbeid')
              .text(`(${waiting_button_enabled})`),
          );
        let it = 0;
        const interval = setInterval(
          (_$button, _waiting_button_enabled) => {
            if (it >= _waiting_button_enabled) {
              _$button.removeClass('disabled');
              _$button.find('#ntbeid').remove();
              clearInterval(interval);
            } else {
              _$button
                .find('#ntbeid')
                .text(`(${_waiting_button_enabled - it})`);

              ++it;
            }
          },
          1000,
          dialog._$dialog.parent().find('.needToBeEnabled'),
          waiting_button_enabled,
        );
      }
    });
  }
}

/**
 * Transforme en MelDialog
 * @returns {MelDialog}
 */
RcmailDialog.prototype.to_mel_dialog = function to_mel_dialog() {
  return MelDialog.Create('index', this.contents, {
    title: this.title,
    buttons: this.buttons,
    options: this.options,
  });
};