<script lang="ts">
	import { untrack } from "svelte";
	import { hasTextView, type BlobKind, type RepoBlobView } from "$lib/api/blob";
	import BlobHeader from "./BlobHeader.svelte";
	import { languageForPath } from "./language-colors";
	import LastCommitPanel from "./LastCommitPanel.svelte";
	import PierreFile from "./PierreFile.svelte";
	import { baseName, rawBlobHref } from "./urls";
	import TabPanel from "$lib/components/ui/TabPanel.svelte";

	interface Props {
		ownerHandle: string;
		repoName: string;
		blob: RepoBlobView;
		// server-prerendered shadow dom for the code view, only for the initial ssr
		prerenderedHTML?: string;
		// enry language name override, defaults to the extension guess
		language?: string | null;
	}

	let { ownerHandle, repoName, blob, prerenderedHTML, language }: Props = $props();

	const rawHref = $derived(rawBlobHref(ownerHandle, repoName, blob.ref, blob.path));
	const fileName = $derived(baseName(blob.path));
	// the xrpc doesn't name a language, fall back to the extension map
	const languageName = $derived(language ?? languageForPath(blob.path));

	// view is seeded from the load and reseeds only when the kind category
	// changes, a "rendered" view must not leak onto a plain code file
	type ViewCategory = "code" | "markup" | "media" | "other";
	const categoryOf = (kind: BlobKind): ViewCategory => {
		switch (kind) {
			case "code":
				return "code";
			case "markup":
				return "markup";
			case "image":
			case "svg":
			case "video":
				return "media";
			default:
				return "other";
		}
	};

	let view = $state(untrack(() => blob.defaultView));
	let lastCategory = untrack(() => categoryOf(blob.kind));

	$effect(() => {
		const category = categoryOf(blob.kind);
		const defaultView = blob.defaultView;
		untrack(() => {
			if (category === lastCategory) return;
			lastCategory = category;
			view = defaultView;
		});
	});

	let wrap = $state(false);

	const textViewActive = $derived(hasTextView(blob.kind) && view === "code" && !blob.fileTooLarge);

	let overflows = $state(false);

	// pierre renders into a shadow root, measure its scroller element
	// directly
	const measure = (host: HTMLElement | undefined) => {
		const scroller = host?.shadowRoot?.querySelector("[data-code]");
		overflows = scroller ? scroller.scrollWidth > scroller.clientWidth : false;
	};

	// rAF races pierre's async shiki pass, the measurement happens in
	// pierre's own post-render hook
	const onPostRender = (node: HTMLElement) => measure(node);

	// sveltekit reuses this component across [...path] navigations, drop the
	// previous file's measurement before pierre reports the new one
	$effect(() => {
		void blob.path;
		void blob.contents;
		overflows = false;
	});

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

	$effect(() => {
		const host = codeHost;
		if (!host || wrap) return;
		const onResize = () => measure(host.querySelector("diffs-container") ?? undefined);
		window.addEventListener("resize", onResize);
		return () => window.removeEventListener("resize", onResize);
	});

	const showWrap = $derived(textViewActive && (overflows || wrap));
</script>

<TabPanel class="relative mx-auto mt-[1px] w-full">
	<BlobHeader
		{ownerHandle}
		{repoName}
		ref={blob.ref}
		path={blob.path}
		kind={blob.kind}
		language={languageName}
		sizeLabel={blob.sizeLabel}
		lines={blob.lines}
		bind:view
		bind:wrap
		{showWrap}
		copyText={blob.contents}
	/>

	{#if blob.lastCommit}
		<LastCommitPanel {ownerHandle} {repoName} commit={blob.lastCommit} />
	{/if}

	{#if blob.kind === "submodule" && blob.submodule}
		<p class="text-center text-foreground-muted">
			This directory is a git submodule of
			<a
				href={blob.submodule.url}
				rel="external ugc nofollow noopener noreferrer"
				class="text-foreground-default no-underline hover:underline">{blob.submodule.url}</a
			>.
		</p>
	{:else if blob.fileTooLarge}
		<p class="py-8 text-center text-foreground-muted">
			This file is too large to render.
			<a href={rawHref} class="text-foreground-default no-underline hover:underline">View raw.</a>
		</p>
	{:else if blob.kind === "image" || (blob.kind === "svg" && view === "rendered")}
		<div class="text-center">
			<img
				src={rawHref}
				alt={blob.path}
				class="mx-auto h-auto max-w-full rounded border border-border-default"
			/>
		</div>
	{:else if blob.kind === "video"}
		<div class="text-center">
			<video controls class="mx-auto h-auto max-w-full rounded border border-border-default">
				<source src={rawHref} />
			</video>
		</div>
	{:else if blob.kind === "markup" && view === "rendered" && blob.renderedHtml !== null}
		<!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in $lib/markup -->
		<div class="markup overflow-x-auto">{@html blob.renderedHtml}</div>
	{:else if blob.contents !== null}
		<div bind:this={codeHost}>
			<PierreFile name={fileName} contents={blob.contents} {wrap} {prerenderedHTML} {onPostRender} />
		</div>
	{:else}
		<p class="py-8 text-center text-foreground-muted">
			Previews are not supported for this file type.
		</p>
	{/if}
</TabPanel>
