<script setup lang="ts">
import { useDebounce } from '@/composables/useDebounce';
import { useI18n } from '@/composables/useI18n';
import { useNDVStore } from '@/stores/ndv.store';
import type {
	AssignmentCollectionValue,
	AssignmentValue,
	INode,
	INodeProperties,
} from 'n8n-workflow';
import { v4 as uuid } from 'uuid';
import { computed, reactive, watch } from 'vue';
import DropArea from '../DropArea/DropArea.vue';
import ParameterOptions from '../ParameterOptions.vue';
import Assignment from './Assignment.vue';
import { inputDataToAssignments, nameFromExpression, typeFromExpression } from './utils';

interface Props {
	parameter: INodeProperties;
	value: AssignmentCollectionValue;
	path: string;
	node: INode | null;
	isReadOnly?: boolean;
}

const props = withDefaults(defineProps<Props>(), { isReadOnly: false });

const emit = defineEmits<{
	(
		event: 'valueChanged',
		value: { name: string; node: string; value: AssignmentCollectionValue },
	): void;
}>();

const i18n = useI18n();

const state = reactive<{ paramValue: AssignmentCollectionValue }>({
	paramValue: {
		assignments: props.value.assignments ?? [],
	},
});

const ndvStore = useNDVStore();
const { callDebounced } = useDebounce();

const issues = computed(() => {
	if (!ndvStore.activeNode) return {};
	return ndvStore.activeNode?.issues?.parameters ?? {};
});

const empty = computed(() => state.paramValue.assignments.length === 0);
const activeDragField = computed(() => nameFromExpression(ndvStore.draggableData));
const inputData = computed(() => ndvStore.ndvInputData?.[0]?.json);
const actions = computed(() => {
	return [
		{
			label: i18n.baseText('assignment.addAll'),
			value: 'addAll',
			disabled: !inputData.value,
		},
		{
			label: i18n.baseText('assignment.clearAll'),
			value: 'clearAll',
			disabled: state.paramValue.assignments.length === 0,
		},
	];
});

watch(state.paramValue, (value) => {
	void callDebounced(
		() => {
			emit('valueChanged', { name: props.path, value, node: props.node?.name as string });
		},
		{ debounceTime: 1000 },
	);
});

function addAssignment(): void {
	state.paramValue.assignments.push({ id: uuid(), name: '', value: '', type: 'string' });
}

function dropAssignment(expression: string): void {
	state.paramValue.assignments.push({
		id: uuid(),
		name: nameFromExpression(expression),
		value: `=${expression}`,
		type: typeFromExpression(expression),
	});
}

function onAssignmentUpdate(index: number, value: AssignmentValue): void {
	state.paramValue.assignments[index] = value;
}

function onAssignmentRemove(index: number): void {
	state.paramValue.assignments.splice(index, 1);
}

function getIssues(index: number): string[] {
	return issues.value[`${props.parameter.name}.${index}`] ?? [];
}

function optionSelected(action: 'clearAll' | 'addAll') {
	if (action === 'clearAll') {
		state.paramValue.assignments = [];
	} else {
		const newAssignments = inputDataToAssignments(inputData.value);
		state.paramValue.assignments = state.paramValue.assignments.concat(newAssignments);
	}
}
</script>

<template>
	<div
		:class="{ [$style.assignmentCollection]: true, [$style.empty]: empty }"
		:data-test-id="`assignment-collection-${parameter.name}`"
	>
		<n8n-input-label
			:label="parameter.displayName"
			:show-expression-selector="false"
			size="small"
			underline
			color="text-dark"
		>
			<template #options>
				<ParameterOptions
					:parameter="parameter"
					:value="value"
					:custom-actions="actions"
					:is-read-only="isReadOnly"
					:show-expression-selector="false"
					@update:model-value="optionSelected"
				/>
			</template>
		</n8n-input-label>
		<div :class="$style.content">
			<div :class="$style.assignments">
				<div v-for="(assignment, index) of state.paramValue.assignments" :key="assignment.id">
					<Assignment
						:model-value="assignment"
						:index="index"
						:path="`${path}.${index}`"
						:issues="getIssues(index)"
						:class="$style.assignment"
						:is-read-only="isReadOnly"
						@update:model-value="(value) => onAssignmentUpdate(index, value)"
						@remove="() => onAssignmentRemove(index)"
					>
					</Assignment>
				</div>
			</div>
			<div
				v-if="!isReadOnly"
				:class="$style.dropAreaWrapper"
				data-test-id="assignment-collection-drop-area"
				@click="addAssignment"
			>
				<DropArea :sticky-offset="empty ? [-4, 32] : [92, 0]" @drop="dropAssignment">
					<template #default="{ active, droppable }">
						<div :class="{ [$style.active]: active, [$style.droppable]: droppable }">
							<div v-if="droppable" :class="$style.dropArea">
								<span>{{ i18n.baseText('assignment.dropField') }}</span>
								<span :class="$style.activeField">{{ activeDragField }}</span>
							</div>
							<div v-else :class="$style.dropArea">
								<span>{{ i18n.baseText('assignment.dragFields') }}</span>
								<span :class="$style.or">{{ i18n.baseText('assignment.or') }}</span>
								<span :class="$style.add">{{ i18n.baseText('assignment.add') }} </span>
							</div>
						</div>
					</template>
				</DropArea>
			</div>
		</div>
	</div>
</template>

<style lang="scss" module>
.assignmentCollection {
	display: flex;
	flex-direction: column;
	margin: var(--spacing-xs) 0;
}

.content {
	display: flex;
	gap: var(--spacing-l);
	flex-direction: column;
}

.assignments {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-4xs);
}

.assignment {
	padding-left: var(--spacing-l);
}

.dropAreaWrapper {
	cursor: pointer;

	&:not(.empty .dropAreaWrapper) {
		padding-left: var(--spacing-l);
	}

	&:hover .add {
		color: var(--color-primary-shade-1);
	}
}

.dropArea {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	justify-content: center;
	font-size: var(--font-size-xs);
	color: var(--color-text-dark);
	gap: 1ch;
	min-height: 24px;

	> span {
		white-space: nowrap;
	}
}

.or {
	color: var(--color-text-light);
	font-size: var(--font-size-2xs);
}

.add {
	color: var(--color-primary);
	font-weight: var(--font-weight-bold);
}

.activeField {
	font-weight: var(--font-weight-bold);
	color: var(--color-ndv-droppable-parameter);
}

.active {
	.activeField {
		color: var(--color-success);
	}
}

.empty {
	.dropArea {
		flex-direction: column;
		align-items: center;
		gap: var(--spacing-3xs);
		min-height: 20vh;
	}

	.droppable .dropArea {
		flex-direction: row;
		gap: 1ch;
	}

	.content {
		gap: var(--spacing-s);
	}
}

.icon {
	font-size: var(--font-size-2xl);
}
</style>
