<template>
  <div :class="classes">
    <slot name="prefix" />
    <div
      class="formulate-element-input-wrapper"
      :data-type="type"
      :data-classification="classification"
      :data-is-disabled="disabled"
    >
      <!-- TEXT STYLE INPUTS -->
      <label
        :for="id"
        v-text="label"
        v-if="label && (!isBoxInput || optionList.length > 1)"
      />
      <input
        ref="input"
        :class="elementClasses"
        :type="type"
        :name="name"
        v-model="val"
        v-bind="attributes"
        v-if="isTextInput"
        @blur="setBlurState"
        @focus="setFocusState"
        :disabled="disabled"
        :step="step"
      >
      <textarea
        ref="textarea"
        :class="elementClasses"
        :type="type"
        :name="name"
        v-model="val"
        v-bind="attributes"
        v-if="isTextareaInput"
        @blur="setBlurState"
        @focus="setFocusState"
        :disabled="disabled"
      />
      <!-- BUTTON INPUTS -->
      <button
        :type="type"
        :class="elementClasses"
        v-if="isButtonInput"
        :disabled="disabled || (type === 'submit' && (form.hasErrors && form.behavior === 'live'))"
      >
        <slot
          v-if="$slots.button"
          name="button"
        />
        <span
          v-text="label || name"
          v-else
        />
      </button>
      <!-- SELECT INPUTS -->
      <select
        v-bind="attributes"
        v-if="isSelectInput"
        :class="elementClasses"
        :name="name"
        v-model="val"
        @blur="setBlurState"
        @focus="setFocusState"
        :disabled="disabled"
      >
        <template
          v-if="!optionGroups"
        >
          <option
            v-for="option in optionList"
            :value="option.value"
            :key="option.id"
            v-bind="option.attributes || {}"
            v-text="option.label"
          />
        </template>
        <template
          v-else
        >
          <optgroup
            v-for="(list, label) in optionGroups"
            :key="label"
            :label="label"
          >
            <option
              v-for="option in createOptionList(list)"
              :value="option.value"
              :key="option.value"
              v-bind="option.attributes || {}"
              v-text="option.label"
            />
          </optgroup>
        </template>
      </select>
      <!-- BOX INPUTS -->
      <div
        class="formulate-element-box-input-wrap"
        v-if="isBoxInput"
      >
        <div
          class="formulate-element-box-input-group"
          v-for="option in optionList"
          :key="option.id"
        >
          <input
            type="radio"
            :class="elementClasses"
            :name="name"
            :id="option.id"
            :value="option.value"
            :key="`${option.id}-input`"
            v-bind="attributes"
            v-model="val"
            v-if="type === 'radio'"
            @blur="setBlurState"
            @focus="setFocusState"
            :disabled="disabled || option.disabled"
          >
          <input
            type="checkbox"
            :class="elementClasses"
            :name="name"
            :id="option.id"
            :value="option.value"
            :key="`${option.id}-input`"
            v-bind="attributes"
            v-model="val"
            v-if="type === 'checkbox'"
            @blur="setBlurState"
            @focus="setFocusState"
            :disabled="disabled || option.disabled"
          >
          <label
            :for="option.id"
            :key="`${option.id}-label`"
            v-text="option.label"
          />
        </div>
      </div>
      <!-- CUSTOM SLOT INPUTS -->
      <slot v-if="hasCustomInput" />

      <!-- UNSUPPORTED INPUT -->
      <div
        style="background-color: red; color: white"
        v-if="isUnsupportedInput"
        v-text="`Unsupported field type: “${type}”.`"
      />
    </div>
    <div
      class="formulate-help"
      v-if="help"
      v-text="help"
    />
    <transition
      name="formulate-errors"
    >
      <transition-group
        tag="ul"
        name="formulate-error-list"
        class="formulate-errors"
        v-if="shouldShowErrors && localAndValidationErrors.length"
      >
        <li
          v-for="error in localAndValidationErrors"
          v-text="error"
          :key="error"
        />
      </transition-group>
    </transition>
    <slot name="suffix" />
  </div>
</template>

<script>
import {inputTypes, equals, reduce, filter} from '../utils'
import shortid from 'shortid'

export default {
  props: {
    type: {
      type: [String, Boolean],
      default: 'text'
    },
    name: {
      type: String,
      required: true
    },
    initial: {
      type: [String, Number, Boolean],
      default: false
    },
    validation: {
      type: [String, Boolean],
      default: false
    },
    errors: {
      type: Array,
      default: () => []
    },
    label: {
      type: [String, Boolean],
      default: false
    },
    id: {
      type: [String],
      default: () => shortid.generate()
    },
    min: {
      type: [String, Number, Boolean],
      default: () => false
    },
    max: {
      type: [String, Number, Boolean],
      default: () => false
    },
    maxlength: {
      type: [String, Number, Boolean],
      default: () => false
    },
    pattern: {
      type: [String, Number, Boolean],
      default: () => false
    },
    minlength: {
      type: [String, Number, Boolean],
      default: () => false
    },
    placeholder: {
      type: [String, Number, Boolean],
      default: () => false
    },
    step: {
      type: [String, Number, Boolean],
      default: () => false
    },
    options: {
      type: [Object, Array],
      default: () => []
    },
    optionGroups: {
      type: [Boolean, Object],
      default: false,
      validator: function (value) {
        if (value === false) {
          return true
        } else if (typeof value === 'boolean') {
          return false
        }
        return true
      }
    },
    multiple: {
      type: Boolean,
      default: false
    },
    showErrors: {
      type: [Object, Boolean],
      default: () => ({})
    },
    validationLabel: {
      type: [String, Boolean],
      default: false
    },
    elementClasses: {
      type: [String, Array, Object],
      default: () => {}
    },
    disabled: {
      type: Boolean,
      default: false
    },
    help: {
      type: [Boolean, String],
      default: false
    }
  },
  data () {
    return {
      errorBlurState: false,
      focusState: false
    }
  },
  computed: {
    classification () {
      if (this.isTextInput) return 'text'
      if (this.isTextareaInput) return 'textarea'
      if (this.isBoxInput) return 'box'
      if (this.isButtonInput) return 'button'
      if (this.isSelectInput) return 'select'
      if (this.hasCustomInput) return 'custom'
      return 'unsupported'
    },
    hasCustomInput () {
      return (this.$slots.default && this.$slots.default.length)
    },
    isTextInput () {
      return !this.hasCustomInput && inputTypes.text.includes(this.type)
    },
    isTextareaInput () {
      return !this.hasCustomInput && inputTypes.textarea.includes(this.type)
    },
    isButtonInput () {
      return !this.hasCustomInput && inputTypes.button.includes(this.type)
    },
    isSelectInput () {
      return !this.hasCustomInput && inputTypes.select.includes(this.type)
    },
    isBoxInput () {
      return !this.hasCustomInput && inputTypes.box.includes(this.type)
    },
    isUnsupportedInput () {
      return (!this.hasCustomInput && !this.isTextInput && !this.isButtonInput && !this.isSelectInput && !this.isBoxInput && !this.isTextareaInput)
    },
    form () {
      let parent = this.$parent
      while (parent && parent.$data && parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
        parent = parent.$parent
      }
      if (!parent.$data || parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
        throw new Error('FormulateElement has no FormulateWrapper element')
      }
      return parent
    },
    values () {
      return this.form.values
    },
    value () {
      let value = this.values[this.name]
      if (value === undefined) {
        switch (this.type) {
          case 'color':
            return '#000000'
          case 'checkbox':
            if (this.optionList.length > 1) {
              return []
            }
            break
        }
      }
      return value
    },
    module () {
      return this.form.$props['module']
    },
    formName () {
      return this.form.$props['name']
    },
    classes () {
      return {
        'formulate-element': true,
        [`formulate-element--type--${this.type}`]: true,
        'formulate-element--has-value': !!this.value,
        'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors,
        'formulate-element--has-prefix': !!this.$slots.prefix,
        'formulate-element--has-suffix': !!this.$slots.suffix,
        'formulate-element--has-focus': !!this.focusState
      }
    },
    validationErrors () {
      return this.form.validationErrors[this.name] || []
    },
    storeErrors () {
      return this.form.storeErrors[this.name] || []
    },
    formErrors () {
      return this.form.errors[this.name] || []
    },
    localAndValidationErrors () {
      return this.errors.concat(this.validationErrors).concat(this.formErrors)
    },
    shouldShowErrors () {
      let show = this.form.shouldShowErrors
      if (this.form.behavior === 'blur') {
        show = show || this.errorBlurState
      }
      if (this.showErrors === false || this.showErrors === true) {
        show = this.showErrors
      }
      return show
    },
    attributes () {
      return ['min', 'max', 'minlength', 'maxlength', 'placeholder', 'id', 'multiple', 'pattern']
        .filter(prop => this[prop] !== false)
        .reduce((attributes, attr) => {
          attributes[attr] = this[attr]
          return attributes
        }, {})
    },
    optionList () {
      return this.createOptionList(this.options)
    },
    val: {
      set (value) {
        this.form.update({field: this.name, value})
        if (this.isTextInput) {
          this.$refs.input.value = value
        }
      },
      get () {
        return this.value
      }
    }
  },
  watch: {
    localAndValidationErrors () {
      if (!equals(this.localAndValidationErrors, this.storeErrors)) {
        this.form.updateFieldErrors({
          field: this.name,
          errors: this.localAndValidationErrors
        })
      }
    },
    initial () {
      this.form.update({field: this.name, value: this.initial})
    }
  },
  created () {
    if (typeof window === 'undefined') {
      this.register()
    }
  },
  mounted () {
    this.register()
  },
  beforeDestroy () {
    this.form.deregisterField(this.name)
  },
  methods: {
    register () {
      this.form.registerField(
        this.name,
        filter(this.$props, (prop, value) => ['name', 'type', 'id', 'label', 'validation', 'validationLabel'].includes(prop))
      )
      if (this.initial !== false) {
        this.form.setInitial(this.name, this.initial)
      }
    },
    setBlurState () {
      this.errorBlurState = true
      this.focusState = false
    },
    setFocusState () {
      this.focusState = true
    },
    createOptionList (options) {
      if (!Array.isArray(options)) {
        return reduce(options, (options, value, label) => options.concat({value, label, id: shortid.generate()}), [])
      } else if (Array.isArray(options) && !options.length) {
        return [{value: this.name, label: (this.label || this.name), id: shortid.generate()}]
      }
      return options
    }
  }
}
</script>