<template>
  <div class="filters" :class="{ 'filters--small': small }">
    <div class="filters__header">
      <div class="filters__header-title">
        <FilterIcon />
        Recherche avancée
      </div>
      <span class="input action">
        <BmsIconButton
          v-if="canSaveFilters"
          class="save"
          tooltipText="Sauvegarder le filtre"
          @click="save"
        >
          <Save />
        </BmsIconButton>
        <BmsIconButton
          class="reset"
          tooltipText="Vider les champs"
          @click="reset"
        >
          <RefreshCcw />
        </BmsIconButton>
      </span>
    </div>
    <div class="filters__inputs" :class="{ 'filters__inputs--small': small }">
      <span
        v-for="filter in modelValue"
        class="input"
        :class="{
          large: isBetweenFilter(filter),
        }"
        :key="filter.key"
      >
        <component
          :is="getFilterComponent(filter.type)"
          v-bind="getComboboxProps(filter)"
          :small="small"
          :label="filter.label"
          :inputType="filter.inputType"
          :modelValue="transformValueForComponent(filter?.value, filter.type)"
          @update:modelValue="
            ($event: any) => onFilter(filter.key, $event as string, 'value')
          "
          @update:valueFrom="
            ($event: any) => onFilter(filter.key, $event as string, 'valueFrom')
          "
          @update:valueTo="
            ($event: any) => onFilter(filter.key, $event as string, 'valueTo')
          "
          :valueFrom="
            transformValueForComponent(filter?.valueFrom, filter.type)
          "
          :valueTo="transformValueForComponent(filter?.valueTo, filter.type)"
          :options="getFilterOptions(filter) as any"
          @input="
            (e: any) =>
              $emits('filterInput', {
                filterKey: filter.key,
                value: (e.target as HTMLInputElement).value,
                e,
              })
          "
        />
      </span>
    </div>
    <BmsModal
      v-model="savedModalOpened"
      title="Sauvegarder votre filtre"
      submitLabel="Sauvegarder"
      @submit="onSavedFilterModalSubmit"
    >
      <div>
        Vous pouvez sauvegarder votre recherche pour la retrouver plus
        facilement.
      </div>
      <BmsInputText
        ref="nameInput"
        label="Nom de la sauvegarde"
        v-model="savedFilterName"
        placeholder="Nom"
      />
    </BmsModal>
  </div>
</template>

<script setup lang="ts">
import { Filter as FilterIcon, RefreshCcw, Save } from 'lucide-vue-next';
import { Filter, FilterType, InputOption, SavedFilter } from '@/models';
import BmsCombobox from '../form/BmsCombobox.vue';
import { useRoute } from 'vue-router';
import { v4 as uuid } from 'uuid';
import { nextTick, Ref, ref, watch } from 'vue';
import { BmsInputDateTime, BmsInputText, BmsModal } from '@/index';
import _cloneDeep from 'lodash/cloneDeep';
import BmsIconButton from '../button/BmsIconButton.vue';
import BmsBetweenInput from '@/components/form/BmsBetweenInput.vue';
import { isBetweenFilter } from '@/composables/search.helper';

const route = useRoute();

const props = withDefaults(
  defineProps<{
    modelValue: Filter[];
    canSaveFilters?: boolean;
    small?: boolean;
  }>(),
  {
    canSaveFilters: false,
    small: false,
  },
);

const $emits = defineEmits<{
  'update:modelValue': [filters: Filter[]];
  saveFilter: [value: SavedFilter];
  resetFilters: [];
  filterInput: [{ filterKey: string; value: any; e: InputEvent }];
  filterChange: [{ filterKey: string; value: any }];
}>();

const savedModalOpened: Ref<boolean> = ref<boolean>(false);
const savedFilterName: Ref<string> = ref<string>('');
const nameInput: Ref<typeof BmsInputText | null> = ref(null);

const onFilter = (key: string, inputValue: string, valueKey: any): void => {
  let filter = props.modelValue.find((f) => f.key === key);
  let updatedValue;
  if (filter) {
    switch (filter.type) {
      case 'boolean':
        updatedValue =
          inputValue === 'true' ? true : inputValue === 'false' ? false : null;
        break;
      case 'select':
      case 'autocomplete':
      case 'betweenDate':
      case 'betweenDateTime':
      case 'betweenNumber':
        updatedValue = inputValue === 'null' ? null : inputValue;
        break;
      default:
        updatedValue = inputValue;
        break;
    }
    filter[valueKey as keyof Filter] = updatedValue;
    $emits('filterChange', { filterKey: key, value: updatedValue });
  }
  $emits('update:modelValue', props.modelValue);
};

const getFilterComponent = (type: FilterType) => {
  switch (type) {
    case 'boolean':
    case 'select':
    case 'autocomplete':
    case 'autocompleteServer':
      return BmsCombobox;
    case 'input':
      return BmsInputText;
    case 'inputDate':
      return BmsInputDateTime;
    case 'betweenNumber':
    case 'betweenDate':
    case 'betweenDateTime':
      return BmsBetweenInput;
  }
};

const getComboboxProps = (filter: Filter) => {
  const shared = {
    multiple: filter.multiple,
    selectAll: filter.selectAll,
    selectAllLabel: filter.selectAllLabel,
  };
  switch (filter.type) {
    case 'autocomplete':
      return { ...shared, autocomplete: true };
    case 'autocompleteServer':
      return {
        ...shared,
        autocomplete: true,
        request: filter.autocompleteRequest,
      };
    default:
      return shared;
  }
};

const getFilterOptions = (
  filter: Filter,
): undefined | InputOption[] | string[] => {
  switch (filter.type) {
    case 'boolean':
      return [
        { label: '', value: 'null' },
        { label: 'Oui', value: 'true' },
        { label: 'Non', value: 'false' },
      ];
    case 'autocomplete':
      return filter.autocompleteOptions;
    case 'select':
      if (filter.selectOptions) {
        return [{ label: '', value: 'null' }].concat(filter.selectOptions);
      }
      return [];
    default:
      return [];
  }
};

const transformValueForComponent = (value: any, type: FilterType): string => {
  const hasEmptyValue = (value: any) => value === null || value === undefined;
  switch (type) {
    case 'boolean':
      return hasEmptyValue(value) ? 'null' : value.toString();
    case 'select':
      return hasEmptyValue(value) ? 'null' : value;
    case 'input':
    case 'inputDate':
    case 'autocomplete':
    case 'betweenDate':
    case 'betweenNumber':
      return hasEmptyValue(value) ? '' : value;
    default:
      return value;
  }
};

const reset = () => {
  $emits('resetFilters');
};

const save = () => {
  savedModalOpened.value = true;
  savedFilterName.value = '';
};

const onSavedFilterModalSubmit = () => {
  $emits(
    'saveFilter',
    _cloneDeep({
      id: uuid(),
      name: savedFilterName.value,
      route: (route.name as string) || route.path,
      filters: props.modelValue.map((filter) => ({
        target: filter.key,
        value: filter.value,
      })),
    }),
  );
};

watch(
  () => savedModalOpened.value,
  async () => {
    await nextTick();
    if (savedModalOpened.value && nameInput.value) {
      nameInput.value.setFocus();
    }
  },
);
</script>

<style lang="scss" scoped>
.filters {
  padding: 1em;
  background: var(--bms-white);
  border: 1px solid var(--bms-border-color);
  border-radius: var(--bms-border-radius-large);
  margin-bottom: 1em;
  &--small {
    margin-bottom: 0.5em;
    padding: 0.5em;
  }

  &__header {
    display: flex;
    justify-content: space-between;

    &-title {
      font-size: 18px;
      font-weight: bold;
      gap: 1rem;
      display: flex;
      align-items: center;
    }
  }

  &__inputs {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    row-gap: 8px;
    padding-top: 1em;
    &--small {
      padding-top: 0.5em;
    }

    .input {
      width: 25%;
      &.large {
        width: 50%;
      }
      padding-right: 32px;
    }
  }
}
</style>
