<script lang="ts">
	import type { Snippet } from 'svelte';
	import { ExternalLink, Frown, MessageCircle, Save, X } from '@lucide/svelte';

	import type { ChatModel } from '$lib/helpers/types';
	import { formatPricingPerToken, getProviderName } from '$lib/index.js';
	import { Button } from '$lib/components/ui/button';
	import { Slider } from '$lib/components/ui/slider';
	import Input from '$lib/components/ui/input/input.svelte';
	import * as Select from '$lib/components/ui/select/index.js';
	import * as Dialog from '$lib/components/ui/dialog/index.js';
	import { PROVIDER_SELECTION_MODES } from '$lib/consts';
	import Separator from '$lib/components/ui/separator/separator.svelte';
	import Switch from '$lib/components/ui/switch/switch.svelte';
	import { fade } from 'svelte/transition';
	import Spinner from '../loading/Spinner.svelte';
	import { modelsState, saveModelSettings } from '$lib/state/models.svelte';
	import * as Tooltip from '$lib/components/ui/tooltip/index.js';
	import ModelImageInput from './ModelImageInput.svelte';

	let {
		model: modelId,
		children,
		selectedModels,
		onToggleModel
	}: {
		model: string;
		children: Snippet;
		selectedModels: string[];
		onToggleModel?: (model: string) => void;
	} = $props();

	let model = $state<ChatModel | null>(modelsState.models.find((m) => m.id === modelId) ?? null);

	let temperature = $state<number | undefined>(undefined);
	let max_tokens = $state<number | undefined>(undefined);
	let top_p = $state<number | undefined>(undefined);
	let provider = $state<string>('preferred');
	let search = $state<string>('');

	$effect(() => {
		if (model) {
			temperature = model.temperature ?? undefined;
			max_tokens = model.max_tokens ?? undefined;
			top_p = model.top_p ?? undefined;
			provider = model.provider ?? 'preferred';
		}
	});

	let open = $state<boolean>(false);
	let loading = $state<boolean>(false);

	let modelsFiltered = $derived(
		search
			? modelsState.models.filter((m) => m.id?.toLowerCase()?.includes(search.toLowerCase()))
			: modelsState.models
	);

	let maxContentLength = $derived(
		provider === 'preferred'
			? model?.providers[0]?.context_length
			: model?.providers.find((p) => p.provider === provider)?.context_length
	);

	async function handleSave() {
		if (loading) return;
		loading = true;
		await new Promise((resolve) => setTimeout(resolve, 600));
		if (!model) return;
		saveModelSettings(model.id, { temperature, top_p, max_tokens, provider });
		loading = false;
		open = false;
	}
</script>

<Dialog.Root bind:open>
	<Dialog.Trigger>{@render children?.()}</Dialog.Trigger>
	<Dialog.Content class="max-w-3xl! gap-0! p-0!">
		<Dialog.Header class="mb-0 gap-1! rounded-none border-b p-5">
			<Dialog.Title>Model Settings</Dialog.Title>
			<Dialog.Description>Manage your model settings.</Dialog.Description>
		</Dialog.Header>
		<section
			class="flex min-h-0 items-stretch justify-start overflow-hidden lg:max-h-[50vh] lg:min-h-[50vh]"
		>
			<aside
				class="flex min-h-0 max-w-64 min-w-64 flex-1 flex-col overflow-hidden overflow-y-auto border-r border-border bg-accent p-3.5 max-lg:hidden dark:bg-card"
			>
				<div class="min-h-0 flex-1">
					<div class="relative">
						<Input
							type="text"
							class="mb-3 text-xs!"
							placeholder="Search models"
							autofocus={false}
							tabindex={-1}
							bind:value={search}
						/>
						{#if search}
							<Button
								variant="outline"
								size="icon-2xs"
								class="absolute top-1.5 right-1.5"
								onclick={() => (search = '')}
							>
								<X class="size-4" />
							</Button>
						{/if}
					</div>
					<div class="space-y-0.5">
						{#if modelsFiltered.length === 0}
							<div class="mt-5 flex flex-col items-center gap-2">
								<p
									class="flex flex-col items-center gap-2 text-center text-xs text-muted-foreground"
								>
									<span
										class="mx-auto flex size-8 items-center justify-center rounded-full bg-linear-to-b from-indigo-500/20 to-indigo-500/5 text-indigo-600"
									>
										<Frown class="size-4" />
									</span>
									No models found
								</p>
								<Button variant="outline" size="2xs" class="mt-3" onclick={() => (search = '')}
									>Clear search</Button
								>
							</div>
						{/if}
						{#each modelsFiltered as mdl (mdl.id)}
							<button
								class="{mdl.id === model?.id
									? 'from-indigo-500/20! to-indigo-500/5! dark:from-indigo-500/50! dark:to-indigo-500/20!'
									: 'hover:from-accent-foreground/10 hover:to-accent-foreground/0 dark:hover:from-indigo-500/20! dark:hover:to-indigo-500/5!'} flex w-full cursor-pointer items-center justify-between gap-2 rounded-md bg-linear-to-r from-transparent to-transparent px-2.5 py-2 text-left text-xs transition-all duration-300"
								onclick={() => (model = mdl)}
							>
								<span class="flex items-center gap-2 truncate">
									<img
										src={`https://huggingface.co/api/avatars/${mdl.owned_by}`}
										alt={mdl.owned_by}
										class="size-4 rounded"
									/>
									<span class="truncate text-ellipsis">
										{mdl.owned_by ?? ''}/
										<span class="font-medium">
											{mdl.id.split('/').pop() ?? mdl.id}
										</span>
									</span>
								</span>
								<div class="flex items-center gap-0.5">
									{#if mdl.architecture?.input_modalities?.includes('image')}
										<ModelImageInput />
									{/if}
									{#if selectedModels.includes(mdl.id)}
										<Tooltip.Root delayDuration={0}>
											<Tooltip.Trigger>
												<span
													class="flex size-[18px] items-center justify-center rounded bg-neutral-900/10 text-neutral-600 dark:bg-neutral-100/10 dark:text-neutral-100"
												>
													<MessageCircle
														class="0 size-2.5 fill-neutral-600 dark:fill-neutral-100"
													/>
												</span>
											</Tooltip.Trigger>
											<Tooltip.Content>
												<p class="flex items-center gap-1">This model will be used in the flow</p>
											</Tooltip.Content>
										</Tooltip.Root>
									{/if}
								</div>
							</button>
						{/each}
					</div>
				</div>
			</aside>
			<article class="min-h-0 flex-1 overflow-y-auto">
				{#if model}
					<header
						class="mb-4 flex items-center justify-between gap-3 border-b border-border bg-white px-5 py-3 dark:bg-card"
					>
						<div class="flex items-center justify-start gap-3">
							<img
								src={`https://huggingface.co/api/avatars/${model.owned_by}`}
								alt={model.owned_by}
								class="size-8 rounded-md"
							/>
							<div>
								<p class="text-sm font-medium">{model.id.split('/').pop() ?? model.id}</p>
								<p class="text-xs text-muted-foreground">by {model.owned_by}</p>
							</div>
						</div>
						<a href={`https://huggingface.co/${model.id}`} target="_blank">
							<Button variant="outline" size="icon-xs">
								<ExternalLink class="size-4" />
							</Button>
						</a>
					</header>
					<main class="mt-0 space-y-5 px-4 pb-5">
						<div class="rounded-lg border border-border p-3.5">
							<h4 class="text-sm leading-none font-medium">Inference provider</h4>
							<p class="mt-0.5 text-xs text-muted-foreground">
								Choose which Inference Provider to use with this model
							</p>
							<Select.Root type="single" bind:value={provider}>
								<Select.Trigger
									class="mt-3 flex w-full items-center justify-between gap-2 capitalize"
								>
									<div class="flex items-center gap-2">
										{#if PROVIDER_SELECTION_MODES.find((m) => m.value === provider)}
											{@const mode = PROVIDER_SELECTION_MODES.find((m) => m.value === provider)!}
											<div class="flex size-5 items-center justify-center rounded {mode.class}">
												<mode.icon class="size-3 {mode.iconClass}" />
											</div>
											<p>
												{mode.label}
												{#if mode.description}
													<span class="text-xs text-muted-foreground lowercase italic"
														>({mode.description})</span
													>
												{/if}
											</p>
										{:else}
											{@const providerName = getProviderName(provider)}
											<img
												src={`https://huggingface.co/api/avatars/${providerName}`}
												alt={provider}
												class="size-4 rounded"
											/>
											{provider}
										{/if}
									</div>
								</Select.Trigger>
								<Select.Content>
									<Select.Group>
										<Select.GroupHeading>Selection mode</Select.GroupHeading>
										{#each PROVIDER_SELECTION_MODES as mode (mode.value)}
											<Select.Item value={mode.value}>
												<div class="flex size-5 items-center justify-center rounded {mode.class}">
													<mode.icon class="size-3 {mode.iconClass}" />
												</div>
												<p>
													{mode.label}
													{#if mode.description}
														<span class="text-xs text-muted-foreground lowercase italic"
															>({mode.description})</span
														>
													{/if}
												</p>
											</Select.Item>
										{/each}
									</Select.Group>
									<Select.Separator />
									<Select.Group>
										<Select.GroupHeading>Specific provider</Select.GroupHeading>
										{#each model.providers as provider (provider.provider)}
											{@const providerName = getProviderName(provider.provider)}
											<Select.Item value={provider.provider}>
												<div class="flex items-center gap-2 capitalize">
													<img
														src={`https://huggingface.co/api/avatars/${providerName}`}
														alt={providerName}
														class="size-4 rounded"
													/>
													{provider.provider}
												</div>
												{#if formatPricingPerToken(provider.pricing)}
													<span class="text-xs text-muted-foreground">
														{formatPricingPerToken(provider.pricing)}
													</span>
												{/if}
											</Select.Item>
										{/each}
									</Select.Group>
								</Select.Content>
							</Select.Root>
						</div>
						<div class="grid gap-3 rounded-lg border border-border p-3.5">
							<div class="space-y-2">
								<div class="flex items-center justify-between gap-2">
									<div>
										<h4 class="text-sm leading-none font-medium">Temperature</h4>
										<p class="mt-0.5 text-xs text-muted-foreground">
											Tunes the creativity vs. predictability trade-off.
										</p>
									</div>
									<Switch
										checked={temperature !== undefined}
										onCheckedChange={(value) => {
											if (value) {
												temperature = 0.5;
											} else {
												temperature = undefined;
											}
										}}
									/>
								</div>
								{#if temperature !== undefined}
									<div class="flex items-center gap-2" transition:fade={{ duration: 100 }}>
										<Slider
											type="single"
											bind:value={temperature}
											min={0}
											max={2}
											step={0.01}
											class="mt-2"
										/>
										<Input
											type="number"
											min={0}
											max={2}
											step={0.1}
											bind:value={temperature}
											class="h-7! w-24!"
										/>
									</div>
								{/if}
							</div>
							<Separator />
							<div class="space-y-2">
								<div class="flex items-center justify-between gap-2">
									<div>
										<h4 class="text-sm leading-none font-medium">Max Tokens</h4>
										<p class="mt-0.5 text-xs text-muted-foreground">
											Sets the absolute limit for generated content length.
										</p>
									</div>
									<Switch
										checked={max_tokens !== undefined}
										onCheckedChange={(value) => {
											if (value) {
												max_tokens = (maxContentLength ?? 32_000) / 2;
											} else {
												max_tokens = undefined;
											}
										}}
									/>
								</div>
								{#if max_tokens !== undefined}
									<div class="flex items-center gap-2" transition:fade={{ duration: 100 }}>
										<Slider
											type="single"
											bind:value={max_tokens}
											min={0}
											max={maxContentLength ?? 32_000}
											step={256}
											class="mt-2"
										/>
										<Input
											type="number"
											min={0}
											max={maxContentLength ?? 32_000}
											step={256}
											bind:value={max_tokens}
											class="h-7! w-24!"
										/>
									</div>
								{/if}
							</div>
							<Separator />
							<div class="space-y-2">
								<div class="flex items-center justify-between gap-2">
									<div>
										<h4 class="text-sm leading-none font-medium">Top-P</h4>
										<p class="mt-0.5 text-xs text-muted-foreground">
											Dynamically filters token selection by probability mass.
										</p>
									</div>
									<Switch
										checked={top_p !== undefined}
										onCheckedChange={(value) => {
											if (value) {
												top_p = 0.5;
											} else {
												top_p = undefined;
											}
										}}
									/>
								</div>
								{#if top_p !== undefined}
									<div class="flex items-center gap-2" transition:fade={{ duration: 100 }}>
										<Slider
											type="single"
											bind:value={top_p}
											min={0}
											max={2}
											step={0.01}
											class="mt-2"
										/>
										<Input
											type="number"
											min={0}
											max={2}
											step={0.1}
											bind:value={top_p}
											class="h-7! w-24!"
										/>
									</div>
								{/if}
							</div>
						</div>
						<div class="flex items-center justify-end gap-3">
							<Button
								variant={model && selectedModels.includes(model.id) ? 'destructive' : 'outline'}
								class="flex-1"
								onclick={() => model && onToggleModel?.(model.id)}
							>
								<MessageCircle class="size-4" />
								{#if model && selectedModels.includes(model.id)}
									Remove from chat
								{:else}
									Use this model
								{/if}
							</Button>
							<Button class="flex-1" onclick={handleSave}>
								{#if loading}
									<Spinner className="text-lg" />
									Saving...
								{:else}
									<Save />
									Save settings
								{/if}
							</Button>
						</div>
					</main>
				{/if}
			</article>
		</section>
	</Dialog.Content>
</Dialog.Root>
