<template>
	<aside :class="$style.floatingNodes">
		<ul
			v-for="connectionGroup in connectionGroups"
			:class="[$style.nodesList, $style[connectionGroup]]"
			:key="connectionGroup"
		>
			<template v-for="{ node, nodeType } in connectedNodes[connectionGroup]">
				<n8n-tooltip
					:placement="tooltipPositionMapper[connectionGroup]"
					v-if="node && nodeType"
					:teleported="false"
					:key="node.name"
					:offset="60"
				>
					<template #content>{{ node.name }}</template>

					<li
						:class="$style.connectedNode"
						@click="$emit('switchSelectedNode', node.name)"
						data-test-id="floating-node"
						:data-node-name="node.name"
						:data-node-placement="connectionGroup"
					>
						<node-icon
							:nodeType="nodeType"
							:nodeName="node.name"
							:tooltip-position="tooltipPositionMapper[connectionGroup]"
							:size="35"
							circle
						/>
					</li>
				</n8n-tooltip>
			</template>
		</ul>
	</aside>
</template>

<script setup lang="ts">
import type { INodeUi } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { computed, onMounted, onBeforeUnmount } from 'vue';
import NodeIcon from '@/components/NodeIcon.vue';
import type { INodeTypeDescription } from 'n8n-workflow';

interface Props {
	rootNode: INodeUi;
	type: 'input' | 'sub-input' | 'sub-output' | 'output';
}
const enum FloatingNodePosition {
	top = 'outputSub',
	right = 'outputMain',
	bottom = 'inputSub',
	left = 'inputMain',
}
const props = defineProps<Props>();
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const workflow = workflowsStore.getCurrentWorkflow();
const emit = defineEmits(['switchSelectedNode']);

interface NodeConfig {
	node: INodeUi;
	nodeType: INodeTypeDescription;
}
function moveNodeDirection(direction: FloatingNodePosition) {
	const matchedDirectionNode = connectedNodes.value[direction][0];
	if (matchedDirectionNode) {
		emit('switchSelectedNode', matchedDirectionNode.node.name);
	}
}

function onKeyDown(e: KeyboardEvent) {
	if (e.shiftKey && e.altKey && (e.ctrlKey || e.metaKey)) {
		/* eslint-disable @typescript-eslint/naming-convention */
		const mapper = {
			ArrowUp: FloatingNodePosition.top,
			ArrowRight: FloatingNodePosition.right,
			ArrowDown: FloatingNodePosition.bottom,
			ArrowLeft: FloatingNodePosition.left,
		};
		/* eslint-enable @typescript-eslint/naming-convention */

		const matchingDirection = mapper[e.key as keyof typeof mapper] || null;
		if (matchingDirection) {
			moveNodeDirection(matchingDirection);
		}
	}
}
function getINodesFromNames(names: string[]): NodeConfig[] {
	return names
		.map((name) => {
			const node = workflowsStore.getNodeByName(name);
			if (node) {
				const nodeType = nodeTypesStore.getNodeType(node.type);
				if (nodeType) {
					return { node, nodeType };
				}
			}
			return null;
		})
		.filter((n): n is NodeConfig => n !== null);
}
const connectedNodes = computed<
	Record<FloatingNodePosition, Array<{ node: INodeUi; nodeType: INodeTypeDescription }>>
>(() => {
	const rootName = props.rootNode.name;
	return {
		[FloatingNodePosition.top]: getINodesFromNames(
			workflow.getChildNodes(rootName, 'ALL_NON_MAIN'),
		),
		[FloatingNodePosition.right]: getINodesFromNames(workflow.getChildNodes(rootName, 'main', 1)),
		[FloatingNodePosition.bottom]: getINodesFromNames(
			workflow.getParentNodes(rootName, 'ALL_NON_MAIN'),
		),
		[FloatingNodePosition.left]: getINodesFromNames(workflow.getParentNodes(rootName, 'main', 1)),
	};
});

const connectionGroups = [
	FloatingNodePosition.top,
	FloatingNodePosition.right,
	FloatingNodePosition.bottom,
	FloatingNodePosition.left,
];
const tooltipPositionMapper = {
	[FloatingNodePosition.top]: 'bottom',
	[FloatingNodePosition.right]: 'left',
	[FloatingNodePosition.bottom]: 'top',
	[FloatingNodePosition.left]: 'right',
};

onMounted(() => {
	document.addEventListener('keydown', onKeyDown, true);
});

onBeforeUnmount(() => {
	document.removeEventListener('keydown', onKeyDown, true);
});
defineExpose({
	moveNodeDirection,
});
</script>

<style lang="scss" module>
.floatingNodes {
	position: fixed;
	bottom: 0;
	top: 0;
	right: 0;
	left: 0;
	z-index: 10;
	pointer-events: none;
}
.floatingNodes {
	right: 0;
}

.nodesList {
	list-style: none;
	padding: 0;
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-direction: column;
	width: min-content;
	margin: auto;
	transform-origin: center;
	gap: var(--spacing-s);
	transition: transform 0.2s ease-in-out;

	&.inputSub,
	&.outputSub {
		right: 0;
		left: 0;
		flex-direction: row;
	}
	&.outputSub {
		top: 0;
	}
	&.inputSub {
		bottom: 0;
	}
	&.outputMain,
	&.inputMain {
		top: 0;
		bottom: 0;
	}
	&.outputMain {
		right: 0;
	}
	&.inputMain {
		left: 0;
	}

	&.outputMain {
		transform: translateX(50%);
	}
	&.outputSub {
		transform: translateY(-50%);
	}
	&.inputMain {
		transform: translateX(-50%);
	}
	&.inputSub {
		transform: translateY(50%);
	}
}
.connectedNode {
	border: var(--border-base);
	background-color: var(--color-canvas-node-background);
	border-radius: 100%;
	padding: var(--spacing-s);
	cursor: pointer;
	pointer-events: all;
	transition: transform 0.2s cubic-bezier(0.19, 1, 0.22, 1);
	position: relative;
	transform: scale(0.8);
	display: flex;
	justify-self: center;
	align-self: center;

	&::after {
		content: '';
		position: absolute;
		top: -35%;
		right: -30%;
		bottom: -35%;
		left: -30%;
		z-index: -1;
	}
	.outputMain &,
	.inputMain & {
		border-radius: var(--border-radius-large);
		display: flex;
		align-items: center;
		justify-content: center;
	}
	.outputMain & {
		&:hover {
			transform: scale(1.2) translateX(-50%);
		}
	}
	.outputSub & {
		&:hover {
			transform: scale(1.2) translateY(50%);
		}
	}
	.inputMain & {
		&:hover {
			transform: scale(1.2) translateX(50%);
		}
	}
	.inputSub & {
		&:hover {
			transform: scale(1.2) translateY(-50%);
		}
	}
}
</style>
