<template>
	<div class="repeater">
		<v-notice v-if="(Array.isArray(internalValue) && internalValue.length === 0) || internalValue == null">
			{{ placeholder }}
		</v-notice>
		<v-notice v-else-if="!Array.isArray(internalValue)" type="warning">
			<p>{{ t('interfaces.list.incompatible_data') }}</p>
		</v-notice>

		<v-list v-if="Array.isArray(internalValue) && internalValue.length > 0">
			<draggable
				:disabled="disabled"
				:force-fallback="true"
				:model-value="internalValue"
				item-key="id"
				handle=".drag-handle"
				@update:model-value="$emit('input', $event)"
			>
				<template #item="{ element, index }">
					<v-list-item :dense="internalValue.length > 4" block @click="openItem(index)">
						<v-icon v-if="!disabled && !sort" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
						<render-template
							:fields="fields"
							:item="{ ...defaults, ...element }"
							:direction="direction"
							:template="templateWithDefaults"
						/>
						<div class="spacer" />
						<v-icon v-if="!disabled" name="close" @click.stop="removeItem(element)" />
					</v-list-item>
				</template>
			</draggable>
		</v-list>
		<v-button v-if="showAddNew" class="add-new" @click="addNew">
			{{ addLabel }}
		</v-button>

		<v-drawer
			:model-value="drawerOpen"
			:title="displayValue || headerPlaceholder"
			persistent
			@update:model-value="checkDiscard()"
			@cancel="checkDiscard()"
		>
			<template #title>
				<h1 class="type-title">
					<render-template :fields="fields" :item="activeItem" :template="templateWithDefaults" />
				</h1>
			</template>

			<template #actions>
				<v-button v-tooltip.bottom="t('save')" icon rounded @click="saveItem(active!)">
					<v-icon name="check" />
				</v-button>
			</template>

			<div class="drawer-item-content">
				<v-form
					:disabled="disabled"
					:fields="fieldsWithNames"
					:model-value="activeItem"
					:direction="direction"
					autofocus
					primary-key="+"
					@update:model-value="trackEdits($event)"
				/>
			</div>
		</v-drawer>

		<v-dialog v-model="confirmDiscard" @esc="confirmDiscard = false">
			<v-card>
				<v-card-title>{{ t('unsaved_changes') }}</v-card-title>
				<v-card-text>{{ t('unsaved_changes_copy') }}</v-card-text>
				<v-card-actions>
					<v-button secondary @click="discardAndLeave()">
						{{ t('discard_changes') }}
					</v-button>
					<v-button @click="confirmDiscard = false">{{ t('keep_editing') }}</v-button>
				</v-card-actions>
			</v-card>
		</v-dialog>
	</div>
</template>

<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed, ref, toRefs } from 'vue';
import { Field } from '@directus/shared/types';
import Draggable from 'vuedraggable';
import { i18n } from '@/lang';
import { renderStringTemplate } from '@/utils/render-string-template';
import { hideDragImage } from '@/utils/hide-drag-image';
import formatTitle from '@directus/format-title';
import { isEqual, sortBy } from 'lodash';

export default defineComponent({
	components: { Draggable },
	props: {
		value: {
			type: Array as PropType<Record<string, any>[]>,
			default: null,
		},
		fields: {
			type: Array as PropType<Partial<Field>[]>,
			default: () => [],
		},
		template: {
			type: String,
			default: null,
		},
		addLabel: {
			type: String,
			default: () => i18n.global.t('create_new'),
		},
		sort: {
			type: String,
			default: null,
		},
		limit: {
			type: Number,
			default: null,
		},
		disabled: {
			type: Boolean,
			default: false,
		},
		headerPlaceholder: {
			type: String,
			default: () => i18n.global.t('empty_item'),
		},
		collection: {
			type: String,
			default: null,
		},
		placeholder: {
			type: String,
			default: () => i18n.global.t('no_items'),
		},
		direction: {
			type: String,
			default: undefined,
		},
	},
	emits: ['input'],
	setup(props, { emit }) {
		const { t } = useI18n();

		const active = ref<number | null>(null);
		const drawerOpen = computed(() => active.value !== null);
		const { value } = toRefs(props);

		const templateWithDefaults = computed(() =>
			props.fields?.[0]?.field ? props.template || `{{${props.fields[0].field}}}` : ''
		);

		const showAddNew = computed(() => {
			if (props.disabled) return false;
			if (props.value === null) return true;
			if (props.limit === null) return true;
			if (Array.isArray(props.value) && props.value.length < props.limit) return true;
			return false;
		});

		const activeItem = computed(() => (active.value !== null ? edits.value : null));

		const { displayValue } = renderStringTemplate(templateWithDefaults, activeItem);

		const defaults = computed(() => {
			const values: Record<string, any> = {};

			for (const field of props.fields) {
				if (field.schema?.default_value !== undefined && field.schema?.default_value !== null) {
					values[field.field!] = field.schema.default_value;
				}
			}

			return values;
		});

		const fieldsWithNames = computed(() =>
			props.fields?.map((field) => {
				return {
					...field,
					name: formatTitle(field.name ?? field.field!),
				};
			})
		);

		const internalValue = computed({
			get: () => {
				if (props.fields && props.sort) return sortBy(value.value, props.sort);
				return value.value;
			},
			set: (newVal) => {
				value.value = props.fields && props.sort ? sortBy(value.value, props.sort) : newVal;
			},
		});

		const isNewItem = ref(false);
		const edits = ref({});
		const confirmDiscard = ref(false);

		return {
			t,
			internalValue,
			updateValues,
			removeItem,
			addNew,
			showAddNew,
			hideDragImage,
			active,
			drawerOpen,
			displayValue,
			activeItem,
			closeDrawer,
			onSort,
			templateWithDefaults,
			defaults,
			fieldsWithNames,
			isNewItem,
			edits,
			confirmDiscard,
			openItem,
			saveItem,
			trackEdits,
			checkDiscard,
			discardAndLeave,
		};

		function openItem(index: number) {
			isNewItem.value = false;

			edits.value = { ...internalValue.value[index] };
			active.value = index;
		}

		function saveItem(index: number) {
			isNewItem.value = false;

			updateValues(index, edits.value);
			closeDrawer();
		}

		function trackEdits(updatedValues: any) {
			const combinedValues = Object.assign({}, defaults.value, updatedValues);
			Object.assign(edits.value, combinedValues);
		}

		function checkDiscard() {
			if (active.value !== null && !isEqual(edits.value, internalValue.value[active.value])) {
				confirmDiscard.value = true;
			} else {
				closeDrawer();
			}
		}

		function discardAndLeave() {
			closeDrawer();
			confirmDiscard.value = false;
		}

		function updateValues(index: number, updatedValues: any) {
			const newValue = internalValue.value.map((item: any, i: number) => {
				if (i === index) {
					return updatedValues;
				}

				return item;
			});

			if (props.fields && props.sort) {
				emitValue(sortBy(newValue, props.sort));
			} else {
				emitValue(newValue);
			}
		}

		function removeItem(item: Record<string, any>) {
			if (value.value) {
				emitValue(internalValue.value.filter((i) => i !== item));
			} else {
				emitValue(null);
			}
		}

		function addNew() {
			isNewItem.value = true;

			const newDefaults: any = {};

			props.fields.forEach((field) => {
				newDefaults[field.field!] = field.schema?.default_value;
			});

			if (Array.isArray(internalValue.value)) {
				emitValue([...internalValue.value, newDefaults]);
			} else {
				if (internalValue.value != null) {
					// eslint-disable-next-line no-console
					console.warn(
						'The repeater interface expects an array as value, but the given value is no array. Overriding given value.'
					);
				}

				emitValue([newDefaults]);
			}

			edits.value = { ...newDefaults };
			active.value = (internalValue.value || []).length;
		}

		function emitValue(value: null | any[]) {
			if (!value || value.length === 0) {
				return emit('input', null);
			}

			return emit('input', value);
		}

		function onSort(sortedItems: any[]) {
			if (sortedItems === null || sortedItems.length === 0) {
				return emit('input', null);
			}

			return emit('input', sortedItems);
		}

		function closeDrawer() {
			if (isNewItem.value) {
				emitValue(internalValue.value.slice(0, -1));
			}

			edits.value = {};
			active.value = null;
		}
	},
});
</script>

<style lang="scss" scoped>
.v-notice {
	margin-bottom: 4px;
}

.v-list {
	--v-list-padding: 0 0 4px;
}

.v-list-item {
	display: flex;
	cursor: pointer;
}

.drag-handle {
	cursor: grap;
}

.drawer-item-content {
	padding: var(--content-padding);
	padding-bottom: var(--content-padding-bottom);
}

.add-new {
	margin-top: 8px;
}
</style>
