<script lang="ts">
	import { untrack } from "svelte";
	import { FileDiff, type FileDiffMetadata } from "@pierre/diffs";
	import { pierreDiffOptions, type DiffStyle } from "./pierre";

	interface Props {
		files: FileDiffMetadata[];
		// side-by-side vs stacked
		diffStyle?: DiffStyle;
		// server-prerendered shadow dom, aligned with files. only set for the
		// initial ssr, client navigations let pierre paint on its own
		prerenderedHTML?: (string | undefined)[];
	}

	let { files, diffStyle = "unified", prerenderedHTML = [] }: Props = $props();

	let container: HTMLElement | undefined = $state();

	let mounted: { instance: FileDiff; file: FileDiffMetadata }[] = [];
	// the style at hydrate time, the options effect skips right after a
	// (re)mount
	let mountedStyle: DiffStyle | undefined;

	// pierre does not update on its own. hydrate paints once per file, prop
	// changes re-render. diffStyle is read untracked so a toggle re-renders
	// through the effect below, a remount would lose expanded hunks
	const mountAll = (source: FileDiffMetadata[], html: (string | undefined)[]) => {
		const host = container;
		if (!host) return;
		for (const { instance } of mounted) instance.cleanUp();
		mounted = [];

		mounted = source.map((file, index) => {
			const element = host.children[index] as HTMLElement;
			// the html prop never reaches the client, pierre marks its ssr
			// output with data-dehydrated
			if (html[index] === undefined && element.shadowRoot?.querySelector("[data-dehydrated]") == null)
				element.shadowRoot?.replaceChildren();
			const instance = new FileDiff(pierreDiffOptions(untrack(() => diffStyle)), undefined, true);
			instance.hydrate({ fileDiff: file, fileContainer: element, prerenderedHTML: html[index] });
			return { instance, file };
		});
		mountedStyle = untrack(() => diffStyle);
	};

	$effect(() => {
		mountAll(files, prerenderedHTML);
		return () => {
			for (const { instance } of mounted) instance.cleanUp();
			mounted = [];
		};
	});

	$effect(() => {
		const style = diffStyle;
		if (style === mountedStyle) return;
		mountedStyle = style;
		for (const { instance, file } of mounted) {
			instance.setOptions(pierreDiffOptions(style));
			instance.render({ fileDiff: file, forceRender: true });
		}
	});
</script>

<div bind:this={container} class="flex flex-col gap-4">
	{#each files as file, index (index)}
		<diffs-container class="diffs-container">
			{#if prerenderedHTML[index]}
				<!-- pierre's own renderer output, token text is escaped by its hast builder -->
				<template shadowrootmode="open">{@html prerenderedHTML[index]}</template>
			{/if}
		</diffs-container>
	{/each}
</div>
