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

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

	import {
		buildAverageSeries,
		buildCurrentTimeSeries,
		buildDaylightMarkAreas,
		buildDaylightSeries,
		buildModelSeries,
		calculateAverage,
		composeChartOption,
		convertTimestamps,
		findUnit,
		getThemeColors
	} from '$lib/utils/echarts';

	import { ChartContainer, ChartToolbar, EChart } from '$lib/components/charts';
	import '$lib/components/charts/echarts.css';
	import { Checkbox } from '$lib/components/ui/checkbox';
	import { Label } from '$lib/components/ui/label';
	import { Switch } from '$lib/components/ui/switch';

	import { hourly, models as modelsFlat } from '../options';
	import { defaultParameters } from '../options';

	import type * as echarts from 'echarts';

	const models = [modelsFlat];

	// ─── Display State (does NOT trigger data re-fetch) ─────────────────────────

	let showLegend = $state(false);

	// ─── Data Fetch State ───────────────────────────────────────────────────────

	let chartComponents: EChart[] = $state([]);
	let chartInstances: echarts.ECharts[] = $state([]);
	let chartOptions: Array<Record<string, unknown>> = $state([]);
	let mounted = $state(false);
	let loading = $state(true);

	const location = get(storedLocation);

	let params = $state({
		latitude: [52.52],
		longitude: [13.41],
		...defaultParameters,
		hourly: ['temperature_2m', 'rain', 'relative_humidity_2m'],
		models: [
			'ecmwf_ifs025',
			'meteofrance_seamless',
			'ukmo_seamless',
			'icon_seamless',
			'gem_seamless'
		]
	});

	// ─── Cached API Response ────────────────────────────────────────────────────

	interface FetchedData {
		hourly: Record<string, unknown>;
		hourly_units: Record<string, string>;
		utc_offset_seconds: number;
		markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]>;
		timestamps: number[];
	}

	let fetchedData: FetchedData | null = $state(null);

	// ─── Lifecycle ──────────────────────────────────────────────────────────────

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

	onDestroy(() => {
		chartInstances = [];
		chartOptions = [];
		chartComponents = [];
	});

	// ─── Chart Instance Tracking ────────────────────────────────────────────────

	function handleChartReady(chart: echarts.ECharts): void {
		chartInstances = [...chartInstances, chart];
	}

	// ─── Data Fetching (only when params.hourly or params.models change) ───────

	$effect(() => {
		const hourlyVars = params.hourly;
		const modelList = params.models;

		if (!mounted || !hourlyVars?.length || !modelList?.length) return;

		const loadData = async () => {
			loading = true;
			chartInstances = [];
			chartComponents = [];

			const dataReq = await fetch(
				`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${hourlyVars.join(',')}&models=${modelList.join(',')}&timeformat=unixtime&daily=sunset,sunrise`
			);
			const data = await dataReq.json();

			let markAreas: FetchedData['markAreas'] = [];

			if ('daily' in data) {
				let dailyFirstModelKey: string[] | string = Object.keys(data.daily)[1].split('_');
				dailyFirstModelKey.shift();
				dailyFirstModelKey = dailyFirstModelKey.join('_');

				const sunriseKey = 'sunrise_' + dailyFirstModelKey;
				const sunsetKey = 'sunset_' + dailyFirstModelKey;

				if (sunriseKey in data.daily && sunsetKey in data.daily) {
					markAreas = buildDaylightMarkAreas(
						data.daily[sunriseKey],
						data.daily[sunsetKey],
						data.utc_offset_seconds
					);
				}
			}

			const timestamps = convertTimestamps(data.hourly.time, data.utc_offset_seconds);

			fetchedData = {
				hourly: data.hourly,
				hourly_units: data.hourly_units,
				utc_offset_seconds: data.utc_offset_seconds,
				markAreas,
				timestamps
			};

			loading = false;
		};

		loadData();
	});

	// ─── Chart Option Building (runs when fetchedData OR display toggles change) ─

	$effect(() => {
		if (!fetchedData) return;

		const {
			hourly: hourlyData,
			hourly_units,
			utc_offset_seconds,
			markAreas,
			timestamps
		} = fetchedData;
		const _showLegend = showLegend;

		const colors = getThemeColors();
		const variableCount = params.hourly?.length || 0;
		const timeLength = (hourlyData.time as number[]).length;
		const newOptions: Array<Record<string, unknown>> = [];

		for (let vi = 0; vi < variableCount; vi++) {
			const variable = params.hourly![vi];
			const unit = findUnit(hourly_units, hourlyData, variable);

			const series: Array<Record<string, unknown>> = [];

			for (const [model, values] of Object.entries(hourlyData)) {
				if (model === 'time') continue;
				if (!model.startsWith(variable)) continue;

				const seriesData = (values as (number | null)[]).map(
					(val, idx) => [timestamps[idx], val] as [number, number | null]
				);

				series.push(
					buildModelSeries({
						name: model,
						data: seriesData,
						unit
					})
				);
			}

			const { average } = calculateAverage(hourlyData, variable, timeLength);
			const averageData = average.map((val, idx) => [timestamps[idx], val] as [number, number]);
			series.push(buildAverageSeries({ variable, data: averageData, unit }));

			series.push(buildCurrentTimeSeries({ utcOffsetSeconds: utc_offset_seconds }));

			const daylightSeries = buildDaylightSeries({ markAreas });
			if (daylightSeries) {
				series.push(daylightSeries);
			}

			const isFirst = vi === 0;
			const isLast = vi === variableCount - 1;

			const option = composeChartOption({
				title: isFirst
					? {
							text: 'Model Compare',
							subtext: `Compare ${params.hourly?.join(', ') || ''} in models: ${params.models?.join(', ') || ''}`
						}
					: null,
				tooltip: { unit },
				legend: { show: _showLegend },
				grid: {
					hasTitle: isFirst,
					hasSubtitle: isFirst,
					showLegend: _showLegend
				},
				yAxis: { unit },
				series,
				toolbox: false,
				showCredit: isLast,
				colors
			});

			newOptions.push(option);
		}

		chartOptions = newOptions;
	});
</script>

<!-- ─── Chart Area ─────────────────────────────────────────────────────────── -->

<ChartContainer
	{loading}
	chartCount={params.hourly?.length || 0}
	chartHeight={showLegend ? 400 : 300}
>
	{#each chartOptions as option, i (i)}
		<EChart
			{option}
			height={showLegend ? '400px' : '300px'}
			onChartReady={handleChartReady}
			bind:this={chartComponents[i]}
		/>
	{/each}
</ChartContainer>

<!-- ─── Toolbar: Controls + Download ───────────────────────────────────────── -->

<div class="mt-6 md:mt-10">
	<ChartToolbar charts={chartInstances} fileName="model-comparison">
		{#snippet controls()}
			<div class="flex gap-2">
				<Switch id="show_legend" name="Show legend" bind:checked={showLegend} />
				<Label for="show_legend" class="mb-0.5 cursor-pointer text-lg">Show legend</Label>
			</div>
		{/snippet}
	</ChartToolbar>
</div>

<!-- ─── Models Selection ───────────────────────────────────────────────────── -->

<div class="mt-4 md:mt-8">
	<div class="flex">
		<a href="#models"><h2 id="models" class="text-2xl md:text-3xl">Models</h2></a>
		{#if params.models && params.models.length > 0}
			<div transition:fade={{ duration: 200 }} class="relative mt-1.25">
				<div
					class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline"
				>
					{params.models?.length || 0}&nbsp;/&nbsp;{models.flat().length}
				</div>
			</div>
		{/if}
	</div>

	<div class="mt-2 grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
		{#each models as group, i (i)}
			<div class="mb-3">
				{#each group as item (item.value)}
					{@const { value, label } = item as { value: string; label: string }}
					<div class="group flex items-center" title={label}>
						<Checkbox
							id="{value}_model"
							class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
							{value}
							checked={params.models?.includes(value)}
							aria-labelledby="{value}_label"
							onCheckedChange={() => {
								if (params.models?.includes(value)) {
									params.models = params.models.filter((item) => {
										return item !== value;
									});
								} else if (params.models) {
									params.models = [...params.models, value];
								}
							}}
						/>
						<Label
							id="{value}_model_label"
							for="{value}_model"
							class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
						>
					</div>
				{/each}
			</div>
		{/each}
	</div>

	<!-- ─── Hourly Variables Selection ─────────────────────────────────────── -->

	<div class="mt-6 md:mt-12">
		<div class="flex">
			<a href="#hourly_weather_variables"
				><h2 id="hourly_weather_variables" class="text-2xl md:text-3xl">
					Hourly Weather Variables
				</h2></a
			>
			{#if params.hourly && params.hourly.length > 0}
				<div transition:fade={{ duration: 200 }} class="relative mt-1.25">
					<div
						class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline"
					>
						{params.hourly?.length || 0}&nbsp;/&nbsp;{hourly.flat().length}
					</div>
				</div>
			{/if}
		</div>

		<div
			class="mt-2 grid grid-flow-row gap-x-2 gap-y-2 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4"
		>
			{#each hourly as group, i (i)}
				<div>
					{#each group as { value, label } (value)}
						<div class="group flex items-center" title={label}>
							<Checkbox
								id="{value}_hourly"
								class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
								{value}
								checked={params.hourly?.includes(value)}
								aria-labelledby="{value}_label"
								onCheckedChange={() => {
									if (params.hourly?.includes(value)) {
										params.hourly = params.hourly.filter((item) => {
											return item !== value;
										});
									} else if (params.hourly) {
										params.hourly = [...params.hourly, value];
									}
								}}
							/>
							<Label
								id="{value}_label"
								for="{value}_hourly"
								class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
							>
						</div>
					{/each}
				</div>
			{/each}
		</div>
	</div>
</div>
