<script lang="ts">
	export let show = false;
	export let selectedModelId = '';

	import { marked } from 'marked';
	import { toast } from 'svelte-sonner';

	import { goto } from '$app/navigation';
	import { onMount, tick, getContext } from 'svelte';

	import {
		OLLAMA_API_BASE_URL,
		OPENAI_API_BASE_URL,
		WEBUI_API_BASE_URL,
		WEBUI_BASE_URL
	} from '$lib/constants';
	import { WEBUI_NAME, config, user, models, settings } from '$lib/stores';

	import { chatCompletion, generateOpenAIChatCompletion } from '$lib/apis/openai';

	import { splitStream } from '$lib/utils';

	import Messages from '$lib/components/notes/NoteEditor/Chat/Messages.svelte';
	import MessageInput from '$lib/components/channel/MessageInput.svelte';
	import XMark from '$lib/components/icons/XMark.svelte';
	import Tooltip from '$lib/components/common/Tooltip.svelte';
	import Pencil from '$lib/components/icons/Pencil.svelte';
	import PencilSquare from '$lib/components/icons/PencilSquare.svelte';

	const i18n = getContext('i18n');

	export let enhancing = false;
	export let streaming = false;
	export let stopResponseFlag = false;

	export let note = null;

	export let files = [];
	export let messages = [];

	export let onInsert = (content) => {};
	export let onStop = () => {};
	export let scrollToBottomHandler = () => {};

	let loaded = false;

	let loading = false;

	let messagesContainerElement: HTMLDivElement;

	let system = '';
	let editorEnabled = false;

	let chatInputElement = null;

	const DEFAULT_DOCUMENT_EDITOR_PROMPT = `You are an expert document editor.

## Task
Based on the user's instruction, update and enhance the existing notes by incorporating relevant and accurate information from the provided context. Ensure all edits strictly follow the user’s intent.

## Input Structure
- Existing notes: Enclosed within <notes></notes> XML tags.
- Additional context: Enclosed within <context></context> XML tags.
- Editing instruction: Provided in the user message.

## Output Instructions
- Deliver a single, rewritten version of the notes in markdown format.
- Integrate information from the context only if it directly supports the user's instruction.
- Use clear, organized markdown elements: headings, bullet points, numbered lists, bold and italic text as appropriate.
- Focus on improving clarity, completeness, and usefulness of the notes.
- Return only the final, fully-edited markdown notes—do not include explanations, reasoning, or XML tags.
`;

	let scrolledToBottom = true;

	const scrollToBottom = () => {
		if (messagesContainerElement) {
			if (scrolledToBottom) {
				messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
			}
		}
	};

	const onScroll = () => {
		if (messagesContainerElement) {
			scrolledToBottom =
				messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
				messagesContainerElement.clientHeight + 10;
		}
	};

	const chatCompletionHandler = async () => {
		if (selectedModelId === '') {
			toast.error($i18n.t('Please select a model.'));
			return;
		}

		const model = $models.find((model) => model.id === selectedModelId);
		if (!model) {
			selectedModelId = '';
			return;
		}

		let responseMessage;
		if (messages.at(-1)?.role === 'assistant') {
			responseMessage = messages.at(-1);
		} else {
			responseMessage = {
				role: 'assistant',
				content: '',
				done: false
			};
			messages.push(responseMessage);
			messages = messages;
		}

		await tick();
		scrollToBottom();

		let enhancedContent = {
			json: null,
			html: '',
			md: ''
		};

		system = '';

		if (editorEnabled) {
			system = `${DEFAULT_DOCUMENT_EDITOR_PROMPT}\n\n`;
		} else {
			system = `You are a helpful assistant. Please answer the user's questions based on the context provided.\n\n`;
		}

		system +=
			`<notes>${note?.data?.content?.md ?? ''}</notes>` +
			(files && files.length > 0
				? `\n<context>${files.map((file) => `${file.name}: ${file?.file?.data?.content ?? 'Could not extract content'}\n`).join('')}</context>`
				: '');

		const chatMessages = JSON.parse(
			JSON.stringify([
				{
					role: 'system',
					content: `${system}`
				},
				...messages
			])
		);

		const [res, controller] = await chatCompletion(
			localStorage.token,
			{
				model: model.id,
				stream: true,
				messages: chatMessages
				// ...(files && files.length > 0 ? { files } : {}) // TODO: Decide whether to use native file handling or not
			},
			`${WEBUI_BASE_URL}/api`
		);

		await tick();
		scrollToBottom();

		let messageContent = '';

		if (res && res.ok) {
			const reader = res.body
				.pipeThrough(new TextDecoderStream())
				.pipeThrough(splitStream('\n'))
				.getReader();

			while (true) {
				const { value, done } = await reader.read();
				if (done || stopResponseFlag) {
					if (stopResponseFlag) {
						controller.abort('User: Stop Response');
					}

					if (editorEnabled) {
						enhancing = false;
						streaming = false;
					}
					break;
				}

				try {
					let lines = value.split('\n');

					for (const line of lines) {
						if (line !== '') {
							console.log(line);
							if (line === 'data: [DONE]') {
								if (editorEnabled) {
									responseMessage.content = '<status title="Edited" done="true" />';
								}

								responseMessage.done = true;
								messages = messages;
							} else {
								let data = JSON.parse(line.replace(/^data: /, ''));
								console.log(data);

								let deltaContent = data.choices[0]?.delta?.content ?? '';
								if (responseMessage.content == '' && deltaContent == '\n') {
									continue;
								} else {
									if (editorEnabled) {
										enhancing = true;
										streaming = true;

										enhancedContent.md += deltaContent;
										enhancedContent.html = marked.parse(enhancedContent.md);

										note.data.content.md = enhancedContent.md;
										note.data.content.html = enhancedContent.html;
										note.data.content.json = null;

										responseMessage.content = '<status title="Editing" done="false" />';

										scrollToBottomHandler();
										messages = messages;
									} else {
										messageContent += deltaContent;

										responseMessage.content = messageContent;
										messages = messages;
									}

									await tick();
								}
							}
						}
					}
				} catch (error) {
					console.log(error);
				}

				scrollToBottom();
			}
		}
	};

	const submitHandler = async (e) => {
		const { content, data } = e;
		if (selectedModelId && content) {
			messages.push({
				role: 'user',
				content: content
			});
			messages = messages;

			await tick();
			scrollToBottom();

			loading = true;
			await chatCompletionHandler();
			messages = messages.map((message) => {
				message.done = true;
				return message;
			});

			loading = false;
			stopResponseFlag = false;
		}
	};

	onMount(async () => {
		if ($user?.role !== 'admin') {
			await goto('/');
		}

		if ($settings?.models) {
			selectedModelId = $settings?.models[0];
		} else if ($config?.default_models) {
			selectedModelId = $config?.default_models.split(',')[0];
		} else {
			selectedModelId = '';
		}

		loaded = true;

		await tick();
		scrollToBottom();
	});
</script>

<div class="flex items-center mb-2 pt-1">
	<div class=" -translate-x-1.5 flex items-center">
		<button
			class="p-0.5 bg-transparent transition rounded-lg"
			on:click={() => {
				show = !show;
			}}
		>
			<XMark className="size-5" strokeWidth="2.5" />
		</button>
	</div>

	<div class=" font-medium text-base flex items-center gap-1">
		<div>
			{$i18n.t('Chat')}
		</div>

		<div>
			<Tooltip
				content={$i18n.t(
					'This feature is experimental and may be modified or discontinued without notice.'
				)}
				position="top"
				className="inline-block"
			>
				<span class="text-gray-500 text-sm">({$i18n.t('Experimental')})</span>
			</Tooltip>
		</div>
	</div>
</div>

<div class="flex flex-col items-center mb-2 flex-1 @container">
	<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
		<div class="mx-auto w-full md:px-0 h-full relative">
			<div class=" flex flex-col h-full">
				<div
					class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 scrollbar-hidden"
					id="messages-container"
					bind:this={messagesContainerElement}
					on:scroll={onScroll}
				>
					<div class=" h-full w-full flex flex-col">
						<div class="flex-1 p-1">
							<Messages bind:messages {onInsert} />
						</div>
					</div>
				</div>

				<div class=" pb-2">
					<MessageInput
						bind:chatInputElement
						acceptFiles={false}
						inputLoading={loading}
						onSubmit={submitHandler}
						{onStop}
					>
						<div slot="menu" class="flex items-center justify-between gap-2 w-full pr-2">
							<div>
								<Tooltip content={$i18n.t('Edit')} placement="top">
									<button
										on:click|preventDefault={() => (editorEnabled = !editorEnabled)}
										type="button"
										class="px-2 @xl:px-2.5 py-2 flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden hover:bg-gray-50 dark:hover:bg-gray-800 {editorEnabled
											? ' text-sky-500 dark:text-sky-300 bg-sky-50 dark:bg-sky-200/5'
											: 'bg-transparent text-gray-600 dark:text-gray-300 '}"
									>
										<PencilSquare className="size-4" strokeWidth="1.75" />
										<span
											class="block whitespace-nowrap overflow-hidden text-ellipsis leading-none pr-0.5"
											>{$i18n.t('Edit')}</span
										>
									</button>
								</Tooltip>
							</div>

							<Tooltip content={selectedModelId}>
								<select
									class=" bg-transparent rounded-lg py-1 px-2 -mx-0.5 text-sm outline-hidden w-20"
									bind:value={selectedModelId}
								>
									{#each $models as model}
										<option value={model.id} class="bg-gray-50 dark:bg-gray-700"
											>{model.name}</option
										>
									{/each}
								</select>
							</Tooltip>
						</div>
					</MessageInput>
				</div>
			</div>
		</div>
	</div>
</div>
