<template>
	<div ref="templateEl" class="render-template">
		<span class="vertical-aligner" />
		<template v-for="(part, index) in parts" :key="index">
			<value-null v-if="part === null || (typeof part === 'object' && part.value === null)" />
			<component
				:is="`display-${part.component}`"
				v-else-if="typeof part === 'object' && part.component"
				v-bind="translate(part.options || {})"
				:value="part.value"
				:interface="part.interface"
				:interface-options="part.interfaceOptions"
				:type="part.type"
				:collection="part.collection"
				:field="part.field"
			/>
			<span v-else-if="typeof part === 'string'" :dir="direction">{{ translate(part) }}</span>
			<span v-else>{{ part }}</span>
		</template>
	</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useFieldsStore } from '@/stores/fields';
import { get } from 'lodash';
import { Field } from '@directus/shared/types';
import { getDefaultDisplayForType } from '@/utils/get-default-display-for-type';
import { translate } from '@/utils/translate-object-values';
import { useExtension } from '@/composables/use-extension';

interface Props {
	template: string;
	collection?: string;
	fields?: Field[];
	item?: Record<string, any>;
	direction?: string;
}

const props = withDefaults(defineProps<Props>(), {
	collection: undefined,
	fields: () => [],
	item: () => ({}),
	direction: undefined,
});

const fieldsStore = useFieldsStore();

const templateEl = ref<HTMLElement>();

const regex = /({{.*?}})/g;

const parts = computed(() =>
	props.template
		.split(regex)
		.filter((p) => p)
		.map((part) => {
			if (part.startsWith('{{') === false) return part;

			let fieldKey = part.replace(/{{/g, '').replace(/}}/g, '').trim();
			let fieldKeyBefore = fieldKey.split('.').slice(0, -1).join('.');
			let fieldKeyAfter = fieldKey.split('.').slice(-1)[0];

			// Try getting the value from the item, return some question marks if it doesn't exist
			let value = get(props.item, fieldKeyBefore);

			return Array.isArray(value) ? handleArray(fieldKeyBefore, fieldKeyAfter) : handleObject(fieldKey);
		})
		.map((p) => p ?? null)
);

function handleArray(fieldKeyBefore: string, fieldKeyAfter: string) {
	const value = get(props.item, fieldKeyBefore);
	const field =
		fieldsStore.getField(props.collection, fieldKeyBefore) ||
		props.fields?.find((field) => field.field === fieldKeyBefore);

	if (value === undefined) return null;

	if (!field) return value;

	const displayInfo = useExtension(
		'display',
		computed(() => field.meta?.display ?? null)
	);

	let component = field.meta?.display;
	let options = field.meta?.display_options;

	if (!displayInfo.value) {
		component = 'related-values';
		options = { template: `{{${fieldKeyAfter}}}` };
	}

	return {
		component,
		options,
		value: value,
		interface: field.meta?.interface,
		interfaceOptions: field.meta?.options,
		type: field.type,
		collection: field.collection,
		field: field.field,
	};
}

function handleObject(fieldKey: string) {
	const field =
		fieldsStore.getField(props.collection, fieldKey) || props.fields?.find((field) => field.field === fieldKey);

	/**
	 * This is for cases where you are rendering a display template directly on
	 * directus_files. The $thumbnail fields doesn't exist, but instead renders a
	 * thumbnail based on the other fields in the file info. In that case, the value
	 * should be the whole related file object, not just the fake "thumbnail" field. By
	 * stripping out the thumbnail part in the field key path, the rest of the function
	 * will extract the value correctly.
	 */
	if (field && field.collection === 'directus_files' && field.field === '$thumbnail') {
		fieldKey = fieldKey
			.split('.')
			.filter((part) => part !== '$thumbnail')
			.join('.');
	}

	const value = get(props.item, fieldKey);

	if (value === undefined) return null;

	if (!field) return value;

	const display = field?.meta?.display || getDefaultDisplayForType(field.type);

	// No need to render the empty display overhead in this case
	if (display === 'raw') return value;

	const displayInfo = useExtension(
		'display',
		computed(() => field.meta?.display ?? null)
	);

	// If used display doesn't exist in the current project, return raw value
	if (!displayInfo.value) return value;

	return {
		component: field.meta?.display,
		options: field.meta?.display_options,
		value: value,
		interface: field.meta?.interface,
		interfaceOptions: field.meta?.options,
		type: field.type,
		collection: field.collection,
		field: field.field,
	};
}
</script>

<style lang="scss" scoped>
@import '@/styles/mixins/no-wrap';

.render-template {
	position: relative;
	max-width: 100%;
	padding-right: 8px;

	.vertical-aligner {
		display: inline-block;
		width: 0;
		height: 100%;
		vertical-align: middle;
	}

	@include no-wrap;

	> * {
		vertical-align: middle;
	}

	.render-template {
		display: inline;
	}
}

.subdued {
	color: var(--foreground-subdued);
}
</style>
