/**
 * @namespace EventView
 * @property {module:EventView} View
 * @property {module:EventView/Constants} Constants
 * @property {module:EventView/Parts} AbstractClassesParts
 * @property {module:EventView/Parts/CalendarOwner} CalendarOwner
 * @property {module:EventView/Parts/Alarm} Alarms
 * @property {module:EventView/Parts/Categories} Categories
 * @property {module:EventView/Parts/Guests} Guests
 * @property {module:EventView/Parts/Guests/FreeBusy} FreeBusy
 * @property {module:EventView/Parts/Location} Location
 * @property {module:EventView/Parts/Reccursivity} Reccursivity
 * @property {module:EventView/Parts/Sensitivity} Sensitivity
 * @property {module:EventView/Parts/State} State
 * @property {module:EventView/Parts/DateTime} DateTime
 * @property {module:EventView/Parts/Constants} PartsConstants
 */

/**
 * @module EventView
 * @local EventViewDialog
 */

import { MelEnumerable } from '../../classes/enum.js';
import { EMPTY_STRING } from '../../constants/constants.js';
import { MelHtml } from '../../html/JsHtml/MelHtml.js';
import {
  ATTENDEE_CONTAINER_SELECTOR,
  ATTENDEE_SELECTOR,
  CUSTOM_DIALOG_CLASS,
  FIRST_ARGUMENT,
  GUEST_DRAGG_CLASS,
  INTERNAL_LOCAL_CHANGE_WARNING_SELECTOR,
  LISTENER_SAVE_EVENT,
  LOADER_SELECTOR,
  LOCAL_CHANGE_WARNING_SELECTOR,
  MAIN_DIV_SELECTOR,
  MAIN_FORM_SELECTOR,
  RECURRING_WARNING_SELECTOR,
  WARNING_PANEL_CLICKED_CLASS,
  WARNING_PANEL_SELECTOR,
} from './event_view.constants.js';
import { AlarmPart } from './parts/alarmpart.js';
import { CalendarOwner } from './parts/calendarparts.js';
import { CategoryPart } from './parts/categoryparts.js';
import { GuestsPart } from './parts/guestspart.js';
import { LocationPartManager } from './parts/location_part.js';
import { RecPart } from './parts/recpart.js';
import { SensitivityPart } from './parts/sensitivitypart.js';
import { StatePart } from './parts/statepart.js';
import { TimePartManager } from './parts/timepart.js';

/**
 * Gère le panel d'avertissement
 * @class
 * @classdesc Gère la visibilité du panel d'avertissement
 * @package
 */
class WarningPanel {
  /**
   * Sélecteur du panel d'avertissement
   * @constructor
   * @param {string} selector La valeur par défaut est {@link WARNING_PANEL_SELECTOR}
   * @see {@link WARNING_PANEL_SELECTOR}
   */
  constructor(selector = WARNING_PANEL_SELECTOR) {
    this._init()._setup(selector);
  }

  /**
   * Initialise les variables membres de la classe
   * @private
   * @returns {WarningPanel} Chaîne
   */
  _init() {
    /**
     * Warning panel (Element jquery)
     * @readonly
     * @member
     * @type {$}
     */
    this.panel = null;

    return this;
  }

  /**
   * Assigne les variables membres de classe
   * @private
   * @param {string} selector Sélecteur du panel
   * @returns {WarningPanel} Chaîne
   */
  _setup(selector) {
    Object.defineProperty(this, 'panel', {
      get() {
        return $(selector);
      },
    });

    return this;
  }

  /**
   * Affiche le panel
   * @returns {WarningPanel} Chaîne
   */
  show() {
    this.panel.css('display', EMPTY_STRING);
    return this;
  }

  /**
   * Cache le panel
   * @returns {WarningPanel} Chaîne
   */
  hide() {
    this.panel.css('display', 'none');
    return this;
  }

  /**
   * Si le panel est affiché ou non
   * @returns {Boolean}
   */
  is_display() {
    return MelEnumerable.from(WarningPanel.CHILDS_ITEMS_SELECTORS)
      .select((x) => $(x))
      .where((x) => x.css('display') !== 'none')
      .any();
  }

  /**
   * Récupère le panel par défaut
   * @static
   * @returns {WarningPanel}
   */
  static Get() {
    return new WarningPanel();
  }

  /**
   * Réinitialise le panel par défaut
   *
   * Cache le panel et enlève la classe {@link WARNING_PANEL_CLICKED_CLASS}
   * @static
   * @returns {WarningPanel} Pannel par défaut
   * @see {@link WARNING_PANEL_CLICKED_CLASS}
   */
  static Reinit() {
    let panel = this.Get();
    panel.panel.css('display', 'none').removeClass(WARNING_PANEL_CLICKED_CLASS);
    return panel;
  }
}

/**
 * Liste des sélecteurs des enfants du panel d'avertissement
 * @static
 * @readonly
 * @type {string[]}
 */
WarningPanel.CHILDS_ITEMS_SELECTORS = [
  RECURRING_WARNING_SELECTOR,
  LOCAL_CHANGE_WARNING_SELECTOR,
  INTERNAL_LOCAL_CHANGE_WARNING_SELECTOR,
];

/**
 * Structure qui donne un nom à un sélecteur d'un champ
 * @class
 * @classdesc Structure qui donne un nom à un sélecteur d'un champ pour l'EventManager. L'idée est de générer les variables membres de l'EventManager à partir de cette classe.
 * @see {@link EventManager}
 * @package
 */
class EventField {
  /**
   *
   * @param {string} name Nom qui sera appeler pour récupérer le sélécteur dans l'EventManager
   * @param {string} selector Selecteur
   * @see {@link EventManager}
   */
  constructor(name, selector) {
    /**
     * Nom qui sera appeler pour récupérer le sélécteur dans l'EventManager
     * @member
     * @type {string}
     */
    this.name = null;
    /**
     * @member
     * @type {string}
     */
    this.selector = null;
    Object.defineProperties(this, {
      name: {
        get() {
          return name;
        },
      },
      selector: {
        get() {
          return selector;
        },
      },
    });
  }
}

/**
 * Contient tout les sélécteurs de la vue
 * @class
 * @classdesc Utilise une liste d'EventField pour générer ses variables membres. l'EventManager contient tout les sélécteurs des champs de la vue.
 * @see {@link EventField}
 * @package
 */
class EventManager {
  /**
   *
   * @param  {...EventField} events EventField qui vont générer les variables membres de cette instance
   */
  constructor(...events) {
    this._events = events ?? [];
  }

  /**
   * Ajoute un EventField
   * @param {EventField} event Ajoute un EventField qui va générer une variable membre
   */
  add(event) {
    this._events.push(event);
  }

  /**
   * Génère les variables membres à partir des EventField
   * @returns {EventManager} Chaîne
   */
  generate() {
    let config = {};

    for (const iterator of this._events) {
      config[iterator.name] = {
        get() {
          return $(iterator.selector);
        },
      };
    }

    Object.defineProperties(this, config);
    config = null;
    this._events = null;

    return this;
  }
}

/**
 * Structure qui contient les parties de la vue
 * @class
 * @classdesc Structure qui contient les parties de la vue. La class doit être initialisé via la fonction {@link EventParts~init}
 * @package
 */
class EventParts {
  /**
   *
   * @param {EventManager} inputs Champs de la vue qui seront utiliser par le plugin calendar pour envoyer les données au serveur.
   * @param {EventManager} fakes Champs visuels qui modifieront les "vrai" champs de la vue.
   * @param {$ | GlobalModal} dialog Modal qui contient la vue.
   */
  constructor(inputs, fakes, dialog) {
    this.status = new StatePart(
      inputs.select_status,
      inputs.select_status.parent().find('span.material-symbols-outlined'),
    );
    this.sensitivity = new SensitivityPart(
      inputs.select_sensivity,
      fakes.button_sensivity,
      fakes.button_sensivity.children().first(),
      dialog,
    );
    this.alarm = new AlarmPart(
      inputs.select_alarm,
      inputs.text_alarm,
      inputs.select_alarm_offset,
      fakes.select_alarm,
    );
    this.category = new CategoryPart(
      inputs.select_category,
      fakes.select_category,
      fakes.check_category,
      fakes.span_category_icon,
      fakes.button_add_members,
    );
    this.date = new TimePartManager(
      inputs.date_startdate,
      inputs.text_starttime,
      fakes.select_starttime,
      inputs.date_enddate,
      inputs.text_endtime,
      fakes.select_endtime,
      inputs.check_all_day,
    );
    this.guests = new GuestsPart(
      inputs.form_attendee,
      fakes.text_attendee,
      fakes.text_attedee_optional,
      fakes.text_attendee_animators,
      fakes.button_attendee_switch,
      this.date,
    );
    this.location = new LocationPartManager(
      fakes.div_eventtype,
      inputs.text_location,
      this.category,
    );
    this.recurrence = new RecPart(
      inputs.select_recurrence,
      fakes.select_recurrence,
    );
    this.owner = null;
  }

  /**
   * Initialise les parties de la vue
   * @param {*} ev Evènement du plugin `calendar`
   * @param {EventManager} inputs Champs visuels qui modifieront les "vrai" champs de la vue.
   */
  init(ev, inputs, fakes) {
    this.owner = new CalendarOwner(
      inputs.select_calendar_owner,
      inputs.select_calendar_owner.parent().find('span'),
      ev?.calendar_blocked ?? false,
    );
    this.status.onUpdate(ev.status ?? '');
    this.sensitivity.onUpdate(
      !ev?.id
        ? SensitivityPart.STATES.public
        : ev?.sensitivity ?? SensitivityPart.STATES.public,
    );
    this.alarm.init(ev);
    this.category.init(ev);
    this.date.init(ev);
    this.location.init(ev);
    this.guests.init(ev);
    this.recurrence.init(ev);

    this._init_no_modified(ev, inputs, fakes);
  }

  /**
   * Initialise les parties de la vue qui ne sont pas altéré par rapport à la vue par défaut du plugin calendar
   * @private
   * @param {*} ev Evènement du plugin `calendar`
   * @param {EventManager} inputs Champs visuels qui modifieront les "vrai" champs de la vue.
   */
  _init_no_modified(ev, inputs, fakes) {
    //Le bouton appèle la fonction _onButtonTitleClicked au click avec le contexte de l'élément jquery qui représente le champ du titre.
    fakes.button_erase_title.click(
      this._onButtonTitleClicked.bind(inputs.text_title),
    );
  }

  /**
   * Est appelé lorsque l'on clique sur le bouton pour effacer le titre
   *
   * Est bind dans la fonction {@link EventParts~_init_no_modified} par l'élément jquery qui représente le champ du titre.
   * @package
   * @this {external:jQuery}
   */
  _onButtonTitleClicked() {
    this.val(EMPTY_STRING);
  }

  destroy() {
    this.location.destroy();
  }
}

/**
 * @typedef {GlobalModal | external:jQuery} EventViewDialog
 */

/**
 * Gère les parties de la vue ainsi que le comportement de la dialog
 * @class
 * @classdesc Initialise la vue et gère le comportement de la dialog
 */
export class EventView {
  /**
   *
   * @param {*} event Evènement du plugin `calendar`
   * @param {EventViewDialog} dialog Modal qui contient la vue.
   */
  constructor(event, dialog) {
    EventView.INSTANCE = this;
    this._init()._setup(event, dialog)._main(event);
  }

  /**
   * Initialise les variables membres de la classe
   * @private
   * @returns {EventView} Chaîne
   */
  _init() {
    /**
     * Evènement du plugin `calendar`
     * @private
     * @member
     * @type {*}
     */
    this._event = null;
    /**
     * Dialog jquery ou GlobalModal
     * @private
     * @member
     * @type {EventViewDialog}
     */
    this._dialog = null;
    /**
     * Liste des inputs de la vue.
     *
     * Les inputs sont des éléments de la fenêtre de dialog qui sont utilisés pour récupérer des données.
     * @member
     * @type {EventManager}
     */
    this.inputs = null;
    /**
     * Liste des faux inputs de la vue.
     *
     * Ces inputs la sont seulement visuels et modifieront les "vrais" inputs.
     * @member
     * @type {EventManager}
     */
    this.fakes = null;
    /**
     * Liste des parties de la vue.
     * @member
     * @type {EventParts}
     */
    this.parts = null;

    return this;
  }

  /**
   * Assigne les variables membres de la classe
   * @private
   * @param {*} event Evènement du plugin `calendar`
   * @param {EventViewDialog} dialog Modal qui contient la vue.
   * @returns {EventView} Chaîne
   */
  _setup(event, dialog) {
    this._event = event;
    this._dialog = dialog;

    this.inputs = new EventManager(...EventView.true_selectors).generate();
    this.fakes = new EventManager(...EventView.false_selectors).generate();
    this.parts = new EventParts(this.inputs, this.fakes, dialog);

    return this;
  }

  /**
   * C'est ici que l'on va éffectuer toute les actions nécessaires à la création et l'initialisation de la fenêtre de dialog.
   * @private
   * @param {*} event Evènement du plugin `calendar`
   */
  _main(event) {
    let warning_panel = WarningPanel.Get();
    this.parts.init(event, this.inputs, this.fakes);

    this._generate_dialog_events();

    $(MAIN_FORM_SELECTOR).css('opacity', '1');
    $(LOADER_SELECTOR).css('display', 'none');

    if (this.is_jquery_dialog()) {
      if (warning_panel.is_display()) warning_panel.show();
      else warning_panel.hide();

      this._dialog.addClass(CUSTOM_DIALOG_CLASS);

      if (!this._dialog[0].ondrop)
        this._dialog[0].ondrop = this.on_drop.bind(this);

      this._update_dialog_buttons();
    } else {
      this._dialog.modal
        .find('iframe')[0]
        .contentWindow.$(MAIN_DIV_SELECTOR)
        .on('drop', this.on_drop.bind(this));
      warning_panel.hide();
    }

    this._generate_listeners();
  }

  /**
   * Génère les évènements de la dialog
   * @private
   */
  _generate_dialog_events() {
    if (
      this.is_jquery_dialog() &&
      !$._data(this._dialog[0], 'events')?.dialogbeforeclose
    ) {
      this._dialog.on(
        'dialogbeforeclose',
        this.on_dialog_before_close.bind(this),
      );
    }
  }

  /**
   * Met à jour les boutons de la dialog
   * @private
   */
  _update_dialog_buttons() {
    let $save_button = this._dialog
      .parent()
      .find('.ui-dialog-buttonpane .save')
      .hide();
    let $cancel_button = this._dialog
      .parent()
      .find('.ui-dialog-buttonpane .cancel')
      .hide();

    this._update_dialog_button(
      $save_button,
      'Sauvegarder',
      'arrow_right_alt',
    )._update_dialog_button($cancel_button, 'Annuler', 'cancel');

    if (!$save_button.hasClass('moved')) {
      $cancel_button.after($save_button);
    }

    $save_button.show();
    $cancel_button.show();
  }

  /**
   * Met à jour un bouton pour qu'il corresponde au visuel voulu
   * @param {external:jQuery} $button Boutton que l'on souhaite modifier
   * @param {string} text Texte du boutton
   * @param {string} icon Icon du bouton
   * @see {@link https://fonts.google.com/icons | Icons}
   * @returns {EventView} Chaînage
   * @private
   */
  _update_dialog_button($button, text, icon) {
    if (!$button.hasClass('mel-button')) {
      const jshtml = MelHtml.start
        .span()
        .text(text)
        .end()
        .icon(icon)
        .addClass('plus')
        .end();
      $button
        .removeClass('save')
        .removeClass('cancel')
        .addClass('mel-button')
        .html(jshtml.generate())
        .css({
          display: 'flex',
          'align-items': 'center',
        });

      if ($button.hasClass('btn-secondary'))
        $button.removeClass('btn-secondary').addClass('btn-danger');
    }

    return this;
  }

  /**
   * Génère les listeners de la vue
   * @private
   */
  _generate_listeners() {
    if ((rcmail._events?.[LISTENER_SAVE_EVENT]?.length ?? 0) > 0)
      delete rcmail._events[LISTENER_SAVE_EVENT];

    rcmail.addEventListener(LISTENER_SAVE_EVENT, this.before_save.bind(this));
  }

  /**
   * Si la dialog est une dialog jquery ou GlobalModal
   * @return {Boolean}
   */
  is_jquery_dialog() {
    return !this._dialog.on_click_minified;
  }

  /**
   * Récupère la dialog.
   * @returns {EventViewDialog}
   */
  get_dialog() {
    return this._dialog;
  }

  destroy() {
    this.parts.destroy();
  }

  /**
   * Est appelé lorsque la dialog est sur le point de se fermer
   *
   * Remet la modal dans son état d'origine et libère les variables.
   * @event
   */
  on_dialog_before_close() {
    $(MAIN_FORM_SELECTOR).css('opacity', '0');
    $(LOADER_SELECTOR).css('display', EMPTY_STRING);
    WarningPanel.Reinit();
    this.dialog_closed = true;
    this.parts.destroy();

    this.inputs = null;
    this.fakes = null;
    this.parts = null;
    EventView.INSTANCE = null;
  }

  /**
   * Est appelé lorsque l'on drop un élément dans la dialog
   * @event
   * @param {DragEvent} ev
   */
  on_drop(ev) {
    const autorized = [
      this.fakes.text_attendee,
      this.fakes.text_attedee_optional,
      this.fakes.text_attendee_animators,
    ];
    ev = ev.dataTransfer ? ev : ev.originalEvent;
    let data = JSON.parse(ev.dataTransfer.getData('text/plain'));

    ev.preventDefault();
    if (
      MelEnumerable.from(autorized)
        .select((x) => x.attr('id'))
        .where((x) => ev.target.id === x)
        .any()
    ) {
      $(ATTENDEE_SELECTOR.replaceAll(FIRST_ARGUMENT, data.email))
        .find('button')
        .click();
      $(ev.target).val(`${data.string},`).change();
    }

    $(
      `${ATTENDEE_CONTAINER_SELECTOR}, [data-linked="attendee-input"]`,
    ).removeClass(GUEST_DRAGG_CLASS);
  }

  /**
   * Est appelé avant que l'on sauvegarde l'évènement
   * @event
   * @async
   * @returns {Boolean} Si l'on peut sauvegarder ou non
   */
  async before_save() {
    let is_valid = true;

    await this.parts.location.waitComplete();

    if (!this.parts.location.is_valid()) {
      this.parts.location.invalid_action();
      is_valid = false;
    } else if (!this.parts.date.is_valid()) {
      this.parts.date.invalid_action();
      is_valid = false;
    }

    return is_valid;
  }

  /**
   * Génère une EventView. Permet d'améliorer la lisibilité du code.
   * @param {*} event Evènement du plugin `calendar`
   * @param {$ | GlobalModal} dialog Modal qui contient la vue.
   * @returns {EventView}
   */
  static Start(event, dialog) {
    return new EventView(event, dialog);
  }

  /**
   * Génère un EventField. Permet d'améliorer la lisibilité du code.
   * @param {string} name Nom qui sera appeler pour récupérer le sélécteur dans l'EventManager
   * @param {string} selector Selecteur
   * @returns {EventField}
   */
  static Create(name, selector) {
    return new EventField(name, selector);
  }
}

/**
 * Instance de la vue en cours
 * @type {EventView}
 * @static
 */
EventView.INSTANCE = null;

/**
 * Liste des sélecteurs de la vue.
 *
 * Se sont les sélecteurs des champs qui seront utiliser pour sauvegarder les données et qui seront envoyé au serveur.
 * @type {EventField[]}
 */
EventView.true_selectors = [
  EventView.Create('select_calendar_owner', '#edit-calendar'),
  EventView.Create('select_alarm', '#edit-alarm-item'),
  EventView.Create('text_alarm', 'input[name="alarmvalue[]"]'),
  EventView.Create('select_alarm_offset', 'select[name="alarmoffset[]"]'),
  EventView.Create('select_status', '#edit-event-status'),
  EventView.Create('text_title', '#edit-title'),
  EventView.Create('select_category', '#edit-categories'),
  EventView.Create('form_attendee', '#edit-attendees-form'),
  EventView.Create('date_startdate', '#edit-startdate'),
  EventView.Create('text_starttime', '#edit-starttime'),
  EventView.Create('date_enddate', '#edit-enddate'),
  EventView.Create('text_endtime', '#edit-endtime'),
  EventView.Create('form_recurrence', '.event-real-recs'),
  EventView.Create('text_location', '#edit-location'),
  EventView.Create('textarea_description', '#edit-description'),
  EventView.Create('select_sensivity', '#edit-sensitivity'),
  EventView.Create('check_all_day', '#edit-allday'),
  EventView.Create('select_recurrence', '#edit-recurrence-frequency'),
];

/**
 * Liste des sélecteurs de la vue.
 *
 * Se sont les sélecteurs des champs visuels qui seront utiliser pour modifier les "vrais" champs.
 * @type {EventField[]}
 */
EventView.false_selectors = [
  EventView.Create('select_alarm', '#mel-calendar-alarm'),
  EventView.Create('check_category', '#edit-wsp'),
  EventView.Create('span_category_icon', '#event-category-icon'),
  EventView.Create('select_category', '#mel-event-category'),
  EventView.Create('button_add_members', '#event-add-member'),
  EventView.Create('check_notify_user', '#notify-users'),
  EventView.Create('text_attendee', '#attendee-input'),
  EventView.Create('text_attedee_optional', '#attendee-input-optional'),
  EventView.Create('text_attendee_animators', '#attendee-animators'),
  EventView.Create('button_attendee_switch', '#mel-attendee-switch'),
  EventView.Create('select_starttime', '#mel-edit-starttime'),
  EventView.Create('select_endtime', '#mel-edit-endtime'),
  EventView.Create('select_recurrence', '#fake-event-rec'),
  EventView.Create('div_eventtype', '#events-type'),
  EventView.Create('button_sensivity', '#update-sensivity'),
  EventView.Create('button_erase_title', '#reset-title-button'),
];