<script lang="ts">
	import type { Application, AsEnum } from '$lib/ApplicationsStore.svelte';
	import { ApplicationStatus, ApplicationStatusMaping } from '$lib/ApplicationsStore.svelte';
	import HasUser from '$lib/HasUser.svelte';
	import { post } from '$lib/utils';
	import NavBar from '../NavBar.svelte';
	import * as d3 from 'd3';
	import { sankey as mySankey } from './sankey';

	let applications: Application[] = $state([]);

	let chartDiv: HTMLDivElement | undefined = $state();

	let showExpired = $state(false);
	let showIgnore = $state(false);
	let showLinked = $state(false);
	let showToApply = $state(false);

	// Handle the graph creation
	$effect(() => {
		if (!chartDiv || applications.length == 0) return;

		chartDiv.innerHTML = '';

		type NodeType =
			| AsEnum<typeof ApplicationStatus>
			| 'Linkedin'
			| 'Glassdoor'
			| 'Direct Source';

		let graph = {} as Record<string, Record<string, number>>;

		function addGraph(inSource: NodeType, inTarget: NodeType) {
			const source = `${inSource}`;
			const target = `${inTarget}`;
			if (graph[source] == undefined) {
				graph[source] = {} as Record<NodeType, number>;
			}
			graph[source][target] = (graph[source][target] ?? 0) + 1;
			return target as NodeType;
		}

		let sourceData: Record<string, number> = {
			'Direct Source': 0,
			Glassdoor: 0,
			Linkedin: 0
		};

		applications.forEach((a) => {
			let source: NodeType;

			if (!showExpired && a.status == ApplicationStatus.Expired) {
				return;
			}
			if (!showIgnore && a.status == ApplicationStatus.Ignore) {
				return;
			}
			if (!showLinked && a.status == ApplicationStatus.LinkedApplication) {
				return;
			}
			if (
				!showToApply &&
				(a.status == ApplicationStatus.ToApply || a.status == ApplicationStatus.WorkingOnIt)
			) {
				return;
			}

			if (a.url.includes('linkedin')) {
				source = 'Linkedin';
				sourceData['Linkedin'] += 1;
			} else if (a.url.includes('glassdoor')) {
				source = 'Glassdoor';
				sourceData['Glassdoor'] += 1;
			} else {
				source = 'Direct Source';
				sourceData['Direct Source'] += 1;
			}

			if (
				(
					[
						ApplicationStatus.Ignore,
						ApplicationStatus.Expired,
						ApplicationStatus.LinkedApplication,
						ApplicationStatus.ToApply,
						ApplicationStatus.Applyed
					] as AsEnum<typeof ApplicationStatus>[]
				).includes(a.status)
			) {
				addGraph(source, a.status);
				return;
			}

			// Edge case for working on it
			if (a.status === ApplicationStatus.WorkingOnIt) {
				addGraph(source, ApplicationStatus.ToApply);
				return;
			}

			source = addGraph(source, ApplicationStatus.Applyed);

			const history = a.status_history.split(',');
			if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
				source = addGraph(source, ApplicationStatus.TasksToDo);
				if (a.status == ApplicationStatus.TasksToDo) {
					return;
				}
			}
			if (history.includes(`${ApplicationStatus.InterviewStep1}`)) {
				source = addGraph(source, ApplicationStatus.InterviewStep1);
				if (a.status == ApplicationStatus.InterviewStep1) {
					return;
				}
			}
			addGraph(source, ApplicationStatus.ApplyedButSaidNo);
		});

		let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
			const arr = Object.keys(graph[elm]).concat(elm);
			for (const i of arr) {
				if (!acc.includes(i)) {
					acc.push(i);
				}
			}
			return acc;
		}, [] as string[]);

		function getGraphValueFor(node: string): number {
			return Object.keys(graph).reduce((acc, i) => {
				if (i == node) return acc;
				if (graph[i][node] != undefined) {
					return acc + graph[i][node];
				}
				return acc;
			}, 0);
		}

		let nodes = inNodes.map((node, i) => {
			let name = '';
			if (Number.isNaN(Number(node))) {
				name = node;
			} else {
				name = ApplicationStatusMaping[Number(node) as AsEnum<typeof ApplicationStatus>];
			}
			const value = sourceData[node] ?? getGraphValueFor(node);
			const base = {
				value: value,
				originalValue: node,
				id: name,
				index: i,
				percentage: Math.trunc((value / applications.length) * 100)
			};
			return base;
		});

		type Link = {
			source: number;
			target: number;
			value: number;
		};

		const links = Object.keys(graph).reduce((acc, source) => {
			return acc.concat(
				Object.keys(graph[source]).map((target) => {
					const ns = inNodes.indexOf(`${source}`);
					const nt = inNodes.indexOf(`${target}`);
					return {
						source: ns,
						target: nt,
						value: graph[source][target]
					};
				})
			);
		}, [] as Link[]);

		const bounding = chartDiv.getBoundingClientRect();

		let sankey = mySankey()
			.nodeWidth(20)
			.nodePadding(10)
			.size([bounding.width, bounding.height - 20]);

		let path = sankey.link();

		sankey.nodes(nodes).links(links).layout(32);

		const svg = d3
			.select(chartDiv)
			.append('svg')
			.attr('width', bounding.width)
			.attr('height', bounding.height)
			.attr('viewBox', [0, 0, bounding.width, bounding.height])
			.attr('style', 'max-width: 100%; height: auto; height: intrinsic;');

		// let color = d3.schemeSpectral[nodes.length];
		// let color = d3.interpolateTurbo(nodes.length);

        function getColor(index: number) {
            return d3.interpolateRainbow(index/nodes.length);
        }

		// add in the links
		var link = svg
			.append('g')
			.selectAll('.link')
			.data(
				links as ((typeof links)[0] & {
					dy: number;
					source: (typeof nodes)[0];
					target: (typeof nodes)[0];
				})[]
			)
			.enter()
			.append('path')
			.attr('class', 'link')
			.attr('d', path)
			.style('stroke', function (d) {
				return d3.rgb(
                    getColor(d.source.index)
                    // color[d.source.index]
                ).toString();
			})
			.style('stroke-width', function (d) {
				return Math.max(1, d.dy);
			})
			.sort(function (a, b) {
				return b.dy - a.dy;
			});

		// add the link titles
		link.append('title').text(function (d) {
			return d.source.id + ' → ' + d.target.id + '\n' + d.value;
		});

		const node = svg
			.append('g')
			.selectAll('.node')
			.data(
				nodes as ((typeof nodes)[0] & {
					x: number;
					y: number;
					dy: number;
					dx: number;
					index: number;
				})[]
			)
			.enter()
			.append('g')
			.attr('class', 'node')
			.attr('transform', function (d) {
				return 'translate(' + d.x + ',' + d.y + ')';
			});

		node.append('rect')
			.attr('height', function (d) {
				return d.dy;
			})
			.attr('width', sankey.getNodeWidth())
			.style('fill', function (d) {
				return getColor(d.index);
                //color[d.index];
			})
			.style('stroke', (d) => {
				return d3.rgb(
				    getColor(d.index)
                    //color[d.index]
                ).darker(2).toString();
			})
			.append('title')
			.text(function (d) {
				return d.id + '\n' + d.value;
			});

		node.append('text')
			.attr('x', -6)
			.attr('y', function (d) {
				return d.dy / 2;
			})
			.attr('dy', '.35em')
			.attr('text-anchor', 'end')
			.attr('transform', null)
			.text(function (d) {
				return `${d.id} (${d.value} ${d.percentage}%)`;
			})
			.filter(function (d) {
				return d.x < bounding.width / 2;
			})
			.attr('x', 6 + sankey.getNodeWidth())
			.attr('text-anchor', 'start');
	});

	async function getData() {
		try {
			applications = await post('application/list', {});
		} catch (e) {
			console.log('TODO, inform the user', e);
		}
	}

	$effect(() => {
		getData();
	});
</script>

<HasUser redirect="/cv">
	<div class="flex flex-col h-[100vh]">
		<NavBar />
		<div class="p-1 px-5">
			<div class="bg-white p-3 rounded-lg gap-5 flex">
				<fieldset>
					<label for="showIgnore">Show Ignore</label>
					<input id="showIgnore" type="checkbox" bind:checked={showIgnore} />
				</fieldset>
				<fieldset>
					<label for="showExpired">Show Expired</label>
					<input id="showExpired" type="checkbox" bind:checked={showExpired} />
				</fieldset>
				<fieldset>
					<label for="showLinked">Show Linked Applicaitons</label>
					<input id="showLinked" type="checkbox" bind:checked={showLinked} />
				</fieldset>
				<fieldset>
					<label for="showToApply">Show To Apply</label>
					<input id="showToApply" type="checkbox" bind:checked={showToApply} />
				</fieldset>
			</div>
		</div>
		<div class="flex flex-grow flex-col p-5">
			<div class="bg-white p-3 rounded-lg" style="width: 100%; height: 100%">
				<div bind:this={chartDiv} style="width: 100%; height: 100%;"></div>
			</div>
		</div>
	</div>
</HasUser>

<style>
	:global(.node rect) {
		cursor: move;
		fill-opacity: 0.9;
		shape-rendering: crispEdges;
	}

	:global(.node text) {
		pointer-events: none;
		text-shadow: 0 1px 0 #fff;
	}

	:global(.link) {
		fill: none;
		stroke-opacity: 0.5;
	}
</style>
