<template>
  <section>
    <ElForm
      ref="form"
      :model="{ listItems: value }"
    >
      <!--Still display the label if all form items in the list have been deleted-->
      <ElFormItem v-if="!value.length">
        <template slot="label">
          <slot name="label">
            {{ label }}
          </slot>
        </template>
      </ElFormItem>
      <!--Render form items normally if there are any present-->
      <TransitionGroup name="slide-in-fade">
        <div
          v-for="(formItem, index) in value"
          :key="`form-${index}`"
          :class="[
            { 'is-hidden-label': isSingleLabel && index },
            'form-list-item'
          ]"
        >
          <ElFormItem
            v-bind="{
              prop: `listItems.${index}`,
              rules,
            }"
          >
            <template slot="label">
              <slot
                name="label"
                v-bind="{ index }"
              >
                {{ label }}
              </slot>
            </template>
            <!-- Class is required for element's styles -->
            <div
              slot="error"
              slot-scope="{ error }"
              class="el-form-item__error"
              :data-test="`error message for listItem_${index}`"
            >
              <slot
                name="error"
                v-bind="{ error }"
              >
                {{ error }}
              </slot>
            </div>
            <TfInputWithOptions
              v-bind="{
                allowEnter: false,
                disabled: isDisabled ||
                  (readonlyValues.length > 0 && readonlyValues.includes(formItem)),
                groupedDisabledOptions,
                groupedDisabledOptionsTitle,
                optionKey: 'value',
                optionLabel: 'label',
                options: formListOptions,
                placeholder,
                prop: `listItems.${index}`,
                value: value[index],
              }"
              :data-test="`list items ${index}`"
              @blur="onBlur(index)"
              @input="onInput(index, $event)"
            />
          </ElFormItem>
          <div
            v-if="
              !hideDeleteForValues.length && (allowDeleteFirst || value.length > 1)
                || hideDeleteForValues.length && !hideDeleteForValues.includes(formItem)"
            class="container-btn-group"
          >
            <AppButton
              data-test="delete form list item"
              v-bind="{
                isDisabled: isDisabled || (disableDeleteFirst && value.length === 1),
                noBranding,
              }"
              icon="fa-solid fa-trash-can"
              size="icon"
              type="decline"
              @click="deleteInput(index)"
            />
          </div>
        </div>
      </TransitionGroup>
    </ElForm>
    <AppButton
      v-if="allowAddAnother"
      data-test="add form list item"
      v-bind="{
        isDisabled,
        noBranding,
      }"
      type="primary"
      size="text"
      @click="addFormItem"
    >
      <slot name="addAnother">
        Add another
      </slot>
    </AppButton>
  </section>
</template>
<script>
  /**
   * A component made to build out a list of the TfInputWithOptions component. The options are required to be in a [{ label, value }] format and the model is a flat array of strings/numbers, both of which are provided as props from the parent.
   * This component will also validate the inputs based on the rules that are passed and clear validation on @input. If the on blur on TfInputWithOptions has a violation, $emit('blur') will not happen.
   *
   * @vuedoc
   * @exports TfForm/FormList
   * @category components
   */
  export default {
    name: 'TfFormList',
    props: {
      /**
       * Render the "Add another" CTA in the template.
       */
      allowAddAnother: {
        type: Boolean,
        default: true,
      },
      /**
       * Allow all form items to be deleted even if it's the last one left
       */
      allowDeleteFirst: {
        type: Boolean,
        default: false,
      },
      /**
       * Allow multiple options to be the value. ['option 1', 'option 1']
       */
      allowDuplicates: {
        type: Boolean,
        default: false,
      },
      /**
       * If there is only one form item left, disable the delete button
       */
      disableDeleteFirst: {
        type: Boolean,
        default: false,
      },
      /**
       * Pass "groupDisabled" as true to group any option that has `isDisabled: true` to a "disabledOptions" array.
       * This combined with `showUsedAsDisabled: true` will group any used values to a "disabledOptions" array.
       */
      groupDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * "groupedDisabledOptionsTitle" will render a title above the list of disabled values in the tfInputWithOptions component.
       */
      groupedDisabledOptionsTitle: {
        type: String,
        default: 'Used values',
      },
      /**
       * An array of values that the Delete CTA should be hidden for
       */
      hideDeleteForValues: {
        type: Array,
        default: () => [],
      },
      /**
       * Disable all interactions in the template.
       */
      isDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Render only the first label in the template.
       */
      isSingleLabel: {
        type: Boolean,
        default: true,
      },
      /**
       * Render custom label string in the template.
       */
      label: {
        type: String,
        default: '',
      },
      /**
       * Render no branding colors template.
       */
      noBranding: {
        type: Boolean,
        default: false,
      },
      /**
       * Render the options to choose from in the template.
       */
      options: {
        type: Array,
        default: () => [],
        validator: (options) => options.every(({ label, value }) => value && label),
      },
      /**
       * Render custom placeholder string in the template.
       */
      placeholder: {
        type: String,
        default: 'Select from list or start typing',
      },
      /**
       * An array of values that should not be editable
       */
      readonlyValues: {
        type: Array,
        default: () => [],
      },
      /**
       * Form rules applied to the form items in the template.
       */
      rules: {
        type: [Array, Object],
        default: () => [],
      },
      /**
       * Pass "showUsedAsDisabled" as true to render a list of the used values in the tfInputWithOptions component.
       */
      showUsedAsDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Model
       */
      value: {
        type: Array,
        required: true,
      },
    },
    data: () => ({
      invalidIndexes: [],
      replacementArray: [],
    }),
    computed: {
      /**
       * Evalutate the "groupDisabled", "showUsedAsDisabled", and "options" props to group any disabled options or any used values if "showUsedAsDisabled" is `true`.
       * By default ("groupDisabled" is `false`), this will return an empty array.
       *
       * @returns {Array}
       */
      groupedDisabledOptions() {
        if (!this.groupDisabled) {
          return [];
        }

        // Push the option if isDisabled or if the value is included && showUsedAsDisabled is true.
        return this.options.reduce((options, option) => (
          option.isDisabled || (this.showUsedAsDisabled && this.value.includes(option.value))
            ? options.concat(option)
            : options
        ), []);
      },
      /**
       * Evaluate the "allowDuplicates", "showUsedAsDisabled", and "groupDisabled" props to determine if the options should include all or filtered values based on different criteria.
       * "allowDuplicates" will return all options
       * "showUsedAsDisabled" will create a new options array that includes used values in a disabled state, unless "groupDisabled" is `true`, to which they are then excluded.
       * "groupDisabled" will create a new options array that excludes any options with `isDisabled: true` or any used values if "showUsedAsDisabled" is `true`.
       *
       * @returns {Array}
       */
      formListOptions() {
        if (this.allowDuplicates) {
          return this.options;
        }

        if (this.showUsedAsDisabled || this.groupDisabled) {
          return this.options.reduce((options, option) => {
            // Ignore any included or disabled if groupDisabled.
            if (this.groupDisabled && (this.value.includes(option.value) || option.isDisabled)) {
              return options;
            }

            return options.concat({
              ...option,
              isDisabled: this.value.includes(option.value) || option.isDisabled || false,
            });
          }, []);
        }

        return this.options.filter(({ value }) => !this.value.includes(value));
      },
    },
    watch: {
      /**
       * Watch for changes in "disabledOptions" to emit any synced data.
       *
       * @param {Array} newValue
       */
      disabledOptions(newValue) {
        this.$emit('update:disabledOptions', newValue);
      },
      /**
       * Watch for changes in "formListOptions" to emit any synced data.
       *
       * @param {Array} newValue
       */
      formListOptions(newValue) {
        this.$emit('update:formListOptions', newValue);
      },
    },
    created() {
      if (!this.value.length) {
        this.addFormItem();
        return;
      }

      this.$set(this, 'replacementArray', [...this.value]);
    },
    methods: {
      /**
       * Add an empty string to the value and emit to the parent.
       */
      addFormItem() {
        this.$emit('input', [...this.value, '']);
        this.replacementArray.push('');
      },
      /**
       * Clear any validation and $emit the new model along with what was deleted.
       *
       * @param {number} index
       */
      deleteInput(index) {
        const deletedValue = this.value[index];
        const newValue = [...this.value];

        this.$refs.form.clearValidate(`listItems.${index}`);

        newValue.splice(index, 1);

        this.$emit('deleted', { index, value: deletedValue });
        this.$emit('input', newValue);
      },
      /**
       * Validate the form input and $emit 'blur' with the appropriate data for the parent.
       *
       * @param {number} index
       */
      onBlur(index) {
        let isValid = true;

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

        const value = this.value[index];
        const replacementValue = this.replacementArray[index];

        this.$set(this, 'replacementArray', this.value);
        this.$emit('blur', {
          index,
          isValid,
          replaced: replacementValue !== '' && (replacementValue !== value)
            ? replacementValue
            : null,
          value,
        });
      },
      /**
       * Clone the value prop and $emit new value to parent.
       *
       * @param {number} index
       * @param {number} value
       */
      onInput(index, value) {
        this.$refs.form.clearValidate(`listItems.${index}`);

        const newValue = [...this.value];

        newValue[index] = value;

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

<style lang="scss" scoped>
  .form-list-item {
    display: flex;

    ::v-deep {
      > .el-form-item {
        flex: 1;
      }

      .el-select {
        width: 100%;
      }

      .el-form-item {
        &__label:before {
          display: none;
        }

        &__error {
          position: relative;
        }
      }
    }
  }

  .is-hidden-label {
    ::v-deep .el-form-item__label {
      display: none;
    }
  }

  .container-btn-group {
    padding: 38px 0 0 3px;

    .is-hidden-label & {
      padding-top: 0;
    }
  }

  .plan-design-dropdown-container {
    width: 100%;
  }

  /*
    I'm commenting this out for now b/c there is a timing issue when deleting a value.
    There's a fix on https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions,
    but we don't have the time for that for now.
  */

  .slide-in-fade {
    // &-enter-active,
    // &-leave-active {
    //   max-height: 0;
    //   opacity: 0;
    //   transition: all .25s ease-in;
    // }

    // &-enter-to,
    // &-leave {
    //   opacity: 1;
    //   overflow: hidden;
    //   max-height: 150px;
    // }
  }
</style>
