<script setup lang="ts" generic="T = unknown">
import type { Ref, WritableComputedRef } from 'vue'

import type { DraggableEvent } from '../../draggable/useDraggable'

import { Btn, resolveI18n, useI18n } from '@bagelink/vue'
import { computed, ref, watch } from 'vue'
import Draggable from '../../draggable/Draggable.vue'

const props = defineProps<{
	label?: string
	helpText?: string
	modelValue: T[]
	allowAdd?: boolean
	allowDelete?: boolean
	allowReorder?: boolean
	addText?: string
	deleteTooltip?: string
	itemLabel?: string
	collapsible?: boolean
}>()

const emit = defineEmits(['update:modelValue'])
defineSlots<{
	default: (props: { item: WritableComputedRef<T>, index: number }) => unknown
}>()

const { $t } = useI18n()

const items = ref(Array.isArray(props.modelValue) ? [...props.modelValue] : []) as Ref<T[]>
const minimizedItems = ref(Array.from({ length: items.value.length }, () => true))

const itemLabelPrefix = computed(() => {
	const resolvedCustomLabel = resolveI18n(props.itemLabel)
	if (typeof resolvedCustomLabel === 'string' && resolvedCustomLabel.length > 0) {
		return resolvedCustomLabel
	}
	return $t('arrayInput.item')
})

const IDENTIFIER_FIELDS = ['title', 'name', 'label', 'displayName', 'display_name', 'heading', 'caption', 'text']

function getItemIdentifier(item: T): string | null {
	if (typeof item === 'string' && item.trim().length > 0) {
		return item.trim()
	}
	if (item !== null && item !== undefined && typeof item === 'object') {
		for (const field of IDENTIFIER_FIELDS) {
			const val = (item as Record<string, unknown>)[field]
			if (typeof val === 'string' && val.trim().length > 0) {
				return val.trim()
			}
		}
	}
	return null
}

function getItemLabel(index: number) {
	const identifier = getItemIdentifier(items.value[index])
	return identifier != null
		? `${index + 1}. ${identifier}`
		: `${itemLabelPrefix.value} ${index + 1}`
}

watch(
	() => props.modelValue,
	(newVal) => {
		if (Array.isArray(newVal)) {
			items.value = [...newVal]
			if (minimizedItems.value.length !== newVal.length) {
				minimizedItems.value = Array.from({ length: newVal.length }, (_, i) => minimizedItems.value[i] ?? true)
			}
		}
	}
)

function addItem() {
	items.value.push(undefined as T)
	minimizedItems.value.push(true)
	updateModel()
}

function deleteItem(i: number) {
	items.value.splice(i, 1)
	minimizedItems.value.splice(i, 1)
	updateModel()
}

function toggleItem(i: number) {
	minimizedItems.value[i] = !minimizedItems.value[i]
}

function moveItem(from: number, to: number) {
	if (to < 0 || to >= items.value.length || from === to) {
		return
	}
	const [movedItem] = items.value.splice(from, 1)
	items.value.splice(to, 0, movedItem)

	const [movedMinimizedState] = minimizedItems.value.splice(from, 1)
	minimizedItems.value.splice(to, 0, movedMinimizedState)

	updateModel()
}

function onDraggableEnd(event: DraggableEvent) {
	moveItem(event.oldIndex, event.newIndex)
}

function updateModel() {
	emit('update:modelValue', [...items.value])
}

function onUpdate(value: T, i: number) {
	items.value[i] = value
	updateModel()
}

// Create a writable computed ref for each item
function getItemRef(i: number) {
	return computed({
		get: () => items.value[i],
		set: (value) => {
			onUpdate(value, i)
		}
	})
}
</script>

<template>
	<div class="array-input testMe1">
		<label v-if="label" class="txt12 txt-gray-90 mb-025 block">
			{{ resolveI18n(label) }}
		</label>
		<p v-if="helpText" class="txt12 txt-gray-50 mb-05">
			{{ helpText }}
		</p>
		<template v-if="!collapsible">
			<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center">
				<slot :item="getItemRef(i)" :index="i" />
				<div v-if="allowDelete" class="ms-075 my-1 border-start h-100p ps-025">
					<Btn
						v-tooltip="resolveI18n(deleteTooltip || 'Delete')" flat icon="delete"
						@click="deleteItem(i)"
					/>
				</div>
			</div>
		</template>
		<Draggable v-if="collapsible" :model-value="items" handle=".grab" :disabled="!allowReorder" class="grid gap-05 mb-05" @end="onDraggableEnd">
			<template #default="{ index: i }">
				<div class="border radius-1 overflow-hidden">
					<div class="grid gap-05 grid-array-line align-items-center pe-025 hover">
						<Btn
							v-if="allowReorder" v-tooltip="resolveI18n('Drag to reorder')" flat thin icon="drag_indicator"
							class="grab"
						/>
						<Btn
							full-width align-txt="start" class="px-05 bg-transparent color-inherit" :ripple="false"
							:icon="minimizedItems[i] ? 'expand_more' : 'expand_less'" @click="toggleItem(i)"
						>
							<p class="ellipsis-1">
								{{ getItemLabel(i) }}
							</p>
						</Btn>

						<Btn v-if="allowDelete" v-tooltip="resolveI18n(deleteTooltip || 'Delete')" flat icon="delete" thin :confirm="resolveI18n('Are you sure you want to delete this item?')" @click="deleteItem(i)" />
					</div>
					<Transition name="array-collapse">
						<div v-if="!minimizedItems[i]" class="array-input-row grid align-items-center px-05">
							<slot :item="getItemRef(i)" :index="i" />
						</div>
					</Transition>
				</div>
			</template>
		</Draggable>
		<Btn v-if="allowAdd" class="radius-1" thin icon="add" :value="resolveI18n(addText || 'Add')" @click="addItem" />
	</div>
</template>

<style>
:root {
	--array-input-bg: var(--input-bg);
}
.array-input-row {
	grid-template-columns: 1fr auto;
}

.array-collapse-enter-active,
.array-collapse-leave-active {
	overflow: hidden;
	transition: max-height 0.2s ease, opacity 0.2s ease;
	max-height: 480px;
	opacity: 1;
}

.array-collapse-enter-from,
.array-collapse-leave-to {
	max-height: 0;
	opacity: 0;
}

.array-list-move {
	transition: transform 0.25s ease;
}

.array-list-enter-active,
.array-list-leave-active {
	transition: all 0.2s ease;
}

.array-list-enter-from,
.array-list-leave-to {
	opacity: 0;
	transform: scale(0.98);
}

.grid-array-line {
	grid-template-columns: auto 1fr auto;
	background: var(--array-input-bg) !important;
	}

	.grid-array-line button:hover {
		filter: brightness(90%);
	}

	.grid-array-line button:active {
		filter: brightness(80%);
}
</style>
