<script setup lang="ts" generic="T extends { [key: string]: any }, P extends Path<T>">
import type { BaseBagelField, Field, Path } from '@bagelink/vue'
import type { SetupContext } from 'vue'
import type { TableSchemaProps } from '../../types/TableSchema'
import type { DraggableEvent } from '../draggable/useDraggable'
import {
	Icon,
	keyToLabel,
	Loading
} from '@bagelink/vue'
import { useSlots, watch, computed, toValue, ref, nextTick, onMounted } from 'vue'
import { useSchemaField } from '../../composables/useSchemaField'
import { useDraggable } from '../draggable/useDraggable'
import { useTableData } from './useTableData'
import { useTableSelection } from './useTableSelection'
import { useTableVirtualization } from './useTableVirtualization'

export type SortDirectionsT = 'ASC' | 'DESC'
export type EmitOrderT = `${string} ${SortDirectionsT}`

const props = defineProps<TableSchemaProps<T>>()

const emit = defineEmits<{
	orderBy: [EmitOrderT]
	select: [T]
	reorder: [{ oldIndex: number, newIndex: number, item: T }]
	lastItemVisible: []
}>()

const slots: SetupContext['slots'] = useSlots()

const loading = defineModel('loading', { default: false })
const itemHeight = defineModel('itemHeight', { default: 50 })
const selectedItems = defineModel<string[]>('selectedItems', { default: [] as string[] })

const {
	computedSchema,
	computedData,
	sortField,
	sortDirection,
	toggleSort,
	cleanTransformedData
} = useTableData<T>({
	data: computed(() => props.data),
	schema: computed(() => toValue(props.schema)),
	columns: computed(() => toValue(props.columns)),
	useServerSort: computed(() => toValue(props.useServerSort)),
	onSort: (field, direction) => {
		emit('orderBy', `${field.trim()} ${direction.trim()}` as EmitOrderT)
	}
})

const {
	computedSelectedItems,
	isSelectable,
	allSelectorEl,
	updateAllSelectorState,
	toggleSelectItem,
	toggleSelectAll
} = useTableSelection<T>({
	data: computedData,
	selectable: props.selectable,
	selectedItems,
	cleanData: cleanTransformedData,
	onSelect: (item) => { emit('select', item) }
})

const {
	list,
	containerProps,
	wrapperProps,
	scrollTo,
	lastItemEl,
	registerLastItemObserver
} = useTableVirtualization<T>({
	data: computedData,
	itemHeight: itemHeight.value,
	onLastItemVisible: () => {
		emit('lastItemVisible')
		props.onLastItemVisible?.()
	}
})

const isSortable = computed(() => !!props.sortable)
const dragHandle = computed(() => {
	if (typeof props.sortable === 'object' && props.sortable?.handle) return props.sortable.handle
	return '.drag-handle-col'
})

// Drag-to-reorder
const tbodyRef = ref<HTMLTableSectionElement>()

const { initDraggableContainer } = useDraggable({
	handle: dragHandle.value,
	mode: 'line',
	items: props.data,
	onEnd: (event: DraggableEvent) => {
		if (event.oldIndex === event.newIndex) return
		const item = computedData.value[event.oldIndex]
		const cleanItem = cleanTransformedData(item)
		emit('reorder', { oldIndex: event.oldIndex, newIndex: event.newIndex, item: cleanItem })
	}
})

function reinitDrag() {
	if (!isSortable.value || !tbodyRef.value) return
	nextTick(() => {
		if (tbodyRef.value) initDraggableContainer(tbodyRef.value)
	})
}

watch(() => props.data, reinitDrag, { deep: true })
watch(isSortable, (val) => { if (val) nextTick(reinitDrag) })
onMounted(reinitDrag)

function renderFieldForRow(field: Field<T>, row: T) {
	const { renderField } = useSchemaField<T>({
		mode: 'table',
		getFormData: () => row,
	})
	return renderField({ ...field, label: '' } as BaseBagelField<T, Path<T>>, slots as unknown as BaseBagelField<T, Path<T>>['slots'])
}

const computedItemHeight = computed(() => `${itemHeight.value}px`)

watch(
	() => loading.value,
	(newLoadingVal, oldLoadingVal) => {
		if (newLoadingVal === oldLoadingVal) { return }
		if (!newLoadingVal) { registerLastItemObserver() }
	},
	{ immediate: true }
)

watch(
	() => computedSelectedItems.value.length,
	() => { updateAllSelectorState() }
)

watch(
	() => computedData.value,
	(newData, oldData) => {
		if (newData.length === oldData.length || props.onLastItemVisible !== undefined) { return }
		scrollTo(0)
	}
)

const showLoading = computed(() => loading.value)
</script>

<template>
	<div class="table-list-wrap h-100p" v-bind="containerProps" :class="{ overflowHiddenLoading: showLoading }">
		<Loading v-if="showLoading" class="h100p" />
		<div v-bind="wrapperProps" :class="{ 'pointer-events-none': showLoading }">
			<table class="infinite-wrapper" :class="{ selecting: computedSelectedItems.length }">
				<thead class="row first-row">
					<th v-if="isSortable" class="col-drag" />
					<th v-if="isSelectable && !isSortable">
						<input ref="allSelectorEl" type="checkbox" @click.stop @change="(e) => toggleSelectAll(e)">
					</th>
					<th
						v-for="field in computedSchema" :key="field.id" class="col"
						@click="!isSortable && toggleSort(field?.id || '')"
					>
						<div class="flex">
							{{ field.label || field.attrs?.label || keyToLabel(field?.id) }}
							<div v-if="!isSortable" class="list-arrows" :class="{ sorted: sortField === field?.id }">
								<Icon :class="{ desc: sortDirection === 'DESC' }" icon="keyboard_arrow_up" />
							</div>
						</div>
					</th>
				</thead>
				<tbody ref="tbodyRef">
					<tr
						v-for="{ data: row } in list" :key="row?.id || `row-${Math.random()}`"
						class="row row-item position-relative"
						:class="{ selected: row?.id && computedSelectedItems.includes(row.id) }"
						@click="(event) => toggleSelectItem(row, event)"
					>
						<td v-if="isSortable" class="col-drag">
							<Icon icon="drag_indicator" :size="1.2" class="drag-handle-col grab" />
						</td>
						<td v-if="isSelectable && !isSortable">
							<div @click.stop>
								<input v-model="selectedItems" type="checkbox" :value="row?.id || ''">
							</div>
						</td>
						<td
							v-for="field in computedSchema" :key="`${field.id}-${row?.id || Math.random()}`"
							class="col"
						>
							<slot v-if="field.id && slots?.[field.id]" :name="field.id" :row="row" :field="field" />
							<div v-else>
								<component :is="renderFieldForRow(field, row)" />
							</div>
						</td>
					</tr>
					<tr v-if="props.onLastItemVisible !== undefined" ref="lastItemEl" style="height: 1px;" />
				</tbody>
			</table>
		</div>
	</div>
</template>

<style scoped>

.col-drag {
width: 2.5rem;
padding: 0 0.25rem;
}

.col-drag .bgl_icon-font {
color: var(--bgl-black-tint);
opacity: 0.5;
transition: opacity 0.15s;
}

.row-item:hover .col-drag .bgl_icon-font {
opacity: 0.8;
}

.grab {
cursor: grab;
}

.table-list-wrap {
min-height: 150px;
}

.selected {
background: var(--bgl-primary-tint);
}

tbody tr.selected:hover {
background: var(--bgl-primary-light);
}

.list-arrows {
opacity: 0;
}

.list-arrows .bgl_icon-font {
transition: all ease-in-out 0.2s;
}

.list-arrows.sorted {
opacity: 1;
}

.col img {
height: 35px;
margin-top: -14px;
margin-bottom: -14px;
border-radius: 5px;
object-fit: cover;
}

.col:has(img) {
padding-inline-end: 0.5rem;
}

.selecting .col {
user-select: none;
}

.overflowHiddenLoading {
overflow: hidden !important;
}

.list-arrows.sorted .desc {
transform: rotate(180deg);
display: inline-block;
}

table {
border-spacing: 0 15px;
border-collapse: collapse;
width: 100%;
}

th {
font-size: 0.8rem;
color: var(--bgl-black-tint);
position: sticky;
top: 0;
z-index: 2;
background: var(--bgl-box-bg);
height: v-bind(computedItemHeight);
vertical-align: bottom;
font-weight: 400;
text-align: start;
}

.embedded-field {
margin-bottom: -0.2rem;
margin-top: -0.2rem;
}

.row {
border-bottom: 1px solid var(--bgl-border-color);
cursor: pointer;
}

.row.first-row {
font-size: 0.8rem;
color: var(--bgl-black-tint);
position: sticky;
top: 0;
z-index: 2;
background: var(--bgl-box-bg);
vertical-align: bottom;
}

.row.first-row::after {
content: '';
border-bottom: 1px solid var(--bgl-border-color);
position: absolute;
left: 0;
right: 0;
bottom: -1px;
}

.first-row .col {
cursor: pointer;
background: var(--bgl-box-bg);
}

.col {
white-space: nowrap;
padding: 0.75rem 1rem;
transition: var(--bgl-transition);
line-height: 1;
align-items: center;
text-overflow: ellipsis;
overflow: hidden;
max-width: 30vw;
}

.col:has(.bagel-input) {
padding: 0rem 0.25rem;
}

.col>div {
display: flex;
gap: 0.5rem;
}

.max-col-width {
max-width: 30vw;
overflow: hidden;
text-overflow: ellipsis;
}

.col.check .bgl_icon-font {
border-radius: 100%;
background: var(--bgl-blue-tint);
color: var(--bgl-primary);
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-top: -2px;
}

.rows {
font-size: 0.88em;
}

.table-list {
height: 100%;
position: relative;
padding-left: 0 !important;
padding-right: 0 !important;
overflow: auto;
}

.BagelTable .table-list {
overflow: unset;
}

.row-item {
height: v-bind(computedItemHeight);
transition: all 200ms ease;
}

.row-item:hover {
background: var(--bgl-gray-light);
}

.row-item input[type='checkbox'] {
margin-top: 0.45rem !important;
accent-color: var(--bgl-accent-color);
}

.infinite-wrapper {
overflow-y: auto;
width: 100%;
}

input[type='checkbox'] {
margin-top: 0.3rem !important;
accent-color: var(--bgl-accent-color);
transform: scale(1.2);
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
height: 0.85rem;
width: 0.85rem;
}

input[type='checkbox']::before {
content: '';
height: 0.85rem;
width: 0.85rem;
background: var(--bgl-primary);
display: block;
text-align: center;
border-radius: 100%;
opacity: 0;
transition: all 200ms ease;
transform: scale(1);
position: absolute;
}

input[type='checkbox']:hover::before {
opacity: 0.2;
transform: scale(2);
}

[lang='he'] [dir='ltr'] {
text-align: right;
}

th input[type='checkbox'] {
transform: translateY(0.2rem) scale(1.2);
accent-color: var(--bgl-accent-color);
}

th::after {
content: '';
border-bottom: 1px solid var(--bgl-border-color);
position: absolute;
left: 0;
right: 0;
bottom: -1px;
}

tr {
border-bottom: 1px solid var(--bgl-border-color);
cursor: pointer;
align-items: center;
}

td,
th {
white-space: nowrap;
padding: 0.75rem 0.65rem;
transition: var(--bgl-transition);
line-height: 1;
}

tbody tr {
font-size: 0.88em;
height: v-bind(computedItemHeight);
transition: all 200ms ease;
}

tbody tr:hover {
background: var(--bgl-gray-light);
}

@media (max-width: 910px) {
.col {
	max-width: 60vw !important;
}
.max-col-width {
max-width: 60vw !important;
}
}

</style>
