<template>
  <div class="itf-form">
    <slot :valid="valid" :validate="doValidation" :reset="resetValidation"/>
  </div>
</template>
<script>
import { Vue, Component, Watch, Model } from 'vue-property-decorator';
import * as validators from '../../helpers/validators';

Vue.prototype.$v = validators;

export default @Component({
  name: 'itfForm',
  components: {},
  provide() {
    return { form: this }; // do not use Provide from vue-property-decorator
  }
})
class itfForm extends Vue {
  @Model('input') valid;

  watchers = [];

  components = [];

  errorBag = {};

  register(component) {
    const unwatch = this.watchComponent(component);
    this.components.push(component);
    this.watchers.push(unwatch);
  }

  unregister(component) {
    const found = this.components.find(i => i._uid === component._uid);

    if (!found) {
      return;
    }

    const unwatch = this.watchers.find(i => i._uid === found._uid);
    unwatch.valid && unwatch.valid();
    unwatch.shouldValidate && unwatch.shouldValidate();

    this.watchers = this.watchers.filter(i => i._uid !== found._uid);
    this.components = this.components.filter(i => i._uid !== found._uid);
    this.$delete(this.errorBag, found._uid);
  }

  watchComponent(component) {
    const watcher = (component) => {
      return component.$watch('hasError', (val) => {
        this.$set(this.errorBag, component._uid, val);
      }, { immediate: true });
    };

    const watchers = {
      _uid: component._uid,
      valid: undefined,
      shouldValidate: undefined
    };

    if (this.lazyValidation) {
      // Only start watching components if we need to
      watchers.shouldValidate = component.$watch('shouldValidate', (val) => {
        if (!val) {
          return;
        }

        // Only watch if we're not already doing it
        if (this.errorBag.hasOwnProperty(component._uid)) {
          return;
        }

        watchers.valid = watcher(component);
      });
    } else {
      watchers.valid = watcher(component);
    }
    return watchers;
  }

  @Watch('errorBag', { deep: true, immediate: true })
  onErrorBagChanged() {
    const errors = Object.values(this.errorBag).includes(true);
    this.$emit('input', !errors);
  }

  doValidation(showError = true) {
    const invalidComponents = this.components.filter(
      (component) => (component.doValidation && !component.doValidation(true)) || (component.validate && !component.validate(true))
    );
    // @todo remove validate when migrate from vuetify
    const isValid = !invalidComponents.length;
    if (showError && !isValid && this.$showError) {
      this.$showError(this.$t('components.pleaseFixTheErrorsBelowToProceed'), { duration: 3000 });
    }
    return isValid;
  }

  reset() {
    for (let i = this.components.length; i--;) {
      this.components[i].reset();
    }
    if (this.lazyValidation) {
      // Account for timeout in validatable
      setTimeout(() => {
        this.errorBag = {};
      }, 0);
    }
  }

  resetValidation() {
    for (let i = this.components.length; i--;) {
      this.components[i].resetValidation();
    }
    if (this.lazyValidation) {
      // Account for timeout in validatable
      setTimeout(() => {
        this.errorBag = {};
      }, 0);
    }
  }
}
</script>
