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

	import {
		storedChartLayout,
		storedLocation,
		storedModel,
		storedUnits,
		storedVariablePrefs
	} from '$lib/stores/settings';

	import { ChartContainer } from '$lib/components/charts';

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

	import { defaultParameters } from '../../options';
	import { computeDayNightWeatherCodes } from '../../utils/weather-codes';
	import DailyCards from './DailyCards.svelte';
	import DailyStripSticky from './DailyStripSticky.svelte';
	import HourlyTable from './HourlyTable.svelte';
	import MeteogramCharts from './MeteogramCharts.svelte';
	import ModelSelector from './ModelSelector.svelte';
	import VariableSidebar from './VariableSidebar.svelte';
	import { neededHourlyApiVars } from './variables';

	import type { PageData } from './$types';
	import type { FetchedDaily, FetchedHourly } from './types';

	let { data }: { data: PageData } = $props();

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

	// units live in a persisted store; mirror them into params so a change
	// re-runs the fetch effect below (which reads params.*_unit)
	$effect(() => {
		params.temperature_unit = $storedUnits.temperature_unit;
		params.wind_speed_unit = $storedUnits.wind_speed_unit;
		params.precipitation_unit = $storedUnits.precipitation_unit;
	});

	let variableSidebarOpen = $state(false);

	// Number of meteogram panels: reserves the chart area height before data
	// arrives (no layout shift)
	let enabledChartCount = $derived($storedChartLayout.filter((p) => p.variables.length > 0).length);

	// Request only the hourly variables the table rows and meteograms actually
	// show, so unused variables are never fetched. weather_code is always
	// included: the day cards / strip derive their day- and night-period icons
	// from the hourly codes locally.
	let hourlyVars = $derived([
		...new Set([
			...neededHourlyApiVars(
				$storedVariablePrefs.table,
				$storedChartLayout.flatMap((p) => p.variables)
			),
			'weather_code'
		])
	]);

	// the URL is the source of truth: location comes from the load function,
	// which is also correct on hydrated prerendered pages. The persisted store
	// only mirrors it so the header and bare /weather/* redirects follow along.
	let location = $derived(data.location);
	$effect(() => {
		storedLocation.set(data.location);
	});

	let mounted = $state(false);
	let loading = $state(true);
	let loadError = $state<string | null>(null);
	let requestVersion = 0;

	// 7 by default; the user can extend to the model's longer range (up to 16 days)
	let forecastDays = $state(7);
	// 0 by default; the user can pull in a few recent past days
	let pastDays = $state(0);

	const selectedDay = new SvelteDate();

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

	// Charts intentionally keep their current range: they show the full week
	// unless the user narrows it via the range presets or Ctrl+scroll.
	const switchDay = (date: Date) => {
		selectedDay.setTime(date.getTime());
	};

	onMount(() => {
		// preselect the persisted model (client-only so prerendered HTML stays stable)
		params.models = [get(storedModel)];
		mounted = true;
	});

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

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

		// versioned so a slow stale response can never overwrite a newer one
		const version = ++requestVersion;
		loading = true;
		loadError = null;

		fetchWeekForecast({
			latitude: loc.latitude!,
			longitude: loc.longitude!,
			model: modelList[0],
			hourlyVariables: requestVars,
			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: forecastDays,
			past_days: pastDays,
			timezone: loc.timezone
		})
			.then((result: WeekForecastResult) => {
				if (version !== requestVersion) return;

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

				// Split the hourly codes into daylight / following-night buckets so
				// the night badge shows the actual night conditions instead of a
				// night-styled copy of the day icon.
				const parts = computeDayNightWeatherCodes(
					result.hourlyTimestamps,
					result.hourly.weather_code,
					result.daily.sunrise,
					result.daily.sunset
				);
				fetchedDaily = {
					daily: result.daily,
					timezone: result.timezone,
					dailyDates: result.dailyDates,
					dayCodes: parts.day.map((c, i) => c ?? result.daily.weather_code[i]),
					nightCodes: parts.night.map((c, i) => c ?? parts.day[i] ?? result.daily.weather_code[i])
				};

				loading = false;
			})
			.catch((err: unknown) => {
				if (version !== requestVersion) return;
				loadError = err instanceof Error ? err.message : String(err);
				loading = false;
			});
	});
</script>

<svelte:head>
	<title>Drizz.li | Weather</title>
	<link rel="canonical" href="https://drizz.li/weather/week" />
	<meta name="description" content="7-day weather forecast with detailed hourly data" />
</svelte:head>

<div class="week-page">
	<div class="weather-content" style="min-height: 50vh">
		<!-- Page hero: prominent location + weather model selection -->
		<div class="relative flex flex-wrap items-center justify-between gap-x-6 gap-y-3 md:mb-5">
			<div class="flex min-w-0 items-center gap-3">
				<img
					class="h-10 w-10 shrink-0 rounded-full shadow-sm ring-2 ring-border"
					src="/images/country-flags/{(
						location.country_code || 'united_nations'
					).toLowerCase()}.svg"
					alt={location.country ?? ''}
				/>
				<div class="min-w-0">
					<h1 class="truncate text-2xl leading-tight font-bold tracking-tight md:text-3xl">
						{location.name}
					</h1>
					<p class="truncate text-sm text-muted-foreground">
						<span class="lg:hidden"
							>{#if location.admin1}{location.admin1},
							{/if}{location.country ?? ''}<span class="mx-1 opacity-50">·</span></span
						>7-day forecast
					</p>
				</div>
			</div>

			<div class="lg:absolute lg:right-0 lg:top-0 flex w-full min-w-0 items-center gap-3 sm:w-auto">
				<ModelSelector
					selectedModel={params.models?.[0] ?? 'best_match'}
					onModelChange={(model) => {
						params.models = [model];
						storedModel.set(model);
						// a new model may not support the extended / past range
						forecastDays = 7;
						pastDays = 0;
					}}
				/>
			</div>
		</div>

		<VariableSidebar open={variableSidebarOpen} onClose={() => (variableSidebarOpen = false)} />

		{#if loadError}
			<div
				class="mb-4 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive"
			>
				Failed to load weather data: {loadError}
			</div>
		{/if}

		<!-- Desktop: the full day cards -->
		<div class="hidden md:block">
			<DailyCards
				daily={fetchedDaily}
				{selectedDay}
				units={params}
				onSelectDay={switchDay}
				canExtend={forecastDays < 15}
				onExtend={() => (forecastDays = 15)}
				canExtendPast={pastDays < 3}
				onExtendPast={() => (pastDays = 3)}
			/>
		</div>

		<!-- The sticky day strip, the hourly table AND the meteograms share this
		     wrapper, so the strip stays stuck for the entire page (it collapses
		     as it sticks on mobile; on md+ it's a slim always-compact bar under
		     the topbar, complementing the full cards above). timeline-scope
		     hoists the strip's sentinel view-timeline so the sticky strip (a
		     sibling of the sentinel) can scrub its collapse from it. -->
		<div style="timeline-scope: --daystrip-sentinel">
			{#if fetchedDaily}
				<DailyStripSticky
					daily={fetchedDaily}
					{selectedDay}
					units={params}
					onSelectDay={switchDay}
					canExtend={forecastDays < 15}
					onExtend={() => (forecastDays = 15)}
					canExtendPast={pastDays < 3}
					onExtendPast={() => (pastDays = 3)}
				/>
			{/if}

			{#if fetchedHourly && fetchedDaily}
				<HourlyTable
					data={fetchedHourly}
					daily={fetchedDaily}
					{selectedDay}
					units={params}
					locationName={location.name ?? ''}
					onCustomize={() => (variableSidebarOpen = true)}
				/>
			{:else}
				<!-- placeholder with the table's approximate height: no layout shift -->
				<div
					transition:fade={{ duration: 200 }}
					class="h-107.5 animate-pulse rounded-2xl border border-border/70 bg-card"
				></div>
			{/if}

			{#if fetchedHourly}
				<MeteogramCharts data={fetchedHourly} {selectedDay} units={params} {loading} />
			{:else}
				<!-- reserve the exact chart area height before the first fetch resolves -->
				<section class="mt-8" transition:fade={{ duration: 200 }}>
					<ChartContainer loading chartCount={enabledChartCount || 1} chartHeight={300} />
				</section>
			{/if}
		</div>
	</div>
</div>
