// SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

/* global:writable */

import { Utility } from '../../core/utility';
import { Datepicker } from '../form/datepicker';
import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager';
import './mass-input.sass';

const MASS_INPUT_CELL_SELECTOR = '.massinput__cell';
const MASS_INPUT_ADD_CELL_SELECTOR = '.massinput__cell--add';
const MASS_INPUT_SUBMIT_BUTTON_CLASS = 'massinput__submit-button';
const MASS_INPUT_INITIALIZED_CLASS = 'mass-input--initialized';

const MASS_INPUT_ADD_CHANGE_FIELD_SELECTOR = 'select, input[type=radio]';

// const MASS_INPUT_SAFETY_SUBMITTED_CLASS = 'massinput--safety-submitted';
// const MASS_INPUT_SAFETY_SUBMITTED_TIMEOUT = 1000;

@Utility({
  selector: '[uw-mass-input]',
})
export class MassInput {

  _element;
  _app;
  _global;

  _massInputId;
  _massInputFormSubmitHandler;
  _massInputForm;

  _changedAdd = new Array();

  _eventManager;

  constructor(element, app) {
    if (!element) {
      throw new Error('Mass Input utility cannot be setup without an element!');
    }

    this._element = element;
    this._app = app;

    this._eventManager = new EventManager();

    if (global !== undefined)
      this._global = global;
    else if (window !== undefined)
      this._global = window;
    else
      throw new Error('Cannot setup Mass Input utility without window or global');


    if (this._element.classList.contains(MASS_INPUT_INITIALIZED_CLASS)) {
      return false;
    }

    this._massInputId = this._element.dataset.massInputIdent || '_';
    this._massInputForm = this._element.closest('form');

    if (!this._massInputForm) {
      throw new Error('Mass Input utility cannot be setup without being wrapped in a <form>!');
    }

    this._massInputFormSubmitHandler = this._makeSubmitHandler();

    // setup submit buttons inside this massinput so browser
    // uses correct submit button for form submission.
    const buttons = this._getMassInputSubmitButtons();
    buttons.forEach((button) => {
      this._setupSubmitButton(button);
    });
    
    const submitEv = new EventWrapper(EVENT_TYPE.SUBMIT, this._massInputFormSubmitHandler.bind(this), this._massInputForm);
    const keyPressEv = new EventWrapper(EVENT_TYPE.KEYDOWN, this._keypressHandler.bind(this), this._massInputForm);
    this._eventManager.registerListeners([submitEv, keyPressEv]);

    Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(this._setupChangedHandlers.bind(this));

    // mark initialized
    this._element.classList.add(MASS_INPUT_INITIALIZED_CLASS);
  }

  destroy() {
    this._reset();
    this._eventManager.cleanUp();
    this._element.classList.remove(MASS_INPUT_INITIALIZED_CLASS);
  }

  _setupChangedHandlers(addCell) {
    Array.from(addCell.querySelectorAll(MASS_INPUT_ADD_CHANGE_FIELD_SELECTOR)).forEach(inputElem => {
      if (inputElem.closest('[uw-mass-input]') !== this._element)
        return;
      const changeEv = new EventWrapper(EVENT_TYPE.CHANGE, (() => { this._changedAdd.push(addCell); }).bind(this), inputElem);
      this._eventManager.registerNewListener(changeEv);
    });
  }

  _unsafeAddCells() {
    let changedAdd = this._changedAdd;
    
    Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(addCell => addCell.querySelectorAll('input:not([type=checkbox]):not([type=radio])').forEach(inputElem => {
      if (inputElem.closest('[uw-mass-input]') === this._element && inputElem.value !== '' && (inputElem.defaultValue || inputElem.getAttribute('value')) !== inputElem.value)
        changedAdd.push(addCell);
    }));

    return changedAdd;
  }

  _makeSubmitHandler() {
    const method = this._massInputForm.getAttribute('method') || 'POST';
    const url = this._massInputForm.getAttribute('action') || window.location.href;
    const enctype = this._massInputForm.getAttribute('enctype') || 'application/json';

    let requestFn;
    if (this._app.httpClient[method.toLowerCase()]) {
      requestFn = this._app.httpClient[method.toLowerCase()].bind(this._app.httpClient);
    }

    return (event) => {
      let submitButton;
      let isAddCell;
      
      let isMassInputSubmit = (() => {
        let activeElement;

        // check if event occured from either a mass input add/delete button or
        // from inside one of massinput's inputs (i.e. a child is focused/active)
        activeElement = this._element.querySelector(':focus, :active');

        if (!activeElement) {
          return false;
        }

        // find the according massinput cell thats hosts the element that triggered the submit
        const massInputCell = activeElement.closest(MASS_INPUT_CELL_SELECTOR);
        if (!massInputCell) {
          return false;
        }

        submitButton = massInputCell.querySelector('.' + MASS_INPUT_SUBMIT_BUTTON_CLASS);
        if (!submitButton) {
          return false;
        }

        isAddCell = massInputCell.matches(MASS_INPUT_ADD_CELL_SELECTOR);
        const submitButtonIsActive = submitButton.matches(':focus, :active');
        // if the cell is not an add cell the active element must at least be the cells submit button
        if (!isAddCell && !submitButtonIsActive) {
          return false;
        }

        return true;
      })();
      
      let unsafeAddCells = this._unsafeAddCells();

      if (unsafeAddCells.length > 0 && !isMassInputSubmit) {
        let addButtons = Array.from(unsafeAddCells[0].querySelectorAll('.' + MASS_INPUT_SUBMIT_BUTTON_CLASS)).filter(addButton => addButton.closest('[uw-mass-input]') === this._element);

        if (addButtons.length > 0) {
          submitButton = addButtons[0];
          isMassInputSubmit = true;
          isAddCell = false;

          this._element.scrollIntoView();
          // this._element.classList.add(MASS_INPUT_SAFETY_SUBMITTED_CLASS);
          // this._global.setTimeout(() => { this._element.classList.remove(MASS_INPUT_SAFETY_SUBMITTED_CLASS) }, MASS_INPUT_SAFETY_SUBMITTED_TIMEOUT)
        }
      }

      if (!isMassInputSubmit) {
        return false;
      }

      event.preventDefault();
      const requestBody = this._serializeForm(submitButton, enctype);

      if (requestFn && requestBody) {
        const headers = {'Mass-Input-Shortcircuit': this._massInputId};

        if (enctype !== 'multipart/form-data') {
          headers['Content-Type'] = enctype;
        }

        requestFn({
          url: url,
          headers: headers,
          body: requestBody,
        }).then((response) => {
          return this._app.htmlHelpers.parseResponse(response);
        }).then((response) => {
            this._processResponse(response.element);
            if (isAddCell) {
              this._reFocusAddCell();
            }
          });
      }
    };
  }

  _keypressHandler = (event) => {
    if (event.keyCode !== 13) {
      return false;
    }

    if (this._massInputFormSubmitHandler) {
      return this._massInputFormSubmitHandler(event);
    }
  };

  _getMassInputSubmitButtons() {
    return Array.from(this._element.querySelectorAll('button[type="submit"][name][value], .' + MASS_INPUT_SUBMIT_BUTTON_CLASS));
  }

  _setupSubmitButton(button) {
    button.setAttribute('type', 'button');
    button.classList.add(MASS_INPUT_SUBMIT_BUTTON_CLASS);
    const buttonClickEv = new EventWrapper(EVENT_TYPE.CLICK, this._massInputFormSubmitHandler.bind(this), button);
    this._eventManager.registerNewListener(buttonClickEv);
  }

  _resetSubmitButton(button) {
    button.setAttribute('type', 'submit');
    button.classList.remove(MASS_INPUT_SUBMIT_BUTTON_CLASS);
  }

  _processResponse(responseElement) {
    this._element.innerHTML = '';
    this._element.appendChild(responseElement);

    this._reset();

    this._app.utilRegistry.initAll(this._element);
  }

  _serializeForm(submitButton, enctype) {
    const rawFormData = new FormData(this._massInputForm);
    const extraneousKeys = new Set();
    for (const k of rawFormData.keys()) {
      const n = k.replace(/\[\]$/, '');
      const inputElements = Array.from(this._massInputForm.querySelectorAll(`[name="${CSS.escape(n)}"]`));
      const isBelowMassinput = inputElements.some((elem) => this._element.contains(elem));
      const isFile = inputElements.some((elem) => elem.type === 'file');

      if (!isBelowMassinput && isFile)
        extraneousKeys.add(k);
    }
    for (const k of extraneousKeys)
      rawFormData.delete(k);
    // create new FormData and format any date values
    const formData = Datepicker.unformatAll(this._massInputForm, rawFormData);

    // manually add name and value of submit button to formData
    formData.append(submitButton.name, submitButton.value);

    if (enctype === 'application/x-www-form-urlencoded') {
      return new URLSearchParams(formData);
    } else if (enctype === 'multipart/form-data') {
      return formData;
    } else {
      throw new Error('Unsupported form enctype: ' + enctype);
    }
  }

  _reFocusAddCell() {
    const addCell = this._element.querySelector(MASS_INPUT_ADD_CELL_SELECTOR);
    if (!addCell) {
      return false;
    }

    const addCellInput = addCell.querySelector('input:not([type="hidden"])');
    if (addCellInput) {
      // Clearing of add-inputs is done in the backend
      addCellInput.focus();
    }
  }

  _reset() {

    const buttons = this._getMassInputSubmitButtons();
    buttons.forEach((button) => {
      this._resetSubmitButton(button);
    });
  }
}
