<script lang="ts">
	import { onMount } from 'svelte';
	import NavBar from '../NavBar.svelte';
	import Link from './Link.svelte';
	import NodeRect from './NodeRect.svelte';
	import Rect from './Rect.svelte';
	import type { Node, ActionType, LinkNode, FullLink, FullLinkApi } from './types';
	import { extractLinkNodePosX, extractLinkNodePosY } from './types';
	import { deleteR, get, post, put } from '$lib/utils';
	import icon_list from './icons-list.json';

	//
	// constatns
	//
	const grid_size = 35;

	//
	// binds
	//
	let activeNode: Node | undefined = $state();
	let linkMode = $state(false);

	//
	// Data
	//

	let nodes: Node[] = $state([]);

	let links: FullLink[] = $state([]);

	// Load Data
	onMount(async () => {
		try {
			const nodesRequest = await get('user/status/node');
			nodes = [
				{
					id: null as any,
					user_id: null as any,
					x: -4,
					y: 10,
					width: 8,
					height: 4,
					name: 'Created',
					icon: 'plus',
					permission: 1,
					visible: false
				} as Node,
				...nodesRequest
			];

			const nodesAsMap: Record<string, Node> = {};
			for (const node of nodes) {
				nodesAsMap[node.id] = node;
			}

			const linkRequests: FullLinkApi[] = await get('user/status/link');
			links = linkRequests.map((a) => ({
				id: a.id,
				bi: a.bi,
				sourceNode: {
					node: nodesAsMap[a.source_node as any],
					x: a.source_x,
					y: a.source_y
				},
				targetNode: {
					node: nodesAsMap[a.target_node as any],
					x: a.target_x,
					y: a.target_y
				}
			}));
			console.log('test', linkRequests);
		} catch (e) {
			console.log('TODO inform user', e);
		}
	});

	//
	// Display stuff
	//

	// The canvas is not really a canvas but a div will kinda act like a canvas
	let canvas: HTMLDivElement | undefined = $state();

	//
	// Position + movement + interation
	//
	//
	let y = $state(0);
	let x = $state(0);

	let initialGridOffsetX = $state(0);
	let initialGridOffsetY = $state(0);

	$effect(() => {
		let box = canvas?.getBoundingClientRect();
		if (!box) return;
		let w = box.width;
		let h = box.height;
		initialGridOffsetX = Math.round(w / 2 - Math.round(w / 2 / grid_size) * grid_size);
		initialGridOffsetY = Math.round(h / 2 - Math.floor(h / 2 / grid_size) * grid_size);
	});

	// Holdes the position for pan and select
	let startMX = $state(0);
	let startMY = $state(0);
	let startX = $state(0);
	let startY = $state(0);
	let mouseAction: ActionType = $state(undefined);

	let tempMouseAction: ActionType = $state(undefined);

	let curPosX = $state(0);
	let curPosY = $state(0);

	//
	// Mouse interation
	//
	// Right click and drag to move around
	function onmousedown(e: MouseEvent) {
		// clear some varibales
		activeNode = undefined;

		let box = canvas?.getBoundingClientRect();
		const mouseX = e.x - (box?.left ?? 0);
		const mouseY = e.y - (box?.top ?? 0);
		const wy = worldY(mouseY);
		const wx = worldX(mouseX);

		startMX = mouseX;
		startMY = mouseY;
		if (e.button === 2 || e.button === 1) {
			tempMouseAction = mouseAction;
			mouseAction = 'drag';
			startX = x;
			startY = y;
		} else if (e.button === 0) {
			if (linkMode) return;
			// if inside a box allow clicks
			for (const node of nodes) {
				if (
					wx >= node.x &&
					wx <= node.x + node.width &&
					wy <= node.y &&
					wy >= node.y - node.height
				) {
					if (!linkMode && node.permission === 0) {
						activeNode = node;
						startX = x;
						startY = y;
						startMX = node.x - wx;
						startMY = node.y - wy;
						mouseAction = 'move';
					}
					return;
				}
			}

			mouseAction = 'create';
			curPosY = canvasY(Math.ceil(wy));
			curPosX = canvasX(Math.floor(wx));
			startMX = curPosX;
			startMY = curPosY;
		}

		e.preventDefault();
		e.stopPropagation();
	}

	async function onmouseup(e: MouseEvent) {
		//
		// Create
		//

		// Maybe don't do this it's super bad
		while (mouseAction === 'create') {
			let width = Math.abs(startMX - curPosX) / grid_size;
			let height = Math.abs(startMY - curPosY) / grid_size;
			if (width <= 8 || height <= 4) break;

			try {
				const result = await post('user/status/node', {
					id: '',
					user_id: '',
					x: worldX(Math.min(startMX, curPosX)),
					y: worldY(Math.min(startMY, curPosY)),
					height,
					width,
					icon: icon_list[Math.floor(Math.random() * icon_list.length)],
					name: 'New Status',
					permission: 0,
					visible: true
				} as Node);
				nodes.push(result);
				// Tell svelte that nodes is updated
				nodes = nodes;
			} catch (e) {
				console.log('TODO inform user', e);
			}
			break;
		}

		//
		// Move
		//
		if (mouseAction === 'move' && activeNode) {
			try {
				await put('user/status/node', activeNode);
			} catch (e) {
				console.log('TODO: inform user', e);
			}
			activeNode = undefined;
		}

		//
		// Ohter
		//
		if (mouseAction === 'drag' && tempMouseAction === 'link' && startX === x && startY === y) {
			mouseAction = undefined;
			tempMouseAction = undefined;
			linkSource = undefined;
			startX = 0;
			startY = 0;
			startMX = 0;
			startMY = 0;
		}

		if (mouseAction !== 'link' && mouseAction) {
			mouseAction = tempMouseAction;
			tempMouseAction = undefined;
			startX = 0;
			startY = 0;
			startMX = 0;
			startMY = 0;
		}
	}

	// if the mouse leaves the area clear all the stuffs
	function onmouseleave(e: MouseEvent) {
		if (mouseAction !== 'link') {
			mouseAction = undefined;
			startX = 0;
			startY = 0;
			startMX = 0;
			startMY = 0;
		}
	}

	function onmousemove(e: MouseEvent) {
		let box = canvas?.getBoundingClientRect();
		let mouseX = e.x - (box?.left ?? 0);
		let mouseY = e.y - (box?.top ?? 0);
		if (mouseAction === 'drag') {
			x = startX + (startMX - mouseX);
			y = startY + (startMY - mouseY);
		} else if (mouseAction === 'create') {
			const wy = worldY(mouseY);
			const wx = worldX(mouseX);
			curPosY = canvasY(Math.floor(wy));
			curPosX = canvasX(Math.ceil(wx));
		} else if (mouseAction === 'move' && activeNode) {
			const wy = worldY(mouseY);
			const wx = worldX(mouseX);
			activeNode.x = Math.floor(wx + startMX);
			activeNode.y = Math.ceil(wy + startMY);
		}
		if (mouseAction === 'link' || tempMouseAction === 'link') {
			curPosX = mouseX;
			curPosY = mouseY;
		}
	}

	/*
	 * For now disable the right click menu
	 */
	function oncontextmenu(e: MouseEvent) {
		e.preventDefault();
		e.stopPropagation();
	}

	/*
	 * Link stuff
	 */
	let linkSource: LinkNode | undefined = $state();

	//
	//
	// Utils
	//
	//
	function canvasX(inX: number) {
		let box = canvas?.getBoundingClientRect();
		return inX * grid_size + Math.round((box?.width ?? 0) / 2) - x;
	}

	function canvasY(inY: number) {
		let box = canvas?.getBoundingClientRect();
		return Math.round((box?.height ?? 0) / 2) - y - inY * grid_size;
	}

	function worldX(canvasX: number) {
		let box = canvas?.getBoundingClientRect();
		return (canvasX + x - Math.round((box?.width ?? 0) / 2)) / grid_size;
	}

	function worldY(canvasY: number) {
		let box = canvas?.getBoundingClientRect();
		return (Math.round((box?.height ?? 0) / 2) - y - canvasY) / grid_size;
	}
</script>

<div class="flex flex-col h-[100vh]">
	<NavBar />
	<div class="w-full flex-grow p-5 px-7">
		<div
			class="bg-white rounded-lg w-full h-full grid relative overflow-hidden"
			style="--grid-size: {grid_size}px; cursor: {mouseAction === 'move' ? 'grab' : 'auto'};"
			role="none"
			bind:this={canvas}
			{onmousedown}
			{onmouseup}
			{oncontextmenu}
			{onmousemove}
			{onmouseleave}
		>
			<!-- Background grid -->
			<!-- x -->
			<div class="absolute background" style="--offset-y: {-y + initialGridOffsetY}px;"></div>
			<!-- y -->
			<div
				class="absolute background"
				style="--grid-angle: 90deg; --offset-x: {-x + initialGridOffsetX}px;"
			></div>

			<!-- Control buttons -->
			<div class="absolute left-5 top-5 flex gap-5 z-50">
				<!-- Reset view port -->
				{#if x !== 0 || y !== 0}
					<button
						class="bg-white p-2 shadow rounded-lg shadow-violet-500 h-[40px] w-[40px] text-violet-500"
						onclick={() => {
							x = 0;
							y = 0;
						}}
					>
						<span class="bi bi-arrow-clockwise"></span>
					</button>
				{/if}

				<!-- Enable link mode -->
				<button
					class="p-2 shadow rounded-lg shadow-violet-500 h-[40px] w-[40px] text-violet-500 {linkMode
						? 'bg-blue-100'
						: 'bg-white'}"
					onclick={() => {
						linkMode = !linkMode;
						linkSource = undefined;
					}}
				>
					<span class="bi bi-link-45deg"></span>
				</button>
			</div>

			<!--
            -
            - Links
            -
            -->

			<!-- Create link -->
			{#if linkSource}
				<Link
					x1={extractLinkNodePosX(canvasX, linkSource, grid_size)}
					y1={extractLinkNodePosY(canvasY, linkSource, grid_size)}
					x2={curPosX}
					y2={curPosY}
				/>
			{/if}

			<!-- Existing nodes -->
			{#each links as _, i}
				{@const sourceN = links[i].sourceNode}
				{@const targetN = links[i].targetNode}
				<Link
					x1={extractLinkNodePosX(canvasX, sourceN, grid_size)}
					y1={extractLinkNodePosY(canvasY, sourceN, grid_size)}
					x2={extractLinkNodePosX(canvasX, targetN, grid_size)}
					y2={extractLinkNodePosY(canvasY, targetN, grid_size)}
				/>
			{/each}

			<!--
            -
            - Nodes
            -
            -->

			<!-- Create rectangle -->
			{#if mouseAction === 'create'}
				<Rect x1={startMX} y1={startMY} x2={curPosX} y2={curPosY}>
					{#if Math.abs(startMX - curPosX) / grid_size <= 8 || Math.abs(startMY - curPosY) / grid_size <= 4}
						<div class="bg-red-400 bg-opacity-40 rounded-lg w-full h-full"></div>
					{:else}
						<div class="bg-green-400 bg-opacity-40 rounded-lg w-full h-full"></div>
					{/if}
				</Rect>
			{/if}

			<!-- Time to do some fun shit here -->
			{#each nodes as _, i}
				<NodeRect
					bind:node={nodes[i]}
					{canvasX}
					{canvasY}
					{grid_size}
					{mouseAction}
					{linkMode}
					linkSource={nodes[i] === linkSource?.node ? linkSource : undefined}
					onremove={async () => {
						try {
							await deleteR(`user/status/node/${nodes[i].id}`);

							links = links.filter(
								(link) =>
									link.sourceNode.node.id !== nodes[i].id && link.targetNode.node.id !== nodes[i].id
							);

							nodes.splice(i, 1);
							nodes = nodes;
						} catch (e) {
							console.log('TODO inform the user', e);
						}
					}}
					onNodePointClick={async (inNodeX, inNodeY) => {
						if (mouseAction === undefined) {
							linkSource = {
								node: nodes[i],
								x: inNodeX,
								y: inNodeY
							};
							mouseAction = 'link';
						} else if (mouseAction === 'link' && linkSource) {
							if (nodes[i] === linkSource.node) {
								linkSource = undefined;
								return;
							}

							try {
								await put('user/status/link', {
									id: '',
									user_id: '',
									source_node: linkSource.node.id,
									target_node: nodes[i].id,
									bi: false,
									source_x: linkSource.x,
									source_y: linkSource.y,

									target_x: inNodeX,
									target_y: inNodeY
								} as FullLinkApi);

								// if mouse action is link then we can assume that linkSource is set
								links.push({
									sourceNode: { ...linkSource },
									targetNode: {
										node: nodes[i],
										x: inNodeX,
										y: inNodeY
									},
									bi: false
								});
								// Tell svelte that the links changed
								links = links;
								linkSource = undefined;
								mouseAction = undefined;
							} catch (e) {
								console.log('inform the user', e);
							}
						}
					}}
				/>
			{/each}
		</div>
	</div>
</div>

<style>
	:root {
		--grid-color: #c1c1c1;
		--grid-size: 35px;
		--border-size: 1px;
		--grid-angle: 0deg;
		--offset-x: 0px;
		--offset-y: 0px;
	}

	.background {
		top: 0;
		left: 0;
		right: -1px;
		bottom: -1px;

		background: repeating-linear-gradient(
			var(--grid-angle),
			transparent,
			transparent calc(var(--grid-size) - var(--border-size)),
			var(--grid-color) calc(var(--grid-size) - var(--border-size)),
			var(--grid-color) var(--grid-size)
		);

		background-position: var(--offset-x) var(--offset-y);
		background-size: var(--grid-size) var(--grid-size);
	}
</style>
