<template>
  <div>
    <!--
      Here we use the index as key to avoid loosing focus when filter id change.
    -->
    <div
      v-for="(filter, index) in filters"
      :key="index"
      class="filters__item"
      :class="{
        'filters__item--loading': filter._ && filter._.loading,
      }"
    >
      <a
        v-if="!disableFilter"
        class="filters__remove"
        @click="deleteFilter(filter)"
      >
        <i class="fas fa-times"></i>
      </a>
      <div class="filters__operator">
        <span v-if="index === 0">{{ $t('viewFilterContext.where') }}</span>
        <Dropdown
          v-if="index === 1 && !disableFilter"
          :value="filterType"
          :show-search="false"
          class="dropdown--floating dropdown--tiny"
          @input="selectBooleanOperator($event)"
        >
          <DropdownItem
            :name="$t('viewFilterContext.and')"
            value="AND"
          ></DropdownItem>
          <DropdownItem
            :name="$t('viewFilterContext.or')"
            value="OR"
          ></DropdownItem>
        </Dropdown>
        <span v-if="index > 1 || (index > 0 && disableFilter)">
          {{
            filterType === 'AND'
              ? $t('viewFilterContext.and')
              : $t('viewFilterContext.or')
          }}
        </span>
      </div>
      <div class="filters__field">
        <Dropdown
          :value="filter.field"
          :disabled="disableFilter"
          class="dropdown--floating dropdown--tiny"
          @input="updateFilter(filter, { field: $event })"
        >
          <DropdownItem
            v-for="field in fields"
            :key="'field-' + field.id"
            :name="field.name"
            :value="field.id"
            :disabled="hasNoCompatibleFilterTypes(field, filterTypes)"
          ></DropdownItem>
        </Dropdown>
      </div>
      <div class="filters__type">
        <Dropdown
          :disabled="disableFilter"
          :value="filter.type"
          class="dropdown--floating dropdown--tiny"
          @input="updateFilter(filter, { type: $event })"
        >
          <DropdownItem
            v-for="fType in allowedFilters(filterTypes, fields, filter.field)"
            :key="fType.type"
            :name="fType.getName()"
            :value="fType.type"
          ></DropdownItem>
        </Dropdown>
      </div>
      <div class="filters__value">
        <component
          :is="getInputComponent(filter.type, filter.field)"
          :ref="`filter-value-${index}`"
          :filter="filter"
          :view="view"
          :fields="fields"
          :disabled="disableFilter"
          :read-only="readOnly"
          @input="updateFilter(filter, { value: $event })"
        />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ViewFieldConditionsForm',
  props: {
    filters: {
      type: Array,
      required: true,
    },
    disableFilter: {
      type: Boolean,
      required: true,
    },
    filterType: {
      type: String,
      required: true,
    },
    fields: {
      type: Array,
      required: true,
    },
    view: {
      type: Object,
      required: true,
    },
    readOnly: {
      type: Boolean,
      required: true,
    },
  },
  computed: {
    filterTypes() {
      return this.$registry.getAll('viewFilter')
    },
    localFilters() {
      // Copy the filters
      return [...this.filters]
    },
  },
  watch: {
    /**
     * When a filter has been created or removed we want to focus on last value. By
     * watching localFilters instead of filters, the new and old values are differents.
     */
    localFilters(value, old) {
      if (value.length !== old.length) {
        this.$nextTick(() => {
          this.focusValue(value.length - 1)
        })
      }
    },
  },
  methods: {
    focusValue(position) {
      const ref = `filter-value-${position}`
      if (
        position >= 0 &&
        Object.prototype.hasOwnProperty.call(this.$refs, ref) &&
        this.$refs[ref][0] &&
        Object.prototype.hasOwnProperty.call(this.$refs[ref][0], 'focus')
      ) {
        this.$refs[ref][0].focus()
      }
    },
    /**
     * Indicates if the field has any compatible filter types.
     */
    hasNoCompatibleFilterTypes(field, filterTypes) {
      for (const type in filterTypes) {
        if (filterTypes[type].fieldIsCompatible(field)) {
          return false
        }
      }
      return true
    },
    /**
     * Returns a list of filter types that are allowed for the given fieldId.
     */
    allowedFilters(filterTypes, fields, fieldId) {
      const field = fields.find((f) => f.id === fieldId)
      return Object.values(filterTypes).filter((filterType) => {
        return field !== undefined && filterType.fieldIsCompatible(field)
      })
    },
    deleteFilter(filter) {
      this.$emit('deleteFilter', filter)
    },
    /**
     * Updates a filter with the given values. Some data manipulation will also be done
     * because some filter types are not compatible with certain field types.
     */
    updateFilter(filter, values) {
      const field = Object.prototype.hasOwnProperty.call(values, 'field')
        ? values.field
        : filter.field
      const type = Object.prototype.hasOwnProperty.call(values, 'type')
        ? values.type
        : filter.type
      const value = Object.prototype.hasOwnProperty.call(values, 'value')
        ? values.value
        : filter.value

      // If the field has changed we need to check if the filter type is compatible
      // and if not we are going to choose the first compatible type.
      if (Object.prototype.hasOwnProperty.call(values, 'field')) {
        const allowedFilterTypes = this.allowedFilters(
          this.filterTypes,
          this.fields,
          field
        ).map((filter) => filter.type)
        if (!allowedFilterTypes.includes(type)) {
          values.type = allowedFilterTypes[0]
        }
      }

      // If the type or value has changed it could be that the value needs to be
      // formatted or prepared.
      if (
        Object.prototype.hasOwnProperty.call(values, 'type') ||
        Object.prototype.hasOwnProperty.call(values, 'value')
      ) {
        const filterType = this.$registry.get('viewFilter', type)
        values.value = filterType.prepareValue(value)
      }

      this.$emit('updateFilter', { filter, values })
    },

    selectBooleanOperator(value) {
      this.$emit('selectOperator', value)
    },
    /**
     * Returns the input component related to the filter type. This component is
     * responsible for updating the filter value.
     */
    getInputComponent(type, fieldId) {
      const field = this.fields.find(({ id }) => id === fieldId)
      return this.$registry.get('viewFilter', type).getInputComponent(field)
    },
  },
}
</script>