<script setup>
import { useLayoutStore } from "~/stores/layout";
import { useWindowSize } from "vue-window-size";
import { useDebounceFn } from "@vueuse/core";

const layoutStore = useLayoutStore();
const mobileWidth = layoutStore.mobileWidth;

const { width } = useWindowSize();
const windowWidth = ref(width);

const props = defineProps({
  field: {
    type: Array,
    default: () => [],
  },
  data: {
    type: Array,
    default: () => [],
  },
  basic: {
    type: Boolean,
    default: true,
  },
  advanced: {
    type: Boolean,
    default: false,
  },
  options: {
    type: Object,
    default: () => ({
      variant: "default",
      striped: false,
      bordered: false,
      borderless: false,
      hover: false,
    }),
  },
  optionsAdvanced: {
    type: Object,
    default: () => ({
      sortable: true,
      filterable: false,
      responsive: false,
      outsideBorder: false,
    }),
  },
  grid: {
    type: Boolean,
    default: false,
  },
  pageSize: {
    type: Number,
    default: 5,
  },
  sort: {
    type: Object,
    default: () => ({
      column: "",
      direction: "asc",
    }),
  },
  showRowNumbers: {
    type: Boolean,
    default: false,
  },
});

// Default varaiable
const dataTable = shallowRef([]);
const dataTitle = ref([]);
const dataLength = ref(0);

// Advanced Option Variable
const currentSort = ref(0);
const currentSortDir = ref("asc");
const currentPage = ref(1);
const pageSize = ref(props.pageSize);
const maxPageShown = ref(3);

// Searching Variable
const keyword = ref("");
const debouncedKeyword = ref("");

// Filtering Variable
const filter = ref([]);
const openFilter = ref(false);

const hideTable = ref(false);

// Other Variable
const sortColumnFirstTime = ref(false);

const isDesktop = computed(() => {
  return windowWidth.value >= mobileWidth ? true : false;
});

if (props.optionsAdvanced.responsive) {
  if (isDesktop.value) {
    hideTable.value = false;
  } else {
    hideTable.value = true;
  }
}

const camelCasetoTitle = (str, exclusions = []) => {
  if (exclusions.includes(str)) {
    return str.replace(/([A-Z])/g, " $1").trim();
  } else if (/\(.*\)/.test(str)) {
    return str; // if the string contains parentheses, return the original string
  } else {
    return str.replace(/([A-Z])/g, " $1").replace(/^./, (str) => {
      return str.toUpperCase();
    });
  }
};

const spacingCharactertoCamelCase = (array) => {
  // Loop array string and convert to camel case

  let result = [];

  array.forEach((element) => {
    if (element.charAt(0) == element.charAt(0).toUpperCase()) {
      // Camelcase the string and remove spacing
      // and if there is () in the string, do Uppercase inside the () and dont spacing it

      let camelCase = element
        .replace(/([A-Z])/g, " $1")
        .replace(/^./, (str) => {
          return str.toUpperCase();
        })
        .replace(/\s/g, "");

      let resultCamelCase = camelCase.replace(/\(([^)]+)\)/, (str) => {
        return str.toUpperCase();
      });

      result.push(resultCamelCase);
    } else {
      result.push(element);
    }
  });

  // console.log(result);
  return result;
};

// watch props.data change and redo all the data
watch(
  () => [props.data, props.field],
  () => {
    if (props.data && props.data.length > 0) {
      dataTable.value = props.data;
      dataLength.value = props.data.length;
      if (props.field && props.field.length > 0) {
        dataTitle.value = spacingCharactertoCamelCase(props.field);
      } else {
        dataTitle.value = Object.keys(dataTable.value[0]);
      }
    } else {
      dataTable.value = [];
      dataLength.value = 0;
      dataTitle.value = [];
    }
  },
  { immediate: true }
);

const setColumnTitle = (data) => {
  try {
    if (props.field && props.field.length == 0) {
      dataTitle.value = Object.keys(data);
    } else {
      dataTitle.value = spacingCharactertoCamelCase(props.field);
    }
  } catch (error) {
    console.log(error);
  }
};

const filteredDatabyTitle = (data, title) => {
  let result = "";
  try {
    if (props.field && props.field.length == 0) {
      Object.entries(data).forEach(([key, value]) => {
        if (key === title) {
          result = value;
          return;
        }
      });
    } else {
      // Get index title from columnTitle
      let index = dataTitle.value.indexOf(title);

      // Convert data json to array
      let arr = Object.values(data);

      result = arr[index];
    }
    if (result === "" || result === null) result = "-";
    return result;
  } catch (error) {
    console.log(error);
    return "-";
  }
};

onMounted(() => {
  if (dataTable.value.length > 0) {
    setColumnTitle(dataTable.value[0]);
  }
});

// Computed data
const computedData = computed(() => {
  if (!dataTable.value.length) return [];

  let result = dataTable.value.slice();

  // Sort (only for advanced tables)
  if (props.advanced && currentSort.value !== null) {
    result.sort((a, b) => {
      let modifier = 1;

      dataTitle.value.forEach((title, index) => {
        // console.log(title, props.sort.column);
        // First sort by column title
        if (title === props.sort.column && !sortColumnFirstTime.value) {
          currentSort.value = index;
          currentSortDir.value = props.sort.direction;
          sortColumnFirstTime.value = true;
        }
      });

      // Check if column title is number or string and convert spacing to camelcase
      let a1 = filteredDatabyTitle(a, dataTitle.value[currentSort.value]);
      let b1 = filteredDatabyTitle(b, dataTitle.value[currentSort.value]);

      if (typeof a1 === "string") a1 = a1.toLowerCase();
      if (typeof b1 === "string") b1 = b1.toLowerCase();

      // Convert string to number if possible
      if (isNumeric(a1)) a1 = parseFloat(a1);
      if (isNumeric(b1)) b1 = parseFloat(b1);

      if (currentSortDir.value === "desc") modifier = -1;
      if (a1 < b1) return -1 * modifier;
      if (a1 > b1) return 1 * modifier;
      return 0;
    });
  }

  // Filter
  if (debouncedKeyword.value) {
    const lowercaseKeyword = debouncedKeyword.value.toLowerCase();
    result = result.filter((row) =>
      Object.values(row).some((value) =>
        String(value).toLowerCase().includes(lowercaseKeyword)
      )
    );
  }

  // Add row numbers if showRowNumbers is true
  if (props.showRowNumbers) {
    result = result.map((row, index) => ({
      No: (currentPage.value - 1) * pageSize.value + index + 1,
      ...row,
    }));
  }

  // Pagination (only for advanced tables)
  if (props.advanced) {
    const start = (currentPage.value - 1) * pageSize.value;
    const end = start + pageSize.value;
    dataLength.value = result.length;
    return result.slice(start, end);
  }

  // For basic tables, return all results without pagination
  dataLength.value = result.length;
  return result;
});

const isNumeric = (n) => {
  return !isNaN(parseFloat(n)) && isFinite(n);
};

const totalEntries = computed(() => {
  return dataLength.value;
});

const sort = (index) => {
  if (index === currentSort.value) {
    currentSortDir.value = currentSortDir.value === "asc" ? "desc" : "asc";
  } else if (index !== currentSort.value && currentSortDir.value == "desc") {
    currentSortDir.value = "asc";
  }
  currentSort.value = index;
};

const pages = computed(() => {
  let totalPG = Math.ceil(dataLength.value / pageSize.value);
  const numShown = Math.min(maxPageShown.value, totalPG);
  let first = currentPage.value - Math.floor(numShown / 2);
  first = Math.max(first, 1);
  first = Math.min(first, totalPG - numShown + 1);
  return [...Array(numShown)].map((k, i) => i + first);
});

const totalPage = computed(() => {
  return Math.ceil(dataLength.value / pageSize.value);
});

const pageChange = (page) => {
  currentPage.value = page;
};

const nextPage = () => {
  if (currentPage.value * pageSize.value < dataLength.value)
    currentPage.value++;
};
const prevPage = () => {
  if (currentPage.value > 1) currentPage.value--;
};

const firstPage = () => {
  currentPage.value = 1;
};

const lastPage = () => {
  currentPage.value = totalPage.value;
};

const hideColumn = (key) => {
  if (!getFilter(key)) {
    // insert into filter variable to tell there is a change in filter
    setFilter(key, "hide", true);
  } else {
    // update filter variable to tell there is a change in filter
    setFilter(key, "hide", false);
  }
};

const setFilter = (key, action, condition) => {
  // Check if key exist inside filter
  let index = filter.value.findIndex((item) => item.key === key);

  if (index == -1) {
    // If key not exist, insert new filter
    filter.value.push({
      key: key,
      action: {
        [action]: condition,
      },
    });
  } else {
    // If key exist, update filter
    filter.value[index].action[action] = condition;
    // Update references to use dataTitle
    let columnIndex = dataTitle.value.indexOf(key);
    if (columnIndex !== -1) {
      // Remove column from dataTitle
      dataTitle.value.splice(columnIndex, 1);
    }
  }
};

const getFilter = (key) => {
  let result = false;
  filter.value.forEach((item) => {
    if (item.key === key) {
      result = item.action.hide;
    }
  });
  return result;
};

// Watch filter.value
watch(
  () => filter.value,
  () => {
    // console.log(filter.value);
    // Loop json object filter.value
    filter.value.forEach((item) => {
      // Hide Column
      if (item.action.hide) {
        // Get index title from dataTitle
        let index = dataTitle.value.indexOf(item.key);

        if (index !== -1) {
          // Remove column from dataTitle
          dataTitle.value.splice(index, 1);
        }
      } else if (!item.action.hide) {
        // Get index title from dataTitle
        let indexData = dataTitle.value.indexOf(item.key);

        if (!dataTitle.value.includes(item.key)) {
          // Add Column back to its original position
          dataTitle.value.splice(indexData, 0, item.key);

          // Sort the dataTitle like dataTitle
          dataTitle.value.sort((a, b) => {
            let indexA = dataTitle.value.indexOf(a);
            let indexB = dataTitle.value.indexOf(b);
            return indexA - indexB;
          });
        }
      }
    });
  },
  { deep: true }
);

const filterComputed = computed(() => {
  let result = [];
  let i = 0;
  filter.value.forEach((item) => {
    if (item.action.hide) {
      result.push({
        title: item.key,
        hide: item.action.hide,
      });
    }
    i++;
  });
  // console.log(result);
  return result;
});

// watch pinia getter windowWidth
watch(
  () => windowWidth.value,
  () => {
    if (props.optionsAdvanced.responsive) {
      if (windowWidth.value <= mobileWidth) {
        hideTable.value = true;
      } else {
        hideTable.value = false;
      }
    }
  },
  { deep: true }
);

// Debounce the search input
watch(
  keyword,
  useDebounceFn((val) => {
    debouncedKeyword.value = val;
  }, 300)
);

// Modify the columnTitle computed property
const computedColumnTitle = computed(() => {
  let titles =
    props.field.length > 0
      ? spacingCharactertoCamelCase(props.field)
      : dataTable.value.length > 0
        ? Object.keys(dataTable.value[0])
        : [];

  if (props.showRowNumbers) {
    titles.unshift("No");
  }

  return titles;
});
</script>

<template>
  <div
    v-if="dataTable && dataTable.length > 0"
    class="table-wrapper"
    :class="{
      '!border': advanced && !hideTable && optionsAdvanced.outsideBorder,
    }"
  >
    <div
      class="table-header"
      :class="{
        open: openFilter,
        '!max-h-full': !optionsAdvanced.filterable,
      }"
      v-if="advanced"
    >
      <div
        class="table-header-filter"
        :class="{
          '!items-center !gap-3': !optionsAdvanced.filterable,
        }"
      >
        <div>
          <div class="flex gap-x-2">
            <FormKit
              v-model="keyword"
              type="search"
              placeholder="Search..."
              outer-class="mb-0"
            />
            <rs-button
              v-if="optionsAdvanced.filterable"
              class="!px-3 sm:!px-6"
              @click="openFilter ? (openFilter = false) : (openFilter = true)"
            >
              <Icon
                name="ic:outline-filter-alt"
                class="mr-0 md:mr-1"
                size="1rem"
              />
              <span class="hidden sm:block">Filter</span>
            </rs-button>
          </div>
        </div>
        <div class="flex justify-center items-center gap-x-2">
          <span class="text-[rgb(var(--text-color))]">Result per page:</span>
          <FormKit
            type="select"
            v-model="pageSize"
            :options="[5, 10, 25, 100]"
            outer-class="mb-0"
          />
        </div>
      </div>
      <div
        class="flex flex-wrap items-center justify-start gap-x-3"
        v-if="optionsAdvanced.filterable"
      >
        <rs-dropdown
          :title="camelCasetoTitle(val)"
          size="sm"
          class="mt-3"
          v-for="(val, index) in dataTitle"
          :key="index"
        >
          <rs-dropdown-item @click="hideColumn(val)">
            {{ getFilter(val) ? "Show Column" : "Hide Column" }}
            <Icon
              :name="getFilter(val) ? 'mdi:eye-outline' : 'mdi:eye-off-outline'"
              size="1rem"
              class="ml-auto"
            ></Icon>
          </rs-dropdown-item>
        </rs-dropdown>
      </div>
    </div>
    <div
      v-if="filterComputed.length > 0"
      class="table-header-filter-list w-full m-4"
    >
      <div class="flex flex-wrap items-center justify-start gap-x-2">
        <div
          class="flex items-center justify-center gap-x-2 border border-primary text-primary rounded-lg py-1 px-2"
          v-for="(val, index) in filterComputed"
          :key="index"
        >
          {{ val ? camelCasetoTitle(val.title) : "" }}
          <Icon
            name="ic:round-close"
            class="mr-0 md:mr-1 hover:text-red-500 cursor-pointer"
            size="1rem"
            @click="hideColumn(val.title)"
          ></Icon>
        </div>
      </div>
    </div>
    <div class="w-full overflow-x-auto">
      <client-only>
        <table
          v-if="!hideTable"
          class="table-content"
          :class="{
            '!border-y !border-0 border-[rgb(var(--bg-1))]': advanced,
            'table-fixed': options.fixed,
            'table-auto': !options.fixed,
          }"
        >
          <thead
            class="text-left border-[rgb(var(--border-color))]"
            :class="{
              'border-y': !options.borderless,
              'border-[rgb(var(--border-color))] bg-[rgb(var(--bg-2))]':
                options.variant === 'default',
              'border-primary/50 bg-primary text-white':
                options.variant === 'primary',
              'border-secondary/50 bg-secondary text-white':
                options.variant === 'secondary',
              'border-info/50 bg-info text-white ': options.variant === 'info',
              'border-success/50 bg-success text-white':
                options.variant === 'success',
              'border-warning/50 bg-warning text-white':
                options.variant === 'warning',
              'border-danger/50 bg-danger text-white':
                options.variant === 'danger',
            }"
          >
            <tr>
              <th
                class="relative py-3 pl-5 pr-8 whitespace-nowrap"
                :class="{
                  'border-r last:border-l last:border-r-0':
                    options.bordered && !options.borderless,
                  'border-[rgb(var(--border-color))]':
                    options.variant === 'default',
                  'border-primary/80': options.variant === 'primary',
                  'border-secondary/80': options.variant === 'secondary',
                  'border-info/80': options.variant === 'info',
                  'border-success/80': options.variant === 'success',
                  'border-warning/80': options.variant === 'warning',
                  'border-danger/80': options.variant === 'danger',
                  'w-36': options.fixed,
                  'cursor-pointer': optionsAdvanced.sortable && advanced,
                  'w-16': val === 'No' && props.showRowNumbers,
                }"
                style="min-width: 100px"
                @click="
                  optionsAdvanced.sortable && advanced && val !== 'No'
                    ? sort(index)
                    : null
                "
                v-for="(val, index) in computedColumnTitle"
                :key="index"
              >
                {{ camelCasetoTitle(val) }}
                <div
                  v-if="optionsAdvanced.sortable && advanced"
                  class="sortable"
                >
                  <Icon
                    class="absolute top-3 right-2 opacity-20"
                    size="1.25rem"
                    name="carbon:chevron-sort"
                  />
                  <Icon
                    v-if="currentSort == index && currentSortDir == 'asc'"
                    class="absolute top-3 right-2 opacity-50"
                    size="1.25rem"
                    name="carbon:chevron-sort-up"
                  />
                  <Icon
                    v-else-if="currentSort == index && currentSortDir == 'desc'"
                    class="absolute top-3 right-2 opacity-50"
                    size="1.25rem"
                    name="carbon:chevron-sort-down"
                  />
                </div>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr
              :class="{
                'border-y border-[rgb(var(--border-color))]':
                  !options.bordered && !options.borderless,
                'border-b': options.bordered && !options.borderless,
                'border-b-0': options.borderless,
                'border-[rgb(var(--border-color))] odd:bg-[rgb(var(--bg-1))] even:bg-[rgb(var(--bg-2))]':
                  options.variant === 'default' && options.striped,
                'border-primary/20 odd:bg-white even:bg-primary/5':
                  options.variant === 'primary' && options.striped,
                'border-secondary/20 odd:bg-white even:bg-secondary/5':
                  options.variant === 'secondary' && options.striped,
                'border-info/20 odd:bg-white even:bg-info/5':
                  options.variant === 'info' && options.striped,
                'border-success/20 odd:bg-white even:bg-success/5':
                  options.variant === 'success' && options.striped,
                'border-warning/20 odd:bg-white even:bg-warning/5':
                  options.variant === 'warning' && options.striped,
                'border-danger/20 odd:bg-white even:bg-danger/5':
                  options.variant === 'danger' && options.striped,
                '': options.hover && options.variant === 'default',
                'cursor-pointer hover:bg-primary/5':
                  options.hover && options.variant === 'primary',
                'cursor-pointer hover:bg-secondary/5':
                  options.hover && options.variant === 'secondary',
                'cursor-pointer hover:bg-info/5':
                  options.hover && options.variant === 'info',
                'cursor-pointer hover:bg-success/5':
                  options.hover && options.variant === 'success',
                'cursor-pointer hover:bg-warning/5':
                  options.hover && options.variant === 'warning',
                'cursor-pointer hover:bg-danger/5':
                  options.hover && options.variant === 'danger',
              }"
              v-for="(val1, index1) in computedData"
              :key="index1"
            >
              <td
                class="p-4 pl-5 break-words"
                :class="{
                  'border-r last:border-l last:border-r-0':
                    options.bordered && !options.borderless,
                  'border-[rgb(var(--border-color))]':
                    options.variant === 'default',
                  'border-primary/20': options.variant === 'primary',
                  'border-secondary/20': options.variant === 'secondary',
                  'border-info/20': options.variant === 'info',
                  'border-success/20': options.variant === 'success',
                  'border-warning/20': options.variant === 'warning',
                  'border-danger/20': options.variant === 'danger',
                }"
                v-for="(val2, index2) in computedColumnTitle"
                :key="index2"
              >
                <slot
                  :name="val2"
                  :text="filteredDatabyTitle(val1, val2)"
                  :value="val1"
                >
                  {{ filteredDatabyTitle(val1, val2) }}
                </slot>
              </td>
            </tr>
          </tbody>
        </table>
        <div v-else>
          <rs-collapse v-if="computedData.length > 0" accordion>
            <rs-collapse-item v-for="(val, index) in computedData" :key="index">
              <template #title>
                <div class="grid grid-cols-2">
                  <div class="flex flex-col col-span-1">
                    <span class="font-semibold leading-tight">
                      {{ Object.values(val)[0] }}
                    </span>
                    <span class="text-sm"> {{ Object.values(val)[1] }} </span>
                  </div>
                  <div class="flex justify-end items-center col-span-1">
                    <div class="mr-4">
                      {{ Object.values(val)[computedData.length] }}
                    </div>
                  </div>
                </div>
              </template>
              <template #default>
                <div
                  class="flex justify-between items-center even:bg-inherit odd:bg-[rgb(var(--bg-1))] rounded-lg p-3"
                  v-for="(val1, index1) in Object.entries(val).slice(
                    2,
                    Object.entries(val).length
                  )"
                  :key="index1"
                >
                  <span>
                    {{ camelCasetoTitle(val1[0]) }}
                  </span>
                  <span>
                    {{ val1[1] }}
                  </span>
                </div>
              </template>
            </rs-collapse-item>
          </rs-collapse>
        </div>
      </client-only>
    </div>
    <div v-if="advanced" class="table-footer">
      <div class="flex justify-center items-center gap-x-2">
        <span class="text-sm text-[rgb(var(--text-color))] hidden md:block"
          >Showing {{ pageSize * currentPage - pageSize + 1 }} to
          {{ pageSize * currentPage }} of {{ totalEntries }} entries</span
        >
      </div>
      <div class="table-footer-page">
        <rs-button
          :variant="`${
            options.variant == 'default' ? 'primary' : options.variant
          }-outline`"
          class="!rounded-full !p-1 !w-8 !h-8"
          @click="firstPage"
          :disabled="currentPage == 1"
        >
          <Icon name="ic:round-keyboard-double-arrow-left" size="1rem"></Icon>
        </rs-button>
        <rs-button
          :variant="`${
            options.variant == 'default' ? 'primary' : options.variant
          }-outline`"
          class="!rounded-full !p-1 !w-8 !h-8"
          @click="prevPage"
          :disabled="currentPage == 1"
        >
          <Icon name="ic:round-keyboard-arrow-left" size="1rem"></Icon>
        </rs-button>
        <rs-button
          :variant="`${
            currentPage == val && options.variant != 'default'
              ? options.variant
              : currentPage == val && options.variant == 'default'
                ? 'primary'
                : options.variant == 'default'
                  ? 'primary-outline'
                  : options.variant + '-outline'
          }`"
          class="!rounded-full !p-1 !w-8 !h-8"
          v-for="(val, index) in pages"
          :key="index"
          @click="pageChange(val)"
        >
          {{ val }}
        </rs-button>
        <rs-button
          :variant="`${
            options.variant == 'default' ? 'primary' : options.variant
          }-outline`"
          class="!rounded-full !p-1 !w-8 !h-8"
          @click="nextPage"
          :disabled="currentPage == totalPage"
        >
          <Icon name="ic:round-keyboard-arrow-right" size="1rem"></Icon>
        </rs-button>
        <rs-button
          :variant="`${
            options.variant == 'default' ? 'primary' : options.variant
          }-outline`"
          class="!rounded-full !p-1 !w-8 !h-8"
          @click="lastPage"
          :disabled="currentPage == totalPage"
        >
          <Icon name="ic:round-keyboard-double-arrow-right" size="1rem"></Icon>
        </rs-button>
      </div>
    </div>
  </div>
  <div v-else class="table-wrapper">
    <div
      class="border border-[rgb(var(--border-color))] rounded-lg overflow-hidden"
    >
      <div
        class="bg-[rgb(var(--bg-2))] p-4 border-b border-[rgb(var(--border-color))]"
      >
        <h3 class="text-lg font-semibold text-[rgb(var(--text-color))]"></h3>
      </div>
      <div class="p-8 text-center">
        <Icon name="mdi:table-off" class="text-gray-300 mb-4" size="48px" />
        <p class="text-[rgb(var(--text-color))] text-lg font-medium">
          No Data Available
        </p>
        <p class="text-gray-500 mt-2">
          There are currently no entries to display in this table.
        </p>
      </div>
    </div>
  </div>
</template>
