<script setup lang="ts">
	import Icon from "../ui/icon.vue";
	import { find, getCurrentLanguage, getElevationForPoint, isSearchId, parseUrlQuery } from "facilmap-utils";
	import { useToasts } from "../ui/toasts/toasts.vue";
	import type { FindOnMapResult, SearchResult } from "facilmap-types";
	import SearchResults from "../search-results/search-results.vue";
	import { flyTo, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult, normalizeZoomDestination, openSpecialQuery } from "../../utils/zoom";
	import { Util } from "leaflet";
	import { isMapResult } from "../../utils/search";
	import storage from "../../utils/storage";
	import type { HashQuery } from "facilmap-leaflet";
	import { type FileResultObject, parseFiles } from "../../utils/files";
	import FileResults from "../file-results.vue";
	import { computed, reactive, ref, watch } from "vue";
	import DropdownMenu from "../ui/dropdown-menu.vue";
	import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
	import { isLanguageExplicit, useI18n } from "../../utils/i18n";

	const emit = defineEmits<{
		"hash-query-change": [query: HashQuery | undefined];
	}>();

	const context = injectContextRequired();
	const client = requireClientContext(context);
	const mapContext = requireMapContext(context);

	const toasts = useToasts();
	const i18n = useI18n();

	const layerId = Util.stamp(mapContext.value.components.searchResultsLayer);

	const searchInput = ref<HTMLInputElement>();

	const searchString = ref("");
	const loadingSearchString = ref("");
	const loadedSearchString = ref("");
	const searchCounter = ref(0);

	const searchResults = ref<SearchResult[]>();
	const mapResults = ref<FindOnMapResult[]>();
	const fileResult = ref<FileResultObject>();

	const zoomDestination = computed(() => getZoomDestinationForResults([
		...(searchResults.value || []),
		...(mapResults.value || []),
		...(fileResult.value?.features || [])
	]));

	const hashQuery = computed(() => {
		if (loadedSearchString.value) {
			return {
				query: loadedSearchString.value,
				...(zoomDestination.value && normalizeZoomDestination(mapContext.value.components.map, zoomDestination.value)),
				description: i18n.t("search-form.search-description", { query: loadedSearchString.value })
			};
		} else if (loadingSearchString.value)
			return { query: loadingSearchString.value, description: i18n.t("search-form.search-description", { query: loadedSearchString.value }) };
		else
			return undefined;
	});

	watch(hashQuery, (hashQuery: HashQuery | undefined) => {
		emit("hash-query-change", hashQuery);
	});

	function setSearchString(query: string) {
		searchString.value = query;
	}

	function handleSubmit(): void {
		searchInput.value?.blur();

		void search(storage.autoZoom, storage.zoomToAll);
	}

	async function search(zoom: boolean, zoomToAll?: boolean, smooth = true): Promise<void> {
		if (searchString.value != loadedSearchString.value) {
			reset();

			const counter = ++searchCounter.value;

			if(searchString.value.trim() != "") {
				try {
					if (await openSpecialQuery(searchString.value, context, zoom)) {
						searchString.value = "";
						return;
					}

					const query = searchString.value;
					loadingSearchString.value = searchString.value;

					const url = parseUrlQuery(query);

					const [newSearchResults, newMapResults] = await Promise.all([
						url ? (
							client.value.find({ query, loadUrls: true })
						) : (
							mapContext.value.runOperation(async () => await find(query, {
								lang: isLanguageExplicit() ? getCurrentLanguage() : undefined
							}))
						),
						client.value.padData ? client.value.findOnMap({ query }) : undefined
					]);

					if (counter != searchCounter.value)
						return; // Another search has been started in the meantime

					loadingSearchString.value = "";
					loadedSearchString.value = query;

					if(isSearchId(query) && Array.isArray(newSearchResults) && newSearchResults.length > 0 && newSearchResults[0].display_name) {
						searchString.value = newSearchResults[0].display_name;
						loadedSearchString.value = query;
					}

					if(typeof newSearchResults == "string") {
						searchResults.value = undefined;
						mapResults.value = undefined;
						fileResult.value = await mapContext.value.runOperation(async () => await parseFiles([ newSearchResults ]));
						mapContext.value.components.searchResultsLayer.setResults(fileResult.value.features);
					} else {
						const reactiveResults = reactive(newSearchResults);
						searchResults.value = reactiveResults;
						mapContext.value.components.searchResultsLayer.setResults(newSearchResults);
						mapResults.value = newMapResults ?? undefined;
						fileResult.value = undefined;

						const points = newSearchResults.filter((res) => (res.lon && res.lat));
						if(points.length > 0) {
							(async () => {
								const elevations = await Promise.all(points.map(async (point) => {
									return await getElevationForPoint({ lat: Number(point.lat), lon: Number(point.lon) });
								}));
								elevations.forEach((elevation, i) => {
									reactiveResults[i].elevation = elevation;
								});
							})().catch((err) => {
								console.warn("Error fetching search result elevations", err);
							});
						}
					}
				} catch(err) {
					toasts.showErrorToast(`fm${context.id}-search-form-error`, () => i18n.t("search-form.search-error"), err);
					return;
				}
			}
		}

		if (zoomToAll || (zoomToAll == null && (searchResults.value?.length ?? 0) + (mapResults.value?.length ?? 0) > 1)) {
			if (zoom)
				zoomToAllResults(smooth);
		} else if (mapResults.value && mapResults.value.length > 0 && (mapResults.value[0].similarity == 1 || (!searchResults.value || searchResults.value.length == 0))) {
			mapContext.value.components.selectionHandler.setSelectedItems([{ type: mapResults.value[0].kind, id: mapResults.value[0].id }])
			if (zoom)
				zoomToResult(mapResults.value[0], smooth);
		} else if (searchResults.value && searchResults.value.length > 0) {
			mapContext.value.components.selectionHandler.setSelectedItems([{ type: "searchResult", result: searchResults.value[0], layerId }]);
			if (zoom)
				zoomToResult(searchResults.value[0], smooth);
		} else if (fileResult.value) {
			if (zoom)
				zoomToAllResults(smooth);
		}
	}

	function reset(): void {
		searchCounter.value++;

		mapContext.value.components.selectionHandler.setSelectedItems(mapContext.value.selection.filter((item) => item.type != "searchResult" || item.layerId != layerId));
		toasts.hideToast(`fm${context.id}-search-form-error`);
		loadingSearchString.value = "";
		loadedSearchString.value = "";
		searchResults.value = undefined;
		mapResults.value = undefined;
		fileResult.value = undefined;
		mapContext.value.components.searchResultsLayer.setResults([]);
	};

	function zoomToResult(result: SearchResult | FindOnMapResult, smooth = true): void {
		const dest = isMapResult(result) ? getZoomDestinationForMapResult(result) : getZoomDestinationForSearchResult(result);
		if (dest)
			flyTo(mapContext.value.components.map, dest, smooth);
	}

	function zoomToAllResults(smooth = true): void {
		if (zoomDestination.value)
			flyTo(mapContext.value.components.map, zoomDestination.value, smooth);
	}

	defineExpose({
		setSearchString,
		search
	});
</script>

<template>
	<div class="fm-search-form">
		<form action="javascript:" @submit.prevent="handleSubmit()">
			<div class="input-group">
				<input type="search" class="form-control fm-autofocus" v-model="searchString" ref="searchInput" />
				<button
					type="submit"
					class="btn btn-secondary"
				>
					<Icon icon="search" :alt="i18n.t('search-form.search-alt')"></Icon>
				</button>
				<button
					v-if="searchResults || mapResults || fileResult"
					type="button"
					class="btn btn-secondary"
					@click="reset()"
				>
					<Icon icon="remove" :alt="i18n.t('search-form.clear-alt')"></Icon>
				</button>
				<DropdownMenu noWrapper>
					<li>
						<a
							href="javascript:"
							class="dropdown-item"
							@click.capture.stop.prevent="storage.autoZoom = !storage.autoZoom"
						>
							<Icon :icon="storage.autoZoom ? 'check' : 'unchecked'"></Icon> {{i18n.t("search-form.auto-zoom")}}
						</a>
					</li>

					<li>
						<a
							href="javascript:"
							class="dropdown-item"
							@click.capture.stop.prevent="storage.zoomToAll = !storage.zoomToAll"
						>
							<Icon :icon="storage.zoomToAll ? 'check' : 'unchecked'"></Icon> {{i18n.t("search-form.zoom-to-all")}}
						</a>
					</li>
				</DropdownMenu>
			</div>
		</form>

		<FileResults
			v-if="fileResult"
			:file="fileResult"
			:auto-zoom="storage.autoZoom"
			:union-zoom="storage.zoomToAll"
			:layer-id="layerId"
		/>
		<SearchResults
			v-else-if="searchResults || mapResults"
			:search-results="searchResults"
			:map-results="mapResults"
			:auto-zoom="storage.autoZoom"
			:union-zoom="storage.zoomToAll"
			:layer-id="layerId"
		></SearchResults>
	</div>
</template>

<style lang="scss">
	.fm-search-form {
		display: flex;
		flex-direction: column;
		min-height: 0;

		fieldset {
			margin-bottom: 0;
		}

		.fm-search-results {
			margin-top: 0.5rem;
		}

		.fm-search-box-collapse-point {
			// Set min-height to one list group item
			min-height: calc(/* line-height */ 1.5rem + /* list-group-item padding */ 2 * 7px + /* list-group-item border */ 2 * 1px);
		}
	}
</style>