import { Utility } from '../../core/utility';
import { AUTO_SUBMIT_BUTTON_UTIL_SELECTOR } from './auto-submit-button';
import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input';

import { InteractiveFieldset } from './interactive-fieldset';
import { Datepicker } from './datepicker';

import * as defer from 'lodash.defer';

/**
 * Key generator from an arbitrary number of FormData objects.
 * @param  {...any} formDatas FormData objects
 */
function* generatorFromFormDatas(...formDatas) {
  for (let formData of formDatas) {
    yield* formData.keys();
  }
}

const NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS = 'navigate-away-prompt--initialized';
const NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT = '[uw-no-navigate-away-prompt]';

@Utility({
  selector: 'form',
})
export class NavigateAwayPrompt {

  _element;

  _initFormData;
  _unloadDueToSubmit = false;

  constructor(element) {
    if (!element) {
      throw new Error('Navigate Away Prompt utility needs to be passed an element!');
    }

    this._element = element;

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

    // ignore forms that get submitted automatically
    if (this._element.querySelector(AUTO_SUBMIT_BUTTON_UTIL_SELECTOR) || this._element.querySelector(AUTO_SUBMIT_INPUT_UTIL_SELECTOR)) {
      return;
    }

    if (this._element.matches(NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT)) {
      return;
    }

    if (this._element.attributes.target === '_blank') {
      return;
    }

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

  static startAfter = [ Datepicker, InteractiveFieldset ];

  start() {
    if (!this._isActive())
      return;
    
    this._initFormData = new FormData(this._element);
    window.addEventListener('beforeunload', this._beforeUnloadHandler.bind(this));

    this._element.addEventListener('submit', () => {
      this._unloadDueToSubmit = true;
      defer(() => { this._unloadDueToSubmit = false; } ); // Restore state after event loop is settled
    });
  }

  destroy() {
    this._element.classList.remove(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
  }

  _isActive() {
    return this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
  }

  _beforeUnloadHandler(event) {
    if (!this._isActive() || !this._initFormData)
      return;
    
    // compare every value of the current FormData with every corresponding value of the initial FormData and set formDataHasChanged to true if there is at least one change
    const currentFormData = new FormData(this._element);
    let formDataHasChanged = false;
    for (const key of generatorFromFormDatas(this._initFormData, currentFormData)) {
      if (currentFormData.get(key) !== this._initFormData.get(key)) {
        formDataHasChanged = true;
        break;
      }
    }

    // allow the event to happen if the form was not touched by the
    // user (i.e. if the current FormData is equal to the initial FormData)
    // or the unload event was initiated by a form submit
    if (!formDataHasChanged || this._unloadDueToSubmit)
      return;

    // cancel the unload event. This is the standard to force the prompt to appear.
    event.preventDefault();
    // chrome
    event.returnValue = true;
    // for all non standard compliant browsers we return a truthy value to activate the prompt.
    return true;
  }
}
