<template>
  <p-table
    :key="tableKey"
    ref="table"
    :expanded-rows="expandedRows"
    :reorderable-columns="reorderableColumns"
    :resizable-columns="resizableColumns"
    :row-class="rowClass"
    :row-style="rowStyle"
    :show-headers="!headless"
    :value="data"
    :sort-field="sortField"
    :data-key="props.dataKey"
    class="wt-table"
    column-resize-mode="expand"
    lazy
    scroll-height="flex"
    scrollable
    :virtual-scroller-options="virtualScroll"
    :pt="{
      tableContainer: 'wt-scrollbar'
    }"
    @sort="sort"
    @update:expanded-rows="expandedRows = $event"
    @column-resize-end="columnResize"
    @column-reorder="columnReorder"
    @row-reorder="({ dragIndex, dropIndex }) => emit('reorder:row', { oldIndex: dragIndex, newIndex: dropIndex })"
  >
    <p-column
      v-if="rowExpansion"
      :pt="{
        columnresizer: {
          class: {
            'hidden': true
          }
        }
      }"
      body-style="width: 1%;"
      column-key="row-expander"
      header-style="width: 1%;"
    >
      <template #body="{ data: row }">
        <wt-icon-btn
          :disabled="props.rowExpansionDisabled(row)"
          :icon="isRowExpanded(row) ? 'arrow-down' : 'arrow-right'"
          @click.stop="toggleRow(row)"
        />
      </template>
    </p-column>
    <p-column
      v-if="rowReorder"
      :pt="{
        columnresizer: {
          class: {
            'hidden': true
          }
        }
      }"
      :reorderable-column="false"
      body-style="width: 1%;"
      column-key="row-reorder"
      header-style="width: 1%;"
      row-reorder
    >
      <template #body="{ data: row }">
        <wt-icon
          v-if="!isRowReorderDisabled(row)"
          data-pc-section="reorderablerowhandle"
          icon="move"
        />
      </template>
    </p-column>
    <p-column
      v-if="selectable"
      :pt="{
        columnresizer: {
          class: {
            'hidden': true
          }
        }
      }"
      :reorderable-column="false"
      body-style="width: 1%;"
      column-key="row-select"
      header-style="width: 1%;"
    >
      <template #header>
        <wt-checkbox
          :selected="isAllSelected"
          @update:selected="selectAll"
        />
      </template>
      <template #body="{ data: row }">
        <!-- check if row exists to prevent rendering errors -->
        <wt-checkbox
          v-if="row"
          :selected="_selected.includes(row)"
          @update:selected="handleSelection(row, $event)"
        />
      </template>
    </p-column>
    <p-column
      v-for="(col, idx) of dataHeaders"
      :key="col.value"
      :column-key="col.field"
      :field="col.field"
      :hidden="isColumnHidden(col)"
      :pt="{
        root: {
          'data-column-field': col.field      // required for column-resizer to get column field
        }
      }"
      :sortable="isColSortable(col)"
    >
      <template #header>
        <slot
          :index="idx"
          :header="col"
          :name="`header-${col.value}`"
        >
          <div class="wt-table__th__content typo-body-1-bold">
            <span v-tooltip="col.text">
              {{ col.text }}
            </span>
            <wt-icon
              v-if="col.sort === 'asc'"
              class="wt-table__th__sort-arrow wt-table__th__sort-arrow--asc"
              icon="sort-arrow-up"
              size="sm"
            />
            <wt-icon
              v-else-if="col.sort === 'desc'"
              class="wt-table__th__sort-arrow wt-table__th__sort-arrow--desc"
              icon="sort-arrow-down"
              size="sm"
            />
          </div>
        </slot>
      </template>
      <template #body="{ data: row, index }">
        <!--
        @slot Customize data columns. Recommended for representing nested data structures like object or array, and adding specific elements like select or chip
        @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
        -->
        <div
          :style="columnStyle(col)"
          class="wt-table__td__content typo-body-1"
        >
          <!-- check if row exists (under certain conditions row can be missing, e.g., during async data loading)
               this guard prevents rendering errors and keeps the table stable -->
          <slot
            v-if="row"
            :index="index"
            :item="row"
            :name="col.value"
          >{{ row[col.value] }}</slot>
        </div>
      </template>
      <!-- empty sorticon slot for hiding default sort icon, custom icon is rendered in header -->
      <template #sorticon>
      </template>
      <template
        v-if="isTableColumnFooters"
        #footer
      >
        <!--
        @slot Add your custom aggregations for column in table footer. Table footer is rendered conditionally depending on templates with "-footer" name
        @scope [ { "name": "header", "description": "header object" } ]
        -->
        <slot :name="`${col.value}-footer`" />
      </template>
    </p-column>
    <p-column
      v-if="gridActions"
      :frozen="fixedActions"
      align-frozen="right"
      column-key="row-actions"
      style="width: 112px;"
    >
      <template #header>
        <!--    @slot Table head actions row slot -->
        <slot name="actions-header" />
      </template>
      <template #body="{ data: actionsData, index }">
        <!--
        @slot Table body actions row slot
        @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
        -->
        <div class="wt-table__td__actions">
          <!-- check if row exists to prevent rendering errors -->
          <slot
            v-if="actionsData"
            :index="index"
            :item="actionsData"
            name="actions"
          />
        </div>
      </template>
    </p-column>
    <template #expansion="{ data: row }">
      <div>
        <slot
          :item="row"
          name="expansion"
        ></slot>
      </div>
    </template>
    <template
      v-if="isTableFooter"
      #footer
    >
      <slot name="footer" />
    </template>
  </p-table>
</template>

<script lang="ts" setup>
import { SortSymbols } from '@webitel/ui-sdk/scripts/sortQueryAdapters';
import type { DataTableProps } from 'primevue';
import type { VirtualScrollerLazyEvent } from 'primevue/virtualscroller';
import {
	computed,
	nextTick,
	onMounted,
	onUnmounted,
	ref,
	useSlots,
	useTemplateRef,
} from 'vue';
import { useI18n } from 'vue-i18n';
import { getNextSortOrder } from '../../scripts/sortQueryAdapters.js';
import { useTableColumnDrag } from '../_internals/composables';
import type { WtTableHeader } from './types/WtTable';

/**
 * Number of items to render outside the visible area for virtual scrolling.
 * This helps maintain smooth scrolling performance by pre-rendering items
 * that are about to come into view, reducing the chance of blank spaces
 * during fast scrolling.
 */
const VIRTUAL_SCROLL_TOLERATED_ITEMS = 10;
const DEFAULT_ITEM_SIZE = 40;

interface Props extends DataTableProps {
	/**
	 * 'Accepts list of header objects. Draws text depending on "text" property, looks for data values through "value", "show" boolean controls visibility of a column (if undefined, all visible by default). ' Column width is calculated by "width" param. By default, sets 140px. '
	 */
	headers?: WtTableHeader[];
	/**
	 * 'List of data, represented by table. '
	 */
	data?: Array<unknown>;
	/**
	 * 'If true, draws sorting arrows and sends sorting events at header click. Draws a sorting arrow by "sort": "asc"/"desc" header value. '
	 */
	sortable?: boolean;
	/**
	 * 'If true, draws row selection checkboxes. Checkbox toggles data object _isSelected property. It's IMPORTANT to set this property before sending data to table. '
	 */
	selectable?: boolean;
	selected?: Array<unknown>;
	/**
	 * 'If true, reserves space for 3 icon actions in the last column. Accessible by "actions" slot. '
	 */
	gridActions?: boolean;
	/**
	 * 'If true, 3 icon actions in the last column have position:sticky and fixed on the right'
	 */
	fixedActions?: boolean;
	/**
	 * 'If true, displays table without header.'
	 */
	headless?: boolean;
	/**
	 * 'If true, allows to reorder rows.'
	 */
	rowReorder?: boolean;
	/**
	 * @author @Oleksandr Palonnyi
	 *
	 * [WTEL-8629](https://webitel.atlassian.net/browse/WTEL-8629)
	 *
	 * Unique field name used to identify a row.
	 * Required for selection, expansion, and virtual scrolling to work correctly.
	 * The value must be stable and unique across all rows.
	 */
	dataKey?: string;
	/**
	 * 'If true, restrict sprecific row reorder.'
	 */
	isRowReorderDisabled?: (row) => boolean;
	rowExpansion?: boolean;
	rowClass?: () => string;
	rowStyle?: () => {
		[key: string]: string;
	};
	resizableColumns?: boolean;
	reorderableColumns?: boolean;
	rowExpansionDisabled?: (row: object) => boolean;

	//lazy loading
	lazy?: boolean;
	onLoading?: (event: VirtualScrollerLazyEvent) => Promise<unknown>;
	loading?: boolean;
	itemSize?: number | undefined;
}

const props = withDefaults(defineProps<Props>(), {
	headers: () => [],
	data: () => [],
	sortable: false,
	selectable: true,
	gridActions: true,
	fixedActions: false,
	headless: false,
	rowReorder: false,
	rowExpansion: false,
	isRowReorderDisabled: () => false,
	rowClass: () => '',
	rowStyle: () => ({}),
	resizableColumns: false,
	reorderableColumns: false,
	rowExpansionDisabled: () => false,
	lazy: false,
	itemSize: DEFAULT_ITEM_SIZE,
	dataKey: '',
});

const { t } = useI18n();

const slots = useSlots();

const emit = defineEmits([
	'sort',
	'update:selected',
	'reorder:row',
	'column-resize',
	'column-reorder',
]);

const table = useTemplateRef('table');
const tableKey = ref(0);
const expandedRows = ref([]);

const { addTableDragListener, removeTableDragListener } = useTableColumnDrag(
	table,
	props.reorderableColumns,
);

// table's columns that should be excluded from reorder
const excludeColumnsFromReorder = [
	'row-select',
	'row-reorder',
	'row-actions',
	'row-expander',
];

const _selected = computed(() => {
	// _isSelected for backwards compatibility
	return props.selectable
		? props.selected || props.data.filter((item) => item._isSelected)
		: [];
});

const dataHeaders = computed(() => {
	return props.headers.map((header) => {
		if (!header.text && header.locale)
			return {
				...header,
				text:
					typeof header.locale === 'string'
						? t(header.locale)
						: t(...header.locale),
			};
		return header;
	});
});

const isColumnHidden = (col) => {
	return col.show === false;
};

const columnStyle = (col) => {
	const baseWidth = 140;
	const width = col.width || `${baseWidth}px`;

	if (props.lazy)
		return {
			width,
		};

	return {
		minWidth: width,
	};
};

const isTableColumnFooters = computed(() => {
	return Object.keys(slots).some((slotName) => slotName.includes('-footer'));
});

const isTableFooter = computed(() => {
	return Object.keys(slots).some((slotName) => slotName === 'footer');
});

const isAllSelected = computed(() => {
	return _selected.value.length === props.data.length && props.data.length > 0;
});

/*
 * @author @Lera24
 * [WTEL-9194] https://webitel.atlassian.net/browse/WTEL-9192
 * save sorted the value field to apply it after reload
 * */
const sortField = computed(() => {
	const sortedCol = props.headers.find(
		(h) => h.sort === SortSymbols.ASC || h.sort === SortSymbols.DESC,
	);
	return sortedCol?.field ?? null;
});

const sort = ({ sortField }) => {
	const col = dataHeaders.value.find((header) => header.field === sortField);
	if (!isColSortable(col)) return;
	const nextSort = getNextSortOrder(col.sort);
	emit('sort', col, nextSort);
};

const isColSortable = ({ sort }) => {
	/*       --sortable = sortable && col.sort === undefined cause there may be some columns we don't want to sort
            strict check for  === undefined is used because col.sort = null is sort order too (actualu, without sort)
            so we need to check if this property is present
    */
	return props.sortable && sort !== undefined;
};

const selectAll = () => {
	if (props.selected) {
		if (isAllSelected.value) {
			emit('update:selected', []);
		} else {
			emit('update:selected', [
				...props.data,
			]);
		}
	} else {
		// for backwards compatibility

		// https://webitel.atlassian.net/browse/WTEL-4634
		// Value for _isSelected must be assigned explicitly.
		// Because allSelected recomputes after each change

		if (isAllSelected.value) {
			props.data.forEach((item) => {
				item._isSelected = false;
			});
		} else {
			props.data.forEach((item) => {
				item._isSelected = true;
			});
		}
	}
};

const handleSelection = (row, select) => {
	if (props.selected) {
		if (select) {
			emit('update:selected', [
				..._selected.value,
				row,
			]);
		} else {
			emit(
				'update:selected',
				_selected.value.filter((item) => item !== row),
			);
		}
	} else {
		// for backwards compatibility
		row._isSelected = !row._isSelected;
	}
};

const columnResize = ({ element }) => {
	// getting column name by custom attribute due Primevue does not provide it
	const field = element.getAttribute('data-column-field');

	const computedStyle = getComputedStyle(element);
	const paddingLeft = Number.parseFloat(computedStyle.paddingLeft);
	const paddingRight = Number.parseFloat(computedStyle.paddingRight);

	const columnWidth = element.offsetWidth - paddingLeft - paddingRight;

	emit('column-resize', {
		columnName: field,
		columnWidth: `${columnWidth}px`,
	});
};

const columnReorder = () => {
	const containerEl = table.value.$el.querySelector(
		'.p-datatable-table-container',
	);
	const containerElScrollLeft = containerEl.scrollLeft;
	const newOrder = table.value.d_columnOrder.filter(
		(col) => !excludeColumnsFromReorder.includes(col),
	);
	tableKey.value += 1;
	emit('column-reorder', newOrder);
	nextTick(() => {
		addTableDragListener();
		table.value.$el.querySelector('.p-datatable-table-container').scrollLeft =
			containerElScrollLeft;
	});
};

const isRowExpanded = (row) => {
	return expandedRows.value.some((r) => r?.id === row?.id);
};

const toggleRow = (row) => {
	const index = expandedRows.value.findIndex((r) => r.id === row.id);
	if (index !== -1) {
		expandedRows.value.splice(index, 1);
	} else {
		expandedRows.value.push(row);
	}
};

const virtualScroll = computed(() => {
	if (!props.lazy) return;

	return {
		lazy: props.lazy,
		onLazyLoad: props.onLoading,
		itemSize: props.itemSize, // The height/width of item according to orientation
		numToleratedItems: VIRTUAL_SCROLL_TOLERATED_ITEMS, // Number of items to pre-render outside visible area
		totalRecords: props.data.length,
		autoSize: true, // Enable auto height recalculation
	};
});

onMounted(() => {
	addTableDragListener();
});

onUnmounted(() => {
	removeTableDragListener();
});
</script>

<style scoped>
.wt-table {
  overflow: auto;
}

/* style for virtual scroller */
.wt-table :deep(.wt-table__th__content) {
  width: 0;
  white-space: nowrap;
}

.wt-table :deep(.wt-table__td__content) {
  position: relative;
  display: flex;
  align-items: center;
}

.wt-table :deep(.wt-table__td__actions) {
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--spacing-xs);
}

.wt-table :deep(.wt-table__th__sort-arrow) {
  position: absolute;
  z-index: 1;
  top: 50%;
  transform: translateY(-50%);
}
</style>
