<script lang="ts">
	import { onDestroy, onMount } from 'svelte';
	import { get } from 'svelte/store';
	import { fade } from 'svelte/transition';

	import * as echarts from 'echarts';

	import { storedLocation } from '$lib/stores/settings';

	import { Label } from '$lib/components/ui/label';
	import { Switch } from '$lib/components/ui/switch';

	import '../compare/echarts.css';
	import { defaultParameters } from './options';

	let node: HTMLElement;
	let charts: echarts.ECharts[] = [];
	let resizeObservers: ResizeObserver[] = [];
	let mounted = $state(false);

	let showLegend = $state(false);
	let averageOnly = $state(false);

	const location = get(storedLocation);

	// Local component state for chart configuration
	let params = $state({
		latitude: [52.52],
		longitude: [13.41],
		...defaultParameters,
		hourly: ['temperature_2m'],
		models: ['gfs_seamless']
	});

	let count = $state(0);

	function isDarkMode(): boolean {
		if (typeof document === 'undefined') return false;
		return (
			document.documentElement.classList.contains('dark') ||
			document.documentElement.getAttribute('data-theme') === 'dark' ||
			(window.matchMedia &&
				window.matchMedia('(prefers-color-scheme: dark)').matches &&
				document.documentElement.getAttribute('data-theme') !== 'light')
		);
	}

	function getTextColor(): string {
		return isDarkMode() ? '#e5e7eb' : '#374151';
	}

	function getAxisLineColor(): string {
		return isDarkMode() ? 'rgba(229, 231, 235, 0.3)' : 'rgba(55, 65, 81, 0.3)';
	}

	function getSplitLineColor(): string {
		return isDarkMode() ? 'rgba(229, 231, 235, 0.1)' : 'rgba(55, 65, 81, 0.1)';
	}

	onMount(() => {
		mounted = true;
	});

	$effect(() => {
		const loadData = async () => {
			count = 0;
			if (mounted) {
				// Dispose existing charts and observers
				resizeObservers.forEach((ro) => ro.disconnect());
				resizeObservers = [];
				charts.forEach((chart) => {
					if (chart) {
						chart.dispose();
					}
				});
				charts = [];
				// eslint-disable-next-line svelte/no-dom-manipulating
				node.replaceChildren();

				const dataDaily = await fetch(
					`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&timeformat=unixtime&daily=sunset,sunrise&forecast_days=14`
				);
				const wd = await dataDaily.json();

				const dataReq = await fetch(
					`https://ensemble-api.open-meteo.com/v1/ensemble?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${params.hourly?.join(',') || ''}&models=${params.models?.join(',') || ''}&timeformat=unixtime&forecast_days=14`
				);
				const data = await dataReq.json();

				// Create day/night plot bands as markArea data
				let markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]> =
					[];
				if ('daily' in wd && 'sunrise' in wd.daily && 'sunset' in wd.daily) {
					let rise = wd.daily.sunrise;
					let set = wd.daily.sunset;
					markAreas = rise.map(function (r: number, i: number) {
						return [
							{
								xAxis: (r + data.utc_offset_seconds) * 1000,
								itemStyle: {
									color: 'rgba(255, 255, 194, 0.3)'
								}
							},
							{
								xAxis: (set[i] + data.utc_offset_seconds) * 1000
							}
						];
					});
				}

				let minValues = new Array(data.hourly.time.length).fill(undefined);
				let maxValues = new Array(data.hourly.time.length).fill(undefined);

				const textColor = getTextColor();
				const axisLineColor = getAxisLineColor();
				const splitLineColor = getSplitLineColor();

				for (let variable of params.hourly || []) {
					const chartDiv = document.createElement('div');
					chartDiv.style.width = '100%';
					chartDiv.style.height = showLegend ? '400px' : '300px';

					// Append to DOM BEFORE echarts.init so it can measure dimensions
					// eslint-disable-next-line svelte/no-dom-manipulating
					node.appendChild(chartDiv);

					let unit: string = '';

					const series: Array<Record<string, unknown>> = [];
					let average = new Array(data.hourly.time.length).fill(0);
					let averageCount = new Array(data.hourly.time.length).fill(0);

					const timestamps = data.hourly.time.map(
						(t: number) => (t + data.utc_offset_seconds) * 1000
					);

					for (let [model, values] of Object.entries(data.hourly)) {
						if (model === 'time') {
							continue;
						}
						if (model.startsWith(variable)) {
							for (let [index, val] of (values as number[]).entries()) {
								if (val !== null && val !== undefined) {
									let avVal = average[index];
									average[index] = avVal + val;
									averageCount[index]++;

									if (minValues[index] > val || minValues[index] === undefined) {
										minValues[index] = val;
									}
									if (maxValues[index] < val || maxValues[index] === undefined) {
										maxValues[index] = val;
									}
								}
							}

							unit = data.hourly_units[model];
						}
					}

					// Calculate average
					for (let [index, val] of average.entries()) {
						average[index] = Math.round((val / averageCount[index]) * 10) / 10;
					}

					// Create min-max area data
					const spreadData: Array<[number, number, number]> = minValues.map(
						(min: number, index: number) => [timestamps[index], min, maxValues[index]]
					);

					const isColumn = unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²';

					// Add spread series (lower bound)
					series.push({
						name: variable + '_spread',
						type: 'line',
						data: spreadData.map((d) => [d[0], d[1]]),
						areaStyle: {
							color: 'rgba(173, 216, 230, 0.3)',
							origin: 'auto'
						},
						lineStyle: {
							width: 0
						},
						showSymbol: false,
						stack: 'spread',
						smooth: true,
						z: 1
					});

					// Add spread series (upper bound delta)
					series.push({
						name: variable + '_spread_max',
						type: 'line',
						data: spreadData.map((d) => [d[0], d[2] - d[1]]),
						areaStyle: {
							color: 'rgba(173, 216, 230, 0.3)',
							origin: 'auto'
						},
						lineStyle: {
							width: 0
						},
						showSymbol: false,
						stack: 'spread',
						smooth: true,
						z: 1
					});

					// Add average line
					const averageData = average.map((val: number, idx: number) => [timestamps[idx], val]);

					series.push({
						name: variable + '_average',
						type: isColumn ? 'bar' : 'line',
						data: averageData,
						smooth: !isColumn,
						showSymbol: false,
						lineStyle: {
							type: 'dashed',
							width: 4,
							color: '#5e5e5e'
						},
						itemStyle: {
							color: '#5e5e5e'
						},
						emphasis: {
							lineStyle: {
								width: 6
							}
						},
						barMaxWidth: 5,
						z: 10
					});

					// Add current time markLine via a helper series
					series.push({
						name: 'Current Time',
						type: 'line',
						data: [],
						markLine: {
							silent: true,
							symbol: 'none',
							data: [
								{
									xAxis: Date.now() + data.utc_offset_seconds * 1000,
									lineStyle: {
										color: 'red',
										width: 2
									},
									label: {
										show: false
									}
								}
							]
						}
					});

					// Add day/night bands via markArea
					if (markAreas.length > 0) {
						series.push({
							name: 'Daylight',
							type: 'line',
							data: [],
							markArea: {
								silent: true,
								data: markAreas
							}
						});
					}

					const option: Record<string, unknown> = {
						title: {
							text: count === 0 ? 'Model Spread' : '',
							left: 'left',
							textStyle: {
								fontWeight: 'normal',
								color: textColor
							},
							...(count === 0
								? {
										subtext: `Compare ${params.hourly?.join(', ') || ''} in models: ${params.models?.join(', ') || ''}`,
										subtextStyle: {
											fontWeight: 'normal',
											color: textColor
										}
									}
								: {})
						},
						tooltip: {
							trigger: 'axis',
							axisPointer: {
								type: 'cross',
								animation: false
							},
							valueFormatter: (value: number) => {
								if (value === null || value === undefined) return '-';
								return value.toFixed(1) + ' ' + unit;
							}
						},
						legend: {
							show: showLegend,
							bottom: 0,
							type: 'scroll',
							data: [variable + '_average'],
							textStyle: {
								color: textColor
							}
						},
						grid: {
							left: 60,
							right: 10,
							top: count === 0 ? 80 : 40,
							bottom: showLegend ? 60 : 40
						},
						xAxis: {
							type: 'time',
							splitLine: {
								show: false
							},
							axisLine: {
								lineStyle: {
									color: axisLineColor
								}
							},
							axisLabel: {
								color: textColor
							}
						},
						yAxis: {
							type: 'value',
							name: unit,
							nameTextStyle: {
								color: textColor
							},
							axisLine: {
								show: false
							},
							axisLabel: {
								color: textColor
							},
							splitLine: {
								lineStyle: {
									color: splitLineColor
								}
							}
						},
						series: series,
						textStyle: {
							color: textColor
						}
					};

					// Add credits for last chart
					if (count === (params.hourly?.length || 0) - 1) {
						option.graphic = [
							{
								type: 'text',
								right: 10,
								bottom: 5,
								style: {
									text: 'Open-Meteo.com',
									fontSize: 10,
									fill: textColor,
									opacity: 0.5
								},
								onclick: function () {
									window.open('https://open-meteo.com', '_blank');
								},
								cursor: 'pointer'
							}
						];
					}

					const chart = echarts.init(chartDiv, null, { renderer: 'canvas' });
					charts.push(chart);
					chart.setOption(option);

					// Handle responsive resize
					const resizeObserver = new ResizeObserver(() => {
						chart.resize();
					});
					resizeObserver.observe(chartDiv);
					resizeObservers.push(resizeObserver);

					count++;
				}
			}
		};
		loadData();
	});

	onDestroy(() => {
		resizeObservers.forEach((ro) => ro.disconnect());
		resizeObservers = [];
		charts.forEach((chart) => {
			chart.dispose();
		});
		charts = [];
	});
</script>

<!-- min-h-[302px] min-h-[602px]  min-h-[902px] min-h-[1202px]  -->
<div
	class="container-wrapper relative -mx-6 md:mx-0"
	style="min-height: {300 * (params.hourly?.length || 0) + 2}px"
>
	<div in:fade={{ duration: 300 }} out:fade={{ duration: 300 }} bind:this={node}></div>
	<div
		class="{count > 0
			? 'pointer-events-none opacity-0'
			: 'opacity-100'} absolute top-0 z-30 flex h-full w-full items-center justify-center rounded-lg bg-accent"
	>
		<svg
			class="lucide lucide-loader-circle animate-spin"
			xmlns="http://www.w3.org/2000/svg"
			width="40"
			height="40"
			viewBox="0 0 24 24"
			fill="none"
			stroke="currentColor"
			stroke-width="2"
			stroke-linecap="round"
			stroke-linejoin="round"
		>
			<path d="M21 12a9 9 0 1 1-6.219-8.56" />
		</svg>
		<span class="hidden">Loading...</span>
	</div>
</div>

<div class="">
	<div class="mt-6 flex flex-col items-center gap-6 md:mt-12 md:flex-row">
		<div class="flex gap-2">
			<Switch
				id="show_legend"
				name="Show legend"
				bind:checked={showLegend}
				onCheckedChange={() => {
					params.hourly = params.hourly;
				}}
			/>
			<Label for="show_legend" class="mb-0.5 cursor-pointer text-lg">Show legend</Label>
		</div>
		<div class="flex gap-2">
			<Switch
				id="average_only"
				name="Average only"
				bind:checked={averageOnly}
				onCheckedChange={() => {
					params.hourly = params.hourly;
				}}
			/>
			<Label for="average_only" class="mb-0.5 cursor-pointer text-lg">Average only</Label>
		</div>
	</div>
</div>
