<script setup lang="ts">
import { ElMenu, ElSubMenu, ElMenuItem, type MenuItemRegistered } from 'element-plus';
import { computed, defineComponent, ref, useSlots } from 'vue';
import type { RouteLocationRaw } from 'vue-router';

import type { IconSize } from '@n8n/design-system/types';

import ConditionalRouterLink from '../ConditionalRouterLink';
import N8nIcon from '../N8nIcon';
import type { IconName } from '../N8nIcon/icons';
import N8nText from '../N8nText';
import N8nTooltip from '../N8nTooltip';

type BaseItem = {
	id: string;
	title: string;
	disabled?: boolean;
	icon?: IconName | { type: 'icon'; value: IconName } | { type: 'emoji'; value: string };
	iconSize?: IconSize;
	iconMargin?: boolean;
	route?: RouteLocationRaw;
	isDivider?: false;
	description?: string;
};

type Divider = { isDivider: true; id: string };

type Item = BaseItem & { submenu?: Array<BaseItem | Divider> };

defineOptions({
	name: 'N8nNavigationDropdown',
});

const props = defineProps<{
	menu: Array<Item | Divider>;
	disabled?: boolean;
	teleport?: boolean;
	submenuClass?: string;
}>();

const menuRef = ref<typeof ElMenu | null>(null);
const ROOT_MENU_INDEX = '-1';

// Passing both expand-close-icon and expand-open-icon to ElSubMenu disables
// Element Plus's default 180° chevron rotation. The displayed chevron for
// nested submenus is fixed to ArrowRight by Element Plus, so this no-op
// component is never actually rendered.
const NoopIcon = defineComponent({ name: 'NoopIcon', render: () => null });

const emit = defineEmits<{
	itemClick: [item: MenuItemRegistered];
	select: [id: Item['id']];
}>();

const slots = useSlots();
const hasAppendSlot = (id: string) => Boolean(slots[`item.append.${id}`]);

const orderedMenu = computed(() => {
	const workflowIndex = props.menu.findIndex((item) => !item.isDivider && item.id === 'workflow');
	const agentIndex = props.menu.findIndex((item) => !item.isDivider && item.id === 'agent');

	if (workflowIndex === -1 || agentIndex === -1 || agentIndex === workflowIndex + 1) {
		return props.menu;
	}

	const ordered = [...props.menu];
	const [agentItem] = ordered.splice(agentIndex, 1);
	const nextWorkflowIndex = ordered.findIndex((item) => !item.isDivider && item.id === 'workflow');
	ordered.splice(nextWorkflowIndex + 1, 0, agentItem);

	return ordered;
});

defineSlots<{
	default?: () => unknown;
	'item-icon'?: (props: { item: BaseItem }) => unknown;
	[key: `item.append.${string}`]: (props: { item: Item }) => unknown;
}>();

const open = () => {
	menuRef.value?.open(ROOT_MENU_INDEX);
};

const close = () => {
	menuRef.value?.close(ROOT_MENU_INDEX);
};

const menuTrigger = ref<'click' | 'hover'>('click');
const onOpen = (index: string) => {
	if (index !== ROOT_MENU_INDEX) return;
	menuTrigger.value = 'hover';
};

const onClose = (index: string) => {
	if (index !== ROOT_MENU_INDEX) return;
	menuTrigger.value = 'click';
};

defineExpose({
	open,
	close,
});
</script>

<template>
	<ElMenu
		ref="menuRef"
		mode="horizontal"
		unique-opened
		:menu-trigger="menuTrigger"
		:ellipsis="false"
		:class="$style.dropdown"
		@select="emit('select', $event)"
		@keyup.escape="close"
		@open="onOpen"
		@close="onClose"
	>
		<ElSubMenu
			:index="ROOT_MENU_INDEX"
			:class="$style.trigger"
			:popper-offset="-10"
			:popper-class="[$style.submenu, submenuClass ?? ''].join(' ')"
			:disabled
			:teleported="teleport"
		>
			<template #title>
				<slot />
			</template>

			<template v-for="item in orderedMenu" :key="item.id">
				<hr v-if="item.isDivider" />
				<template v-else-if="item.submenu">
					<ElSubMenu
						:popper-class="$style.nestedSubmenu"
						:index="item.id"
						:popper-offset="-10"
						:expand-close-icon="NoopIcon"
						:expand-open-icon="NoopIcon"
						data-test-id="navigation-submenu"
					>
						<template #title>
							<div :class="$style.subMenuTitle">
								<slot name="item-icon" v-bind="{ item }">
									<!-- Default icon rendering -->
									<template v-if="item.icon">
										<N8nIcon
											v-if="typeof item.icon === 'string' || item.icon.type === 'icon'"
											:class="{ [$style.submenu__icon]: item.iconMargin !== false }"
											:icon="typeof item.icon === 'object' ? item.icon.value : item.icon"
											:size="item.iconSize"
										/>
										<N8nText
											v-else-if="item.icon.type === 'emoji'"
											:class="{ [$style.submenu__icon]: item.iconMargin !== false }"
										>
											{{ item.icon.value }}
										</N8nText>
									</template>
								</slot>
								{{ item.title }}
							</div>
						</template>
						<template v-for="subitem in item.submenu" :key="subitem.id">
							<hr v-if="subitem.isDivider" />
							<ConditionalRouterLink v-else :to="(!subitem.disabled && subitem.route) || undefined">
								<ElMenuItem
									data-test-id="navigation-submenu-item"
									:index="subitem.id"
									:disabled="subitem.disabled"
									@click="emit('itemClick', $event)"
								>
									<slot name="item-icon" v-bind="{ item: subitem }">
										<!-- Default icon rendering -->
										<template v-if="subitem.icon">
											<N8nIcon
												v-if="typeof subitem.icon === 'string' || subitem.icon.type === 'icon'"
												:class="{ [$style.submenu__icon]: subitem.iconMargin !== false }"
												:icon="typeof subitem.icon === 'object' ? subitem.icon.value : subitem.icon"
												:size="subitem.iconSize"
											/>
											<N8nText
												v-else-if="subitem.icon.type === 'emoji'"
												:class="{ [$style.submenu__icon]: subitem.iconMargin !== false }"
											>
												{{ subitem.icon.value }}
											</N8nText>
										</template>
									</slot>

									<span :class="$style.menuItemTitle">{{ subitem.title }}</span>
									<N8nTooltip
										v-if="subitem.description"
										:content="subitem.description"
										placement="right"
										:class="$style.infoTooltip"
									>
										<N8nIcon icon="info" size="medium" :class="$style.infoIcon" />
									</N8nTooltip>
									<span v-if="hasAppendSlot(item.id)" :class="$style.menuItemAppend">
										<slot :name="`item.append.${item.id}`" v-bind="{ item }" />
									</span>
								</ElMenuItem>
							</ConditionalRouterLink>
						</template>
					</ElSubMenu>
				</template>
				<ConditionalRouterLink v-else :to="(!item.disabled && item.route) || undefined">
					<ElMenuItem
						:index="item.id"
						:disabled="item.disabled"
						data-test-id="navigation-menu-item"
					>
						{{ item.title }}
						<span v-if="hasAppendSlot(item.id)" :class="$style.menuItemAppend">
							<slot :name="`item.append.${item.id}`" v-bind="{ item }" />
						</span>
					</ElMenuItem>
				</ConditionalRouterLink>
			</template>
		</ElSubMenu>
	</ElMenu>
</template>

<style lang="scss" module>
:global(.el-menu).dropdown {
	border-bottom: 0 !important;
	background-color: transparent;

	> :global(.el-sub-menu) {
		> :global(.el-sub-menu__title) {
			height: auto;
			line-height: initial;
			border-bottom: 0 !important;
			padding: 0;
			:global(.el-sub-menu__icon-arrow) {
				display: none;
			}
		}

		&:global(.is-active) {
			:global(.el-sub-menu__title) {
				border: 0;
			}
		}
	}
}

.nestedSubmenu {
	:global(.el-menu) {
		max-height: 450px;
		overflow: auto;
	}
}

.submenu {
	padding: 5px 0 !important;

	:global(.el-menu--horizontal .el-menu .el-menu-item),
	:global(.el-menu--horizontal .el-menu .el-sub-menu__title) {
		color: var(--color--text--shade-1);
		background-color: var(--menu--color--background);
	}

	:global(.el-menu--horizontal .el-menu .el-menu-item:not(.is-disabled):hover),
	:global(.el-menu--horizontal .el-menu .el-sub-menu__title:not(.is-disabled):hover) {
		background-color: var(--menu--color--background--hover);
	}

	:global(.el-popper) {
		padding: 0 10px !important;
	}

	:global(.el-menu--popup) {
		border: 1px solid var(--color--foreground);
		border-radius: var(--radius);
	}

	:global(.el-menu--horizontal .el-menu .el-menu-item.is-disabled) {
		opacity: 1;
		cursor: default;
		color: var(--color--text--tint-1);
	}

	:global(.el-sub-menu__icon-arrow svg) {
		margin-top: auto;
	}

	& hr {
		border-top: none;
		border-bottom: var(--border);
		margin-block: var(--spacing--4xs);
	}
}

.subMenuTitle {
	display: inline-flex;
	align-items: center;
	gap: var(--spacing--2xs);
}

.submenu__icon {
	margin-right: var(--spacing--2xs);
	color: var(--color--text);
}

.menuItemTitle {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	min-width: 0;
}

.menuItemAppend {
	display: inline-flex;
	align-items: center;
	margin-left: auto;
	padding-left: var(--spacing--2xs);
}

.infoTooltip {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	padding-left: var(--spacing--xs);
}

.infoIcon {
	color: var(--color--text--tint-1);
	outline: none;
	margin-left: var(--spacing--2xs);
}
</style>
