<script lang="ts" setup>
import BmsEmptyScreen from '@/components/table/BmsEmptyScreen.vue';
import BmsPagination from '@/components/table/BmsPagination.vue';
import UiBmsTable from '@/components/table/UiBmsTable.vue';
import UiFilterButton from '@/components/table/UiFilterButton.vue';
import { usePagination } from '@/composables/pagination.composable';
import { useSearch } from '@/composables/search.composable';
import { bmsDefaultSearchFilterFunction } from '@/composables/search.helper';
import { useSort } from '@/composables/sort.composable';
import {
  Filter,
  SavedFilter,
  SelectMode,
  Sort,
  SortValue,
  TableHeader,
  TableMode,
} from '@/models';
import { computed, Ref, ref, watch, watchEffect } from 'vue';
import BmsTableFilters from './BmsTableFilters.vue';
import BmsSearch from '../form/BmsSearch.vue';
import { BmsTag } from '@/index';
import * as lucideIcons from 'lucide-vue-next';
import { Trash } from 'lucide-vue-next';
import { useRoute } from 'vue-router';

const route = useRoute();
const slots = defineSlots();

interface UiTableProps {
  disableSearch?: boolean;
  headers: TableHeader[];
  filters?: Filter[];
  savedFilters?: SavedFilter[];
  canSaveFilters?: boolean;
  items: unknown[];
  mode?: TableMode | string; // string for retro compatibility
  persistent?: boolean;
  pagination?: number[];
  size?: number;
  loading?: boolean;
  defaultFiltersOpened?: boolean;
  defaultSort?: Sort;
  selectable?: boolean;
  selectableDisabled?: boolean;
  selectMode?: SelectMode.DEFAULT | SelectMode.SINGLE;
  customSearch?: (item: unknown, searchValue: string) => boolean;
}

const props = withDefaults(defineProps<UiTableProps>(), {
  mode: TableMode.NORMAL,
  persistent: true,
  pagination: () => [25, 50, 100],
  size: 25,
  filters: () => [],
  savedFilters: () => [],
  canSaveFilters: false,
  defaultFiltersOpened: false,
  defaultSort: () => ({
    key: null,
    value: SortValue.default,
  }),
  selectable: false,
  selectableDisabled: false,
  selectMode: SelectMode.DEFAULT,
});

const {
  items,
  size,
  pagination,
  currentPage,
  paginationsOptions,
  currentItems,
  totalPages,
  goToPage,
  isMounting: isPaginationMounting,
} = usePagination(props.persistent, props.size, props.pagination);

const {
  sortItems,
  changeSort,
  sort,
  isMounting: isSortMounting,
} = useSort(props.persistent, props.headers, props.defaultSort);

const {
  search,
  reflect,
  filters,
  isFilterVisible,
  numberOfActiveFilters,
  isMounting: isSearchMounting,
  toggleFilters,
  filterItems,
  isSavedFilterActive,
  resetFilters,
  selectSavedFilter,
  resetAllFilters,
  updateFiltersFromProps,
} = useSearch(props.persistent, props.filters, props.defaultFiltersOpened);

const emits = defineEmits<{
  deleteSavedFilter: [value: SavedFilter];
  saveFilter: [value: SavedFilter];
  filterInput: [{ filterKey: string; value: any; e: InputEvent }];
  filterChange: [{ filterKey: string; value: any }];
}>();

const selectedItems: Ref<unknown[]> = defineModel('selectedItems', {
  required: false,
  default: [] as unknown[],
});

const emptyText = computed(() => ({
  title: search.value ? `Aucun résultat` : `Aucune donnée`,
  description: search.value
    ? `Votre recherche n'a retourné aucun résultat.`
    : `Il semblerait qu'il n'y ait rien ici...`,
}));

const getFilteredItems = () => {
  const applySearch = props.customSearch
    ? (item: unknown) => props.customSearch!(item, search.value)
    : (item: unknown) => bmsDefaultSearchFilterFunction(item, search.value);

  return sortItems(filterItems(props.items).filter(applySearch));
};

const isMounting = ref(true);

watch(
  () => props.filters,
  () => {
    updateFiltersFromProps(props.filters);
  },
  { deep: true },
);

watchEffect(
  async () => {
    isMounting.value =
      isPaginationMounting.value ||
      isSortMounting.value ||
      isSearchMounting.value;
    if (!isMounting.value) {
      items.value = getFilteredItems();
    }
  },
  { flush: 'post' },
);

watch(
  [() => filters.value, () => sort.value, size, search],
  () => {
    if (!isMounting.value) {
      currentPage.value = 0;
      items.value = getFilteredItems();
    }
  },
  { deep: true },
);

watch(route, () => {
  if (!isMounting.value && import.meta.env.STORYBOOK !== 'true') {
    resetAllFilters();
  }
});

watch(
  () => props.items,
  () => {
    if (!isMounting.value) {
      items.value = getFilteredItems();
      selectedItems.value = [];
    }
  },
);

watchEffect(() => {
  reflect.value = props.persistent && !props.disableSearch;
  pagination.value = props.pagination;
  size.value = props.size;
});

const getIcon = (name: string) => {
  return lucideIcons[
    name as keyof typeof lucideIcons
  ] as lucideIcons.LucideIcon;
};

const onPrevClick = () => {
  goToPage(currentPage.value - 1);
};

const onNextClick = () => {
  goToPage(currentPage.value + 1);
};

const go = (index: number) => {
  goToPage(index);
};

const onSavedFilterDismiss = (savedFilter: SavedFilter) => {
  emits('deleteSavedFilter', savedFilter);
};

const onSaveFilter = (savedFilter: SavedFilter) => {
  emits('saveFilter', savedFilter);
};

const onClickHeader = (header: TableHeader) => {
  changeSort(header);
};

const elementsAndChildElements = computed(() => {
  const childElements = items.value
    .map((item) => item.childElement)
    .filter((childElement) => !!childElement);
  return [...items.value, ...childElements];
});

const onSelectAll = () => {
  selectedItems.value = elementsAndChildElements.value;
};

const totalSize = computed(() => elementsAndChildElements.value.length);

const isTableSmall = computed(() => props.mode === TableMode.SMALL);
</script>

<template>
  <UiBmsTable
    v-model:selectedItems="selectedItems"
    :loading="loading"
    :items="currentItems"
    :headers="headers"
    :mode="mode as TableMode"
    :hasFilters="filters.length > 0"
    :sort="sort"
    :selectable="selectable"
    :selectableDisabled="selectableDisabled"
    :totalSize="totalSize"
    :selectMode="selectMode"
    @clickHeader="onClickHeader"
    @selectAll="onSelectAll"
  >
    <template v-for="(_, name) in slots" v-slot:[name]="slotData">
      <slot :name="name" v-bind="slotData" />
    </template>

    <template #custom-actions>
      <slot name="custom-action"></slot>
    </template>

    <template #filters>
      <Transition name="table-fade">
        <div v-show="isFilterVisible" class="filters-container">
          <BmsTableFilters
            v-model="filters"
            :small="isTableSmall"
            :canSaveFilters="canSaveFilters"
            @saveFilter="onSaveFilter"
            @reset-filters="resetFilters"
            @filterInput="(filterEvent) => emits('filterInput', filterEvent)"
            @filterChange="(filterEvent) => emits('filterChange', filterEvent)"
          />
        </div>
      </Transition>
    </template>

    <template #saved-filters>
      <BmsTag
        v-if="canSaveFilters"
        v-for="savedFilter in savedFilters"
        :key="savedFilter.id"
        :canBeDismissed="!savedFilter.unDeletable"
        :active="isSavedFilterActive(savedFilter)"
        @click="selectSavedFilter(savedFilter)"
        @dismiss="onSavedFilterDismiss(savedFilter)"
      >
        <template #default>
          <component v-if="savedFilter.icon" :is="getIcon(savedFilter.icon)" />
          {{ savedFilter.name }}
        </template>
        <template #closeIcon>
          <Trash />
        </template>
      </BmsTag>
    </template>

    <template #search>
      <UiFilterButton
        v-if="filters.length > 0"
        :activeFiltersNb="numberOfActiveFilters"
        :isFilterVisible="isFilterVisible"
        @toggleFilters="toggleFilters"
        :small="isTableSmall"
      />
      <template v-if="!disableSearch">
        <slot name="search">
          <BmsSearch
            v-model="search"
            class="table-search"
            :small="isTableSmall"
          />
        </slot>
      </template>
    </template>

    <template #empty>
      <slot name="empty">
        <BmsEmptyScreen
          :title="emptyText.title"
          :description="emptyText.description"
        />
      </slot>
    </template>

    <template #pagination>
      <div v-if="props.size" class="bms-table__pagination">
        <BmsPagination
          v-model:currentSize="size"
          :total="items.length"
          :currentPage="currentPage"
          :sizes="paginationsOptions"
          :pages="totalPages"
          :small="isTableSmall"
          @prev="onPrevClick"
          @next="onNextClick"
          @go="go($event)"
        />
      </div>
    </template>
  </UiBmsTable>
</template>
<style lang="scss" scoped>
.table-search {
  width: 260px;
}
</style>
