<script lang="ts">
	import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';

	import { getTempStyle } from '../../utils/colors';
	import weatherCodes from '../../utils/weather-codes';
	import {
		type FetchedDaily,
		type FetchedHourly,
		type WeatherUnits,
		getPrecipUnit,
		getTempUnit,
		getWindArrowRotation,
		getWindUnit
	} from './types';

	interface Props {
		data: FetchedHourly;
		daily: FetchedDaily;
		selectedDay: Date;
		units: WeatherUnits;
		locationName: string;
	}

	let { data, daily, selectedDay, units, locationName }: Props = $props();

	let hourlyInterval = $state<1 | 3>(3);

	const today = new Date();
	const tempUnit = $derived(getTempUnit(units));
	const windUnit = $derived(getWindUnit(units));
	const precipUnit = $derived(getPrecipUnit(units));
	let sunTimes = $derived(getSunTimes());

	const PRECIP_MAX_MM = 10;
	const PRECIP_MAX_INCH = 0.4;

	let precipAbsMax = $derived(units.precipitation_unit === 'mm' ? PRECIP_MAX_MM : PRECIP_MAX_INCH);

	function getSelectedDayDailyIndex(): number {
		return findDailyIndex(selectedDay);
	}

	function getSunTimes(): { sunrise: Date; sunset: Date } | null {
		const di = getSelectedDayDailyIndex();
		if (di < 0) return null;
		const rise = daily.daily.sunrise[di];
		const set = daily.daily.sunset[di];
		if (!rise || !set) return null;
		return {
			sunrise: new Date(rise * 1000),
			sunset: new Date(set * 1000)
		};
	}

	function timeToFraction(date: Date): number {
		const tz = data.timezone;
		const hour = getZonedHour(date, tz);
		const minutes = parseInt(formatZoned(date, tz, 'mm'), 10);
		const totalMinutes = hour * 60 + minutes;

		const firstHour = getZonedHour(cellData[0].date, tz);
		const firstMin = firstHour * 60;

		const step = is3h ? 3 : 1;
		const lastHour = getZonedHour(cellData[cellData.length - 1].date, tz);
		const lastMin = lastHour * 60 + step * 60;

		const range = lastMin - firstMin;
		if (range <= 0) return 0;
		return Math.max(0, Math.min(1, (totalMinutes - firstMin) / range));
	}

	function formatTime(date: Date): string {
		return formatZoned(date, data.timezone, 'HH:mm');
	}

	let timezoneLabel = $derived(formatUtcOffset(data.utc_offset_seconds));

	function findDailyIndex(date: Date): number {
		return daily.dailyDates.findIndex((dd) => isSameDayInZone(dd, date, data.timezone));
	}

	function isDaytime(hourDate: Date): boolean {
		const di = findDailyIndex(hourDate);
		if (di < 0) return true;
		const sunrise = daily.daily.sunrise[di];
		const sunset = daily.daily.sunset[di];
		if (!sunrise || !sunset) return true;
		const ts = Math.floor(hourDate.getTime() / 1000);
		return ts >= sunrise && ts < sunset;
	}

	function getDayIndices(dates: Date[], day: Date): number[] {
		const tz = data.timezone;
		return dates.reduce<number[]>((acc, d, i) => {
			if (isSameDayInZone(d, day, tz) && (hourlyInterval === 1 || getZonedHour(d, tz) % 3 === 0)) {
				acc.push(i);
			}
			return acc;
		}, []);
	}

	function getPrecipBarHeight(val: number): number {
		if (!val || val <= 0) return 0;
		return Math.min(100, (val / precipAbsMax) * 100);
	}

	function getPrecipProbBg(prob: number): string {
		if (!prob || prob <= 0) return 'transparent';
		return `rgba(30, 100, 220, ${(Math.round(prob / 10) / 100) * 10 * 0.45})`;
	}

	function getCloudOpacity(cover: number): number {
		return Math.min(0.55, (cover ?? 0) / 150);
	}

	function getHumidityBg(hum: number): string {
		return `rgba(0, 180, 200, ${hum ** 3.5 / 10 ** 7.8})`;
	}

	function formatPrecipTooltip(precip: number | null, prob: number | null): string {
		const parts: string[] = [];
		if (prob != null && prob > 0) parts.push(`Probability: ${prob}%`);
		if (precip != null && precip > 0) parts.push(`Amount: ${precip.toFixed(1)} ${precipUnit}`);
		return parts.join('\n');
	}

	function formatTemp(temp: number | null): string {
		return temp != null ? `${temp.toFixed(0)}°` : '-';
	}

	function formatValue(val: number | null): string {
		return val != null ? val.toFixed(0) : '-';
	}

	let dayIdx = $derived(getDayIndices(data.hourlyDates, selectedDay));
	let is3h = $derived(hourlyInterval === 3);
	let daytimeFlags = $derived(dayIdx.map((idx) => isDaytime(data.hourlyDates[idx])));

	let cellData = $derived(
		dayIdx.map((idx, i) => {
			const date = data.hourlyDates[idx];
			const tz = data.timezone;
			const isNow =
				formatZoned(date, tz, 'yyyy-MM-dd HH') === formatZoned(today, tz, 'yyyy-MM-dd HH');

			return {
				idx,
				date,
				isNow,
				isDaytime: daytimeFlags[i]
			};
		})
	);

	let sunrisePercent = $derived(
		sunTimes && cellData.length > 0 ? timeToFraction(sunTimes.sunrise) * 100 : null
	);
	let sunsetPercent = $derived(
		sunTimes && cellData.length > 0 ? timeToFraction(sunTimes.sunset) * 100 : null
	);

	function getWeatherIconName(code: number, daytime: boolean): string {
		const prefix = daytime ? 'day' : 'night';
		const name = weatherCodes[code as keyof typeof weatherCodes] ?? 'clear';
		return `wi-${prefix}-${name}`;
	}
</script>

{#snippet weatherIcon(name: string, size: number = 16)}
	<svg class="inline-block fill-foreground" width={size} height={size}>
		<use xlink:href="/images/weather-icons/{name}.svg#Layer_1"></use>
	</svg>
{/snippet}

{#snippet rowHeader(iconName?: string, unit?: string, label?: string)}
	<th class="hdr" scope="row">
		<div class="flex flex-col items-center leading-tight">
			{#if iconName}
				{@render weatherIcon(iconName)}
			{/if}
			{#if label}
				<span class="text-[11px] font-semibold text-muted-foreground">{label}</span>
			{/if}
			{#if unit}
				<span class="text-[10px] font-semibold text-muted-foreground">{unit}</span>
			{/if}
		</div>
	</th>
{/snippet}

<!-- Header -->
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
	<h3 class="text-lg font-bold">
		{formatZoned(selectedDay, data.timezone, 'EEEE')} – Hourly
		<span class="ml-1 text-xs font-normal text-muted-foreground">({timezoneLabel})</span>
	</h3>
	<div class="flex items-center gap-1.5 text-[13px] font-semibold">
		<span class="select-none text-muted-foreground">3h</span>
		<button
			class="relative h-6 w-11 cursor-pointer rounded-full border transition-colors
				{hourlyInterval === 1 ? 'border-primary/40 bg-primary' : 'border-border bg-muted'}"
			onclick={() => (hourlyInterval = hourlyInterval === 1 ? 3 : 1)}
			title="Toggle between 1-hour and 3-hour intervals"
		>
			<span
				class="absolute top-[3px] size-[18px] rounded-full bg-white shadow-sm transition-[left] duration-200
					{hourlyInterval === 1 ? 'left-[22px]' : 'left-[3px]'}"
			></span>
		</button>
		<span class="select-none text-muted-foreground">1h</span>
	</div>
</div>

{#if cellData.length > 0}
	{@const hourly = data.hourly}
	{@const iconPx = is3h ? 40 : 26}
	<div class="overflow-hidden rounded-lg border border-border">
		<table class="w-full table-fixed border-collapse whitespace-nowrap">
			<caption class="sr-only">Hourly weather details for {locationName}</caption>
			<colgroup>
				<col class="w-14 md:w-16" />
				{#each cellData as _ (_.idx)}
					<col />
				{/each}
			</colgroup>
			<tbody>
				<!-- Time + Daylight bar (merged) -->
				<tr class="!border-t-0">
					<th class="hdr" scope="row">
						<span class="text-[10px] font-semibold text-muted-foreground">{timezoneLabel}</span>
					</th>
					<td colspan={cellData.length} class="relative h-10 overflow-visible p-0">
						<!-- Daylight background -->
						{#if sunTimes && sunrisePercent != null && sunsetPercent != null}
							<div
								class="absolute inset-y-0 left-0 bg-indigo-950/10 dark:bg-indigo-950/30"
								style="width:{sunrisePercent}%"
							></div>
							<div
								class="absolute inset-y-0 bg-amber-400/15 dark:bg-amber-400/10"
								style="left:{sunrisePercent}%;width:{sunsetPercent - sunrisePercent}%"
							></div>
							<div
								class="absolute inset-y-0 right-0 bg-indigo-950/10 dark:bg-indigo-950/30"
								style="width:{100 - sunsetPercent}%"
							></div>
							<!-- Sunrise marker + label -->
							<div class="absolute inset-y-0 w-px bg-amber-500/70" style="left:{sunrisePercent}%">
								<span
									class="absolute bottom-0.5 left-1 whitespace-nowrap text-[10px] font-semibold leading-none text-amber-700 dark:text-amber-300"
								>
									<svg
										class="fill-foreground inline-block"
										width="12px"
										height="12px"
										aria-hidden="true"
									>
										<use class="stroke-2" xlink:href="/images/weather-icons/wi-sunrise.svg#Layer_1"
										></use>
									</svg>
									<span class="align-middle">{formatTime(sunTimes.sunrise)}</span>
								</span>
							</div>
							<!-- Sunset marker + label -->
							<div class="absolute inset-y-0 w-px bg-indigo-400/70" style="left:{sunsetPercent}%">
								<span
									class="absolute bottom-0.5 right-1 whitespace-nowrap text-[10px] font-semibold leading-none text-indigo-600 dark:text-indigo-300 inline-flex items-center gap-1"
								>
									<svg
										class="fill-foreground inline-block"
										width="12px"
										height="12px"
										aria-hidden="true"
									>
										<use class="stroke-2" xlink:href="/images/weather-icons/wi-sunset.svg#Layer_1"
										></use>
									</svg>
									<span class="align-middle">{formatTime(sunTimes.sunset)}</span>
								</span>
							</div>
						{/if}
						<!-- Hour labels -->
						{#each cellData as cell, i (cell.idx)}
							{@const leftPct = (i / cellData.length) * 100}
							{@const widthPct = 100 / cellData.length}
							<span
								class="absolute top-0 flex items-start pt-1 font-bold pl-0.5 text-sm
								{cell.isNow ? 'text-destructive' : ''}"
								style="left:{leftPct}%;width:{widthPct}%"
							>
								{#if is3h}
									{formatZoned(cell.date, data.timezone, 'HH')}
								{:else}
									<span class="inline-flex items-baseline gap-1">
										<span class="text-[11px] font-semibold"
											>{formatZoned(cell.date, data.timezone, 'HH')}</span
										>
										<sup
											class="text-[9px] font-semibold text-muted-foreground leading-none align-baseline"
											>00</sup
										>
									</span>
								{/if}
							</span>
						{/each}
					</td>
				</tr>

				<!-- Weather Icons -->
				<tr>
					{@render rowHeader('wi-day-cloudy')}
					{#each cellData as cell, i (cell.idx)}
						{@const wCode = hourly.weather_code[cell.idx]}
						<td
							class="cell leading-[0] {is3h ? 'px-1 py-2.5' : 'px-0.5 py-1.5'}"
							class:now={cell.isNow}
							class:icon-day={cell.isDaytime}
							class:icon-night={!cell.isDaytime}
							class:icon-dawn={!cell.isDaytime && cellData[i + 1]?.isDaytime}
							class:icon-dusk={cell.isDaytime && cellData[i + 1] && !cellData[i + 1].isDaytime}
						>
							{@render weatherIcon(getWeatherIconName(wCode, cell.isDaytime), iconPx)}
						</td>
					{/each}
				</tr>

				<!-- Temperature -->
				<tr>
					{@render rowHeader('wi-thermometer', tempUnit)}
					{#each cellData as cell (cell.idx)}
						{@const temp = hourly.temperature_2m[cell.idx]}
						{@const style = getTempStyle(temp, String(units.temperature_unit))}
						<td
							class="cell font-bold {is3h ? 'py-2.5 text-lg' : 'py-2 text-[15px]'}"
							class:now={cell.isNow}
							style="background-color:{style.bg};color:{style.fg}"
						>
							{formatTemp(temp)}
						</td>
					{/each}
				</tr>

				<!-- Feels Like -->
				<tr>
					{@render rowHeader(undefined, tempUnit, 'Feels')}
					{#each cellData as cell (cell.idx)}
						{@const temp = hourly.apparent_temperature[cell.idx]}
						<td
							class="cell text-muted-foreground {is3h ? 'py-1 text-[13px]' : 'py-0.5 text-[11px]'}"
							class:now={cell.isNow}
						>
							{formatTemp(temp)}
						</td>
					{/each}
				</tr>

				<!-- Wind -->
				<tr>
					{@render rowHeader('wi-strong-wind', windUnit)}
					{#each cellData as cell (cell.idx)}
						{@const wind = hourly.windspeed_10m[cell.idx]}
						{@const windDir = hourly.winddirection_10m[cell.idx]}
						<td class="cell text-center align-middle leading-tight" class:now={cell.isNow}>
							{#if windDir != null && !isNaN(windDir)}
								<span
									class="inline-block leading-[0]"
									style="transform:{getWindArrowRotation(windDir)}"
								>
									{@render weatherIcon('wi-direction-down', 24)}
								</span>
							{/if}
							<span class="block font-semibold {is3h ? 'mt-0.5 text-sm' : 'text-xs'}">
								{formatValue(wind)}
							</span>
						</td>
					{/each}
				</tr>

				<!-- Humidity -->
				<tr>
					{@render rowHeader('wi-humidity', '%')}
					{#each cellData as cell (cell.idx)}
						{@const hum = hourly.relative_humidity_2m[cell.idx]}
						<td class="cell" class:now={cell.isNow} style="background:{getHumidityBg(hum ?? 0)}">
							{formatValue(hum)}
						</td>
					{/each}
				</tr>

				<!-- Cloud Cover -->
				<tr>
					{@render rowHeader('wi-cloud', '%')}
					{#each cellData as cell (cell.idx)}
						{@const cloud = hourly.cloud_cover[cell.idx]}
						<td
							class="cell"
							class:now={cell.isNow}
							style="background:rgba(140,160,180,{getCloudOpacity(cloud ?? 0)})"
						>
							{formatValue(cloud)}
						</td>
					{/each}
				</tr>

				<!-- Precipitation -->
				<tr>
					{@render rowHeader('wi-raindrop', precipUnit)}
					{#each cellData as cell (cell.idx)}
						{@const precip = hourly.precipitation[cell.idx]}
						{@const prob = hourly.precipitation_probability[cell.idx]}
						<td
							class="precip-cell {is3h ? 'h-14' : 'h-11'}"
							class:now={cell.isNow}
							style="background:{getPrecipProbBg(prob ?? 0)}"
							title={formatPrecipTooltip(precip, prob)}
						>
							{#if precip > 0}
								<div class="precip-bar" style="height:{getPrecipBarHeight(precip)}%"></div>
								<span class="precip-label {is3h ? 'text-[13px]' : 'text-[10px]'}">
									{precip.toFixed(1)}
								</span>
							{/if}
						</td>
					{/each}
				</tr>
			</tbody>
		</table>
	</div>
{/if}

<style>
	tr {
		border-top: 1px solid hsl(var(--border));
	}

	/* ── Base cell ──────────────────────────────────────────── */
	.cell {
		padding: 6px 2px;
		text-align: center;
		font-size: 13px;
		font-weight: 500;
		border-right: 1px solid hsl(var(--border) / 0.2);
		overflow: hidden;
	}

	.cell:last-child {
		border-right: none;
	}

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

	/* ── Row header ─────────────────────────────────────────── */
	.hdr {
		padding: 4px;
		text-align: center;
		font-weight: 600;
		font-size: 11px;
		background: hsl(var(--background));
		border-right: 2px solid hsl(var(--border));
		white-space: nowrap;
		overflow: hidden;
	}

	/* ── Precipitation ──────────────────────────────────────── */
	.precip-cell {
		position: relative;
		padding: 0;
		text-align: center;
		overflow: hidden;
		border-right: 1px solid hsl(var(--border) / 0.2);
	}

	.precip-cell:last-child {
		border-right: none;
	}

	.precip-cell.now {
		box-shadow: inset 0 0 0 2px hsl(var(--destructive) / 0.45);
	}

	.precip-bar {
		position: absolute;
		bottom: 0;
		left: 15%;
		right: 15%;
		min-height: 3px;
		border-radius: 2px 2px 0 0;
		background: linear-gradient(to top, rgba(30, 120, 220, 0.5), rgba(30, 120, 220, 0.9));
		pointer-events: none;
	}

	.precip-label {
		position: relative;
		z-index: 1;
		display: flex;
		align-items: center;
		justify-content: center;
		height: 100%;
		font-weight: 700;
		color: rgba(20, 60, 160, 0.9);
		pointer-events: none;
	}

	:global(.dark) .precip-label {
		color: rgba(120, 180, 255, 0.95);
	}

	/* ── Responsive ─────────────────────────────────────────── */
	@media (max-width: 768px) {
		.hdr {
			padding: 3px 2px;
			font-size: 10px;
		}
		.cell {
			font-size: 11px;
			padding: 4px 1px;
		}
	}
</style>
