<script setup lang="ts">
	import { computed, markRaw, onBeforeUnmount, onMounted, ref, toRaw, watch, watchEffect } from "vue";
	import Icon from "../ui/icon.vue";
	import { decodeRouteQuery, encodeRouteQuery, formatCoordinates, formatDistance, formatRouteMode, formatRouteTime, formatTypeName, isSearchId, normalizeMarkerName } from "facilmap-utils";
	import { useToasts } from "../ui/toasts/toasts.vue";
	import type { ExportFormat, FindOnMapResult, SearchResult } from "facilmap-types";
	import { getMarkerIcon, type HashQuery, MarkerLayer, RouteLayer } from "facilmap-leaflet";
	import { getZoomDestinationForRoute, flyTo, normalizeZoomDestination } from "../../utils/zoom";
	import { latLng, type LatLng } from "leaflet";
	import Draggable from "vuedraggable";
	import RouteMode from "../ui/route-mode.vue";
	import DraggableLines from "leaflet-draggable-lines";
	import { cloneDeep, throttle } from "lodash-es";
	import ElevationStats from "../ui/elevation-stats.vue";
	import ElevationPlot from "../ui/elevation-plot.vue";
	import { isMapResult } from "../../utils/search";
	import type { LineWithTags } from "../../utils/add";
	import vTooltip from "../../utils/tooltip";
	import DropdownMenu from "../ui/dropdown-menu.vue";
	import ZoomToObjectButton from "../ui/zoom-to-object-button.vue";
	import type { RouteDestination } from "../facil-map-context-provider/route-form-tab-context";
	import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
	import AddToMapDropdown from "../ui/add-to-map-dropdown.vue";
	import ExportDropdown from "../ui/export-dropdown.vue";
	import { useI18n } from "../../utils/i18n";

	type SearchSuggestion = SearchResult;
	type MapSuggestion = FindOnMapResult & { kind: "marker" };
	type Suggestion = SearchSuggestion | MapSuggestion;

	interface Destination extends RouteDestination {
		query: string;
		loadingQuery?: string;
		loadingPromise?: Promise<void>;
		loadedQuery?: string;
		searchSuggestions?: SearchSuggestion[];
		mapSuggestions?: MapSuggestion[];
		selectedSuggestion?: Suggestion;
	}

	function makeCoordDestination(latlng: LatLng) {
		const disp = formatCoordinates({ lat: latlng.lat, lon: latlng.lng });
		let suggestion = {
			lat: latlng.lat,
			lon: latlng.lng,
			display_name: disp,
			short_name: disp,
			type: "coordinates",
			id: disp
		};
		return {
			query: disp,
			loadingQuery: disp,
			loadedQuery: disp,
			selectedSuggestion: suggestion,
			searchSuggestions: [ suggestion ]
		};
	}

	function makeDestination({ query, searchSuggestions, mapSuggestions, selectedSuggestion }: { query: string; searchSuggestions?: SearchResult[]; mapSuggestions?: FindOnMapResult[]; selectedSuggestion?: SearchResult | FindOnMapResult }): Destination {
		return {
			query,
			loadedQuery: searchSuggestions || mapSuggestions ? query : undefined,
			searchSuggestions,
			mapSuggestions: mapSuggestions?.filter((result) => result.kind == "marker") as MapSuggestion[],
			selectedSuggestion: selectedSuggestion as MapSuggestion
		};
	}

	const startMarkerColour = "00ff00";
	const dragMarkerColour = "ffd700";
	const endMarkerColour = "ff0000";

	function getIcon(i: number, length: number, highlight = false) {
		return getMarkerIcon(i == 0 ? `#${startMarkerColour}` : i == length - 1 ? `#${endMarkerColour}` : `#${dragMarkerColour}`, 35, undefined, undefined, highlight);
	}

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

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

	const submitButton = ref<HTMLButtonElement>();

	const props = withDefaults(defineProps<{
		/** If false, the route layer will be opaque and not draggable. */
		active?: boolean;
		routeId?: string;
		showToolbar?: boolean;
		noClear?: boolean;
	}>(), {
		active: true,
		showToolbar: true
	});

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

	const routeObj = computed(() => props.routeId ? client.value.routes[props.routeId] : client.value.route);
	const hasRoute = computed(() => !!routeObj.value);

	const routeMode = ref(routeObj.value?.mode ?? "car");
	const destinations = ref<Destination[]>(routeObj.value ? (
		routeObj.value.routePoints.map((point) => makeCoordDestination(latLng(point.lat, point.lon)))
	) : (
		[{ query: "" }, { query: "" }]
	));
	const submittedQuery = ref<{ destinations: Destination[]; mode: string }>();
	const routeError = ref<string>();
	const hoverDestinationIdx = ref<number>();
	const hoverInsertIdx = ref<number>();
	const suggestionMarker = ref<MarkerLayer>();

	// TODO: Handle client.value change
	const routeLayer = new RouteLayer(client.value, props.routeId, { weight: 7, opacity: 1, raised: true });
	routeLayer.on("click", (e) => {
		if (!props.active && !(e.originalEvent as any).ctrlKey) {
			emit("activate");
		}
	});

	const draggable = new DraggableLines(mapContext.value.components.map, {
		enableForLayer: false,
		tempMarkerOptions: () => ({
			icon: getMarkerIcon(`#${dragMarkerColour}`, 35),
			pane: "fm-raised-marker"
		}),
		plusTempMarkerOptions: () => ({
			icon: getMarkerIcon(`#${dragMarkerColour}`, 35),
			pane: "fm-raised-marker"
		}),
		dragMarkerOptions: (layer, i, length) => ({
			icon: getIcon(i, length),
			pane: "fm-raised-marker"
		})
	});
	draggable.on({
		insert: (e: any) => {
			destinations.value.splice(e.idx, 0, makeCoordDestination(e.latlng));
			void reroute(false);
		},
		dragstart: (e: any) => {
			hoverDestinationIdx.value = e.idx;
			hoverInsertIdx.value = undefined;
			if (e.isNew)
				destinations.value.splice(e.idx, 0, makeCoordDestination(e.to));
		},
		drag: throttle((e: any) => {
			destinations.value[e.idx] = makeCoordDestination(e.to);
		}, 300),
		dragend: (e: any) => {
			destinations.value[e.idx] = makeCoordDestination(e.to);
			void reroute(false);
		},
		remove: (e: any) => {
			hoverDestinationIdx.value = undefined;
			destinations.value.splice(e.idx, 1);
			void reroute(false);
		},
		dragmouseover: (e: any) => {
			destinationMouseOver(e.idx);
		},
		dragmouseout: (e: any) => {
			destinationMouseOut(e.idx);
		},
		plusmouseover: (e: any) => {
			hoverInsertIdx.value = e.idx;
		},
		plusmouseout: (e: any) => {
			hoverInsertIdx.value = undefined;
		},
		tempmouseover: (e: any) => {
			hoverInsertIdx.value = e.idx;
		},
		tempmousemove: (e: any) => {
			if (e.idx != hoverInsertIdx.value)
				hoverInsertIdx.value = e.idx;
		},
		tempmouseout: (e: any) => {
			hoverInsertIdx.value = undefined;
		}
	} as any);

	onMounted(() => {
		routeLayer.addTo(mapContext.value.components.map);
		draggable.enable();
	});

	onBeforeUnmount(() => {
		draggable.disable();
		routeLayer.remove();
	});

	const zoomDestination = computed(() => routeObj.value && getZoomDestinationForRoute(routeObj.value));

	const hashQuery = computed(() => {
		if (submittedQuery.value) {
			return {
				query: encodeRouteQuery({
					queries: submittedQuery.value.destinations.map((dest) => (getSelectedSuggestionId(dest) ?? dest.query)),
					mode: submittedQuery.value.mode
				}),
				...(zoomDestination.value ? normalizeZoomDestination(mapContext.value.components.map, zoomDestination.value) : {}),
				description: i18n.t("route-form.route-description-outer", {
					inner: i18n.t("route-form.route-description-inner", {
						destinations: submittedQuery.value.destinations.map((dest) => (getSelectedSuggestionName(dest) ?? dest.query)).join(i18n.t("route-form.route-description-inner-joiner")),
						mode: formatRouteMode(submittedQuery.value.mode)
					})
				})
			};
		} else
			return undefined;
	});

	const destinationsMeta = computed(() => destinations.value.map((destination) => ({
		isInvalid: getValidationState(destination) === false
	})));

	watchEffect(() => {
		if (hasRoute.value)
			routeLayer.setStyle({ opacity: props.active ? 1 : 0.35, raised: props.active });

		// Enable dragging after updating the style, since that might re-add the layer to the map
		if (props.active)
			draggable.enableForLayer(routeLayer);
		else
			draggable.disableForLayer(routeLayer);
	});

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

	watch(routeMode, () => {
		void reroute(false);
	});

	function addDestination(): void {
		destinations.value.push({
			query: ""
		});
	}

	function removeDestination(idx: number): void {
		if (destinations.value.length > 2)
			destinations.value.splice(idx, 1);
	}

	function getSelectedSuggestion(dest: Destination): Suggestion | undefined {
		if(dest.selectedSuggestion && [...(dest.searchSuggestions || []), ...(dest.mapSuggestions || [])].includes(dest.selectedSuggestion))
			return dest.selectedSuggestion;
		else if(dest.mapSuggestions && dest.mapSuggestions.length > 0 && (dest.mapSuggestions[0].similarity == 1 || (dest.searchSuggestions || []).length == 0))
			return dest.mapSuggestions[0];
		else if((dest.searchSuggestions || []).length > 0)
			return dest.searchSuggestions![0];
		else
			return undefined;
	}

	function getSelectedSuggestionId(dest: Destination): string | undefined {
		const sugg = getSelectedSuggestion(dest);
		if (!sugg)
			return undefined;

		if (isMapResult(sugg))
			return (sugg.kind == "marker" ? "m" : "l") + sugg.id;
		else
			return sugg.id;
	}

	function getSelectedSuggestionName(dest: Destination): string | undefined {
		const sugg = getSelectedSuggestion(dest);
		if (!sugg)
			return undefined;

		if (isMapResult(sugg))
			return sugg.name;
		else
			return sugg.short_name;
	}

	async function loadSuggestions(dest: Destination): Promise<void> {
		if (dest.loadingQuery == dest.query.trim()) {
			await dest.loadingPromise;
			return;
		} else if (dest.loadedQuery == dest.query.trim())
			return;

		const idx = destinations.value.indexOf(dest);
		toasts.hideToast(`fm${context.id}-route-form-suggestion-error-${idx}`);
		dest.searchSuggestions = undefined;
		dest.mapSuggestions = undefined;
		dest.selectedSuggestion = undefined;
		dest.loadingQuery = undefined;
		dest.loadingPromise = undefined;
		dest.loadedQuery = undefined;

		const query = dest.query.trim();

		if(query != "") {
			dest.loadingQuery = query;
			let resolveLoadingPromise = (): void => undefined;
			dest.loadingPromise = new Promise((resolve) => { resolveLoadingPromise = resolve; });

			try {
				const [searchResults, mapResults] = await Promise.all([
					client.value.find({ query: query }),
					(async () => {
						if (client.value.padData) {
							const m = query.match(/^m(\d+)$/);
							if (m) {
								const marker = await client.value.getMarker({ id: Number(m[1]) });
								return marker ? [{ kind: "marker" as const, similarity: 1, ...marker }] : [];
							} else
								return (await client.value.findOnMap({ query })).filter((res) => res.kind == "marker") as MapSuggestion[];
						}
					})()
				])

				if(query != dest.loadingQuery)
					return; // The destination has changed in the meantime

				dest.loadingQuery = undefined;
				dest.loadedQuery = query;
				dest.searchSuggestions = searchResults;
				dest.mapSuggestions = mapResults;

				if(isSearchId(query) && searchResults.length > 0 && searchResults[0].display_name) {
					if (dest.query == query)
						dest.query = searchResults[0].display_name;
					dest.loadedQuery = searchResults[0].display_name;
					dest.selectedSuggestion = searchResults[0];
				}

				if(mapResults) {
					const referencedMapResult = mapResults.find((res) => query == `m${res.id}`);
					if(referencedMapResult) {
						if (dest.query == query)
							dest.query = normalizeMarkerName(referencedMapResult.name);
						dest.loadedQuery = referencedMapResult.name;
						dest.selectedSuggestion = referencedMapResult;
					}
				}

				if(dest.selectedSuggestion == null)
					dest.selectedSuggestion = getSelectedSuggestion(dest);
			} catch (err: any) {
				if(query != dest.loadingQuery)
					return; // The destination has changed in the meantime

				console.warn(err.stack || err);
				toasts.showErrorToast(`fm${context.id}-route-form-suggestion-error-${idx}`, () => i18n.t("route-form.find-destination-error", { query }), err);
			} finally {
				resolveLoadingPromise();
			}
		}
	}

	function suggestionMouseOver(suggestion: Suggestion): void {
		suggestionMarker.value = markRaw((new MarkerLayer([ suggestion.lat!, suggestion.lon! ], {
			highlight: true,
			marker: {
				colour: dragMarkerColour,
				size: 35,
				symbol: "",
				shape: "drop"
			}
		})).addTo(mapContext.value.components.map));
	}

	function suggestionMouseOut(): void {
		if(suggestionMarker.value) {
			suggestionMarker.value.remove();
			suggestionMarker.value = undefined;
		}
	}

	function suggestionZoom(suggestion: Suggestion): void {
		mapContext.value.components.map.flyTo([suggestion.lat!, suggestion.lon!]);
	}

	function destinationMouseOver(idx: number): void {
		const marker = routeLayer._draggableLines?.dragMarkers[idx];

		if (marker) {
			hoverDestinationIdx.value = idx;
			marker.setIcon(getIcon(idx, routeLayer._draggableLines!.dragMarkers.length, true));
		}
	}

	function destinationMouseOut(idx: number): void {
		hoverDestinationIdx.value = undefined;

		const marker = routeLayer._draggableLines?.dragMarkers[idx];
		if (marker) {
			void Promise.resolve().then(() => {
				// If mouseout event is directly followed by a dragend event, the marker will be removed. Only update the icon if the marker is not removed.
				if (marker["_map"])
					marker.setIcon(getIcon(idx, routeLayer._draggableLines!.dragMarkers.length));
			});
		}
	}

	function getValidationState(destination: Destination): boolean | null {
		if (routeError.value && destination.query.trim() == '')
			return false;
		else if (destination.loadedQuery && destination.query == destination.loadedQuery && getSelectedSuggestion(destination) == null)
			return false;
		else
			return null;
	}

	async function route(zoom: boolean, smooth = true): Promise<void> {
		reset();

		try {
			const mode = routeMode.value;

			submittedQuery.value = { destinations: cloneDeep(toRaw(destinations.value)), mode };

			await Promise.all(destinations.value.map((dest) => loadSuggestions(dest)));
			const points = destinations.value.map((dest) => getSelectedSuggestion(dest));

			submittedQuery.value = { destinations: cloneDeep(toRaw(destinations.value)), mode };

			if(points.some((point) => point == null)) {
				routeError.value = i18n.t("route-form.some-destinations-not-found");
				return;
			}

			const route = await client.value.setRoute({
				routePoints: points.map((point) => ({ lat: point!.lat!, lon: point!.lon! })),
				mode,
				routeId: props.routeId
			});

			if (route && zoom)
				flyTo(mapContext.value.components.map, getZoomDestinationForRoute(route), smooth);
		} catch (err: any) {
			toasts.showErrorToast(`fm${context.id}-route-form-error`, () => i18n.t("route-form.route-calculation-error"), err);
		}
	}

	async function reroute(zoom: boolean, smooth = true): Promise<void> {
		if(hasRoute.value) {
			await Promise.all(destinations.value.map((dest) => loadSuggestions(dest)));
			const points = destinations.value.map((dest) => getSelectedSuggestion(dest));

			if(!points.some((point) => point == null))
				await route(zoom, smooth);
		}
	}

	function reset(): void {
		toasts.hideToast(`fm${context.id}-route-form-error`);
		submittedQuery.value = undefined;
		routeError.value = undefined;

		if(suggestionMarker.value) {
			suggestionMarker.value.remove();
			suggestionMarker.value = undefined;
		}

		client.value.clearRoute({ routeId: props.routeId });
	}

	function clear(): void {
		reset();

		destinations.value = [
			{ query: "" },
			{ query: "" }
		];
	}

	function handleSubmit(event: Event): void {
		submitButton.value?.focus();
		void route(true);
	}

	const linesWithTags = computed((): LineWithTags[] | undefined => routeObj.value && [{
		routePoints: routeObj.value.routePoints,
		mode: routeObj.value.mode
	}]);

	async function getExport(format: ExportFormat): Promise<string> {
		return await client.value.exportRoute({ format });
	}

	function setQuery(query: string, zoom = true, smooth = true): void {
		clear();
		const split = decodeRouteQuery(query);
		destinations.value = split.queries.map((query) => ({ query }));
		while (destinations.value.length < 2)
			destinations.value.push({ query: "" });
		routeMode.value = split.mode ?? "car";
		void route(zoom, smooth);
	}

	function setFrom(data: Parameters<typeof makeDestination>[0]): void {
		destinations.value[0] = makeDestination(data);
		void reroute(true);
	}

	function addVia(data: Parameters<typeof makeDestination>[0]): void {
		destinations.value.splice(destinations.value.length - 1, 0, makeDestination(data));
		void reroute(true);
	}

	function setTo(data: Parameters<typeof makeDestination>[0]): void {
		destinations.value[destinations.value.length - 1] = makeDestination(data);
		void reroute(true);
	}

	defineExpose({ setQuery, setFrom, addVia, setTo });
</script>

<template>
	<div class="fm-route-form">
		<form action="javascript:" @submit.prevent="handleSubmit">
			<Draggable
				v-model="destinations"
				handle=".fm-drag-handle"
				@end="reroute(true)"
				:itemKey="(destination: any) => destinations.indexOf(destination)"
			>
				<template #item="{ element: destination, index: idx }">
					<div class="destination" :class="{ active: hoverDestinationIdx == idx }">
						<hr class="fm-route-form-hover-insert" :class="{ active: hoverInsertIdx === idx }"/>
						<div
							class="input-group"
							@mouseenter="destinationMouseOver(idx)"
							@mouseleave="destinationMouseOut(idx)"
						>
							<span class="input-group-text px-2">
								<a href="javascript:" class="fm-drag-handle" @contextmenu.prevent>
									<Icon icon="resize-vertical" :alt="i18n.t('route-form.reorder-alt')"></Icon>
								</a>
							</span>
							<input
								class="form-control"
								v-model="destination.query"
								:placeholder="idx == 0 ? i18n.t('route-form.from-placeholder') : idx == destinations.length-1 ? i18n.t('route-form.to-placeholder') : i18n.t('route-form.via-placeholder')"
								:tabindex="idx+1"
								:class="{
									'is-invalid': destinationsMeta[idx].isInvalid,
									'fm-autofocus': idx === 0
								}"
								@blur="loadSuggestions(destination)"
							/>
							<template v-if="destination.query.trim() != ''">
								<DropdownMenu
									menuClass="fm-route-form-suggestions"
									noWrapper
									@update:isOpen="$event && loadSuggestions(destination)"
									:isLoading="!destination.searchSuggestions && !destination.mapSuggestions"
								>
									<template v-for="suggestion in destination.mapSuggestions" :key="suggestion.id">
										<li
											@mouseenter="suggestionMouseOver(suggestion)"
											@mouseleave="suggestionMouseOut()"
										>
											<a
												href="javascript:"
												class="dropdown-item fm-route-form-suggestions-zoom"
												:class="{ active: suggestion === getSelectedSuggestion(destination) }"
												@click.capture.stop.prevent="suggestionZoom(suggestion)"
											><Icon icon="zoom-in" :alt="i18n.t('route-form.zoom-alt')"></Icon></a>

											<a
												href="javascript:"
												class="dropdown-item"
												:class="{ active: suggestion === getSelectedSuggestion(destination) }"
												@click="destination.selectedSuggestion = suggestion; reroute(true)"
											>{{suggestion.name}} ({{formatTypeName(client.types[suggestion.typeId].name)}})</a>
										</li>
									</template>

									<li v-if="(destination.searchSuggestions || []).length > 0 && (destination.mapSuggestions || []).length > 0">
										<hr class="dropdown-divider fm-route-form-suggestions-divider">
									</li>

									<template v-for="suggestion in destination.searchSuggestions" :key="suggestion.id">
										<li
											@mouseenter="suggestionMouseOver(suggestion)"
											@mouseleave="suggestionMouseOut()"
										>
											<a
												href="javascript:"
												class="dropdown-item fm-route-form-suggestions-zoom"
												:class="{ active: suggestion === getSelectedSuggestion(destination) }"
												@click.capture.stop.prevent="suggestionZoom(suggestion)"
											><Icon icon="zoom-in" :alt="i18n.t('route-form.zoom-alt')"></Icon></a>
											<a
												href="javascript:"
												class="dropdown-item"
												:class="{ active: suggestion === getSelectedSuggestion(destination) }"
												@click="destination.selectedSuggestion = suggestion; reroute(true)"
											>{{suggestion.display_name}}<span v-if="suggestion.type"> ({{suggestion.type}})</span></a>
										</li>
									</template>
								</DropdownMenu>
							</template>
							<button
								v-if="destinations.length > 2"
								type="button"
								class="btn btn-secondary"
								@click="removeDestination(idx); reroute(false)"
								v-tooltip.right="i18n.t('route-form.remove-destination-tooltip')"
							>
								<Icon icon="minus" :alt="i18n.t('route-form.remove-destination-alt')" size="1.0em"></Icon>
							</button>
						</div>
					</div>
				</template>
				<template #footer>
					<hr class="fm-route-form-hover-insert" :class="{ active: hoverInsertIdx === destinations.length }"/>
				</template>
			</draggable>

			<div class="btn-toolbar">
				<button
					type="button"
					class="btn btn-secondary"
					@click="addDestination()"
					v-tooltip.bottom="i18n.t('route-form.add-destination-tooltip')"
					:tabindex="destinations.length+1"
				>
					<Icon icon="plus" :alt="i18n.t('route-form.add-destination-alt')"></Icon>
				</button>

				<RouteMode v-if="context.settings.routing" v-model="routeMode" :tabindex="destinations.length+2" tooltip-placement="bottom"></RouteMode>

				<button
					type="submit"
					class="btn btn-primary flex-grow-1"
					:tabindex="destinations.length+7"
					ref="submitButton"
				>{{i18n.t("route-form.submit")}}</button>
				<button
					v-if="hasRoute && !props.noClear"
					type="button"
					class="btn btn-secondary"
					:tabindex="destinations.length+8"
					@click="reset()"
					v-tooltip.right="i18n.t('route-form.clear-route-tooltip')"
				>
					<Icon icon="remove" :alt="i18n.t('route-form.clear-route-alt')"></Icon>
				</button>
			</div>

			<template v-if="routeError">
				<hr />

				<div class="alert alert-danger">{{routeError}}</div>
			</template>

			<template v-if="routeObj">
				<hr />

				<dl class="fm-search-box-dl">
					<dt>{{i18n.t("route-form.distance")}}</dt>
					<dd>{{formatDistance(routeObj.distance)}} <span v-if="routeObj.time != null">({{formatRouteTime(routeObj.time, routeObj.mode)}})</span></dd>

					<template v-if="routeObj.ascent != null">
						<dt>{{i18n.t("route-form.ascent-descent")}}</dt>
						<dd><ElevationStats :route="routeObj"></ElevationStats></dd>
					</template>
				</dl>

				<ElevationPlot :route="routeObj" v-if="routeObj.ascent != null"></ElevationPlot>

				<div v-if="showToolbar && !client.readonly" class="btn-toolbar" role="group">
					<ZoomToObjectButton
						v-if="zoomDestination"
						:label="i18n.t('route-form.zoom-to-object-label')"
						size="sm"
						:destination="zoomDestination"
					></ZoomToObjectButton>

					<AddToMapDropdown
						:lines="linesWithTags"
						size="sm"
						isSingle
					></AddToMapDropdown>

					<ExportDropdown
						:filename="i18n.t('route-form.export-filename')"
						:getExport="getExport"
						size="sm"
					></ExportDropdown>
				</div>
			</template>
		</form>
	</div>
</template>

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

		form {
			display: flex;
			flex-direction: column;
			flex-grow: 1;
		}

		.destination.active .input-group {
			box-shadow: 0 0 3px;
			border-radius: 0.25rem;
		}

		.destination:first-child {
			margin-top: calc(-0.5rem + 2px); // Offset space of first fm-route-form-hover-insert
		}

		&#{&} hr.fm-route-form-hover-insert {
			margin: 0.1rem -0.5rem;
			width: auto;
			border-width: 2px;
			border-color: inherit;
			border-top-style: dashed;

			&:not(.active) {
				border-color: transparent;
			}
		}

		.fm-elevation-plot {
			margin-bottom: 0.5rem;
		}
	}

	.dropdown-menu.fm-route-form-suggestions.show {
		opacity: 0.6;

		> li {
			display: flex;

			> :nth-child(2) {
				flex-grow: 1;
			}
		}

		.dropdown-item {
			width: auto;
			padding: 0.25rem 0.75rem 0.25rem 0.25rem;

			&.fm-route-form-suggestions-zoom {
				padding: 0.25rem 0.25rem 0.25rem 0.75rem;
			}
		}
	}
</style>