import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import { deepEqual } from '../helpers/deepEqual';

export default
@Component({
  name: 'ValidatableMixin',
  methods: {
    validate(force = false, value) {
      return this.doValidation(force, value);
    }
  }
})
class ValidatableMixin extends Vue {
  @Prop(Boolean) manualValidation;
  @Prop(Boolean) disabled;
  @Prop(Boolean) readonly;
  @Prop(Boolean) error;
  @Prop(Boolean) success;

  @Prop({ type: [Number, String], default: 1 }) errorCount;
  @Prop({ type: [String, Array], default: () => [] }) errorMessages;
  @Prop({ type: [String, Array], default: () => [] }) successMessages;
  @Prop({ type: [String, Array], default: () => [] }) messages;
  @Prop({ type: Array, default: () => [], validator: (value) => Array.isArray(value) }) rules;
  @Prop({ required: false }) value;

  errorBucket = [];

  hasFocused = false;

  hasInput = false;

  isFocused = false;

  isResetting = false;

  valid = false;

  data () {
    return { lazyValue: this.value }
  }

  beforeMount() {
    this.doValidation();
  }

  created () {
    this.form && this.form.register(this);
  }

  beforeDestroy () {
    this.form && this.form.unregister(this);
  }

  genInternalMessages (messages) {
    if (!messages) return [];
    else if (Array.isArray(messages)) return messages;
    else return [messages];
  }

  reset () {
    this.isResetting = true;
    this.internalValue = Array.isArray(this.internalValue) ? [] : undefined
  }

  resetValidation () {
    this.isResetting = true;
  }

  doValidation(force = false, value) {
    const errorBucket = [];
    value = value || this.internalValue;

    if (force) {
      this.hasInput = this.hasFocused = true;
    }

    for (let index = 0; index < this.rules.length; index++) {
      const rule = this.rules[index];
      const valid = typeof rule === 'function' ? rule(value, this.$t.bind(this)) : rule;

      if (typeof valid === 'string') {
        errorBucket.push(valid);
      } else if (typeof valid !== 'boolean') {
        console.error(`Rules should return a string or boolean, received '${typeof valid}' instead`, this);
      }
    }
    this.errorBucket = errorBucket;
    this.valid = this.error ? false : errorBucket.length === 0;
    return this.valid;
  }

  get hasError () {
    return this.internalErrorMessages.length > 0 || (this.errorBucket && this.errorBucket.length) > 0 || this.error;
  }

  get hasSuccess () {
    return this.internalSuccessMessages.length > 0 || this.success;
  }

  get externalError () {
    return this.internalErrorMessages.length > 0 || this.error
  }

  get hasMessages () {
    return this.validationTarget.length > 0
  }

  get hasState () {
    return this.hasSuccess || (this.shouldValidate && this.hasError);
  }

  get internalErrorMessages () {
    return this.genInternalMessages(this.errorMessages)
  }

  get internalMessages () {
    return this.genInternalMessages(this.messages)
  }

  get internalSuccessMessages () {
    return this.genInternalMessages(this.successMessages)
  }

  get internalValue () {
    return this.lazyValue
  }

  set internalValue (val) {
    this.lazyValue = val;
    this.$emit('input', val);
  }

  get shouldValidate () {
    if (this.externalError) return true;
    if (this.isResetting) return false;

    return this.manualValidation
      ? this.hasFocused && !this.isFocused
      : (this.hasInput || this.hasFocused)
  }

  get validations () {
    return this.validationTarget.slice(0, Number(this.errorCount))
  }

  get validationState () {
    if (this.hasError && this.shouldValidate) return 'error'
    if (this.hasSuccess) return 'success'
    return undefined
  }

  getValidationState () {
    if (this.hasError && this.shouldValidate) return 'error'
    if (this.hasSuccess) return 'success'
    return undefined
  }

  get validationTarget () {
    if (this.internalErrorMessages.length > 0) {
      return this.internalErrorMessages
    } else if (this.successMessages.length > 0) {
      return this.internalSuccessMessages
    } else if (this.messages.length > 0) {
      return this.internalMessages
    } else if (this.shouldValidate) {
      return this.errorBucket
    } else return []
  }

  @Watch('rules', { deep: true })
  onRulesChange (newVal, oldVal) {
    if (deepEqual(newVal, oldVal)) return
    this.doValidation()
  }

  @Watch('internalValue')
  onInternalValueChanged () {
    // If it's the first time we're setting input,
    // mark it with hasInput
    this.hasInput = true;
    this.manualValidation || this.$nextTick(this.doValidation);
  }

  @Watch('isFocused')
  onIsFocusedChanged (val) {
    // Should not check validation
    // if disabled or readonly
    if (!val && !this.disabled && !this.readonly) {
      this.hasFocused = true
      this.manualValidation && this.doValidation()
    }
  }

  @Watch('isResetting')
  onIsResettingChanged () {
    setTimeout(() => {
      this.hasInput = false;
      this.hasFocused = false;
      this.isResetting = false;
      this.doValidation();
    }, 0)
  }

  @Watch('hasError')
  onHasErrorChanged (val) {
    if (this.shouldValidate) {
      this.$emit('update:error', val)
    }
  }

  @Watch('value')
  onValueChanged (val) {
    this.lazyValue = val
  }
}
