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

	import * as echarts from 'echarts';

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

	import { buildCurrentTimeSeries, buildDaylightSeries, getThemeColors } from '$lib/utils/echarts';
	import { pad } from '$lib/utils/index';

	import { ChartContainer, ChartToolbar, EChart } from '$lib/components/charts';
	import '$lib/components/charts/echarts.css';
	import { Label } from '$lib/components/ui/label';
	import * as Select from '$lib/components/ui/select';

	import {
		type MarkArea,
		type WeekDailyData,
		type WeekForecastResult,
		type WeekHourlyData,
		fetchWeekForecast
	} from '$lib/services/weather';

	import { defaultParameters, models } from '../../options';
	import { getColor } from '../../utils/colors';
	import weatherCodes from '../../utils/weather-codes';

	import type { GeoLocation } from '$lib/stores/settings';

	// ─── Constants ──────────────────────────────────────────────────────────────

	const CHART_GROUP = 'week-meteogram';
	const MS_PER_DAY = 24 * 3600 * 1000;

	// ─── State ──────────────────────────────────────────────────────────────────

	let params = $state({
		latitude: [$storedLocation.latitude],
		longitude: [$storedLocation.longitude],
		models: ['best_match'],
		...defaultParameters
	});

	let location = $state<GeoLocation>($storedLocation);
	storedLocation.subscribe((value) => {
		location = value;
	});

	let mounted = $state(false);
	let loading = $state(true);

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

	const today = new Date();
	const selectedDay = new SvelteDate();
	let selectedDayIndex = $state(1);

	let tableScrollDiv: HTMLElement | undefined = $state();

	// ─── Fetched Data ───────────────────────────────────────────────────────────

	interface FetchedHourly {
		hourly: WeekHourlyData;
		utc_offset_seconds: number;
		timestamps: number[];
		hourlyDates: Date[];
		markAreas: MarkArea[];
	}

	interface FetchedDaily {
		daily: WeekDailyData;
		dailyDates: Date[];
	}

	let fetchedHourly: FetchedHourly | null = $state(null);
	let fetchedDaily: FetchedDaily | null = $state(null);

	// ─── Scroll-to-Day ──────────────────────────────────────────────────────────

	function scrollChartsToDay(day: Date): void {
		if (!fetchedHourly || chartInstances.length === 0) return;

		const dayStart = new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();
		const dayEnd = dayStart + MS_PER_DAY;

		const timestamps = fetchedHourly.timestamps;
		const rangeStart = timestamps[0];
		const rangeEnd = timestamps[timestamps.length - 1];
		const totalRange = rangeEnd - rangeStart;

		if (totalRange <= 0) return;

		const startPct = Math.max(0, ((dayStart - rangeStart) / totalRange) * 100);
		const endPct = Math.min(100, ((dayEnd - rangeStart) / totalRange) * 100);

		for (const chart of chartInstances) {
			if (chart && !chart.isDisposed()) {
				chart.dispatchAction({
					type: 'dataZoom',
					start: startPct,
					end: endPct
				});
			}
		}
	}

	function scrollTableToDay(day: Date): void {
		if (!tableScrollDiv) return;
		const cells = tableScrollDiv.querySelectorAll<HTMLElement>('td.hour-cell[data-date]');
		for (const cell of cells) {
			if (Number(cell.dataset['date']) === day.getDate()) {
				tableScrollDiv.scrollTo({ left: cell.offsetLeft - 120, behavior: 'smooth' });
				break;
			}
		}
	}

	const switchDay = (date: Date, index: number) => {
		selectedDay.setTime(date.getTime());
		selectedDayIndex = index;
		scrollChartsToDay(date);
		requestAnimationFrame(() => scrollTableToDay(date));
	};

	function resetZoom(): void {
		for (const chart of chartInstances) {
			if (chart && !chart.isDisposed()) {
				chart.dispatchAction({
					type: 'dataZoom',
					start: 0,
					end: 100
				});
			}
		}
	}

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

	onMount(() => {
		mounted = true;

		document.onkeydown = (e) => {
			if (!fetchedDaily) return;
			const days = fetchedDaily.dailyDates;
			if (e.key === 'ArrowLeft') {
				const newIndex = selectedDayIndex - 1;
				if (newIndex >= 0 && newIndex < days.length) {
					switchDay(days[newIndex], newIndex);
				}
			}
			if (e.key === 'ArrowRight') {
				const newIndex = selectedDayIndex + 1;
				if (newIndex >= 0 && newIndex < days.length) {
					switchDay(days[newIndex], newIndex);
				}
			}
			if (e.key === 'Escape') {
				resetZoom();
			}
		};
	});

	onDestroy(() => {
		document.onkeydown = null;
		chartInstances = [];
		chartOptions = [];
		chartComponents = [];
	});

	// ─── Helpers ────────────────────────────────────────────────────────────────

	function handleChartReady(chart: echarts.ECharts): void {
		chart.group = CHART_GROUP;
		chartInstances = [...chartInstances, chart];
		if (chartInstances.length === 3) {
			echarts.connect(CHART_GROUP);
			requestAnimationFrame(() => {
				scrollChartsToDay(selectedDay);
				scrollTableToDay(selectedDay);
			});
		}
	}

	let modelSelected = $derived(models.find((mo) => String(mo.value) === params.models?.[0]));

	function getWindDirectionLabel(deg: number): string {
		const dirs = [
			'N',
			'NNE',
			'NE',
			'ENE',
			'E',
			'ESE',
			'SE',
			'SSE',
			'S',
			'SSW',
			'SW',
			'WSW',
			'W',
			'WNW',
			'NW',
			'NNW'
		];
		return dirs[Math.round(deg / 22.5) % 16];
	}

	function isDaytimeHour(hour: number): boolean {
		return hour >= 6 && hour < 21;
	}

	function isCurrentHour(date: Date): boolean {
		return (
			date.getDate() === today.getDate() &&
			date.getMonth() === today.getMonth() &&
			date.getFullYear() === today.getFullYear() &&
			date.getHours() === today.getHours()
		);
	}

	function getTextColorForTemp(temp: number, unit: string): string {
		const threshold = unit === 'celsius' ? { low: -13, high: 40 } : { low: 7, high: 104 };
		return temp < threshold.low || temp >= threshold.high ? 'white' : 'black';
	}

	function getPrecipProbBg(prob: number): string {
		return `rgba(0, 0, 230, ${prob / 120})`;
	}

	function getPrecipProbColor(prob: number): string {
		return prob > 50 ? 'white' : 'inherit';
	}

	function getHumidityBg(hum: number): string {
		return `rgba(0, 240, 240, ${hum ** 3.8 / 10 ** 8.2})`;
	}

	// ─── Data Fetching ──────────────────────────────────────────────────────────

	$effect(() => {
		const loc = location;
		const modelList = params.models;

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

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

			const result: WeekForecastResult = await fetchWeekForecast({
				latitude: loc.latitude!,
				longitude: loc.longitude!,
				model: modelList[0],
				temperature_unit: params.temperature_unit as 'celsius' | 'fahrenheit',
				wind_speed_unit: params.wind_speed_unit as 'kmh' | 'ms' | 'mph' | 'kn',
				precipitation_unit: params.precipitation_unit as 'mm' | 'inch',
				forecast_days: 6,
				past_days: 1
			});

			fetchedHourly = {
				hourly: result.hourly,
				utc_offset_seconds: result.utcOffsetSeconds,
				timestamps: result.hourlyTimestamps,
				hourlyDates: result.hourlyDates,
				markAreas: result.markAreas
			};

			fetchedDaily = {
				daily: result.daily,
				dailyDates: result.dailyDates
			};

			loading = false;
		};

		loadData();
	});

	// ─── Chart Option Building ──────────────────────────────────────────────────

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

		const { hourly, utc_offset_seconds, timestamps, markAreas } = fetchedHourly;
		const colors = getThemeColors();
		const tempUnit = params.temperature_unit === 'celsius' ? '°C' : '°F';
		const precipUnit = params.precipitation_unit === 'mm' ? 'mm' : 'in';
		const windUnit = params.wind_speed_unit === 'kmh' ? 'km/h' : params.wind_speed_unit;

		const temps = hourly.temperature_2m;
		const precip = hourly.precipitation;
		const precipProb = hourly.precipitation_probability;
		const cloudCov = hourly.cloud_cover;
		const windSpeed = hourly.windspeed_10m;
		const humidity = hourly.relative_humidity_2m;

		const validTemps = temps.filter((t: number) => t !== null && !isNaN(t));
		const minTemp = Math.min(...validTemps);
		const maxTemp = Math.max(...validTemps);

		const tempData = temps.map((v: number, i: number) => [timestamps[i], v]);
		const cloudData = cloudCov.map((v: number, i: number) => [timestamps[i], v]);
		const precipData = precip.map((v: number, i: number) => [timestamps[i], v]);
		const precipProbData = precipProb.map((v: number, i: number) => [timestamps[i], v]);
		const windData = windSpeed.map((v: number, i: number) => [timestamps[i], v]);
		const humidityData = humidity.map((v: number, i: number) => [timestamps[i], v]);

		const buildAnnotations = (): Array<Record<string, unknown>> => {
			const series: Array<Record<string, unknown>> = [];
			series.push(buildCurrentTimeSeries({ utcOffsetSeconds: utc_offset_seconds }));
			const dl = buildDaylightSeries({ markAreas });
			if (dl) series.push(dl);
			return series;
		};

		const timeXAxis = (showLabel: boolean): Record<string, unknown> => ({
			type: 'time',
			splitLine: { show: false },
			axisLine: { lineStyle: { color: colors.axisLine } },
			axisLabel: { color: colors.text, hideOverlap: true, show: showLabel },
			axisTick: { lineStyle: { color: colors.axisLine } }
		});

		const dataZoomBase = (): Array<Record<string, unknown>> => [
			{
				type: 'inside',
				xAxisIndex: 0,
				filterMode: 'none',
				zoomOnMouseWheel: true,
				moveOnMouseMove: true,
				moveOnMouseWheel: false
			}
		];

		const dataZoomWithSlider = (): Array<Record<string, unknown>> => [
			...dataZoomBase(),
			{
				type: 'slider',
				xAxisIndex: 0,
				filterMode: 'none',
				height: 20,
				bottom: 4,
				borderColor: colors.axisLine,
				fillerColor: 'rgba(100, 140, 200, 0.2)',
				handleStyle: { color: colors.text },
				textStyle: { color: colors.text, fontSize: 10 },
				dataBackground: {
					lineStyle: { color: colors.axisLine },
					areaStyle: { color: colors.splitLine }
				},
				selectedDataBackground: {
					lineStyle: { color: colors.axisLine },
					areaStyle: { color: 'rgba(100, 140, 200, 0.15)' }
				}
			}
		];

		// ── Chart 1: Temperature + Cloud Cover ──────────────────────────────

		const tempOption: Record<string, unknown> = {
			title: {
				text: 'Temperature & Cloud Cover',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: {
				trigger: 'axis',
				axisPointer: {
					type: 'cross',
					animation: false,
					label: {
						backgroundColor: colors.tooltipBg,
						color: colors.text,
						borderColor: colors.tooltipBorder,
						borderWidth: 1
					}
				},
				backgroundColor: colors.tooltipBg,
				borderColor: colors.tooltipBorder,
				textStyle: { color: colors.text },
				formatter: (params: Record<string, unknown>[]) => {
					if (!params || !params.length) return '';
					const p = params[0] as Record<string, unknown>;
					const date = new Date(p.axisValue as number);
					let html = `<b>${date.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })} ${pad(date.getHours())}:${pad(date.getMinutes())}</b><br/>`;
					for (const item of params as Record<string, unknown>[]) {
						const name = item.seriesName as string;
						if (name === 'Daylight' || name === 'Current Time') continue;
						const val = (item.value as [number, number])?.[1];
						if (val === undefined || val === null) continue;
						const marker = item.marker as string;
						if (name === 'Temperature') {
							html += `${marker} ${name}: <b>${val.toFixed(1)} ${tempUnit}</b><br/>`;
						} else if (name === 'Cloud Cover') {
							html += `${marker} ${name}: <b>${val.toFixed(0)}%</b><br/>`;
						}
					}
					return html;
				}
			},
			legend: {
				show: true,
				bottom: 0,
				textStyle: { color: colors.text },
				data: ['Temperature', 'Cloud Cover']
			},
			grid: { left: 60, right: 60, top: 50, bottom: 40 },
			dataZoom: dataZoomBase(),
			xAxis: timeXAxis(false),
			yAxis: [
				{
					type: 'value',
					name: tempUnit,
					nameTextStyle: { color: colors.text },
					axisLine: { show: false },
					axisLabel: { color: colors.text },
					splitLine: { lineStyle: { color: colors.splitLine } }
				},
				{
					type: 'value',
					name: '',
					min: 0,
					max: 250,
					inverse: true,
					show: false
				}
			],
			series: [
				{
					name: 'Temperature',
					type: 'line',
					data: tempData,
					smooth: true,
					showSymbol: false,
					lineStyle: { width: 3, color: '#ef6c00' },
					itemStyle: { color: '#ef6c00' },
					areaStyle: {
						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
							{
								offset: 0,
								color: getColor(Math.round(maxTemp), String(params.temperature_unit)) + '88'
							},
							{
								offset: 0.5,
								color:
									getColor(Math.round((maxTemp + minTemp) / 2), String(params.temperature_unit)) +
									'44'
							},
							{
								offset: 1,
								color: getColor(Math.round(minTemp), String(params.temperature_unit)) + '08'
							}
						])
					},
					z: 5
				},
				{
					name: 'Cloud Cover',
					type: 'line',
					data: cloudData,
					smooth: true,
					showSymbol: false,
					yAxisIndex: 1,
					lineStyle: { width: 0 },
					itemStyle: { color: colors.text },
					areaStyle: {
						color: 'rgba(150, 150, 150, 0.25)',
						origin: 'start'
					},
					z: 1,
					silent: true
				},
				...buildAnnotations()
			],
			textStyle: { color: colors.text }
		};

		// ── Chart 2: Precipitation + Probability ────────────────────────────

		const precipOption: Record<string, unknown> = {
			title: {
				text: 'Precipitation & Probability',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: {
				trigger: 'axis',
				axisPointer: {
					type: 'cross',
					animation: false,
					label: {
						backgroundColor: colors.tooltipBg,
						color: colors.text,
						borderColor: colors.tooltipBorder,
						borderWidth: 1
					}
				},
				backgroundColor: colors.tooltipBg,
				borderColor: colors.tooltipBorder,
				textStyle: { color: colors.text },
				formatter: (params: Record<string, unknown>[]) => {
					if (!params || !params.length) return '';
					const p = params[0] as Record<string, unknown>;
					const date = new Date(p.axisValue as number);
					let html = `<b>${date.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })} ${pad(date.getHours())}:${pad(date.getMinutes())}</b><br/>`;
					for (const item of params as Record<string, unknown>[]) {
						const name = item.seriesName as string;
						if (name === 'Daylight' || name === 'Current Time') continue;
						const val = (item.value as [number, number])?.[1];
						if (val === undefined || val === null) continue;
						const marker = item.marker as string;
						if (name === 'Precipitation') {
							html += `${marker} ${name}: <b>${val.toFixed(1)} ${precipUnit}</b><br/>`;
						} else if (name === 'Precip. Probability') {
							html += `${marker} ${name}: <b>${val.toFixed(0)}%</b><br/>`;
						}
					}
					return html;
				}
			},
			legend: {
				show: true,
				bottom: 0,
				textStyle: { color: colors.text },
				data: ['Precipitation', 'Precip. Probability']
			},
			grid: { left: 60, right: 60, top: 50, bottom: 40 },
			dataZoom: dataZoomBase(),
			xAxis: timeXAxis(false),
			yAxis: [
				{
					type: 'value',
					name: precipUnit,
					min: 0,
					nameTextStyle: { color: colors.text },
					axisLine: { show: false },
					axisLabel: { color: colors.text },
					splitLine: { lineStyle: { color: colors.splitLine } }
				},
				{
					type: 'value',
					name: '%',
					min: 0,
					max: 100,
					nameTextStyle: { color: colors.text },
					axisLine: { show: false },
					axisLabel: { color: colors.text },
					splitLine: { show: false }
				}
			],
			series: [
				{
					name: 'Precipitation',
					type: 'bar',
					data: precipData,
					barMaxWidth: 8,
					itemStyle: {
						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
							{ offset: 0, color: 'rgba(30, 136, 229, 0.9)' },
							{ offset: 1, color: 'rgba(30, 136, 229, 0.4)' }
						])
					},
					yAxisIndex: 0,
					z: 5
				},
				{
					name: 'Precip. Probability',
					type: 'line',
					data: precipProbData,
					smooth: true,
					showSymbol: false,
					lineStyle: { width: 2, type: 'dashed', color: '#5c6bc0' },
					itemStyle: { color: '#5c6bc0' },
					yAxisIndex: 1,
					z: 4
				},
				...buildAnnotations()
			],
			textStyle: { color: colors.text }
		};

		// ── Chart 3: Wind & Humidity ─────────────────────────────────────────

		const windOption: Record<string, unknown> = {
			title: {
				text: 'Wind Speed & Humidity',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: {
				trigger: 'axis',
				axisPointer: {
					type: 'cross',
					animation: false,
					label: {
						backgroundColor: colors.tooltipBg,
						color: colors.text,
						borderColor: colors.tooltipBorder,
						borderWidth: 1
					}
				},
				backgroundColor: colors.tooltipBg,
				borderColor: colors.tooltipBorder,
				textStyle: { color: colors.text },
				formatter: (params: Record<string, unknown>[]) => {
					if (!params || !params.length) return '';
					const p = params[0] as Record<string, unknown>;
					const date = new Date(p.axisValue as number);
					const idx = timestamps.indexOf(p.axisValue as number);
					let html = `<b>${date.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })} ${pad(date.getHours())}:${pad(date.getMinutes())}</b><br/>`;
					for (const item of params as Record<string, unknown>[]) {
						const name = item.seriesName as string;
						if (name === 'Daylight' || name === 'Current Time') continue;
						const val = (item.value as [number, number])?.[1];
						if (val === undefined || val === null) continue;
						const marker = item.marker as string;
						if (name === 'Wind Speed') {
							const windDir = idx >= 0 ? hourly.winddirection_10m[idx] : null;
							html += `${marker} ${name}: <b>${val.toFixed(0)} ${windUnit}</b>`;
							if (windDir !== null && windDir !== undefined && !isNaN(windDir)) {
								html += ` (${getWindDirectionLabel(windDir)})`;
							}
							html += '<br/>';
						} else if (name === 'Humidity') {
							html += `${marker} ${name}: <b>${val.toFixed(0)}%</b><br/>`;
						}
					}
					return html;
				}
			},
			legend: {
				show: true,
				bottom: 28,
				textStyle: { color: colors.text },
				data: ['Wind Speed', 'Humidity']
			},
			grid: { left: 60, right: 60, top: 50, bottom: 60 },
			dataZoom: dataZoomWithSlider(),
			xAxis: timeXAxis(true),
			yAxis: [
				{
					type: 'value',
					name: windUnit,
					min: 0,
					nameTextStyle: { color: colors.text },
					axisLine: { show: false },
					axisLabel: { color: colors.text },
					splitLine: { lineStyle: { color: colors.splitLine } }
				},
				{
					type: 'value',
					name: '%',
					min: 0,
					max: 100,
					nameTextStyle: { color: colors.text },
					axisLine: { show: false },
					axisLabel: { color: colors.text },
					splitLine: { show: false }
				}
			],
			series: [
				{
					name: 'Wind Speed',
					type: 'line',
					data: windData,
					smooth: true,
					showSymbol: false,
					lineStyle: { width: 2, color: '#26a69a' },
					itemStyle: { color: '#26a69a' },
					areaStyle: {
						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
							{ offset: 0, color: 'rgba(38, 166, 154, 0.25)' },
							{ offset: 1, color: 'rgba(38, 166, 154, 0.02)' }
						])
					},
					yAxisIndex: 0,
					z: 5
				},
				{
					name: 'Humidity',
					type: 'line',
					data: humidityData,
					smooth: true,
					showSymbol: false,
					lineStyle: { width: 2, type: 'dotted', color: '#8d6e63' },
					itemStyle: { color: '#8d6e63' },
					yAxisIndex: 1,
					z: 4
				},
				...buildAnnotations()
			],
			graphic: [
				{
					type: 'text',
					right: 10,
					bottom: 30,
					style: {
						text: 'Open-Meteo.com',
						fontSize: 10,
						fill: colors.text,
						opacity: 0.4
					},
					cursor: 'pointer'
				}
			],
			textStyle: { color: colors.text }
		};

		chartOptions = [tempOption, precipOption, windOption];
	});
</script>

<svelte:head>
	<title>Weather | Open-Meteo.com</title>
	<link rel="canonical" href="https://open-meteo.com/weather" />
	<meta name="description" content="7-day weather forecast with detailed hourly data" />
</svelte:head>

<div class="">
	<div class="weather-content" style="min-height: 50vh">
		<!-- ─── Daily Summary Cards ────────────────────────────────────────── -->
		<div
			in:fade
			out:fade
			style="min-height: 256px"
			class="weather-week gap-md-2 mb-4 flex flex-col md:flex-row"
		>
			{#if fetchedDaily}
				{#each fetchedDaily.dailyDates as time, index (index)}
					{@const selected = time.getDate() === selectedDay.getDate()}
					{@const tempMax = fetchedDaily.daily.temperature_2m_max[index]}
					{@const tempMin = fetchedDaily.daily.temperature_2m_min[index]}
					{@const wCode = fetchedDaily.daily.weather_code[index]}
					{@const sunDuration = fetchedDaily.daily.sunshine_duration[index]}
					{@const precipSum = fetchedDaily.daily.precipitation_sum[index]}
					{#if tempMax != null && !isNaN(tempMax)}
						<button
							style="transition: 300ms; min-width: 13%; {selected ? 'transform: scale(1.025)' : ''}"
							class="cursor-pointer"
							onclick={() => {
								switchDay(time, index);
							}}
						>
							<div
								class="gap-md-1 flex flex-row items-center justify-center rounded-xl p-1 md:flex-col md:justify-center md:p-3 {selected
									? 'bg-accent'
									: ''}"
							>
								<div class="weather-week-date">
									<b>{time.getDate()} - {time.getMonth() + 1}</b>
								</div>

								<div
									data-text={time.toLocaleDateString('en-GB', { weekday: 'long' })}
									class="grow-text relative mx-auto inline-flex flex-col {selected
										? 'font-bold'
										: ''}"
								>
									{time.toLocaleDateString('en-GB', { weekday: 'long' })}
								</div>

								<div class="weather-week-icon pe-none py-2">
									<svg class="fill-foreground" width="60px" height="60px">
										<use
											xlink:href="/images/weather-icons/wi-day-{weatherCodes[
												wCode as keyof typeof weatherCodes
											] ?? 'clear'}.svg#Layer_1"
										></use>
									</svg>
								</div>
								<div
									class="weather-temp-max flex min-w-[65px] justify-center rounded-t p-1 text-sm"
									style="background-color: {getColor(
										tempMax,
										String(params.temperature_unit)
									)}; color: {tempMin < (params.temperature_unit === 'celsius' ? 4 : 7) ||
									tempMin >= (params.temperature_unit === 'celsius' ? 30 : 104)
										? 'white'
										: 'black'}"
								>
									{tempMax.toFixed(1)}
									{params.temperature_unit === 'celsius' ? '°C' : '°F'}
								</div>
								<div
									class="weather-temp-min flex min-w-[65px] justify-center rounded-b p-1 text-sm"
									style="background: {getColor(
										tempMin,
										String(params.temperature_unit)
									)}; color: {tempMin < (params.temperature_unit === 'celsius' ? 4 : 7) ||
									tempMin >= (params.temperature_unit === 'celsius' ? 30 : 104)
										? 'white'
										: 'black'}"
								>
									{tempMin.toFixed(1)}
									{params.temperature_unit === 'celsius' ? '°C' : '°F'}
								</div>
								<div class="mt-2 flex items-center justify-center gap-1">
									<div class="relative flex h-6 w-6 items-center justify-center">
										<div class="absolute">
											<svg class="fill-foreground" width="26px" height="26px">
												<use
													class="stroke-2"
													xlink:href="/images/weather-icons/wi-day-sunny.svg#Layer_1"
												></use>
											</svg>
										</div>
									</div>
									{Number((sunDuration ?? 0) / 3600).toFixed(0)}h
								</div>
								<div class="mt-1 flex items-center justify-center">
									<div class="relative flex h-6 w-6 items-center justify-center">
										<div class="absolute">
											<svg class="fill-foreground" width="28px" height="28px">
												<use
													class="stroke-2"
													xlink:href="/images/weather-icons/wi-raindrop.svg#Layer_1"
												></use>
											</svg>
										</div>
									</div>
									{Number(precipSum).toFixed(1)}{params.precipitation_unit === 'mm' ? 'mm' : "'"}
								</div>
							</div>
						</button>
					{/if}
				{/each}
			{/if}
		</div>

		<!-- ─── Selected Day Header ───────────────────────────────────────── -->
		<div class="mb-2 flex items-center gap-3">
			<h3 class="text-xl font-bold">
				{selectedDay.toLocaleDateString('en-GB', { weekday: 'long' })}
				<small>
					{selectedDay.getDate() === new Date(today.getTime() - 24 * 60 * 60 * 1000).getDate()
						? ' (Yesterday)'
						: ''}
					{selectedDay.getDate() === today.getDate() ? ' (Today)' : ''}
					{selectedDay.getDate() === new Date(today.getTime() + 24 * 60 * 60 * 1000).getDate()
						? ' (Tomorrow)'
						: ''}
				</small>
			</h3>
			<button type="button" class="zoom-reset-btn" title="Show all days (Esc)" onclick={resetZoom}>
				Show All
			</button>
		</div>

		<!-- ─── ECharts Meteogram ─────────────────────────────────────────── -->

		<ChartContainer {loading} chartCount={3} chartHeight={300}>
			{#each chartOptions as option, i (i)}
				<EChart
					{option}
					height={i === 2 ? '320px' : '300px'}
					onChartReady={handleChartReady}
					bind:this={chartComponents[i]}
				/>
			{/each}
		</ChartContainer>

		<!-- ─── Hourly Data Table ──────────────────────────────────────────── -->
		{#if fetchedHourly}
			{@const hourly = fetchedHourly.hourly}
			{@const dates = fetchedHourly.hourlyDates}
			<div class="mt-4">
				<h4 class="mb-2 text-lg font-semibold">Hourly Details</h4>
			</div>
			<div
				bind:this={tableScrollDiv}
				class="hourly-table-scroll -mx-5 overflow-x-auto overflow-y-hidden md:mx-0"
			>
				<table class="hourly-table">
					<caption class="sr-only">Hourly weather details for {location.name}</caption>
					<tbody>
						<!-- Time row -->
						<tr>
							<th class="row-header" scope="row">Time</th>
							{#each dates as date, i (i)}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td
									class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}"
									data-date={date.getDate()}
								>
									{pad(date.getHours())}
								</td>
							{/each}
						</tr>

						<!-- Weather icons row -->
						<tr>
							<th class="row-header" scope="row">
								<svg class="fill-foreground inline-block" width="18px" height="18px">
									<use xlink:href="/images/weather-icons/wi-day-cloudy.svg#Layer_1"></use>
								</svg>
							</th>
							{#each dates as date, i (i)}
								{@const wCode = hourly.weather_code[i]}
								{@const isNow = isCurrentHour(date)}
								{@const daytime = isDaytimeHour(date.getHours())}
								{@const isMidnight = date.getHours() === 0}
								<td class="hour-cell icon-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}">
									<svg
										class="fill-foreground {isNow ? 'scale-125' : ''}"
										width="22px"
										height="22px"
									>
										<use
											xlink:href="/images/weather-icons/wi-{daytime
												? 'day'
												: 'night'}-{weatherCodes[wCode as keyof typeof weatherCodes] ??
												'clear'}.svg#Layer_1"
										></use>
									</svg>
								</td>
							{/each}
						</tr>

						<!-- Temperature row -->
						<tr>
							<th class="row-header" scope="row">Temperature</th>
							{#each dates as date, i (i)}
								{@const temp = hourly.temperature_2m[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td
									class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}"
									style="background: {getColor(
										temp,
										String(params.temperature_unit)
									)}; color: {getTextColorForTemp(temp ?? 0, String(params.temperature_unit))}"
								>
									{temp?.toFixed(1) ?? '-'}
								</td>
							{/each}
						</tr>

						<!-- Precipitation row -->
						<tr>
							<th class="row-header" scope="row">Precipitation</th>
							{#each dates as date, i (i)}
								{@const val = hourly.precipitation[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}">
									{val?.toFixed(1) ?? '-'}
								</td>
							{/each}
						</tr>

						<!-- Precipitation Probability row -->
						<tr>
							<th class="row-header" scope="row">Precip Prob.</th>
							{#each dates as date, i (i)}
								{@const prob = hourly.precipitation_probability[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td
									class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}"
									style="background: {getPrecipProbBg(prob ?? 0)}; color: {getPrecipProbColor(
										prob ?? 0
									)}"
								>
									{prob?.toFixed(0) ?? '-'}
								</td>
							{/each}
						</tr>

						<!-- Wind Speed row -->
						<tr>
							<th class="row-header" scope="row">Wind</th>
							{#each dates as date, i (i)}
								{@const wind = hourly.windspeed_10m[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}">
									{wind?.toFixed(0) ?? '-'}
								</td>
							{/each}
						</tr>

						<!-- Relative Humidity row -->
						<tr>
							<th class="row-header" scope="row">Rel. Hum.</th>
							{#each dates as date, i (i)}
								{@const hum = hourly.relative_humidity_2m[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td
									class="hour-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}"
									style="background: {getHumidityBg(hum ?? 0)}"
								>
									{hum?.toFixed(0) ?? '-'}
								</td>
							{/each}
						</tr>

						<!-- Wind Direction row -->
						<tr>
							<th class="row-header" scope="row">Wind Dir.</th>
							{#each dates as date, i (i)}
								{@const windDir = hourly.winddirection_10m[i]}
								{@const isNow = isCurrentHour(date)}
								{@const isMidnight = date.getHours() === 0}
								<td class="hour-cell icon-cell {isNow ? 'now' : ''} {isMidnight ? 'midnight' : ''}">
									{#if windDir != null && !isNaN(windDir)}
										<div style="transform: rotate({windDir}deg); display: inline-block">
											<svg class="fill-foreground" width="22px" height="22px">
												<use xlink:href="/images/weather-icons/wi-direction-down.svg#Layer_1"></use>
											</svg>
										</div>
									{:else}
										-
									{/if}
								</td>
							{/each}
						</tr>
					</tbody>
				</table>
			</div>
		{/if}

		<!-- ─── Toolbar ───────────────────────────────────────────────────── -->

		<div class="mt-6 md:mt-10">
			<ChartToolbar charts={chartInstances} fileName="week-forecast" />
		</div>
	</div>

	<!-- ─── Sunrise / Sunset ──────────────────────────────────────────────── -->
	{#if fetchedDaily}
		{@const sunriseTs = fetchedDaily.daily.sunrise[selectedDayIndex]}
		{@const sunsetTs = fetchedDaily.daily.sunset[selectedDayIndex]}
		{#if sunriseTs && sunsetTs}
			{@const sunrise = new Date(sunriseTs * 1000)}
			{@const sunset = new Date(sunsetTs * 1000)}
			<div class="mt-6">
				<div class="flex items-center gap-1">
					<svg class="fill-foreground" width="28px" height="28px">
						<use class="stroke-2" xlink:href="/images/weather-icons/wi-sunrise.svg#Layer_1"></use>
					</svg>Sunrise: {pad(sunrise.getHours())}:{pad(sunrise.getMinutes())}
				</div>
				<div class="flex items-center gap-1">
					<svg class="fill-foreground" width="28px" height="28px">
						<use class="stroke-2" xlink:href="/images/weather-icons/wi-sunset.svg#Layer_1"></use>
					</svg>
					Sunset: {pad(sunset.getHours())}:{pad(sunset.getMinutes())}
				</div>
			</div>
		{/if}
	{/if}

	<!-- ─── Model Selector ────────────────────────────────────────────────── -->
	<div>
		<div class="mt-6 flex gap-6 md:mt-12">
			<div class="relative w-1/2">
				{#if params.models && params.models.length > 0}
					{@const modelValue = params.models[0]}
					<Select.Root
						name="model_selection"
						type="single"
						value={modelValue}
						onValueChange={(val) => {
							if (params.models && val) {
								params.models = [val];
							}
						}}
					>
						<Select.Trigger
							aria-label="Forecast model selection"
							class="h-12 cursor-pointer pt-6 [&_svg]:mb-3">{modelSelected?.label}</Select.Trigger
						>
						<Select.Content preventScroll={false} class="border-border">
							{#each models as mo (mo.value)}
								<Select.Item class="cursor-pointer" value={mo.value}>{mo.label}</Select.Item>
							{/each}
						</Select.Content>
						<Label class="absolute top-[0.35rem] left-2 z-10 px-1 text-xs text-muted-foreground"
							>Weather model</Label
						>
					</Select.Root>
				{/if}
			</div>
		</div>
	</div>
</div>

<style>
	.weather-week-icon {
		width: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		background: #0061a5;
		margin: 5px 0;
		border-radius: 5px;
	}

	/* ─── Zoom Reset Button ─────────────────────────────────────────────── */

	.zoom-reset-btn {
		display: inline-flex;
		align-items: center;
		padding: 0.25rem 0.625rem;
		font-size: 0.75rem;
		font-weight: 500;
		color: hsl(var(--muted-foreground));
		background: hsl(var(--muted) / 0.5);
		border: 1px solid hsl(var(--border));
		border-radius: var(--radius, 0.375rem);
		cursor: pointer;
		transition:
			color 150ms ease,
			background-color 150ms ease;
		white-space: nowrap;
		user-select: none;
	}

	.zoom-reset-btn:hover {
		color: hsl(var(--foreground));
		background: hsl(var(--muted));
	}

	/* ─── Hourly Data Table ─────────────────────────────────────────────── */

	.hourly-table-scroll {
		scrollbar-width: thin;
	}

	.hourly-table {
		border-collapse: collapse;
		white-space: nowrap;
		font-size: 12px;
		border: 1px solid hsl(var(--border));
	}

	.hourly-table tr {
		border-top: 1px solid hsl(var(--border));
	}

	.hourly-table tr:first-child {
		border-top: none;
	}

	.row-header {
		position: sticky;
		left: 0;
		z-index: 10;
		min-width: 90px;
		max-width: 90px;
		padding: 4px 8px;
		text-align: left;
		font-weight: 600;
		font-size: 11px;
		background: hsl(var(--background));
		border-right: 2px solid hsl(var(--border));
		white-space: nowrap;
	}

	.hour-cell {
		min-width: 36px;
		max-width: 36px;
		padding: 3px 2px;
		text-align: center;
		font-size: 12px;
		border-right: 1px solid hsl(var(--border) / 0.5);
	}

	.hour-cell.midnight {
		border-left: 2px solid hsl(var(--border));
	}

	.hour-cell.now {
		font-weight: bold;
		box-shadow: inset 0 0 0 2px hsl(var(--destructive) / 0.5);
	}

	.icon-cell {
		padding: 2px;
		vertical-align: middle;
		line-height: 0;
	}

	.icon-cell > div,
	.icon-cell > svg {
		display: inline-block;
	}
</style>
