<script lang="ts">
	import { fade } from 'svelte/transition';

	import * as echarts from 'echarts';

	import { formatZoned, getRelativeDayLabel } from '$lib/utils/date';
	import { buildCurrentTimeSeries, buildDaylightSeries, getThemeColors } from '$lib/utils/echarts';

	import { ChartContainer, ChartToolbar, EChart } from '$lib/components/charts';
	import '$lib/components/charts/echarts.css';

	import { getColor } from '../../utils/colors';
	import {
		type FetchedHourly,
		type WeatherUnits,
		getPrecipUnit,
		getTempUnit,
		getWindDirectionLabel,
		getWindUnit
	} from './types';

	interface Props {
		data: FetchedHourly;
		selectedDay: Date;
		units: WeatherUnits;
		loading: boolean;
		onResetZoom?: () => void;
	}

	let { data, selectedDay, units, loading, onResetZoom }: Props = $props();

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

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

	export function scrollToDay(day: Date): void {
		if (chartInstances.length === 0 || !data) return;

		const tz = data.timezone;
		const targetDayStr = formatZoned(day, tz, 'yyyy-MM-dd');
		const firstHourIdx = data.hourlyDates.findIndex(
			(d) => formatZoned(d, tz, 'yyyy-MM-dd') === targetDayStr
		);

		if (firstHourIdx === -1) return;

		const dayStart = data.timestamps[firstHourIdx];
		const dayEnd = dayStart + MS_PER_DAY;
		const timestamps = data.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 resetZoom(): void {
		for (const chart of chartInstances) {
			if (chart && !chart.isDisposed()) {
				chart.dispatchAction({ type: 'dataZoom', start: 0, end: 100 });
			}
		}
		onResetZoom?.();
	}

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

	$effect(() => {
		// Reset chart instances when data changes
		if (data) {
			chartInstances = [];
			chartComponents = [];
		}
	});

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

		const { hourly, timestamps, markAreas } = data;
		const colors = getThemeColors();
		const tempUnit = getTempUnit(units);
		const precipUnit = getPrecipUnit(units);
		const windUnit = getWindUnit(units);

		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 annotations = (): Array<Record<string, unknown>> => {
			const series: Array<Record<string, unknown>> = [];
			series.push(buildCurrentTimeSeries());
			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,
				formatter: (value: number) => formatZoned(new Date(value), data.timezone, 'HH:mm')
			},
			axisTick: { lineStyle: { color: colors.axisLine } }
		});

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

		const sliderZoom = (): Record<string, unknown> => ({
			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)' }
			}
		});

		const tooltipBase = (
			formatter: (
				params: Array<{
					axisValue: number;
					seriesName: string;
					marker: string;
					value: number | number[] | null;
				}>
			) => string
		): Record<string, unknown> => ({
			trigger: 'axis',
			axisPointer: {
				type: 'cross',
				animation: false,
				label: {
					backgroundColor: colors.tooltipBg,
					color: colors.text,
					borderColor: colors.tooltipBorder,
					borderWidth: 1,
					formatter: (params: { axisDimension: string; value: number }) => {
						if (params.axisDimension === 'x') {
							return formatZoned(new Date(params.value), data.timezone, 'EEE d MMM HH:mm');
						}
						return params.value.toFixed(1);
					}
				}
			},
			backgroundColor: colors.tooltipBg,
			borderColor: colors.tooltipBorder,
			textStyle: { color: colors.text },
			formatter
		});

		const formatDate = (ts: number): string => {
			const date = new Date(ts);
			const dateStr = formatZoned(date, data.timezone, 'EEE d MMM HH:mm');
			return `<b>${dateStr}</b><br/>`;
		};

		const isAnnotation = (name: string): boolean => name === 'Daylight' || name === 'Current Time';

		const tempOption: Record<string, unknown> = {
			title: {
				text: 'Temperature & Cloud Cover',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: tooltipBase((params) => {
				if (!params?.length) return '';
				let html = formatDate(params[0].axisValue as number);
				for (const item of params) {
					const name = item.seriesName as string;
					if (isAnnotation(name)) continue;
					const val = (item.value as [number, number])?.[1];
					if (val == null) continue;
					if (name === 'Temperature')
						html += `${item.marker} ${name}: <b>${val.toFixed(1)} ${tempUnit}</b><br/>`;
					else if (name === 'Cloud Cover')
						html += `${item.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: [insideZoom()],
			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', 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(maxTemp, String(units.temperature_unit)) + '88'
							},
							{
								offset: 0.5,
								color: getColor((maxTemp + minTemp) / 2, String(units.temperature_unit)) + '44'
							},
							{
								offset: 1,
								color: getColor(minTemp, String(units.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
				},
				...annotations()
			],
			textStyle: { color: colors.text }
		};

		const precipOption: Record<string, unknown> = {
			title: {
				text: 'Precipitation & Probability',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: tooltipBase((params) => {
				if (!params?.length) return '';
				let html = formatDate(params[0].axisValue as number);
				for (const item of params) {
					const name = item.seriesName as string;
					if (isAnnotation(name)) continue;
					const val = (item.value as [number, number])?.[1];
					if (val == null) continue;
					if (name === 'Precipitation')
						html += `${item.marker} ${name}: <b>${val.toFixed(1)} ${precipUnit}</b><br/>`;
					else if (name === 'Precip. Probability')
						html += `${item.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: [insideZoom()],
			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
				},
				...annotations()
			],
			textStyle: { color: colors.text }
		};

		const windOption: Record<string, unknown> = {
			title: {
				text: 'Wind Speed & Humidity',
				left: 'left',
				textStyle: { fontWeight: 'normal', fontSize: 16, color: colors.text }
			},
			tooltip: tooltipBase((params) => {
				if (!params?.length) return '';
				let html = formatDate(params[0].axisValue as number);
				for (const item of params) {
					const name = item.seriesName as string;
					if (isAnnotation(name)) continue;
					const val = (item.value as [number, number])?.[1];
					if (val == null) continue;
					if (name === 'Wind Speed') {
						const idx = timestamps.indexOf(
							(params[0] as Record<string, unknown>).axisValue as number
						);
						const windDir = idx >= 0 ? hourly.winddirection_10m[idx] : null;
						html += `${item.marker} ${name}: <b>${val.toFixed(0)} ${windUnit}</b>`;
						if (windDir != null && !isNaN(windDir)) html += ` (${getWindDirectionLabel(windDir)})`;
						html += '<br/>';
					} else if (name === 'Humidity') {
						html += `${item.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: [insideZoom(), sliderZoom()],
			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
				},
				...annotations()
			],
			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>

<div class="charts-toggle-section">
	<button class="charts-toggle-btn" onclick={() => (showCharts = !showCharts)}>
		<span>Detailed Meteogram Charts</span>
		<svg
			class="toggle-chevron {showCharts ? 'open' : ''}"
			xmlns="http://www.w3.org/2000/svg"
			width="18"
			height="18"
			viewBox="0 0 24 24"
			fill="none"
			stroke="currentColor"
			stroke-width="2"
			stroke-linecap="round"
			stroke-linejoin="round"
		>
			<polyline points="6 9 12 15 18 9" />
		</svg>
	</button>
</div>

{#if showCharts}
	<div class="detailed-charts" in:fade={{ duration: 200 }}>
		<div class="charts-header">
			<h3 class="charts-title">
				{formatZoned(selectedDay, data.timezone, 'EEEE')}
				<small>
					{getRelativeDayLabel(selectedDay, data.timezone) !==
					formatZoned(selectedDay, data.timezone, 'EEEE')
						? ` (${getRelativeDayLabel(selectedDay, data.timezone)})`
						: ''}
				</small>
			</h3>
			<button type="button" class="zoom-reset-btn" title="Show all days (Esc)" onclick={resetZoom}>
				Show All
			</button>
		</div>

		<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>

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

<style>
	.charts-toggle-section {
		margin-top: 1.5rem;
		margin-bottom: 0.5rem;
	}

	.charts-toggle-btn {
		display: inline-flex;
		align-items: center;
		gap: 6px;
		padding: 0.5rem 1rem;
		font-size: 0.875rem;
		font-weight: 600;
		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: all 150ms ease;
	}

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

	.toggle-chevron {
		transition: transform 200ms;
	}

	.toggle-chevron.open {
		transform: rotate(180deg);
	}

	.detailed-charts {
		margin-top: 0.5rem;
	}

	.charts-header {
		display: flex;
		align-items: center;
		gap: 0.75rem;
		margin-bottom: 0.5rem;
	}

	.charts-title {
		font-size: 1.25rem;
		font-weight: 700;
	}

	.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));
	}
</style>
