<template>
  <div class="grow flex flex-col gap-2 overflow-hidden">
    <div v-if="useSearch" class="relative self-end flex flex-row items-center gap-2">
      <Spinner v-if="deferingSearch"  />
      <input
        type="text"
        class="!max-w-64 !w-64 rounded-md shadow-sm relative block px-3 py-2 pr-5 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
        placeholder="Suche"
        v-model="searchString"
      />
      <XMarkIcon
        class="absolute h-4 stroke-2 right-2 top-1/2 -translate-y-1/2 cursor-pointer z-10"
        @click="searchString = ''"
      />
    </div>
    <div class="flex flex-col w-full grow gap-2 pr-2 overflow-y-scroll">
      <div v-if="indicateLoading" class="flex flex-row justify-center items-center w-full p-1">
        <Spinner />
      </div>
      <p v-if="visibleRows.length == 0" class="flex flex-row w-full gap-2 p-1">Kein Inhalt</p>
      <slot
        v-else
        name="pageRow"
        v-for="(item, index) in visibleRows"
        :key="index"
        :row="item"
        @click="$emit('clickRow', item)"
      >
        <p>{{ item }}</p>
      </slot>
    </div>
    <div class="flex flex-row w-full justify-between select-none">
      <p class="text-sm font-normal text-gray-500">
        Elemente <span class="font-semibold text-gray-900">{{ showingText }}</span> von
        <span class="font-semibold text-gray-900">{{ entryCount }}</span>
      </p>
      <ul class="flex flex-row text-sm h-8">
        <li
          class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg"
          :class="[currentPage > 0 ? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700' : 'opacity-50']"
          @click="loadPage(currentPage - 1)"
        >
          <ChevronLeftIcon class="h-4" />
        </li>
        <li
          v-for="page in displayedPagesNumbers"
          :key="page"
          class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 first:rounded-s-lg last:rounded-e-lg"
          :class="[currentPage == page ? 'font-bold border-primary' : '', page != '.' ? ' cursor-pointer' : '']"
          @click="loadPage(page)"
        >
          {{ typeof page == "number" ? page + 1 : "..." }}
        </li>
        <li
          class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg"
          :class="[
            currentPage + 1 < countOfPages ? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700' : 'opacity-50',
          ]"
          @click="loadPage(currentPage + 1)"
        >
          <ChevronRightIcon class="h-4" />
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts" generic="T extends { id: FieldType }">
import { computed, ref, watch } from "vue";
import { ChevronRightIcon, ChevronLeftIcon, XMarkIcon } from "@heroicons/vue/20/solid";
import Spinner from "./Spinner.vue";
import type { FieldType } from "@/types/dynamicQueries";

const props = defineProps({
  items: { type: Array<T>, default: [] },
  maxEntriesPerPage: { type: Number, default: 25 },
  totalCount: { type: Number, default: null },
  config: { type: Array<{ key: string }>, default: [] },
  useSearch: { type: Boolean, default: false },
  enablePreSearch: { type: Boolean, default: false },
  indicateLoading: { type: Boolean, default: false },
});

const slots = defineSlots<{
  pageRow(props: { row: T }): void;
}>();

const timer = ref(undefined) as undefined | any;
const currentPage = ref(0);
const searchString = ref("");
const deferingSearch = ref(false)

watch(searchString, async () => {
  deferingSearch.value = true
  clearTimeout(timer.value);
  timer.value = setTimeout(() => {
    currentPage.value = 0;
    deferingSearch.value = false
    emit("search", searchString.value);
  }, 600);
});

watch(() => props.totalCount, async () => {
  currentPage.value = 0;
});

const emit = defineEmits({
  submit(id: number) {
    return typeof id == "number";
  },
  loadData(offset: number, count: number, searchString: string) {
    return typeof offset == "number" && typeof offset == "number" && typeof searchString == "number";
  },
  search(search: string) {
    return typeof search == "string";
  },
  clickRow(elem: T) {
    return true;
  },
});

const entryCount = computed(() => props.totalCount ?? props.items.length);
const showingStart = computed(() => currentPage.value * props.maxEntriesPerPage);
const showingEnd = computed(() => {
  let max = currentPage.value * props.maxEntriesPerPage + props.maxEntriesPerPage;
  if (max > entryCount.value) max = entryCount.value;
  return max;
});
const showingText = computed(() => `${entryCount.value != 0 ? showingStart.value + 1 : 0} - ${showingEnd.value}`);
const countOfPages = computed(() => Math.ceil(entryCount.value / props.maxEntriesPerPage));
const displayedPagesNumbers = computed(() => {
  let stateOfPush = false;

  return [...new Array(countOfPages.value)].reduce((acc, curr, index) => {
    if (
      index <= 1 ||
      index >= countOfPages.value - 2 ||
      (currentPage.value - 1 <= index && index <= currentPage.value + 1)
    ) {
      acc.push(index);
      stateOfPush = false;
      return acc;
    }
    if (stateOfPush == true) return acc;
    acc.push(".");
    stateOfPush = true;

    return acc;
  }, []);
});
const visibleRows = computed(() => filterData(props.items, searchString.value, showingStart.value, showingEnd.value));

const loadPage = (newPage: number | ".") => {
  if (newPage == ".") return;
  if (newPage < 0 || newPage >= countOfPages.value) return;

  let pageStart = newPage * props.maxEntriesPerPage;
  let pageEnd = newPage * props.maxEntriesPerPage + props.maxEntriesPerPage;
  if (pageEnd > entryCount.value) pageEnd = entryCount.value;

  let loadedElementCount = filterData(props.items, searchString.value, pageStart, pageEnd).length;
  
  if (loadedElementCount < props.maxEntriesPerPage && (pageEnd != props.totalCount || loadedElementCount == 0))
    emit("loadData", pageStart, props.maxEntriesPerPage, searchString.value);

  currentPage.value = newPage;
};

const filterData = (array: Array<any>, searchString: string, start: number, end: number): Array<any> => {
  return array
    .filter(
      (elem) =>
        !props.enablePreSearch ||
        searchString.trim() == "" ||
        props.config.some(
          (col) =>
            typeof elem?.[col.key] == "string" &&
            elem[col.key].toLowerCase().includes(searchString.trim().toLowerCase())
        )
    )
    .filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
};
</script>

<!-- 
<script lang="ts">
export default defineComponent({
  computed: {
    entryCount() {
      return this.totalCount ?? this.items.length;
    },
    showingStart() {
      return this.currentPage * this.maxEntriesPerPage;
    },
    showingEnd() {
      let max = this.currentPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
      if (max > this.entryCount) max = this.entryCount;
      return max;
    },
    showingText() {
      return `${this.entryCount != 0 ? this.showingStart + 1 : 0} - ${this.showingEnd}`;
    },
    countOfPages() {
      return Math.ceil(this.entryCount / this.maxEntriesPerPage);
    },
    displayedPagesNumbers(): Array<number | "."> {
      //indicate if "." or page number gets pushed
      let stateOfPush = false;

      return [...new Array(this.countOfPages)].reduce((acc, curr, index) => {
        if (
          // always display first 2 pages
          index <= 1 ||
          // always display last 2 pages
          index >= this.countOfPages - 2 ||
          // always display 1 pages around current page
          (this.currentPage - 1 <= index && index <= this.currentPage + 1)
        ) {
          acc.push(index);
          stateOfPush = false;
          return acc;
        }
        // abort if placeholder already added to array
        if (stateOfPush == true) return acc;
        // show placeholder if pagenumber is not actively rendered
        acc.push(".");
        stateOfPush = true;

        return acc;
      }, []);
    },
    visibleRows() {
      return this.filterData(this.items, this.searchString, this.showingStart, this.showingEnd);
    },
  },
  methods: {
    loadPage(newPage: number | ".") {
      if (newPage == ".") return;
      if (newPage < 0 || newPage >= this.countOfPages) return;

      let pageStart = newPage * this.maxEntriesPerPage;
      let pageEnd = newPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
      if (pageEnd > this.entryCount) pageEnd = this.entryCount;

      let loadedElementCount = this.filterData(this.items, this.searchString, pageStart, pageEnd).length;
      if (loadedElementCount < this.maxEntriesPerPage)
        this.$emit("loadData", { offset: pageStart, count: this.maxEntriesPerPage, search: this.searchString });

      this.currentPage = newPage;
    },
    filterData(array: Array<any>, searchString: string, start: number, end: number): Array<any> {
      return array
        .filter(
          (elem) =>
            !this.enablePreSearch ||
            searchString.trim() == "" ||
            this.config.some((col) => typeof elem?.[col.key] == "string" && elem[col.key].includes(searchString.trim()))
        )
        .filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
    },
  },
});
</script> -->
