<script lang="ts">
	import {
		Handle,
		useNodesData,
		Position,
		type NodeProps,
		useNodes,
		useEdges,
		useUpdateNodeInternals,
		useSvelteFlow,
		type Edge,
		type Node
	} from '@xyflow/svelte';
	import { MessageCirclePlus } from '@lucide/svelte';
	import { mode } from 'mode-watcher';

	import type { ChatMessage, TokenUsage } from '$lib/helpers/types';
	import { Button } from '$lib/components/ui/button';
	import Message from './Message.svelte';
	import Spinner from '$lib/components/loading/Spinner.svelte';
	import { formatUsageCost } from '$lib';
	import { modelsState } from '$lib/state/models.svelte';
	import { PROVIDER_SELECTION_MODES } from '$lib/consts';
	import ListModels from '$lib/components/model/ListModels.svelte';

	let { id }: NodeProps = $props();

	// svelte-ignore state_referenced_locally
	const nodeData = useNodesData(id);
	const { update: updateNodes } = useNodes();
	const { update: updateEdges } = useEdges();
	const updateNodeInternals = useUpdateNodeInternals();
	const { fitView } = useSvelteFlow();

	let selectedModel = $derived((nodeData.current?.data.selectedModel as string) ?? '');
	let messages = $derived((nodeData.current?.data.messages as ChatMessage[]) ?? []);
	let loading = $derived((nodeData.current?.data.loading as boolean) ?? false);
	let usage = $derived((nodeData.current?.data.usage as TokenUsage) ?? null);
	let message = $derived(
		nodeData.current?.data.content != null
			? ({
					role: 'assistant',
					content: String(nodeData.current?.data.content ?? ''),
					reasoning: String(nodeData.current?.data.reasoning ?? ''),
					timestamp: (nodeData.current?.data.timestamp as number) ?? 0
				} as unknown as ChatMessage)
			: null
	);
	let containerRef: HTMLDivElement | null = $state(null);
	let articleRef: HTMLElement | null = $state(null);
	let selectedText = $state<string | null>(null);
	let selectedTextPosition = $state<{ y: number } | null>(null);

	// $effect(() => {
	// 	document.addEventListener('selectionchange', handleTextSelectionChange);
	// 	return () => {
	// 		document.removeEventListener('selectionchange', handleTextSelectionChange);
	// 	};
	// });

	function handleTextSelectionChange(e: Event) {
		const selection = document.getSelection();
		const text = selection?.toString();
		if (!text || text.trim() === '') {
			selectedText = null;
			return;
		}
		if (containerRef?.contains(selection?.anchorNode as unknown as HTMLElement)) {
			selectedText = text;
			let rect = selection?.getRangeAt(0).getBoundingClientRect();
			if (rect) {
				selectedTextPosition = {
					y: rect.top - containerRef.getBoundingClientRect().top
				};
			} else {
				selectedTextPosition = {
					y:
						(selection?.anchorNode?.parentElement?.getBoundingClientRect().top ?? 0) -
						containerRef.getBoundingClientRect().top
				};
			}
		} else {
			selectedText = null;
		}
	}

	function handleAddFollowUpMessage(prompt: string) {
		document.getSelection()?.removeAllRanges();
		const newNodes: Node[] = [];
		const newEdges: Edge[] = [];

		const newNodeId = `user-follow-up-${crypto.randomUUID()}`;
		const newNode: Node = {
			id: newNodeId,
			type: 'user-follow-up',
			position: {
				x: 0,
				y: 0
			},
			data: {
				role: 'user',
				prompt,
				selectedModels: [selectedModel],
				messages: [...messages, message]
			}
		};
		const newEdge: Edge = {
			id: `edge-${crypto.randomUUID()}`,
			source: id,
			target: newNodeId,
			sourceHandle: 's-right',
			targetHandle: 't-left'
		};
		newNodes.push(newNode);
		newEdges.push(newEdge);
		updateNodes((currentNodes) => [...currentNodes, ...newNodes]);
		updateEdges((currentEdges) => [...currentEdges, ...newEdges]);
	}

	let fitViewTimeout: ReturnType<typeof setTimeout>;

	$effect(() => {
		if (!articleRef) return;

		const observer = new ResizeObserver(() => {
			updateNodeInternals(id);
			clearTimeout(fitViewTimeout);
			fitViewTimeout = setTimeout(() => {
				fitView({ duration: 300 });
			}, 150);
		});

		observer.observe(articleRef);
		return () => {
			observer.disconnect();
			clearTimeout(fitViewTimeout);
		};
	});
</script>

<article
	bind:this={articleRef}
	class="group/assistant relative flex w-[calc(100dvw-2rem)] flex-col rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]"
>
	<div class="nodrag pointer-events-auto flex flex-1 cursor-auto flex-col justify-between">
		<div>
			<header class="mb-3 flex items-center justify-between">
				<div class="flex flex-wrap items-center gap-1">
					<ListModels selectedModels={[selectedModel]} showSelector={false} />
				</div>
			</header>

			{#if loading}
				<Spinner className="text-sm text-muted-foreground/70">Reaching out...</Spinner>
			{/if}
			{#if message}
				<div bind:this={containerRef}>
					<Message {message} />
				</div>
			{/if}
		</div>
		{#if usage && !loading && message}
			{@const modelSettings = modelsState.models.find((m) => m.id === selectedModel)}
			{@const provider = modelSettings?.provider ?? 'auto'}
			<div class="mt-3 flex items-center justify-between gap-2 border-t border-border pt-3">
				<p class="text-xs text-muted-foreground">
					{usage.total_tokens} tokens
					{#if formatUsageCost(modelSettings?.providers.find((p) => p.provider === provider)?.pricing, usage)}
						<span class="mx-0.5">&middot;</span>
						{formatUsageCost(
							modelSettings?.providers.find((p) => p.provider === provider)?.pricing,
							usage
						)}
					{/if}
					{#if message.timestamp}
						<span class="mx-0.5">&middot;</span> Latency {message.timestamp}ms
					{/if}
				</p>
				<p class="text-xs text-muted-foreground">
					<span class="inline-flex items-center gap-0.5">
						Using
						<span class="inline-flex items-center gap-1 rounded-full bg-muted py-0.5 pr-2 pl-1">
							{#if PROVIDER_SELECTION_MODES.find((m) => m.value === provider)}
								{@const mode = PROVIDER_SELECTION_MODES.find((m) => m.value === provider)!}
								<span
									class="inline-flex size-4 items-center justify-center rounded-full {mode.class}"
								>
									<mode.icon class="size-2.5 {mode.iconClass}" />
								</span>
							{:else}
								<img
									src={`https://huggingface.co/api/avatars/${provider}`}
									alt={provider}
									class="size-4 rounded-full"
								/>
							{/if}
							{provider}
						</span>
						provider
					</span>
				</p>
			</div>
		{/if}
	</div>
	<Button
		variant={mode.current === 'dark' ? 'default' : 'outline'}
		size="icon-lg"
		class="absolute top-0 right-0 z-50 translate-x-1/2 {selectedText
			? 'opacity-100'
			: 'pointer-events-none opacity-0'}"
		style="top: {(selectedTextPosition?.y ?? 0) + 50}px;"
		onclick={() => selectedText && handleAddFollowUpMessage(selectedText)}
	>
		<MessageCirclePlus class="size-5" />
	</Button>
</article>
<Handle type="target" id="t-top" class="opacity-0" position={Position.Top} />
<Handle type="target" id="t-left" class="opacity-0" position={Position.Left} />
<Handle type="target" id="t-right" class="opacity-0" position={Position.Right} />
<Handle type="source" id="s-bottom" class="opacity-0" position={Position.Bottom} />
<Handle type="source" id="s-left" class="opacity-0" position={Position.Left} />
<Handle type="source" id="s-right" class="opacity-0" position={Position.Right} />
