<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;

	async function getData() {
		try {
			applications = await post('application/list', {});

			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;
			}

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

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

				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;
				}

				if (a.status == ApplicationStatus.ApplyedButSaidNo) {
					const history = a.status_history.split(',');
					source = addGraph(source, ApplicationStatus.Applyed);
					if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
						source = addGraph(source, ApplicationStatus.TasksToDo);
					}
					addGraph(source, ApplicationStatus.ApplyedButSaidNo);
				} else if (
					([ApplicationStatus.TasksToDo] as AsEnum<typeof ApplicationStatus>[]).includes(
						a.status
					)
				) {
					addGraph(source, ApplicationStatus.Applyed);
					addGraph(ApplicationStatus.Applyed, a.status);
				}
			});

			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 = getGraphValueFor(node);
				const base = {
					value: value,
					originalValue: node,
					id: name,
					index: i,
					percentage: Math.trunc((value / applications.length) * 100),
					end: true
				};
				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);

            console.log("here2", nodes, links);

			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];

			// 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(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 color[d.index];
				})
				.style('stroke', (d) => {
					return d3.rgb(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.end ? `${d.percentage}%` : ''})`;
				})
				.filter(function (d) {
					return d.x < bounding.width / 2;
				})
				.attr('x', 6 + sankey.getNodeWidth())
				.attr('text-anchor', 'start');
		} 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="flex flex-grow 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>
