<template>
  <ElForm
    ref="form"
    v-bind="{ model: value }"
    v-loading="!isLoaded"
    label-position="top"
  >
    <div
      v-for="(formItemRow, index) in formItems"
      :key="index"
      class="form-group"
    >
      <ElFormItem
        v-for="formItem in formItemRow"
        :key="formItem.props.prop"
        v-bind="{
          ...formItem.props,
          rules: formItem.rules,
        }"
        :style="{ width: formItem.width }"
        size="medium"
        :data-test="`form item ${index}`"
      >
        <template slot="label">
          <slot
            name="label"
            v-bind="{ prop: formItem.props.prop }"
          >
            {{ formItem.props.label }}
          </slot>
        </template>
        <slot
          name="formElement"
          v-bind="{ formItem }"
        >
          <ElInput
            v-if="[
              'text',
              'number',
              'textarea',
            ].includes(formItem.props.type)"
            v-bind="{
              ...formItem.props,
              disabled: isSaving || formItem.props.disabled,
              value: value[formItem.props.prop],
            }"
            :data-test="`edit ${formItem.props.prop}`"
            @blur="onBlur(formItem.props.prop)"
            @input="onInput(formItem.props.prop, $event)"
            @keydown.native.enter="onSubmit"
          />
          <TfDatepicker
            v-else-if="formItem.props.type === 'date'"
            v-bind="{
              ...formItem.props,
              disabled: isSaving || formItem.props.disabled,
              value: value[formItem.props.prop],
            }"
            :data-test="`edit ${formItem.props.prop}`"
            @input="onInput(formItem.props.prop, $event)"
            @blur="onBlur(formItem.props.prop)"
          />
          <TfInputWithOptions
            v-else-if="formItem.props.type === 'select'"
            v-bind="{
              ...formItem.props,
              disabled: isSaving || formItem.props.disabled,
              value: value[formItem.props.prop],
            }"
            :data-test="`edit ${formItem.props.prop}`"
            @blur="onBlur(formItem.props.prop)"
            @input="onInput(formItem.props.prop, $event)"
          />
          <ElSwitch
            v-else-if="formItem.props.type === 'switch'"
            v-bind="{
              ...formItem.props,
              disabled: isSaving || formItem.props.disabled,
              value: value[formItem.props.prop],
            }"
            :class="{ 'is-inline': formItem.isInline }"
            :data-test="`edit ${formItem.props.prop}`"
            @change="$emit('switchChange', { prop: formItem.props.prop, value: $event })"
            @input="onInput(formItem.props.prop, $event)"
          />
        </slot>
      </ElFormItem>
    </div>
  </ElForm>
</template>

<script>
  /**
   * TfFormBuilder is a component that is fed a nested array of form items and an object model. The form items array should be arranged in a format of `{Array<Array<Object>>}`. Where each first level of the index is a row in the template and each object is a column in that row. The form items should also contain any rules that are validated on blur.
   *
   * @vuedoc
   * @exports TfForm/TfFormBuilder
   * @category components
   */
  export default {
    name: 'TfFormBuilder',
    props: {
      formItems: {
        type: Array,
        required: true,
      },
      isLoaded: {
        type: Boolean,
        default: false,
      },
      isSaving: {
        type: Boolean,
        default: false,
      },
      value: {
        type: Object,
        required: true,
      },
    },
    methods: {
      /**
       * Validate the field and emit $blur, which should include the prop and its validity.
       *
       * @param {string} prop
       */
      onBlur(prop) {
        let isValid = true;

        this.$refs.form.validateField(prop, (error) => {
          isValid = !error;
        });

        this.$emit('blur', { isValid, prop });
      },
      /**
       * Clone the model, mutate the appropriate prop, $emit and clear any validation.
       *
       * @param {string} prop
       * @param {number | string} value
       */
      onInput(prop, value) {
        const newValue = {
          ...this.value,
          [prop]: value,
        };
        this.$emit('input', newValue);
        this.$refs.form.clearValidate(prop);
      },
      /**
       * Validate the form and $emit 'submit' to the parent if passing when a user hits the ENTER key on an input. The parent component handles the logic for validating and submitting each of the detail sections holistically.
       */
      onSubmit() {
        this.$refs.form.validate((valid) => {
          if (!valid) {
            return;
          }

          this.$emit('submit');
        });
      },
    },
  };
</script>

<style scoped lang="scss">
  @import 'src/assets/styles/variables';

  .form-group {
    display: flex;
    margin: 0 (-$tf-form-half-gutter);
  }

  ::v-deep {
    .el-form {
      &,
      &-item {
        width: 100%;
      }

      &-item {
        margin: {
          left: $tf-form-half-gutter;
          right: $tf-form-half-gutter;
        }

        &__error {
          position: relative;
        }

        > .el-form-item__label {
          line-height: 40px;
        }
      }
    }

    .el-select {
      display: inline-block;
    }

    .el-switch {
      &.is-inline {
        height: 40px;
      }

      &:not(.is-inline) {
        width: 100%;
      }
    }

    .el-textarea__inner {
      resize: none;
    }

    .vdp-datepicker {
      display: block;
      z-index: 100;

      &,
      input {
        width: 100%;
      }
    }
  }
</style>
