<script lang="ts">
	import type { Snippet } from 'svelte';

	type FuncBase = (n: number) => number;

	let {
		grid = true,
		grid_size = 35,
		grid_scale_size = 1,
		show_controll_buttons = false,
		controll_buttons,
		children,
		onmousedown: ext_onmousedown,
		onmouseup: ext_onmouseup,
		onmouseleave: ext_onmouseleave,
		onmousemove: ext_onmousemove
	}: {
		children?: Snippet<[FuncBase, FuncBase, FuncBase, FuncBase]>;
		grid?: boolean;
		grid_size?: number;
		grid_scale_size?: number;
		controll_buttons?: Snippet;
		show_controll_buttons?: boolean;
		onmousedown?: (e: MouseEvent) => void;
		onmouseup?: (e: MouseEvent) => void;
		onmouseleave?: (e: MouseEvent) => void;
		onmousemove?: (e: MouseEvent) => void;
	} = $props();

	//
	// 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 isDragging = $state(false);

	//
	// Mouse interation
	//
	// Right click and drag to move around
	function onmousedown(e: MouseEvent) {
		ext_onmousedown?.(e);
		if (e.defaultPrevented) {
			return;
		}

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

		startMX = mouseX;
		startMY = mouseY;
		if (e.button === 2 || e.button === 1) {
			isDragging = true;
			startX = x;
			startY = y;
		}

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

	async function onmouseup(e: MouseEvent) {
		ext_onmouseup?.(e);
		if (e.defaultPrevented) return;

		if (isDragging) {
			isDragging = false;
			startX = 0;
			startY = 0;
			startMX = 0;
			startMY = 0;
		}
	}

	// if the mouse leaves the area clear all the stuffs
	function onmouseleave(e: MouseEvent) {
		ext_onmouseleave?.(e);
		if (e.defaultPrevented) return;
	}

	function onmousemove(e: MouseEvent) {
		ext_onmousemove?.(e);
		if (e.defaultPrevented) return;
		if (isDragging) {
			let box = canvas?.getBoundingClientRect();
			let mouseX = e.x - (box?.left ?? 0);
			let mouseY = e.y - (box?.top ?? 0);
			x = startX + (startMX - mouseX);
			y = startY + (startMY - mouseY);
		}
	}

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

	//
	//
	// 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="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 * grid_scale_size}px;"
		role="none"
		bind:this={canvas}
		{onmousedown}
		{onmouseup}
		{oncontextmenu}
		{onmousemove}
		{onmouseleave}
	>
		{#if grid}
			<!-- 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>
		{/if}

		{#if show_controll_buttons}
			<!-- 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}
			</div>

			{#if controll_buttons}
				{@render controll_buttons()}
			{/if}
		{/if}

		{#if children}
			{@render children(canvasX, canvasY, worldX, worldY)}
		{/if}
	</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>
