<script lang="ts">
	import { diffFileName, type DiffFile, type DiffStat } from "$lib/api/diff";
	import { fileStats } from "$lib/api/rawdiff";
	import type { Snippet } from "svelte";
	import DiffFileCard from "./DiffFileCard.svelte";
	import DiffFileList from "./DiffFileList.svelte";
	import DiffTopbar from "./DiffTopbar.svelte";
	import { diffRowKey, toFileDiffMetadata } from "./fileDiff";
	import type { DiffStyle } from "./pierre";
	import { encodePathSegments } from "./urls";

	interface Props {
		files: DiffFile[];
		stat?: DiffStat;
		// side-by-side or stacked, persisted by the route in ?diff=
		diffStyle?: DiffStyle;
		// raw .patch/.diff targets, only present for full-hash refs
		downloadUrls?: { patch: string; diff: string };
		// blob route prefix for the per-file "view file" links, hidden when absent
		blobBase?: string;
		// server-prerendered shadow dom per row key, only for the initial ssr
		prerendered?: Record<string, string>;
		// round/interdiff indicator slot in the topbar (pull pages)
		topbarCenter?: Snippet;
		// subscription-toggle slot at the topbar's far end (pull review panel)
		topbarActions?: Snippet;
		// review-button slot on each file header (pull pages)
		fileHeaderActions?: Snippet<[DiffFile]>;
	}

	let {
		files,
		stat,
		diffStyle = "unified",
		downloadUrls,
		blobBase,
		prerendered,
		topbarCenter,
		topbarActions,
		fileHeaderActions
	}: Props = $props();

	// one structured diff per text file, binary notes stay
	// interleaved in the original diff order
	const rows = $derived(
		files.map((file) => ({
			file,
			name: diffFileName(file),
			key: diffRowKey(file),
			stat: { ...fileStats(file), files_changed: 1 },
			fileDiff: file.is_binary ? undefined : toFileDiffMetadata(file),
			prerenderedHTML: file.is_binary ? undefined : prerendered?.[diffRowKey(file)],
			blobUrl:
				blobBase && file.name.new ? `${blobBase}/${encodePathSegments(file.name.new)}` : undefined
		}))
	);

	// cards default to open
	let openStates = $state<Record<string, boolean>>({});
	const isOpen = (key: string) => openStates[key] ?? true;
	const allOpen = $derived(rows.every((row) => isOpen(row.key)));
	const setAll = (open: boolean) => {
		openStates = Object.fromEntries(rows.map((row) => [row.key, open]));
	};

	let filesOpen = $state(true);
</script>

<div class="flex flex-col">
	<DiffTopbar
		{stat}
		fileCount={files.length}
		{diffStyle}
		{downloadUrls}
		{filesOpen}
		onToggleFiles={rows.length > 0 ? () => (filesOpen = !filesOpen) : undefined}
		{allOpen}
		onSetAllOpen={setAll}
		center={topbarCenter}
		actions={topbarActions}
	/>

	<div class="flex grow gap-4">
		{#if rows.length > 0 && filesOpen}
			<aside
				class="sticky top-[calc(3rem+env(safe-area-inset-top))] hidden max-h-[calc(100vh-3rem-env(safe-area-inset-top))] w-fit max-w-60 shrink-0 overflow-y-auto md:block"
			>
				<section
					class="mx-auto min-h-full w-full overflow-x-auto rounded border border-border-default bg-background-default px-6 py-2 typography-paragraph-regular"
				>
					<DiffFileList files={rows.map((row) => ({ name: row.name }))} />
				</section>
			</aside>
		{/if}

		<div class="flex min-w-0 flex-1 flex-col gap-4">
			{#if rows.length === 0}
				<div class="py-8 text-center text-foreground-muted">
					<p>No differences found between the selected revisions.</p>
				</div>
			{/if}
			{#each rows as row (row.key)}
				<DiffFileCard
					file={row.file}
					name={row.name}
					stat={row.stat}
					fileDiff={row.fileDiff}
					prerenderedHTML={row.prerenderedHTML}
					{diffStyle}
					blobUrl={row.blobUrl}
					bind:open={() => isOpen(row.key), (open) => (openStates[row.key] = open)}
					headerActions={fileHeaderActions}
				/>
			{/each}
		</div>
	</div>
</div>
