<template>
  <div
    class="formulate-input"
    :data-classification="classification"
    :data-has-errors="hasErrors"
    :data-is-showing-errors="hasErrors && showFieldErrors"
    :data-type="type"
  >
    <div class="formulate-input-wrapper">
      <slot
        v-if="context.label && context.labelPosition === 'before'"
        name="label"
        v-bind="context"
      >
        <label
          class="formulate-input-label formulate-input-label--before"
          :for="context.attributes.id"
          v-text="context.label"
        />
      </slot>
      <slot v-bind="context">
        <component
          :is="context.component"
          :context="context"
        />
      </slot>
      <slot
        v-if="context.label && context.labelPosition === 'after'"
        name="label"
        v-bind="context.label"
      >
        <label
          class="formulate-input-label formulate-input-label--after"
          :for="context.attributes.id"
          v-text="context.label"
        />
      </slot>
    </div>
    <div
      v-if="help"
      class="formulate-input-help"
      v-text="help"
    />
    <FormulateInputErrors
      v-if="showFieldErrors"
      :errors="mergedErrors"
    />
  </div>
</template>

<script>
import context from './libs/context'
import { shallowEqualObjects, parseRules } from './libs/utils'
import nanoid from 'nanoid'

export default {
  name: 'FormulateInput',
  inheritAttrs: false,
  inject: {
    formulateFormSetter: { default: undefined },
    formulateFormRegister: { default: undefined },
    getFormValues: { default: () => () => ({}) }
  },
  model: {
    prop: 'formulateValue',
    event: 'input'
  },
  props: {
    type: {
      type: String,
      default: 'text'
    },
    name: {
      type: [Boolean, String],
      default: true
    },
    /* eslint-disable */
    formulateValue: {
      default: ''
    },
    value: {
      default: false
    },
    /* eslint-enable */
    options: {
      type: [Object, Array, Boolean],
      default: false
    },
    optionGroups: {
      type: [Object, Boolean],
      default: false
    },
    id: {
      type: [String, Boolean, Number],
      default: false
    },
    label: {
      type: [String, Boolean],
      default: false
    },
    labelPosition: {
      type: [String, Boolean],
      default: false
    },
    help: {
      type: [String, Boolean],
      default: false
    },
    debug: {
      type: Boolean,
      default: false
    },
    errors: {
      type: [String, Array, Boolean],
      default: false
    },
    validation: {
      type: [String, Boolean, Array],
      default: false
    },
    validationName: {
      type: [String, Boolean],
      default: false
    },
    error: {
      type: [String, Boolean],
      default: false
    },
    errorBehavior: {
      type: String,
      default: 'blur',
      validator: function (value) {
        return ['blur', 'live'].includes(value)
      }
    },
    showErrors: {
      type: Boolean,
      default: false
    },
    showImage: {
      type: Boolean,
      default: true
    },
    uploadUrl: {
      type: [String, Boolean],
      default: false
    },
    uploader: {
      type: [Function, Object, Boolean],
      default: false
    },
    uploadBehavior: {
      type: Boolean,
      default: true
    },
    preventWindowDrops: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      defaultId: nanoid(9),
      localAttributes: {},
      internalModelProxy: this.formulateValue,
      behavioralErrorVisibility: (this.errorBehavior === 'live'),
      validationErrors: []
    }
  },
  computed: {
    ...context,
    classification () {
      const classification = this.$formulate.classify(this.type)
      return (classification === 'box' && this.options) ? 'group' : classification
    },
    component () {
      return (this.classification === 'group') ? 'FormulateInputGroup' : this.$formulate.component(this.type)
    }
  },
  watch: {
    '$attrs': {
      handler (value) {
        this.updateLocalAttributes(value)
      },
      deep: true
    },
    internalModelProxy (newValue, oldValue) {
      this.performValidation()
      if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
        this.context.model = newValue
      }
    },
    formulateValue (newValue, oldValue) {
      if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
        this.context.model = newValue
      }
    }
  },
  created () {
    if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') {
      this.formulateFormRegister(this.nameOrFallback, this)
    }
    this.updateLocalAttributes(this.$attrs)
    this.performValidation()
  },
  methods: {
    updateLocalAttributes (value) {
      if (!shallowEqualObjects(value, this.localAttributes)) {
        this.localAttributes = value
      }
    },
    performValidation () {
      const rules = parseRules(this.validation, this.$formulate.rules())
      Promise.all(
        rules.map(([rule, args]) => {
          return rule(this.context.model, ...args)
            .then(res => res ? false : this.$formulate.validationMessage(rule.name, {
              args,
              name: this.mergedValidationName,
              value: this.context.model,
              vm: this,
              formValues: this.getFormValues()
            }))
        })
      )
        .then(result => result.filter(result => result))
        .then(errorMessages => { this.validationErrors = errorMessages })
    }
  }
}
</script>
