<script lang="ts">
	import { stopPropagation } from 'svelte/legacy'

	import { getContext, untrack } from 'svelte'
	import { initCss } from '../../utils'
	import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
	import { twMerge } from 'tailwind-merge'
	import { Map, View, Feature } from 'ol'
	import { useGeographic } from 'ol/proj.js'
	import { OSM, Vector as VectorSource } from 'ol/source.js'
	import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer.js'
	import { Point } from 'ol/geom.js'
	import { defaults as defaultControls } from 'ol/control.js'
	import { initConfig, initOutput } from '../../editor/appUtils'
	import InitializeComponent from '../helpers/InitializeComponent.svelte'
	import ResolveStyle from '../helpers/ResolveStyle.svelte'
	import { components } from '../../editor/component'
	import ResolveConfig from '../helpers/ResolveConfig.svelte'
	import { Style, Circle, Fill, Stroke, Text } from 'ol/style.js'
	import { findGridItem } from '../../editor/appUtilsCore'
	interface Marker {
		lon: number
		lat: number
		title?: string
		radius?: number
		color?: string
		strokeWidth?: number
		strokeColor?: string
	}

	const LAYER_NAME = {
		MARKER: 'Marker'
	} as const

	interface Props {
		id: string
		configuration: RichConfigurations
		customCss?: ComponentCustomCSS<'mapcomponent'> | undefined
		render: boolean
		extraKey?: string | undefined
	}

	let { id, configuration, customCss = undefined, render, extraKey = undefined }: Props = $props()

	const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
		getContext<AppViewerContext>('AppViewerContext')

	const resolvedConfig = $state(
		initConfig(components['mapcomponent'].initialData.configuration, untrack(() => configuration))
	)

	let outputs = initOutput($worldStore, untrack(() => id), {
		mapRegion: {
			topLeft: { lat: 0, lon: 0 },
			bottomRight: { lat: 0, lon: 0 }
		}
	})

	let map: Map | undefined = $state(undefined)
	let mapElement: HTMLDivElement | undefined = $state(undefined)

	function selectComponent() {
		if (!$connectingInput.opened) {
			$selectedComponent = [id]
			$focusedGrid = undefined
		}
	}

	function getLayersByName(name: keyof typeof LAYER_NAME) {
		return map
			?.getLayers()
			?.getArray()
			?.filter((l) => l.getProperties().name === LAYER_NAME[name])
	}

	function getMarkerArray(): Marker[] | undefined {
		let array: Marker[] | undefined = undefined
		try {
			if (typeof resolvedConfig.markers === 'string') {
				const json = JSON.parse(resolvedConfig.markers)
				array = Array.isArray(json) ? json : [json]
			} else {
				array = resolvedConfig.markers
			}
			return array?.filter((m) => !isNaN(+m.lat) && !isNaN(+m.lon))
		} catch (error) {
			console.log(error)
			return undefined
		}
	}

	function createMarkerLayers() {
		const markerArray = getMarkerArray()
		return markerArray?.map((m) => {
			const feature = new Feature({
				geometry: new Point([+m.lon, +m.lat]),
				name: m.title
			})

			feature.setStyle(
				new Style({
					image: new Circle({
						radius: m.radius ?? 7,
						fill: new Fill({ color: m.color ?? '#dc2626' }),
						stroke: new Stroke({ width: m.strokeWidth ?? 3, color: m.strokeColor ?? '#fca5a5' })
					}),
					text: new Text({
						text: m.title,
						font: '12px Calibri,sans-serif',
						fill: new Fill({ color: '#000' }),
						stroke: new Stroke({
							color: '#fff',
							width: 2
						}),
						offsetY: -15
					})
				})
			)

			return new VectorLayer({
				properties: {
					name: LAYER_NAME.MARKER
				},
				source: new VectorSource({
					features: [feature]
				})
			})
		})
	}
	function updateMarkers() {
		const layers = getLayersByName('MARKER')
		if (layers?.length) {
			layers.forEach((l) => map?.removeLayer(l))
		}
		createMarkerLayers()?.forEach((l) => map?.addLayer(l))
	}

	let previousLock: boolean | undefined = undefined

	function updateInteractions(map: Map) {
		if (previousLock === resolvedConfig.lock) {
			return
		}

		previousLock = resolvedConfig.lock

		map.getInteractions().forEach((i) => {
			i.setActive(!resolvedConfig.lock)
		})

		map.changed()
	}

	let css = $state(initCss($app.css?.mapcomponent, untrack(() => customCss)))

	function updateRegionOutput() {
		if (map && !resolvedConfig.lock) {
			let extent = map.getView().calculateExtent(map.getSize())
			const [left, bottom, right, top] = extent

			if (outputs?.mapRegion) {
				outputs.mapRegion.set({
					topLeft: { lat: top, lon: left },
					bottomRight: { lat: bottom, lon: right }
				})
			}
		}
	}

	async function handleSyncRegion() {
		const gridItem = findGridItem($app, id)
		if (!map || !gridItem) {
			return
		}

		const z = map.getView().getZoom()
		updateRegionOutput()

		if (z) {
			//@ts-ignore
			gridItem.data.configuration.zoom.value = z
		}

		const center = map.getView().getCenter()
		if (!center) {
			return
		}

		if (gridItem) {
			//@ts-ignore
			gridItem.data.configuration.longitude.value = center[0]
			//@ts-ignore
			gridItem.data.configuration.latitude.value = center[1]

			$app = $app
		}
	}
	$effect(() => {
		if (!render) {
			map = undefined
			mapElement = undefined
		}
	})
	$effect(() => {
		if (!map && mapElement) {
			useGeographic()
			map = new Map({
				target: mapElement,
				layers: [
					new TileLayer({
						source: new OSM()
					}),
					...(createMarkerLayers() || [])
				],
				view: new View({
					center: [resolvedConfig.longitude ?? 0, resolvedConfig.latitude ?? 0],
					zoom: resolvedConfig.zoom ?? 2
				}),
				controls: defaultControls({
					attribution: false
				})
			})
			updateRegionOutput()
		}
	})
	$effect(() => {
		if (map && resolvedConfig.longitude && resolvedConfig.latitude) {
			map.getView().setCenter([resolvedConfig.longitude, resolvedConfig.latitude])
		}
	})
	$effect(() => {
		if (map && resolvedConfig.zoom) {
			map.getView().setZoom(resolvedConfig.zoom)
		}
	})
	$effect(() => {
		if (map && resolvedConfig.markers) {
			updateMarkers()
		}
	})
	$effect(() => {
		map && resolvedConfig && updateInteractions(map)
	})
</script>

{#each Object.entries(components['mapcomponent'].initialData.configuration) as [key, initialConfig] (key)}
	<ResolveConfig
		{id}
		{extraKey}
		{key}
		bind:resolvedConfig={resolvedConfig[key]}
		configuration={configuration[key]}
		{initialConfig}
	/>
{/each}

<InitializeComponent {id} />

{#each Object.keys(css ?? {}) as key (key)}
	<ResolveStyle
		{id}
		{customCss}
		{key}
		bind:css={css[key]}
		componentStyle={$app.css?.mapcomponent}
	/>
{/each}

{#if render}
	<div class="relative h-full w-full component-wrapper">
		<!-- svelte-ignore a11y_no_static_element_interactions -->
		<div
			onpointermove={updateRegionOutput}
			onwheel={updateRegionOutput}
			ontouchmove={updateRegionOutput}
			onpointerdown={stopPropagation(selectComponent)}
			bind:this={mapElement}
			class={twMerge(`w-full h-full`, css?.map?.class, 'wm-map')}
			style={css?.map?.style ?? ''}
		></div>

		{#if $mode !== 'preview'}
			<!-- svelte-ignore a11y_no_static_element_interactions -->
			<div
				class="absolute bottom-0 left-0 px-1 py-0.5 bg-indigo-500 text-white text-2xs"
				onpointerdown={handleSyncRegion}
			>
				Set region
			</div>
		{/if}
	</div>
{/if}
