<script setup lang="ts" generic="T extends Record<string, any> = Record<string, any>">
import { computed, reactive, ref } from 'vue'
import Badge from '../Badge.vue'
import Btn from '../Btn.vue'
import EmptyState from '../EmptyState.vue'
import Loading from '../Loading.vue'
import type { KanbanBoardProps, KanbanColumn, KanbanMoveEvent } from './kanbanTypes'

defineOptions({ name: 'BglKanbanBoard' })

const props = withDefaults(defineProps<KanbanBoardProps<T>>(), {
	emptyText: 'Nothing here',
})

const emit = defineEmits<{
	move: [event: KanbanMoveEvent<T>]
	'card-click': [item: T, columnId: string]
}>()

const slots = defineSlots<{
	card?: (p: { item: T; column: KanbanColumn<T> }) => any
	'column-header'?: (p: { column: KanbanColumn<T>; count: number }) => any
	'column-footer'?: (p: { column: KanbanColumn<T> }) => any
}>()

/* ── mode resolution ───────────────────────────────────────────── */
const serverMode = computed(() => Array.isArray(props.columns))

function read<V>(item: T, key: keyof T | ((i: T) => V) | undefined): V | undefined {
	if (key == null) return undefined
	return typeof key === 'function' ? key(item) : (item[key] as unknown as V)
}

const keyOf = (item: T): string | number => {
	const k = props.itemKey
	const v = k ? read<any>(item, k) : (item as any).id
	return v ?? JSON.stringify(item)
}

/** Resolved columns with their items, for both modes. */
const resolvedColumns = computed<Array<KanbanColumn<T> & { items: T[] }>>(() => {
	if (serverMode.value) {
		return (props.columns ?? []).map(c => ({ ...c, items: c.items ?? [] }))
	}
	// group-by mode
	const defs = props.columnsDef ?? []
	const groupOf = (item: T) => String(read<string>(item, props.groupBy) ?? '')
	const buckets = new Map<string, T[]>()
	for (const c of defs) buckets.set(c.id, [])
	for (const item of props.data ?? []) {
		const g = groupOf(item)
		if (!buckets.has(g)) {
			// when no columnsDef provided, create columns on the fly
			if (!defs.length) buckets.set(g, [])
			else continue
		}
		buckets.get(g)!.push(item)
	}
	if (!defs.length) {
		return [...buckets.entries()].map(([id, items]) => ({ id, title: id, items }))
	}
	return defs.map(c => ({ ...c, items: buckets.get(c.id) ?? [] }))
})

/* ── load more (one handler, per-column page tracking) ─────────── */
const pageByColumn = reactive<Record<string, number>>({})

function canLoadMore(col: KanbanColumn<T> & { items: T[] }) {
	if (!props.loadMore) return false
	if (typeof col.total === 'number') return col.items.length < col.total
	return false
}
async function onLoadMore(col: KanbanColumn<T> & { items: T[] }) {
	const next = (pageByColumn[col.id] ?? 1) + 1
	pageByColumn[col.id] = next
	await props.loadMore?.(col.id, next)
}

/* ── drag & drop (HTML5, cross-column, controlled) ─────────────── */
const dragging = ref<{ item: any; from: string } | null>(null)
const dragOverCol = ref<string | null>(null)

function onDragStart(item: T, from: string) {
	if (!props.draggable) return
	dragging.value = { item, from }
}
function onDragEnd() {
	dragging.value = null
	dragOverCol.value = null
}
function onDrop(toCol: KanbanColumn<T> & { items: T[] }, index?: number) {
	if (!props.draggable || !dragging.value) return
	const { item, from } = dragging.value
	const newIndex = index ?? toCol.items.length
	emit('move', { item: item as T, fromColumn: from, toColumn: toCol.id, newIndex })
	onDragEnd()
}

/* ── default card field resolution ─────────────────────────────── */
function cardTitle(item: T) { return read<string>(item, props.cardSchema?.title) }
function cardSubtitle(item: T) { return read<string>(item, props.cardSchema?.subtitle) }
function cardId(item: T) { return read<string>(item, props.cardSchema?.id) }
function cardBadges(item: T) {
	const out: Array<{ label: string; color?: any }> = []
	for (const b of props.cardSchema?.badges ?? []) {
		const v = read<any>(item, b)
		if (v == null || v === '') continue
		out.push(typeof v === 'object' ? v : { label: String(v) })
	}
	return out
}
</script>

<template>
	<div class="bgl-kanban">
		<div
			v-for="col in resolvedColumns" :key="col.id"
			class="bgl-kanban-col"
			:class="{ 'is-dragover': dragOverCol === col.id }"
			@dragover.prevent="draggable && (dragOverCol = col.id)"
			@dragleave="dragOverCol === col.id && (dragOverCol = null)"
			@drop="onDrop(col)"
		>
			<!-- header -->
			<div class="bgl-kanban-head">
				<slot name="column-header" :column="col" :count="col.items.length">
					<Badge v-if="col.color" dot :color="col.color" :value="col.title" flat sm />
					<span v-else class="bgl-kanban-title">
						<Badge v-if="col.icon" :icon="col.icon" flat sm :value="col.title" />
						<template v-else>{{ col.title }}</template>
					</span>
					<Badge soft color="gray" sm :value="String(typeof col.total === 'number' ? col.total : col.items.length)" class="ms-auto" />
				</slot>
			</div>

			<!-- cards -->
			<div class="bgl-kanban-list">
				<div
					v-for="(item, i) in col.items" :key="keyOf(item)"
					class="bgl-kanban-card-wrap"
					:draggable="draggable || undefined"
					@dragstart="onDragStart(item, col.id)"
					@dragend="onDragEnd"
					@drop.stop="onDrop(col, i)"
					@click="emit('card-click', item, col.id)"
				>
					<slot name="card" :item="item" :column="col">
						<!-- default schema card -->
						<div class="bgl-kanban-card">
							<div v-if="cardTitle(item)" class="bgl-kanban-card-title">{{ cardTitle(item) }}</div>
							<div v-if="cardSubtitle(item)" class="bgl-kanban-card-sub">{{ cardSubtitle(item) }}</div>
							<div v-if="cardBadges(item).length" class="bgl-kanban-card-badges">
								<Badge v-for="(b, bi) in cardBadges(item)" :key="bi" sm soft :color="b.color || 'gray'" :value="b.label" />
							</div>
							<div v-if="cardId(item)" class="bgl-kanban-card-foot">{{ cardId(item) }}</div>
						</div>
					</slot>
				</div>

				<EmptyState v-if="!col.items.length && !col.loading" compact icon="inbox" :title="emptyText" class="bgl-kanban-empty" />
				<Loading v-if="col.loading" class="bgl-kanban-loading" />
			</div>

			<!-- footer / load more -->
			<div v-if="canLoadMore(col) || slots['column-footer']" class="bgl-kanban-foot">
				<slot name="column-footer" :column="col">
					<Btn
						v-if="canLoadMore(col)" flat thin sm full-width icon="expand_more"
						:value="`Load more`" :disabled="col.loading" @click="onLoadMore(col)"
					/>
				</slot>
			</div>
		</div>
	</div>
</template>

<style scoped>
.bgl-kanban {
	display: flex;
	align-items: stretch;
	gap: 1rem;
	overflow-x: auto;
	min-height: 0;
	min-width: 0;
}
.bgl-kanban-col {
	display: flex;
	flex-direction: column;
	gap: .6rem;
	width: 280px;
	min-width: 280px;
	flex-shrink: 0;
	border-radius: var(--bgl-radius, 12px);
	transition: background .12s;
}
.bgl-kanban-col.is-dragover {
	background: color-mix(in srgb, var(--bgl-primary) 8%, transparent);
	outline: 1px dashed color-mix(in srgb, var(--bgl-primary) 40%, transparent);
	outline-offset: 2px;
}
.bgl-kanban-head {
	display: flex;
	align-items: center;
	gap: .5rem;
	padding: 0 .25rem;
}
.bgl-kanban-title { font-size: 13px; font-weight: 600; }
/* flex:1 → list fills the column so empty columns stay full-height drop targets */
.bgl-kanban-list { display: flex; flex-direction: column; gap: .6rem; flex: 1; min-height: 8px; }
.bgl-kanban-card-wrap { cursor: pointer; }
.bgl-kanban-card-wrap[draggable='true'] { cursor: grab; }
.bgl-kanban-card-wrap[draggable='true']:active { cursor: grabbing; }
.bgl-kanban-card {
	display: flex;
	flex-direction: column;
	gap: .5rem;
	padding: .75rem .85rem;
	background: var(--bgl-box-bg);
	border: 1px solid var(--bgl-border-color);
	border-radius: var(--bgl-radius, 12px);
	transition: border-color .12s, box-shadow .12s;
}
.bgl-kanban-card:hover { border-color: color-mix(in srgb, var(--bgl-primary) 50%, var(--bgl-border-color)); }
.bgl-kanban-card-title { font-size: 13px; font-weight: 600; line-height: 1.3; }
.bgl-kanban-card-sub { font-size: 12px; opacity: .7; }
.bgl-kanban-card-badges { display: flex; gap: .25rem; flex-wrap: wrap; }
.bgl-kanban-card-foot { font-size: 11px; font-family: ui-monospace, monospace; opacity: .5; }
.bgl-kanban-empty { padding: .75rem 0; opacity: .5; }
.bgl-kanban-loading { padding: 1rem; display: flex; justify-content: center; }
.bgl-kanban-foot { padding-top: .1rem; }
</style>
