<script lang="ts">
	import {
		beforeUpdate,
		afterUpdate,
		createEventDispatcher,
		tick
	} from "svelte";
	import { text_area_resize, resize } from "../shared/utils";
	import { BlockTitle } from "@gradio/atoms";
	import { Upload } from "@gradio/upload";
	import { Image } from "@gradio/image/shared";
	import type { FileData, Client } from "@gradio/client";
	import { Clear, File, Music, Paperclip, Video, Send } from "@gradio/icons";
	import type { SelectData } from "@gradio/utils";

	export let value: { text: string; files: FileData[] } = {
		text: "",
		files: []
	};

	export let value_is_output = false;
	export let lines = 1;
	export let placeholder = "Type here...";
	export let disabled = false;
	export let label: string;
	export let info: string | undefined = undefined;
	export let show_label = true;
	export let container = true;
	export let max_lines: number;
	export let submit_btn: string | boolean | null = null;
	export let rtl = false;
	export let autofocus = false;
	export let text_align: "left" | "right" | undefined = undefined;
	export let autoscroll = true;
	export let root: string;
	export let file_types: string[] | null = null;
	export let max_file_size: number | null = null;
	export let upload: Client["upload"];
	export let stream_handler: Client["stream"];
	export let file_count: "single" | "multiple" | "directory" = "multiple";

	let upload_component: Upload;
	let hidden_upload: HTMLInputElement;
	let el: HTMLTextAreaElement | HTMLInputElement;
	let can_scroll: boolean;
	let previous_scroll_top = 0;
	let user_has_scrolled_up = false;
	export let dragging = false;
	let uploading = false;
	let oldValue = value.text;
	$: dispatch("drag", dragging);

	let full_container: HTMLDivElement;

	$: if (oldValue !== value.text) {
		dispatch("change", value);
		oldValue = value.text;
	}
	let accept_file_types: string | null;
	if (file_types == null) {
		accept_file_types = null;
	} else {
		file_types = file_types.map((x) => {
			if (x.startsWith(".")) {
				return x;
			}
			return x + "/*";
		});
		accept_file_types = file_types.join(", ");
	}

	$: if (value === null) value = { text: "", files: [] };
	$: value, el && lines !== max_lines && resize(el, lines, max_lines);

	const dispatch = createEventDispatcher<{
		change: typeof value;
		submit: undefined;
		blur: undefined;
		select: SelectData;
		input: undefined;
		focus: undefined;
		drag: boolean;
		upload: FileData[] | FileData;
		clear: undefined;
		load: FileData[] | FileData;
		error: string;
	}>();

	beforeUpdate(() => {
		can_scroll = el && el.offsetHeight + el.scrollTop > el.scrollHeight - 100;
	});

	const scroll = (): void => {
		if (can_scroll && autoscroll && !user_has_scrolled_up) {
			el.scrollTo(0, el.scrollHeight);
		}
	};

	async function handle_change(): Promise<void> {
		dispatch("change", value);
		if (!value_is_output) {
			dispatch("input");
		}
	}

	afterUpdate(() => {
		if (autofocus && el !== null) {
			el.focus();
		}
		if (can_scroll && autoscroll) {
			scroll();
		}
		value_is_output = false;
	});

	function handle_select(event: Event): void {
		const target: HTMLTextAreaElement | HTMLInputElement = event.target as
			| HTMLTextAreaElement
			| HTMLInputElement;
		const text = target.value;
		const index: [number, number] = [
			target.selectionStart as number,
			target.selectionEnd as number
		];
		dispatch("select", { value: text.substring(...index), index: index });
	}

	async function handle_keypress(e: KeyboardEvent): Promise<void> {
		await tick();
		if (e.key === "Enter" && e.shiftKey && lines > 1) {
			e.preventDefault();
			dispatch("submit");
		} else if (
			e.key === "Enter" &&
			!e.shiftKey &&
			lines === 1 &&
			max_lines >= 1
		) {
			e.preventDefault();
			dispatch("submit");
		}
	}

	function handle_scroll(event: Event): void {
		const target = event.target as HTMLElement;
		const current_scroll_top = target.scrollTop;
		if (current_scroll_top < previous_scroll_top) {
			user_has_scrolled_up = true;
		}
		previous_scroll_top = current_scroll_top;

		const max_scroll_top = target.scrollHeight - target.clientHeight;
		const user_has_scrolled_to_bottom = current_scroll_top >= max_scroll_top;
		if (user_has_scrolled_to_bottom) {
			user_has_scrolled_up = false;
		}
	}

	async function handle_upload({
		detail
	}: CustomEvent<FileData | FileData[]>): Promise<void> {
		handle_change();
		if (Array.isArray(detail)) {
			for (let file of detail) {
				value.files.push(file);
			}
			value = value;
		} else {
			value.files.push(detail);
			value = value;
		}
		await tick();
		dispatch("change", value);
		dispatch("upload", detail);
	}

	function remove_thumbnail(event: MouseEvent, index: number): void {
		handle_change();
		event.stopPropagation();
		value.files.splice(index, 1);
		value = value;
	}

	function handle_upload_click(): void {
		if (hidden_upload) {
			hidden_upload.value = "";
			hidden_upload.click();
		}
	}

	async function handle_submit(): Promise<void> {
		dispatch("submit");
	}

	function handle_paste(event: ClipboardEvent): void {
		if (!event.clipboardData) return;
		const items = event.clipboardData.items;
		for (let index in items) {
			const item = items[index];
			if (item.kind === "file" && item.type.includes("image")) {
				const blob = item.getAsFile();
				if (blob) upload_component.load_files([blob]);
			}
		}
	}

	function handle_dragenter(event: DragEvent): void {
		event.preventDefault();
		dragging = true;
	}

	function handle_dragleave(event: DragEvent): void {
		event.preventDefault();
		const rect = full_container.getBoundingClientRect();
		const { clientX, clientY } = event;
		if (
			clientX <= rect.left ||
			clientX >= rect.right ||
			clientY <= rect.top ||
			clientY >= rect.bottom
		) {
			dragging = false;
		}
	}

	function handle_drop(event: DragEvent): void {
		event.preventDefault();
		dragging = false;
		if (event.dataTransfer && event.dataTransfer.files) {
			upload_component.load_files(Array.from(event.dataTransfer.files));
		}
	}
</script>

<div
	class="full-container"
	class:dragging
	bind:this={full_container}
	on:dragenter={handle_dragenter}
	on:dragleave={handle_dragleave}
	on:dragover|preventDefault
	on:drop={handle_drop}
	role="group"
	aria-label="Multimedia input field"
>
	<!-- svelte-ignore a11y-autofocus -->
	<label class:container>
		<BlockTitle {show_label} {info}>{label}</BlockTitle>
		{#if value.files.length > 0 || uploading}
			<div
				class="thumbnails scroll-hide"
				aria-label="Uploaded files"
				data-testid="container_el"
				style="display: {value.files.length > 0 || uploading
					? 'flex'
					: 'none'};"
			>
				{#each value.files as file, index}
					<span role="listitem" aria-label="File thumbnail">
						<button class="thumbnail-item thumbnail-small">
							<button
								class:disabled
								class="delete-button"
								on:click={(event) => remove_thumbnail(event, index)}
								><Clear /></button
							>
							{#if file.mime_type && file.mime_type.includes("image")}
								<Image
									src={file.url}
									title={null}
									alt=""
									loading="lazy"
									class={"thumbnail-image"}
								/>
							{:else if file.mime_type && file.mime_type.includes("audio")}
								<Music />
							{:else if file.mime_type && file.mime_type.includes("video")}
								<Video />
							{:else}
								<File />
							{/if}
						</button>
					</span>
				{/each}
				{#if uploading}
					<div class="loader" role="status" aria-label="Uploading"></div>
				{/if}
			</div>
		{/if}
		<div class="input-container">
			<Upload
				bind:this={upload_component}
				on:load={handle_upload}
				{file_count}
				{root}
				{max_file_size}
				bind:dragging
				bind:uploading
				show_progress={false}
				disable_click={true}
				bind:hidden_upload
				on:error
				hidden={true}
				{upload}
				{stream_handler}
			></Upload>
			<button
				data-testid="upload-button"
				class="upload-button"
				on:click={handle_upload_click}><Paperclip /></button
			>
			<textarea
				data-testid="textbox"
				use:text_area_resize={{
					text: value.text,
					lines: lines,
					max_lines: max_lines
				}}
				class="scroll-hide"
				dir={rtl ? "rtl" : "ltr"}
				bind:value={value.text}
				bind:this={el}
				{placeholder}
				rows={lines}
				{disabled}
				{autofocus}
				on:keypress={handle_keypress}
				on:blur
				on:select={handle_select}
				on:focus
				on:scroll={handle_scroll}
				on:paste={handle_paste}
				style={text_align ? "text-align: " + text_align : ""}
			/>
			{#if submit_btn}
				<button
					class="submit-button"
					class:padded-button={submit_btn !== true}
					on:click={handle_submit}
				>
					{#if submit_btn === true}
						<Send />
					{:else}
						Hello World
					{/if}
				</button>
			{/if}
		</div>
	</label>
</div>

<style>
	.full-container {
		width: 100%;
		position: relative;
	}

	.full-container.dragging::after {
		content: "";
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		pointer-events: none;
	}

	.input-container {
		display: flex;
		position: relative;
		align-items: flex-end;
	}

	textarea {
		flex-grow: 1;
		outline: none !important;
		background: var(--input-background-fill);
		padding: var(--input-padding);
		color: var(--body-text-color);
		font-weight: var(--input-text-weight);
		font-size: var(--input-text-size);
		line-height: var(--line-sm);
		border: none;
		margin-top: 0px;
		margin-bottom: 0px;
		resize: none;
		position: relative;
		z-index: 1;
	}

	textarea:disabled {
		-webkit-opacity: 1;
		opacity: 1;
	}

	textarea::placeholder {
		color: var(--input-placeholder-color);
	}

	.upload-button,
	.submit-button {
		background: var(--button-secondary-background-fill);
		color: var(--button-secondary-text-color);
		border: none;
		text-align: center;
		text-decoration: none;
		font-size: 14px;
		cursor: pointer;
		border-radius: 15px;
		min-width: 30px;
		height: 30px;
		flex-shrink: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		margin-bottom: 5px;
		z-index: var(--layer-1);
	}
	.padded-button {
		padding: 0 10px;
	}

	.upload-button:hover,
	.submit-button:hover {
		background: var(--button-secondary-background-fill-hover);
	}

	.upload-button:active,
	.submit-button:active {
		box-shadow: var(--button-shadow-active);
	}

	.submit-button :global(svg) {
		height: 22px;
		width: 22px;
	}
	.upload-button :global(svg) {
		height: 17px;
		width: 17px;
	}

	.loader {
		display: flex;
		justify-content: center;
		align-items: center;
		--ring-color: transparent;
		position: relative;
		border: 5px solid #f3f3f3;
		border-top: 5px solid var(--color-accent);
		border-radius: 50%;
		width: 25px;
		height: 25px;
		animation: spin 2s linear infinite;
	}

	@keyframes spin {
		0% {
			transform: rotate(0deg);
		}
		100% {
			transform: rotate(360deg);
		}
	}

	.thumbnails :global(img) {
		width: var(--size-full);
		height: var(--size-full);
		object-fit: cover;
		border-radius: var(--radius-lg);
	}

	.thumbnails {
		display: flex;
		align-items: center;
		gap: var(--spacing-lg);
		overflow-x: scroll;
		padding-top: var(--spacing-sm);
	}

	.thumbnail-item {
		display: flex;
		justify-content: center;
		align-items: center;
		--ring-color: transparent;
		position: relative;
		box-shadow:
			0 0 0 2px var(--ring-color),
			var(--shadow-drop);
		border: 1px solid var(--border-color-primary);
		border-radius: var(--radius-lg);
		background: var(--background-fill-secondary);
		aspect-ratio: var(--ratio-square);
		width: var(--size-full);
		height: var(--size-full);
		cursor: default;
	}

	.thumbnail-small {
		flex: none;
		transform: scale(0.9);
		transition: 0.075s;
		width: var(--size-12);
		height: var(--size-12);
	}

	.thumbnail-item :global(svg) {
		width: 30px;
		height: 30px;
	}

	.delete-button {
		display: flex;
		justify-content: center;
		align-items: center;
		position: absolute;
		right: -7px;
		top: -7px;
		color: var(--button-secondary-text-color);
		background: var(--button-secondary-background-fill);
		border: none;
		text-align: center;
		text-decoration: none;
		font-size: 10px;
		cursor: pointer;
		border-radius: 50%;
		width: 20px;
		height: 20px;
	}

	.disabled {
		display: none;
	}

	.delete-button :global(svg) {
		width: 12px;
		height: 12px;
	}

	.delete-button:hover {
		filter: brightness(1.2);
		border: 0.8px solid var(--color-grey-500);
	}
</style>
