<template>
    <table class="ais w-full" :class="tableClasses" ref="table" :aria-busy="busy">
      <caption v-if="isCaption" :class="captionClasses"><slot name="caption">{{caption}}</slot></caption>

      <thead :class="theadClasses" ref="thead">
      <!-- Header Label Row -->
        <tr :class="theadTrClasses">
          <th v-for="field in fields"
              :key="field.key"
              :class="getThClass(field)"
              @click="emitHeadClick(field.key, field, $event, false)"
          >
              <div v-if="field.sortable"
                   class="cursor-pointer flex flex-row gap-2 items-center relative z-0"
                   :class="getThDivClass(field)"
                   style="text-align: inherit"
                   @click.shift="sort(field, true)"
                   @click.meta="sort(field, true)"
                   @click.ctrl="sort(field, true)"
                   @click.exact="sort(field, false)"
              >
                <!-- ICON -->
                <AisIcon v-if="field.icon" :icon="field.icon" />

                <!-- LABEL -->
                <div>
                  <slot :name="`head(${field.key})`" :field="field">
                    <slot v-if="!isSlot(`head(${field.key})`)" name="head()" :field="field">
                      {{ field.label }}
                    </slot>
                  </slot>
                </div>

                <!-- SORT ICONS -->
                <div v-if="getIndex(field.key) >= 0" class="absolute right-0 flex-none w-2 cursor-pointer z-10 flex items-center -mt-1">
                  <div class="grid gap-1 justify-center items-center">
                    <span class="leading-3">
                      <AisIcon v-if="isAscending(field.key)" icon="caret-up" />
                      <AisIcon v-else-if="isDescending(field.key)" icon="caret-down" />
                    </span>
                    <div class="p-0 m-0 text-center leading-3" style="margin-top: -0.5rem"><small>{{ getIndex(field.key) + 1 }}</small></div>
                  </div>
                </div>
              </div>

              <div v-else class="flex flex-row gap-2 items-center relative z-0" :class="getThDivClass(field)">
                <AisIcon v-if="field.icon" :icon="field.icon" />

                <div>
                  <slot :name="`head(${field.key})`" :field="field">
                    <slot v-if="!isSlot(`head(${field.key})`)" name="head()" :field="field">
                      {{ field.label }}
                    </slot>
                  </slot>
                </div>
              </div>
          </th>
        </tr>

        <!-- Header Column Filters Row -->
        <tr v-if="filtering" class="filter">
          <th v-for="field in fields" :key="field.key" :class="[getThClass(field), 'cursor-pointer hover:bg-gray-200']">
            <div v-if="field.filter" class="flex items-center">
              <AisTableColumnFilter v-model="colFilter[field.key]"
                                    :items="items"
                                    :field="field"
                                    :dark="headVariant === 'secondary' || headRowVariant === 'secondary'"
              ></AisTableColumnFilter>
            </div>

            <div v-else-if="field.dateFilter" class="text-center">
              <AisTableColumnDateFilter v-model="dateFilter[field.key]"
                                        :dark="headVariant === 'secondary' || headRowVariant === 'secondary'"
              ></AisTableColumnDateFilter>
            </div>
          </th>
        </tr>
      </thead>

      <tbody v-if="busy">
        <tr class="busy-row">
          <td :colspan="fields.length">
            <slot name="table-busy">
              <AisIcon icon="busy"/>&nbsp;&nbsp;<b>Loading...</b>
            </slot>
          </td>
        </tr>
      </tbody>
      <tbody v-else-if="showEmpty && isEmpty">
        <tr>
          <td :colspan="fields.length">
            <slot name="empty" :emptyText="emptyText" :emptyFilteredText="emptyFilteredText" :fields="fields" :items="items">
              {{ emptyText }}
            </slot>
          </td>
        </tr>
      </tbody>
      <tbody v-else-if="showEmpty && isEmptyFiltered">
        <tr>
          <td :colspan="fields.length">
            <slot name="emptyFiltered" :emptyText="emptyText" :emptyFilteredText="emptyFilteredText" :fields="fields" :items="items">
              {{ emptyFilteredText }}
            </slot>
          </td>
        </tr>
      </tbody>
      <tbody v-else :class="tbodyClass" ref="tbody">
        <tr v-for="(item, index) in tableItems"
            :key="item"
            :class="getTbodyTrClass(item)"
            @click="emitRowClicked(item, index, $event)"
            @dblclick="emitRowDoubleClicked(item, index, $event)"
            @@contextmenu="emitRowRightClicked(item, index, $event)"
            @mouseover="emitRowHovered(item, index, $event)"
            @mouseout="emitRowUnhovered(item, index, $event)"
        >
          <td v-for="field in fields" :key="field.key" :class="getTdClass(item, field)" v-bind="getTdAttr(item, field)">
            <slot :name="`body(${field.key})`" :index="index" :item="item" :field="field" :value="getDisplayValue(item, field)" :unformatted="getFieldValue(item, field.key, field.formatter).rawValue">
              <slot v-if="!isSlot(`body(${field.key})`)" name="body()" :index="index" :item="item" :field="field" :value="getDisplayValue(item, field)" :unformatted="getFieldValue(item, field.key, field.formatter).rawValue">
                {{ getDisplayValue(item, field) }}
              </slot>
            </slot>
          </td>
        </tr>
      </tbody>

      <tfoot v-if="footClone && !busy" :class="tfootClasses" ref="tfoot">
        <tr :class="tfootTrClasses">
          <th v-for="field in fields"
              :key="field.key"
              :class="getThClass(field)"
              @click="emitHeadClick(field.key, field, $event, true)"
          >
            <div v-if="field.sortable"
                 class="cursor-pointer flex flex-row gap-2 items-center relative z-0"
                 :class="getThDivClass(field)"
                 @click.shift="sort(field, true)"
                 @click.meta="sort(field, true)"
                 @click.ctrl="sort(field, true)"
                 @click.exact="sort(field, false)"
            >
              <!-- ICON -->
              <AisIcon v-if="field.icon" :icon="field.icon" />

              <!-- LABEL -->
              <div>
                <slot :name="`foot(${field.key})`" :field="field">
                  <slot v-if="!isSlot(`foot(${field.key})`)" :name="`head(${field.key})`" :field="field">
                    <slot v-if="!isSlot(`head(${field.key})`)" name="foot()" :field="field">
                      <slot v-if="!isSlot('foot()')" name="head()" :field="field">
                        {{ field.label }}
                      </slot>
                    </slot>
                  </slot>
                </slot>
              </div>

              <!-- SORT ICONS -->
              <div v-if="getIndex(field.key) >= 0" class="absolute right-0 flex-none w-2 cursor-pointer z-10 flex items-center -mt-1">
                <div class="grid gap-1 justify-center items-center">
                    <span class="leading-3">
                      <AisIcon v-if="isAscending(field.key)" icon="caret-up" />
                      <AisIcon v-else-if="isDescending(field.key)" icon="caret-down" />
                    </span>
                  <div class="p-0 m-0 text-center leading-3" style="margin-top: -0.5rem"><small>{{ getIndex(field.key) + 1 }}</small></div>
                </div>
              </div>
            </div>

            <div v-else class="flex flex-row gap-2 items-center relative z-0" :class="getThDivClass(field)">
              <AisIcon v-if="field.icon" :icon="field.icon" />

              <div>
                <slot :name="`foot(${field.key})`" :field="field">
                  <slot v-if="!isSlot(`foot(${field.key})`)" :name="`head(${field.key})`" :field="field">
                    <slot v-if="!isSlot(`head(${field.key})`)" name="foot()" :field="field">
                      <slot v-if="!isSlot('foot()')" name="head()" :field="field">
                        {{ field.label }}
                      </slot>
                    </slot>
                  </slot>
                </slot>
              </div>
            </div>
          </th>
        </tr>
      </tfoot>
    </table>
</template>


<script setup lang="ts">
  import { ref, computed, nextTick, onMounted, useSlots } from 'vue'
  import { fieldSorter } from '../../util/array-util'
  import { classToArray } from "@/util/class-util";
  import { expectedValues } from "@/util/prop-validators";
  import { useDateFormat } from "@vueuse/core";
  import { getDisplayValue, getFieldValue, getFieldByKey } from "../../util/table-util";
  import AisIcon from '@/components/icon/AisIcon.vue'
  import AisTableColumnFilter from "@/components/table/AisTableColumnFilter.vue";
  import AisTableColumnDateFilter from "@/components/table/AisTableColumnDateFilter.vue";
  import { faCaretDown, faCaretUp } from '@fortawesome/free-solid-svg-icons'
  import { library } from "@fortawesome/fontawesome-svg-core";
  library.add(faCaretDown, faCaretUp)

  type Size = '' | 'sm' | 'lg'
  type HeadOrRowVariant = '' | 'primary' | 'secondary'
  type CommonTableType = string | Array<any> | object

  interface Props {
    id?: string
    items: [] | Array<any> | Function
    fields: [] | Array<any>
    primaryKey?: string
    striped?: boolean
    bordered?: boolean
    borderless?: boolean
    outlined?: boolean
    hover?: boolean
    size?: Size
    small?: boolean
    large?: boolean
    flush?: boolean
    stickyHeaders?: boolean | string
    stickyTop?: string
    stickyBottom?: string
    noBorderCollapse?: boolean
    tableClass?: CommonTableType
    headVariant?: HeadOrRowVariant
    headRowVariant?: HeadOrRowVariant
    theadClass?: CommonTableType
    theadTrClass?: CommonTableType
    footClone?: boolean
    footVariant?: HeadOrRowVariant
    footRowVariant?: HeadOrRowVariant
    tfootClass?: CommonTableType
    tfootTrClass?: CommonTableType
    tbodyTrClass?: CommonTableType
    tbodyClass?: CommonTableType
    filter?: string | object
    sortBy?: [] | Array<any>
    sortNullFirst?: boolean
    perPage?: number | string
    currentPage?: number | string
    caption?: string
    showEmpty?: boolean
    emptyText?: string
    emptyFilteredText?: string
    busy?: boolean
  }

  const props = withDefaults(defineProps<Props>(),{
    id: () => '',
    items: () => [],
    fields: () => null,
    primaryKey: () => 'id',
    striped: false,
    bordered: false,
    borderless: false,
    outlined: false,
    hover: false,
    size: "",
    small: false,
    large: false,
    flush: false,
    stickyHeaders: false,
    stickyTop: 'top-0',
    stickyBottom: 'bottom-0',
    noBorderCollapse: false,
    tableClass: "",
    headVariant: "",
    headRowVariant: "",
    theadClass: () => "",
    theadTrClass: () => "",
    footClone: false,
    footVariant: () => "",
    footRowVariant: () => "",
    tfootClass: () => "",
    tfootTrClass: () => "",
    tbodyClass: () => "",
    filter: () => "",
    sortBy: () => [""],
    sortNullFirst: false,
    perPage: () => 0,
    currentPage: () => 1,
    caption: () => "",
    showEmpty: () => false,
    emptyText: () => "There are no records to show",
    emptyFilteredText: () => "There are no records matching your filter values",
    busy: () => false,
    tbodyTrClass: () => ""
  })


  // const props = defineProps({
  //     /**
  //      * Used to set the 'id' attribute on the rendered content, and used as the base to generate any additional
  //      * element IDs as needed
  //      */
  //     id: { type: String, default: () => '' },
  //
  //     /**
  //      * Array of items to display, or an items provider function reference. Refer to the docs for details
  //      */
  //     items: { type: [Array, Function], default: () => [] },
  //
  //     /**
  //      * Array of field names or array of field definition objects
  //      */
  //     fields: { type: Array, default: () => null },
  //
  //     /**
  //      * Name of a table field that contains a guaranteed unique value per row. Needed for tbody transition
  //      * support, and also speeds up table rendering
  //      */
  //     primaryKey: { type: String, default: () => 'id' },
  //
  //     /**
  //      * Applies striping to the tbody rows
  //      */
  //     striped: { type: Boolean, default: false },
  //
  //     /**
  //      * Adds borders to all the cells and headers
  //      */
  //     bordered: { type: Boolean, default: false },
  //
  //     /**
  //      * Removes all borders from cells
  //      */
  //     borderless: { type: Boolean, default: false },
  //
  //     /**
  //      * Adds an outline border to the table element
  //      */
  //     outlined: { type: Boolean, default: false },
  //
  //     /**
  //      * Enables hover styling on rows
  //      */
  //     hover: { type: Boolean, default: false },
  //
  //     /**
  //      * Renders the table with appropriate cell padding and font-size
  //      */
  //     size: { type: String, default: '', validator: expectedValues('', 'sm', 'lg') },
  //
  //     /**
  //      * Renders the table with smaller cell padding and smaller text size. Same as size="sm"
  //      */
  //     small: { type: Boolean, default: false },
  //
  //     /**
  //      * Renders the table with larger cell padding and larger text size. Same as size="lg"
  //      */
  //     large: { type: Boolean, default: false },
  //
  //     /** when it's nested inside a card that has 'no-body' prop set to true */
  //     flush: { type: Boolean, default: false },
  //
  //     /**
  //      * Makes the table header sticky. Set to true for a maximum height 300px tall table, or set to any valid CSS
  //      * height (including units)
  //      */
  //     stickyHeaders: { type: [Boolean, String], default: false },
  //
  //     /**
  //      * The value that the sticky headers should begin sticking (i.e. top-0 or top-[20px] etc...)
  //      */
  //     stickyTop: { type: String, default: 'top-0' },
  //
  //     /**
  //      * The value that the sticky footer should begin sticking (i.e. bottom-0 or bottom-[20px] etc...)
  //      */
  //     stickyBottom: { type: String, default: 'bottom-0' },
  //
  //     /**
  //      * Disable's the collapsing of table borders. Useful when table has sticky headers or columns
  //      */
  //     noBorderCollapse: { type: Boolean, default: false },
  //
  //     /**
  //      * CSS class (or classes) to apply to the table element
  //      */
  //     tableClass: { type: [String, Array, Object], default: '' },
  //
  //     /**
  //      * Apply a color variant to the thead element ('primary' or 'secondary'. May take precedence over head-row-variant
  //      */
  //     headVariant: { type: String, default: '', validator: expectedValues('', 'primary', 'secondary') },
  //
  //     /**
  //      * Apply a color variant to the tr element in the thead
  //      */
  //     headRowVariant: { type: String, default: '', validator: expectedValues('', 'primary', 'secondary') },
  //
  //     /**
  //      * CSS class (or classes) to apply to the thead element
  //      */
  //     theadClass: { type: [String, Array, Object], default: () => '' },
  //
  //     /**
  //      * CSS class (or classes) to apply to the tr element in the thead
  //      */
  //     theadTrClass: { type: [String, Array, Object], default: () => '' },
  //
  //     /**
  //      * Enable to the footer of the table, and clone the header content by default
  //      */
  //     footClone: { type: Boolean, default: false },
  //
  //     /**
  //      * Apply a color variant to the tfoot element ('primary' or 'secondary'). Falls back to foot-variant.
  //      * May take precedence over foot-row-variant
  //      */
  //     footVariant: { type: String, default: () => '', validator: expectedValues('', 'primary', 'secondary') },
  //
  //     /**
  //      * Apply a color variant to the tr element in the tfoot. Falls back to head-row-variant
  //      */
  //     footRowVariant: { type: String, default: () => '', validator: expectedValues('', 'primary', 'secondary') },
  //
  //     /**
  //      * CSS class (or classes) to apply to the tfoot element
  //      */
  //     tfootClass: { type: [String, Array, Object], default: () => '' },
  //
  //     /**
  //      * CSS class (or classes) to apply to the tr element in the tfoot
  //      */
  //     tfootTrClass: { type: [String, Array, Object], default: () => '' },
  //
  //     /**
  //      * CSS class (or classes) to apply to the tr element in the tbody. Can be a function that returns a class
  //      * (see docs for details)
  //      */
  //     tbodyTrClass: { type: [String, Array, Object, Function], default: () => '' },
  //
  //     /**
  //      * CSS class (or classes) to apply to the tbody element
  //      */
  //     tbodyClass: { type: [String, Array, Object], default: () => '' },
  //
  //     /**
  //      * Criteria for filtering. Internal filtering supports only string or RegExpr criteria.
  //      */
  //     filter: { type: [String, Object], default: () => '' },
  //
  //     /**
  //      * Field name that is currently being sorted. Set to null to clear sorting. Syncable with the .sync prop modifier
  //      */
  //     sortBy: { type: Array, default: () => [''] },
  //
  //     /**
  //      * When sorting, null and undefined values will be sorted first (or last, depending on 'sort-desc'). Set
  //      * this prop to sort null values last. Only applicable to internal sorting
  //      */
  //     sortNullFirst: { type: Boolean, default: () => false },
  //     //
  //     // /**
  //     //  * When the build in formatter is used, setting this prop will disable the sorting ability in the footer
  //     //  */
  //     // noFooterSorting: { type: Boolean, default: () => false },
  //
  //     /**
  //      * Number of rows to show per page. Set to 0 to disable pagination
  //      */
  //     perPage: { type: [Number, String], default: () => 0 },
  //
  //     /**
  //      * The current page number to display when the table is paginated. Starting from 1 and up
  //      */
  //     currentPage: { type: [Number, String], default: () => 1 },
  //
  //     /**
  //      * Text string to place in the caption element
  //      */
  //     caption: { type: String, default: () => '' },
  //
  //     /**
  //      * When enabled, and there are no item records to show, shows a message that there are no rows to show
  //      */
  //     showEmpty: { type: Boolean, default: () => false },
  //
  //     /**
  //      * Text string to show when the table has no items to show
  //      */
  //     emptyText: { type: String, default: () => 'There are no records to show' },
  //
  //     /**
  //      * Text string to show when the table has no items to show due to filtering
  //      */
  //     emptyFilteredText: { type: String, default: () => 'There are no records matching your filter values' },
  //
  //     /**
  //      * When set, forces the table into the busy state.Automatically set when an items provider function is
  //      * being called
  //      */
  //     busy: { type: Boolean, default: () => false },
  // })

  const emit = defineEmits(['row-clicked', 'row-dblclicked', 'row-contextmenu', 'row-hovered',
                            'row-unhovered', 'head-clicked', 'sort-changed', 'filtered'])

  const table = ref(null)
  const thead = ref(null)
  const tbody = ref(null)
  const orderBy = ref([...props.sortBy] || [])
  const colFilter = ref({})
  const dateFilter = ref({})

  /* COMPUTED */
  const tableItems = computed(() => {
      // console.log(`tableItems: perPage=[${props.perPage}], currentPage=[${props.currentPage}]`);
      if(props.perPage > 0) {
          const currentPage = Math.max(Number(props.currentPage), 1);
          const perPage = Math.max(Number(props.perPage), 0);
          const items = sortedItems.value.slice((currentPage - 1) * perPage, currentPage * perPage);
          // console.log(`tableItems: items.length=[${items.length}]`);

          return items;
      }

      return sortedItems.value;
  })
  const sortedItems = computed(() => {
      return filteredItems.value ? [...filteredItems.value].sort(fieldSorter(orderBy.value, props.sortNullFirst, props.fields)) : []; //ask ben: sortNullsFirst
  })
  const filteredItems = computed(() => {
    if (props.items == null || props.fields == null) return [];
    let filter = ''
    if(typeof props.filter === 'string')
      filter = props.filter
    // @ts-ignore
    else if(typeof props.filter === 'object' && props.filter.query)
        // @ts-ignore
      filter = props.filter.query

    // @ts-ignore
    const filtered = props.items.filter(item => {
        let include = !filter;

        /* Check for match from text input 'query' */
        if (filter) {
          props.fields.forEach(field => {
            if (field && field.key) {
              const { rawValue, formattedValue } = getFieldValue(item, field.key, field.formatter)
              const fieldValue = field.filterByFormatted ? formattedValue : rawValue
              if (fieldValue && (fieldValue + '').toLowerCase().indexOf(filter.toLowerCase()) > -1)
                include = true;
            }
          });
        }

        /* Check match in field filters */
        if(include) {
          const colKeys = Object.keys(colFilter.value)
          colKeys.forEach(key => {
            if (include && colFilter.value[key].length > 0) {
              const field = getFieldByKey(props.fields, key)
              const { rawValue, formattedValue } = getFieldValue(item, field.key, field.formatter)
              const fieldValue = field.filterByFormatted ? formattedValue : rawValue
              include = colFilter.value[key] === fieldValue || colFilter.value[key].includes(String(fieldValue ?? ''))
            }
          })
        }

        /* Check match in date field filters */
        if(include) {
          const colKeys = Object.keys(dateFilter.value)
          colKeys.forEach(key => {
            const dateField = dateFilter.value[key]
            if (dateField.type) {
              const field = getFieldByKey(props.fields, key)
              const { rawValue, formattedValue } = getFieldValue(item, field.key, field.formatter)
              const fieldValue = field.filterByFormatted ? formattedValue : rawValue
              const value = fieldValue ? useDateFormat(fieldValue, 'YYYY-MM-DD').value : null
              switch(dateField.type) {
                case 'equals':
                  include = value === dateField.from
                  break;
                case 'before':
                  include = value < dateField.from
                  break;
                case 'beforeNull':
                  include = value === null || value < dateField.from
                  break;
                case 'after':
                  include = value > dateField.from
                  break;
                case 'afterNull':
                  include = value === null || value > dateField.from
                  break;
                case 'between':
                  include = value >= dateField.from && value <= dateField.to
                  break;
                case 'betweenNull':
                  include = value === null || (value >= dateField.from && value <= dateField.to)
                  break;
                case 'blank':
                  include = value === null
                  break;
              }

              if(!include) return
            }
          })
        }

        /* Check for matches in other, non-displayed column filters */
      // @ts-ignore
        if(include && props.filter && props.filter.cols) {
          // @ts-ignore
          const colKeys = Object.keys(props.filter.cols)
          colKeys.forEach(key => {
            const value = item[key]
            if(include && value != null) {
              // @ts-ignore
              const colValue = props.filter.cols[key]
              if(colValue) {
                if (typeof colValue === 'string') {
                  if(typeof value === 'boolean')
                    include = value.toString() === colValue
                  else
                    include = value === colValue
                } else if (Array.isArray(colValue)) {
                  include = colValue.includes(value)
                }
              }
            }
          })
        }

        return include;
    }) || [];

    if(table.value) {
      const tableFilters = { filter: props.filter, colFilter: colFilter.value, dateFilter: dateFilter.value }
      emitFiltered( { filters: tableFilters, data: filtered } );
    }

    return filtered;
  })
  // @ts-ignore
  const filtering = computed(() => props.fields.filter(field => field.filter || field.dateFilter).length > 0)

  const isEmpty = computed(() => !props.busy && (props.items || []).length === 0)
  const isEmptyFiltered = computed(() => !props.busy && (props.items || []).length > 0 && filteredItems.value.length === 0)
  const isCaption = computed(() => props.caption || isSlot('caption'))

  const tableClasses = computed(() => {
      let classList = [];
      if (props.stickyHeaders) classList.push('table-sticky');
      if (props.striped) classList.push('table-striped');
      if (props.bordered) classList.push('table-bordered');
      if (props.borderless) classList.push('table-borderless');
      if (props.outlined) classList.push('table-outlined');
      if (props.hover) classList.push('table-hover');
      if (props.small || (props.size && props.size.toLowerCase() === 'sm'))
          classList.push('table-small');
      if(props.large || (props.size && props.size.toLowerCase() === 'lg'))
          classList.push('table-large');
      if(props.noBorderCollapse)
          classList.push('table-no-border-collapse')

      if(typeof props.tableClass === 'string')
          classList.push(...classToArray(props.tableClass));
      else if(Array.isArray(props.tableClass))
          classList.push(...props.tableClass);

      return classList.join(' ');
  })
  const captionClasses = computed(() => {
      const classes = ['font-bold border-secondary']

      if(props.headVariant === 'secondary' || props.headRowVariant === 'secondary') {
        classes.push('bg-ui-gold', 'text-secondary')

        if(props.bordered || props.outlined)
          classes.push('shadow-[1px_0_0_rgb(255,205,0),-1px_0_0_rgb(255,205,0)]')
      } else {
        classes.push('bg-secondary', 'text-white')

        if(props.bordered || props.outlined)
          classes.push('shadow-[1px_0_0_rgb(21,21,21),-1px_0_0_rgb(21,21,21)]')
      }

      if(props.small || (props.size && props.size.toLowerCase() === 'sm'))
        classes.push('p-2')
      else if(props.large || (props.size && props.size.toLowerCase() === 'lg'))
        classes.push('p-4')
      else
        classes.push('p-3')

      return classes
  })
  const theadTrClasses = computed(() => {
      const classes = []

    if(props.headRowVariant === 'primary')
      classes.push('bg-primary')
    else if(props.headRowVariant === 'secondary')
      classes.push('bg-secondary')

      classes.push(...classToArray(props.theadTrClass))
      return classes;
  })
  const theadClasses = computed(() => {
      const classes = []

      if(props.headVariant === 'primary')
        classes.push('bg-primary')
      else if(props.headVariant === 'secondary')
        classes.push('bg-secondary')

      if(props.stickyHeaders) {
          classes.push('sticky', 'z-20')
          classes.push(props.stickyTop)
          classes.push('shadow')
      }

    // @ts-ignore
    if(!props.headVariant && (!props.theadClass || !props.theadClass.includes('bg-')))
      classes.push('bg-gray-50')

      classes.push(...classToArray(props.theadClass))
      return classes;
  })
  const tfootTrClasses = computed(() => {
      const classes = []

      if(props.footRowVariant)
          classes.push(props.footRowVariant)
      else if(props.headRowVariant && props.footClone) {
        if(props.headRowVariant === 'primary')
          classes.push('bg-primary')
        else if(props.headRowVariant === 'secondary')
          classes.push('bg-secondary')
      }

      if(props.footClone)
        classes.push(...classToArray(props.theadTrClass))

      classes.push(...classToArray(props.tfootTrClass))
      return classes;
  })
  const tfootClasses = computed(() => {
      const classes = []

      if(props.footVariant)
          classes.push(props.footVariant)
      else if(props.headVariant && props.footClone) {
        if(props.headVariant === 'primary')
          classes.push('bg-primary')
        else if(props.headVariant === 'secondary')
          classes.push('bg-secondary')
      }

      if(props.stickyHeaders && props.footClone) {
          classes.push('sticky', 'z-10')
          classes.push(props.stickyBottom)
          classes.push('shadow-top')
      }

      // @ts-ignore
      if( !props.footVariant && (!props.tfootClass || !props.tfootClass.includes('bg-')) &&
          // @ts-ignore
          !props.headVariant && (!props.theadClass || !props.theadClass.includes('bg-'))) {
        classes.push('bg-gray-50')
      }

      if(props.footClone)
        classes.push(...classToArray(props.theadClass))

      classes.push(...classToArray(props.tfootClass))

      return classes.join(' ');
  })


  /* METHODS */
  const isAscending = (key) => orderBy.value.indexOf(key) >= 0
  const isDescending = (key) => orderBy.value.indexOf('-'.concat(key)) >= 0
  const getIndex = (key) => {
    let index = orderBy.value.indexOf(key);
    if(index < 0)
        index = orderBy.value.indexOf('-' + key);

    return index;
  }
  const sort = (field, multi) => {
    console.log(`sort(): field=[${JSON.stringify(field)}], multi=[${multi}]`);
    if(field.sortable) {
        const index = getIndex(field.key);
        // console.log(`sortable! key=[${field.key}], multi=[${multi}], index=[${index}]`);
        if((multi && index > -1) || (!multi && index === 0 && orderBy.value.length === 1)) {
            if(isAscending(field.key))
                orderBy.value.splice(index, 1, '-'.concat(field.key));
            else
                orderBy.value.splice(index, 1);
        } else {
            if(!multi)
                orderBy.value = [];

            orderBy.value.push(field.key);
        }
    }

    if(orderBy.value.length === 0)
        orderBy.value = [...props.sortBy] || [];

    emitSortChanged(orderBy.value);
  }

  const getTbodyTrClass = (item) => {
      const classes = []

      if(typeof props.tbodyTrClass === 'function')
          // @ts-ignore
        classes.push(...classToArray(props.tbodyTrClass(item, 'row')))
      else
        classes.push(...classToArray(props.tbodyTrClass))

      return classes.join(' ');
  }
  const getTdClass = (item, field) => {
      const tdClasses = [...classToArray(field.class)]

      if(field.tdClass && typeof field.tdClass === 'function')
        tdClasses.push(...classToArray(field.tdClass(getDisplayValue(item, field), field.key, item)));
      else
        tdClasses.push(...classToArray(field.tdClass))

      return tdClasses.join(' ');
  }
  const getThClass = (field) => { return [...classToArray(field.class), ...classToArray(field.thClass)].join(' ') }
  const getThDivClass = (field) => {
    const classes = []
    const thClasses = classToArray(getThClass(field))
    if(thClasses.includes('text-center')) {
      classes.push('justify-center')

      if(getIndex(field.key) >= 0)
        classes.push('px-3')
    } else {
      if(thClasses.includes('text-right'))
        classes.push('justify-end')
      else
        classes.push('justify-start')

      if(getIndex(field.key) >= 0)
        classes.push('pr-4')
    }

    return classes.join(' ')
  }
  const getTdAttr = (item, field) => {
    if(field.tdAttr) {
      if(typeof field.tdAttr === 'function')
        return field.tdAttr(getDisplayValue(item, field), field.key, item)
      else if(typeof field.tdAttr === 'object')
        return field.tdAttr
    }
    return undefined
  }
  const isSlot = (name) => { return !!useSlots()[name] }

  const initColFilters = () => {
    // @ts-ignore
    props.fields.filter(f => f.filter).forEach(f => colFilter.value[f.key] = [])
    // @ts-ignore
    props.fields.filter(f => f.dateFilter).forEach(f => dateFilter.value[f.key] = { type: '', from: null, to: null })
  }
  
  const emitRowClicked = (item, index, e) => {
      /**
       * Emitted when a row is clicked.
       */
      emit('row-clicked', { item: item, index: index, event: e } );

  }
  const emitRowDoubleClicked = (item, index, e) => {
      /**
       * Emitted when a row is double clicked.
       */
      emit('row-dblclicked', { item: item, index: index, event: e });

  }
  const emitRowRightClicked = (item, index, e) => {
      /**
       * Emitted when a row is right clicked.
       */
      emit('row-contextmenu', { item: item, index: index, event: e });

  }
  const emitRowHovered = (item, index, e) => {
      /**
       * Emitted when a row is hovered.
       */
      emit('row-hovered', { item: item, index: index, event: e });

  }
  const emitRowUnhovered = (item, index, e) => {
      /**
       * Emitted when a row is unhovered.
       */
      emit('row-unhovered', { item: item, index: index, event: e });

  }
  const emitHeadClick = (key, field, $event, isFooter) => {
      // console.log(`emitHeadClick:  key=[${key}], field=[${field}], event=[${$event}], isFooter=[${isFooter}]`);
      /**
       * Emitted when a header or footer cell is clicked. Not applicable for 'custom-foot' slot.
       */
      emit('head-clicked', { key: key, field: field, event: $event, isFooter: isFooter });

  }
  const emitSortChanged = (sort) => {
      /**
       * Emitted when the sorting on the table has changed
       */
      emit('sort-changed', sort);

  }
  const emitFiltered = (filteredItems) => {
      // console.log('emitFiltered: ' + JSON.stringify(filteredItems));
      /**
       * Emitted when local filtering causes a change in the number of items.
       */
      emit('filtered', filteredItems);

  }

  const setColFilters = async (colFilters) => {
    await nextTick()
    colFilter.value = colFilters
  }
  const setDateColFilters = async (dateFilters) => {
    await nextTick()
    dateFilter.value = dateFilters
  }

  defineExpose({ setColFilters, setDateColFilters })

  onMounted(() => {
    initColFilters()
  })
</script>