<script lang="ts">
	import * as d3 from 'd3';

	const {
		title,
		data,
		sensitivity = 0.01,
		width = 500,
		height = 500,
		dounout = false
	}: {
		title: string;
		data: Record<string, number>;
		sensitivity?: number;
		width?: number;
		height?: number;
		dounout?: boolean;
	} = $props();

	let div: HTMLDivElement;

	$effect(() => {
		if (!div) return;

		const labelsArray = Object.keys(data); //.toSorted((a, b) => data[a] - data[b]);
		const valueArray = labelsArray.map((a) => data[a]);

		const sum = valueArray.reduce((acc, d) => acc + d, 0);

		let dataf = labelsArray.map((l) => ({
			name: l,
			value: data[l],
			title: `${l}: ${data[l]}`
		}));

		const other = dataf.filter((v) => v.value / sum < sensitivity);

		if (other.length > 1) {
			dataf = dataf.filter((f) => f.value / sum > sensitivity);

			const otherSum = other.reduce((acc, v) => {
				return acc + v.value;
			}, 0);

			if (otherSum > 0) {
				dataf.push({
					value: otherSum,
					name: 'Other',
					title: `Other: ${otherSum}\n${other
						.toSorted((a, b) => b.value - a.value)
						.reduce((acc, a) => `${acc}${a.name}: ${a.value}\n`, '')}`
				});
			}
		}

		dataf = dataf.toSorted((a, b) => a.value - b.value);

		const names = dataf.map((d) => d.name);
		const values = dataf.map((d) => d.value);
		const range = d3.range(names.length).filter((i) => !Number.isNaN(values[i]));

		let colors: readonly string[] = [];
		const groups = names;
		const len = groups.length;
		if (len === 0) {
			// Do nothing
		} else if (len >= 3 && len < 10) {
			colors = d3.schemeBlues[len];
		} else {
			colors = [...groups].map((_, i) => d3.interpolateBlues(i / len));
		}
		const color = d3.scaleOrdinal(groups, colors);

		const innerRadius = 0; // inner radius of pie, in pixels (non-zero for donut)
		const outerRadius = Math.min(width, height) / 2; // outer radius of pie, in pixels
		const labelRadius = innerRadius * 0.2 + outerRadius * 0.8; // center radius of labels
		const stroke = innerRadius > 0 ? 'none' : 'white'; // stroke separating widths
		const strokeWidth = 1; // width of stroke separating wedges
		const strokeLinejoin = 'round'; // line join of stroke separating wedges
		const padAngle = stroke === 'none' ? 1 / outerRadius : 0; // angular separation between wedges, in radians

		const arcs = d3
			.pie()
			.padAngle(padAngle)
			.sort(null)
			.value((i) => values[i as number])(range);

		const arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);

		const arcLabel = d3.arc().innerRadius(labelRadius).outerRadius(labelRadius);

		const svg = d3
			.create('svg')
			.attr('width', width * 2)
			.attr('height', height * 2)
			.attr('viewBox', [-width, -height, width * 2, height * 2]);

		const shapes = svg
			.append('g')
			.attr('stroke', stroke)
			.attr('stroke-width', strokeWidth)
			.attr('stroke-linejoin', strokeLinejoin);

		const svg_arcs = shapes
			.selectAll('path')
			.data(arcs)
			.join('path')
			.attr('fill', (d, i) => color(`${names[d.data as number]}-${i}`))
			.attr('d', arc as unknown as number);

		svg_arcs.append('title').text((d) => dataf[d.data as number].title);

		svg.append('g')
			.attr('font-family', 'sans-serif')
			.attr('text-anchor', 'middle')
			.attr('font-size', 10)
			.selectAll('text')
			.data(arcs)
			.join('text')
			.attr('transform', (d) => `translate(${arcLabel.centroid(d as unknown as any)})`)
			.selectAll('tspan')
			.data((d) => {
				if (d.endAngle - d.startAngle <= 0.2) {
					return [];
				}
				if (d.endAngle - d.startAngle <= 0.25) {
					return [{ t: 'title', text: names[d.data as number] }];
				}

				return [
					{ t: 'title', text: names[d.data as number] },
					{
						t: 'value',
						text: values[d.data as number].toLocaleString('en-GB', {
							notation: 'compact'
						})
					}
				];
			})
			.join('tspan')
			.attr('x', 0)
			.attr('y', (_, i) => `${i * 1.1}em`)
			.attr('font-weight', (d) => (d.t === 'value' ? null : 'bold'))
			.text((d) => {
				return d.text;
			});

		if (dounout) {
			const mask = svg.append('mask').attr('id', 'center-mask');

			mask.append('circle')
				.attr('cx', 0)
				.attr('cy', 0)
				.attr('fill', 'white')
				.attr('r', width);

			mask.append('circle')
				.attr('cx', 0)
				.attr('cy', 0)
				.attr('fill', 'black')
				.attr('r', width / 4);

			shapes.attr('mask', 'url(#center-mask)');
		}

		div.innerHTML = '';
		div.appendChild(svg.node() as unknown as Node);
	});
</script>

<div>
	{title}
	<div class="relative m-auto overflow-hidden" style="width: {width}px; height: {height}px; ">
		<div bind:this={div} style="transform: translate(-25%, -25%)" class="m-auto absolute"></div>
	</div>
</div>
