<script lang="ts">
	import type { Conversation } from "$lib/types.js";

	import hljs from "highlight.js/lib/core";
	import http from "highlight.js/lib/languages/http";
	import javascript from "highlight.js/lib/languages/javascript";
	import python from "highlight.js/lib/languages/python";
	import { createEventDispatcher } from "svelte";

	import { token } from "$lib/stores/token.js";
	import { entries, fromEntries, keys } from "$lib/utils/object.js";
	import type { InferenceProvider } from "@huggingface/inference";
	import IconExternal from "~icons/carbon/arrow-up-right";
	import IconCopyCode from "~icons/carbon/copy";
	import { getInferenceSnippet, type GetInferenceSnippetReturn, type InferenceSnippetLanguage } from "./utils.js";

	hljs.registerLanguage("javascript", javascript);
	hljs.registerLanguage("python", python);
	hljs.registerLanguage("http", http);

	interface Props {
		conversation: Conversation;
	}

	let { conversation }: Props = $props();

	const dispatch = createEventDispatcher<{ closeCode: void }>();

	const labelsByLanguage = {
		javascript: "JavaScript",
		python: "Python",
		http: "cURL",
	} as const satisfies Record<string, string>;
	type Language = keyof typeof labelsByLanguage;

	let lang: Language = $state("javascript");
	let showToken = $state(false);

	type GetSnippetArgs = {
		tokenStr: string;
		conversation: Conversation;
		lang: InferenceSnippetLanguage;
	};
	function getSnippet({ tokenStr, conversation, lang }: GetSnippetArgs) {
		return getInferenceSnippet(conversation.model, conversation.provider as InferenceProvider, lang, tokenStr, {
			messages: conversation.messages,
			streaming: conversation.streaming,
			max_tokens: conversation.config.max_tokens,
			temperature: conversation.config.temperature,
			top_p: conversation.config.top_p,
		});
	}

	// { javascript: 0, python: 0, http: 0 } at first
	const selectedSnippetIdxByLang: Record<Language, number> = $state(
		fromEntries(
			keys(labelsByLanguage).map(lang => {
				return [lang, 0];
			})
		)
	);

	type InstallInstructions = {
		title: string;
		content: string;
		docs: string;
	};

	function getTokenStr(showToken: boolean) {
		if ($token.value && showToken) {
			return $token.value;
		}
		return "YOUR_HF_TOKEN";
	}

	function highlight(code?: string, language?: InferenceSnippetLanguage) {
		if (!code || !language) return "";
		return hljs.highlight(code, { language: language === "curl" ? "http" : language }).value;
	}

	function copy(el: HTMLElement, _content?: string) {
		let timeout: ReturnType<typeof setTimeout>;
		let content = _content ?? "";

		function update(_content?: string) {
			content = _content ?? "";
		}

		function onClick() {
			el.classList.add("text-green-500");
			navigator.clipboard.writeText(content);
			clearTimeout(timeout);
			timeout = setTimeout(() => {
				el.classList.remove("text-green-500");
			}, 400);
		}
		el.addEventListener("click", onClick);

		return {
			update,
			destroy() {
				clearTimeout(timeout);
				el.removeEventListener("click", onClick);
			},
		};
	}
	let tokenStr = $derived(getTokenStr(showToken));
	let snippetsByLang = $derived({
		javascript: getSnippet({ lang: "js", tokenStr, conversation }),
		python: getSnippet({ lang: "python", tokenStr, conversation }),
		http: getSnippet({ lang: "curl", tokenStr, conversation }),
	} as Record<Language, GetInferenceSnippetReturn>);
	let selectedSnippet = $derived(snippetsByLang[lang][selectedSnippetIdxByLang[lang]]);
	let installInstructions = $derived(
		(function getInstallInstructions(): InstallInstructions | undefined {
			if (lang === "javascript") {
				const isHugging = selectedSnippet?.client.includes("hugging");
				const toInstall = isHugging ? "@huggingface/inference" : "openai";
				const docs = isHugging
					? "https://huggingface.co/docs/huggingface.js/inference/README"
					: "https://platform.openai.com/docs/libraries";
				return {
					title: `Install ${toInstall}`,
					content: `npm install --save ${toInstall}`,
					docs,
				};
			} else if (lang === "python") {
				const isHugging = selectedSnippet?.client.includes("hugging");
				const toInstall = isHugging ? "huggingface_hub" : "openai";
				const docs = isHugging
					? "https://huggingface.co/docs/huggingface_hub/guides/inference"
					: "https://platform.openai.com/docs/libraries";
				return {
					title: `Install the latest`,
					content: `pip install --upgrade ${toInstall}`,
					docs,
				};
			}
		})()
	);
</script>

<div class="px-2 pt-2">
	<div
		class="border-b border-gray-200 text-center text-sm font-medium text-gray-500 dark:border-gray-700 dark:text-gray-400"
	>
		<ul class="-mb-px flex flex-wrap">
			{#each entries(labelsByLanguage) as [language, label]}
				<li>
					<button
						onclick={() => (lang = language)}
						class="inline-block rounded-t-lg border-b-2 p-4 {lang === language
							? 'border-black text-black dark:border-blue-500 dark:text-blue-500'
							: 'border-transparent hover:border-gray-300 hover:text-gray-600 dark:hover:text-gray-300'}"
						aria-current="page">{label}</button
					>
				</li>
			{/each}
			<li class="ml-auto self-center max-sm:hidden">
				<button
					onclick={() => {
						dispatch("closeCode");
					}}
					class="flex size-7 items-center justify-center rounded-lg px-3 py-2.5 text-xs font-medium text-gray-900 focus:ring-4 focus:ring-gray-100 focus:outline-hidden dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
				>
					✕
				</button>
			</li>
		</ul>
	</div>

	{#if (snippetsByLang[lang]?.length ?? 0) > 1}
		<div class="flex gap-x-2 px-2 pt-6">
			{#each snippetsByLang[lang] ?? [] as { client }, idx}
				{@const isActive = idx === selectedSnippetIdxByLang[lang]}
				<button
					class="rounded-lg border px-1.5 py-0.5 text-sm leading-tight
					{isActive
						? 'bg-black text-gray-100 dark:border-gray-500 dark:bg-gray-700 dark:text-white'
						: 'text-gray-500 hover:text-gray-600 dark:border-gray-600 dark:hover:text-gray-400'}"
					onclick={() => (selectedSnippetIdxByLang[lang] = idx)}>{client}</button
				>
			{/each}
		</div>
	{/if}

	{#if installInstructions}
		<div class="flex items-center justify-between px-2 pt-6 pb-4">
			<h2 class="flex items-baseline gap-2 font-semibold">
				{installInstructions.title}
				<a
					href={installInstructions.docs}
					target="_blank"
					class="relative -bottom-[1px] flex items-center gap-1 text-sm font-normal text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
				>
					<IconExternal class="text-xs" />
					Docs
				</a>
			</h2>
			<div class="flex items-center gap-x-4">
				<button
					class="flex items-center gap-x-2 rounded-md border bg-white px-1.5 py-0.5 text-sm shadow-xs transition dark:border-gray-800 dark:bg-gray-800"
					use:copy={installInstructions.content}
				>
					<IconCopyCode class="text-2xs" /> Copy code
				</button>
			</div>
		</div>
		<pre
			class="overflow-x-auto rounded-lg border border-gray-200/80 bg-white px-4 py-6 text-sm shadow-xs dark:border-gray-800 dark:bg-gray-800/50">{@html highlight(
				installInstructions.content,
				selectedSnippet?.language
			)}</pre>
	{/if}

	<div class="flex items-center justify-between px-2 pt-6 pb-4">
		{#if conversation.streaming}
			<h2 class="font-semibold">Streaming API</h2>
		{:else}
			<h2 class="font-semibold">Non-Streaming API</h2>
		{/if}
		<div class="flex items-center gap-x-4">
			<label class="flex items-center gap-x-1.5 text-sm select-none">
				<input type="checkbox" bind:checked={showToken} />
				<p class="leading-none">With token</p>
			</label>
			<button
				class="flex items-center gap-x-2 rounded-md border bg-white px-1.5 py-0.5 text-sm shadow-xs transition dark:border-gray-800 dark:bg-gray-800"
				use:copy={selectedSnippet?.content}
			>
				<IconCopyCode class="text-2xs" /> Copy code
			</button>
		</div>
	</div>
	<pre
		class="overflow-x-auto rounded-lg border border-gray-200/80 bg-white px-4 py-6 text-sm shadow-xs dark:border-gray-800 dark:bg-gray-800/50">{@html highlight(
			selectedSnippet?.content,
			selectedSnippet?.language
		)}</pre>
</div>
