/* eslint-disable no-console */
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

import { getFormCtrl } from '../controller/form_ctrl';
import { getListeners } from '../extension';
import { getFilterSrv } from '../service/filter_service';
import { getModalSrv } from '../service/modal_service';
import { getTemplateSrv } from '../service/template_service';
import { getUserStorage } from '../service/userstorage_service';
import { removeRndFromCtx } from '../util';

/**
 * Tiny WidgetHub plugin.
 *
 * @module      tiny_widgethub/plugin
 * @copyright   2024 Josep Mulet Pol <pep.mulet@gmail.com>
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

export class WidgetParamsCtrl {
   /** @type {import('../service/modal_service').ModalDialogue | null} */
   modal = null;

   /**
    * @type {import('./widgetpicker_ctrl').WidgetPickerCtrl | undefined }
    */
   parentCtrl;
   /**
    * @param {import('../plugin').TinyMCE} editor
    * @param {import('../service/userstorage_service').UserStorageSrv} userStorage
    * @param {import('../service/template_service').TemplateSrv} templateSrv
    * @param {import('../service/modal_service').ModalSrv} modalSrv
    * @param {import('../controller/form_ctrl').FormCtrl} formCtrl
    * @param {import('../service/filter_service').FilterSrv} filterSrv
    * @param {import('../options').Widget} widget
    */
   constructor(editor, userStorage, templateSrv, modalSrv, formCtrl, filterSrv, widget) {
      /** @type {import('../plugin').TinyMCE} */
      this.editor = editor;
      /** @type {import('../service/userstorage_service').UserStorageSrv} */
      this.storage = userStorage;
      /** @type {import('../service/template_service').TemplateSrv} */
      this.templateSrv = templateSrv;
      /** @type {import('../service/modal_service').ModalSrv} */
      this.modalSrv = modalSrv;
      /** @type {import('../controller/form_ctrl').FormCtrl} */
      this.formCtrl = formCtrl;
      this.filterSrv = filterSrv;
      /** @type {import('../options').Widget} */
      this.widget = widget;
   }
   /**
    * Displays a dialogue for configuring the parameters of the selected snpt
    */
   async handleAction() {
      // Show modal with buttons.
      const data = this.formCtrl.createContext(this.widget);
      const modal = await this.modalSrv.create('params', data, () => {
         this.modal?.destroy();
         this.modal = null;
      });
      /** @type {import('../service/modal_service').ListenerTracker} */
      const listenerTracker = (/** @type {Element}*/ el, /** @type {string} */ evType, /** @type {EventListener} */ handler) => {
         this.modal?.twhRegisterListener(el, evType, handler);
      };
      this.modal = modal;
      const bodyElem = modal.body[0];
      const formElem = modal.body.find("form")[0];
      modal.body.find(`a[href="#${data.idtabpane}_1"`).on("click", async () => {
         // Handle preview;
         const ctxFromDialogue = this.formCtrl.extractFormParameters(this.widget, formElem, true);
         await this.updatePreview(data.idtabpane, ctxFromDialogue);
      });
      this.formCtrl.attachRepeatable(bodyElem, this.widget);
      this.formCtrl.attachPickers(bodyElem, listenerTracker);
      modal.footer.show();
      modal.footer.find("button.tiny_widgethub-btn-secondary").on("click", async (evt) => {
         evt.preventDefault();
         // Go back to main menú
         // TODO detachPicker and detachRepeatable
         modal.hide();
         if (this.parentCtrl) {
            await this.parentCtrl.handleAction();
         }
      });
      modal.footer.find("button.tiny_widgethub-btn-primary").on("click", async (evt) => {
         evt.preventDefault();
         // Insert widget and close modal.
         const ctxFromDialogue = this.formCtrl.extractFormParameters(this.widget, formElem, true);
         await this.insertWidget(ctxFromDialogue);
         modal.hide();
      });

      // Change input fields visibilities upon conditions
      const selectmode = this.editor.selection.getContent().trim() != '';
      this.formCtrl.applyFieldWatchers(bodyElem, this.widget.defaults, this.widget, selectmode, listenerTracker);

      // Help circles require popover
      try {
         // @ts-ignore
         modal.body.popover({
            container: "body",
            selector: "[data-toggle=popover][data-trigger=hover]",
            trigger: "hover"
         });
      } catch (ex) {
         console.error(ex);
      }

      modal.show();
   }

   destroy() {
      this.modal?.destroy();
   }

   /**
    * @param {object} ctx
    * @returns {Promise<string>} The rendered template previously sanitized
    */
   render(ctx) {
      const toInterpolate = Object.assign(Object.create(null), this.widget.defaults, ctx ?? {});
      // Decide which template engine to use
      let engine = this.widget.prop('engine');
      return this.templateSrv.render(this.widget.template ?? "", toInterpolate,
         this.widget.I18n, engine);
   }

   /**
    * @param {Object.<string, any>} ctxFromDialogue
    * @returns {Promise<string>}
    */
   async generateInterpolatedCode(ctxFromDialogue) {
      const sel = this.editor.selection.getContent();
      // Decideix quin mode de selecció estam
      let interpoledComponentCode = await this.render(ctxFromDialogue);

      if (sel.trim() && this.widget.insertquery) {
         let query = this.widget.insertquery.trim();
         let replaceMode = query.startsWith('r!');
         if (replaceMode) {
            query = query.substring(2).trim();
         }
         // We are in selection mode
         const tmpDiv = document.createElement("div");
         tmpDiv.innerHTML = interpoledComponentCode;
         const insertPoint = tmpDiv.querySelector(query);
         if (insertPoint) {
            if (replaceMode) {
               // Replace the insertPoint by the interpolated HTML
               insertPoint.outerHTML = sel;
            } else {
               // Inserts the interpolated HTML into the insertPoint
               insertPoint.innerHTML = sel;
            }
            interpoledComponentCode = tmpDiv.innerHTML;
         } else {
            console.error("Cannot find insert point", query);
         }
      }
      return interpoledComponentCode;
   }

   /**
    * @param {string} idtabpane
    * @param {Object.<string, any>} ctxFromDialogue
    * @returns
    */
   async updatePreview(idtabpane, ctxFromDialogue) {
      if (!this.modal?.body) {
         return;
      }
      const body = this.modal.body[0];
      const interpoledCode = await this.generateInterpolatedCode(ctxFromDialogue);
      const previewPanel = body.querySelector(`#${idtabpane}_1`);
      if (previewPanel) {
         // Remove all preview iframes
         previewPanel.querySelectorAll('iframe').forEach(iframe => {
            iframe.src = 'about:blank';
            iframe.remove();
         });
         previewPanel.innerHTML = interpoledCode;
      }
   }

   /**
    * @param {Object.<string, any>} ctxFromDialogue
    * @param {boolean} [skipRecent]
    * @returns
    */
   async insertWidget(ctxFromDialogue, skipRecent) {
      if (!skipRecent) {
         /** @type {{key: string, p: Record<string, any>}[]} */
         const recentList = this.storage.getRecentUsed();
         const pos = recentList.map(e => e.key).indexOf(this.widget.key);
         if (pos >= 0) {
            recentList.splice(pos, 1);
         }
         // Never store values that are obtained from $RND
         const ctxFiltered = removeRndFromCtx(ctxFromDialogue, this.widget.parameters);
         recentList.unshift({ key: this.widget.key, p: ctxFiltered });
         if (recentList.length > 4) {
            recentList.splice(5, recentList.length - 4);
         }

         this.storage.setToSession("recent", JSON.stringify(recentList), true);
      }

      if (this.widget.isFilter()) {
         this.filterSrv.applyWidgetFilter([{
            name: this.widget.name,
            code: this.widget.prop('filter') ?? '',
            opts: ctxFromDialogue
         }], false);
         this.editor.focus();
         return;
      }
      const interpoledCode = await this.generateInterpolatedCode(ctxFromDialogue);
      // Normal insert mode
      // Deprecated: this.editor.selection.setContent(interpoledCode);
      this.editor.insertContent(interpoledCode);
      this.editor.focus();

      // Call any subscriber
      getListeners('widgetInserted').forEach(listener => listener(this.editor, this.widget, ctxFromDialogue));
   }
}


/**
 * @param {import('../plugin').TinyMCE} editor
 * @returns {(widget: import('../options').Widget) => WidgetParamsCtrl}
 */
export function getWidgetParamsFactory(editor) {
   // @ts-ignore
   return (widget) => new WidgetParamsCtrl(editor, getUserStorage(editor), getTemplateSrv(editor),
      getModalSrv(), getFormCtrl(editor), getFilterSrv(editor), widget);

}
