<script lang="ts">
import { onMount, tick } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url, FSNode } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
import Modal from "util/Modal.svelte";

let { nav }: {
	nav: FSNavigator;
} = $props();

let modal: Modal
let search_bar: HTMLInputElement = $state()
let error = $state("")
let search_term = $state("")
let search_results: string[] = $state([])
let selected_result = $state(0)
let searching = false
let last_searched_term = ""
let last_limit = $state(10)

// The path in which we're searching. This is used for stripping the path prefix
// of search results, and showing the title of the Modal
let search_prefix_node: FSNode = $state({} as FSNode)

onMount(() => {
	// Clear results when the user moves to a new directory
	return nav.subscribe(nav => {
		if (nav.initialized) {
			clear_search(false)
		}
	})
})

export const open = async () => {
	search_prefix_node = nav.base
	modal.show()
	await tick()
	search_bar.focus()
}

const search = async (limit = 10) => {
	if (search_term.length < 2 || search_term.length > 100) {
		// These are the length limits defined by the API
		search_results = []
		return
	} else if (search_term === last_searched_term && limit === last_limit) {
		// If the term is the same we don't have to search
		return
	} else if (searching) {
		// If a search is already running we also don't search
		return
	}

	console.debug("Searching for", search_term)

	error = ""
	last_searched_term = search_term
	last_limit = limit

	searching = true

	try {
		loading_start()
		let search_node = nav.base
		// If the base is not a directory, we use the parent
		if (search_node.type !== "dir") {
			search_node = nav.path[nav.path.length-2]
		}

		let search_results_tmp = await fs_search(search_node.path, search_term, limit)

		// If there are no results, and we are not searching the filesystem
		// root, we will search the filesystem root
		if (search_results_tmp.length === 0 && search_node.path != nav.path[0].path) {
			search_node = nav.path[0]
			search_results_tmp = await fs_search(search_node.path, search_term, limit)
		}

		search_prefix_node = search_node
		search_results = search_results_tmp
	} catch (err) {
		if (err.value) {
			error = err.value
		} else {
			alert(err)
			console.error(err)
		}
	} finally {
		loading_finish()
	}

	if (search_results.length > 0 && selected_result > search_results.length-1) {
		selected_result = search_results.length-1
	}

	searching = false

	// It's possible that the user entered another letter while we were
	// performing the search request. If this happens we run the search function
	// again
	if (last_searched_term !== search_term) {
		console.debug("Search term changed during search. Searching again")
		await search()
	}
}

const clear_search = (blur: boolean) => {
	error = ""
	search_term = ""
	search_results = []
	selected_result = 0
	searching = false
	last_searched_term = ""
	last_limit = 10

	// If blur is true we unfocus the search field. This should only happen when
	// the user presses escape
	if (blur) {
		search_bar.blur()
	}

	modal.hide()
}

// Cursor navigation events can only be prevented with keydown. But we want to
// use keyup for searching, so we use two listeners here
const input_keydown = (e: KeyboardEvent) => {
	if (e.key === "Escape" || e.key === "ArrowUp" || e.key === "ArrowDown") {
		e.preventDefault()
	}
}
const input_keyup = (e: KeyboardEvent) => {
	if (e.key === "Escape") {
		clear_search(true)
	} else if (e.key === "ArrowUp") {
		if (selected_result > 0) {
			selected_result--
		}
	} else if (e.key === "ArrowDown") {
		if (selected_result+1 < search_results.length) {
			selected_result++
		}
	} else {
		search()
	}
}

// Submitting opens the selected result
const submit_search = (e: Event) => {
	e.preventDefault()
	if (search_results.length !== 0) {
		open_result(selected_result)
	}
}

const open_result = (index: number, e?: MouseEvent) => {
	if (e !== undefined) {
		e.preventDefault()
	}
	nav.navigate(search_results[index], true)
	clear_search(false)
}

const window_keydown = (e: KeyboardEvent) => {
	if (e.ctrlKey || e.altKey || e.metaKey) {
		return // prevent custom shortcuts from interfering with system shortcuts
	} else if ((document.activeElement as any).type !== undefined && (document.activeElement as any).type === "text") {
		return // Prevent shortcuts from interfering with input fields
	}

	if (e.key === "Escape" && search_term !== "") {
		clear_search(true)
		e.preventDefault()
	} else if (e.key === "/" || e.key === "f") {
		e.preventDefault()
		e.stopPropagation()
		// search_bar.focus()
		open()
		return
	}
}
</script>

<svelte:window onkeydown={window_keydown} />


<Modal bind:this={modal} title="Searching in {search_prefix_node.name}">
	{#if error === "path_not_found" || error === "node_is_a_directory"}
		<div class="highlight_yellow center">
			Search index not found. The search index is a file called
			'.search_index.zstd' in your home directory. If you delete this file
			then search will not work. The file is regenerated 10 minutes after
			modifying a file in your filesystem.
		</div>
	{:else if error !== ""}
		<div class="highlight_red center">
			An error ocurred while executing the search request: {error}
		</div>
	{/if}
	<div class="center">
		<form class="search_form" onsubmit={submit_search}>
			<i class="icon">search</i>
			<input
				bind:this={search_bar}
				class="term"
				type="text"
				placeholder="Enter search term"
				style="width: 100%;"
				bind:value={search_term}
				onkeydown={input_keydown}
				onkeyup={input_keyup}
			/>
		</form>

		<div class="results">
			{#if search_term !== "" && search_results.length === 0}
				No results found
			{/if}

			{#each search_results as result, index}
				<a
					href={"/d"+fs_encode_path(result)}
					onclick={(e) => open_result(index, e)}
					class="node"
					class:node_selected={selected_result === index}
				>
					<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
					<span class="node_name">
						<!-- Remove the search directory from the result -->
						{result.slice(search_prefix_node.path.length+1)}
					</span>
				</a>
			{/each}

			{#if search_results.length === last_limit}
				<div class="node">
					<div class="node_name" style="text-align: center;">
						<button onclick={() => {search(last_limit + 100)}}>
							<i class="icon">expand_more</i>
							More results
						</button>
					</div>
				</div>
			{/if}
		</div>
	</div>
</Modal>

<style>
.center {
	margin: auto;
	width: 1000px;
	max-width: 100%;
	padding-top: 2px;
	padding-bottom: 2px;
}

.search_form {
	display: flex;
	flex-direction: row;
	align-items: center;
}
.search_form > * {
	flex: 0 0 auto;
}
.search_form > .term {
	flex: 1 1 auto;
}

.results {
	display: flex;
	flex-direction: column;
	position: relative;
	overflow-x: hidden;
	overflow-y: auto;
	max-height: 80vh;
	text-align: initial;
}
.results > * {
	display: flex;
	flex-direction: row;
}
.node {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 4px;
	text-decoration: none;
	color: var(--text-color);
	padding: 2px;
}
.node_selected {
	background: var(--highlight_background);
	color: var(--highlight_text_color);
}
.node:hover:not(.node_selected) {
	background: var(--input_background);
	color: var(--input_text);
	text-decoration: none;
}
.node_icon {
	flex: 0 0 auto;
	height: 32px;
	width: 32px;
	vertical-align: middle;
	border-radius: 4px;
}
.node_name {
	flex: 1 1 auto;
	width: 100%;
	line-height: 1.2em;
	word-break: break-all;
}
</style>
