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

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

import { Btn, resolveI18n, useI18n, pathKeyToURL } from '@bagelink/vue'
import { computed, ref, watch, useSlots, type VNode } from 'vue'
import Draggable from '../../draggable/Draggable.vue'
import type { BagelInputShellProps } from './bagelInputShell'
import { useBagelInputShell } from './bagelInputShell'

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

const { shellClass, shellStyle } = useBagelInputShell(props)

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

const slots = useSlots()
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 hasValue = computed(() => items.value.some(item => item !== undefined && item !== null && item !== ''))

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']
const IMAGE_FIELDS = ['image', 'thumbnail', 'avatar', 'photo', 'picture', 'profilePic', 'profile_pic', 'icon', 'logo']

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 getItemThumbnail(item: T): string | null {
	if (item !== null && item !== undefined && typeof item === 'object') {
		for (const field of IMAGE_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()
}

const isSimple = computed(() => {
	if (props.simple) return true
	if (!slots.default) return false
	const dummyItem = computed({ get: () => undefined as T, set: () => { } })
	const vnodes = slots.default({ item: dummyItem, index: 0 }) as VNode[]
	return vnodes.filter(vn => typeof vn.type !== 'symbol').length === 1
})

// 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="bagel-input array-input testMe1" :class="[shellClass, { 'has-value': hasValue }]" :style="shellStyle"
	>
		<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" :class="{ 'simpleArray': isSimple, 'pb-1': !isSimple }">
				<slot :item="getItemRef(i)" :index="i" />
				<div v-if="allowDelete" class=" h-100p" :class="{ 'border-start ps-025 ms-075': !simple }">
					<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-0 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"
						/>
						<div v-else />
					<Btn
						full-width align-txt="start" class="px-025 bg-transparent color-inherit w-100p overflow-hidden" :ripple="false"
						:icon="minimizedItems[i] ? 'expand_more' : 'expand_less'" @click="toggleItem(i)"
					>
						<div class="flex align-items-center gap-05 w-100p overflow-hidden">
							<img
								v-if="getItemThumbnail(items[i])"
								:src="pathKeyToURL(getItemThumbnail(items[i])!)"
								class="array-item-thumb"
								@error="($event.target as HTMLImageElement).style.display = 'none'"
							>
							<p class="ellipsis-1">
								{{ getItemLabel(i) }}
							</p>
						</div>
					</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)" icon-size="0.8" />
					</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(--bgl-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%);
}
.simpleArray .label-text {
display: none;
}
.array-item-thumb {
width: 24px;
height: 24px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}

</style>
