<script lang="ts">
	import type { UptimeCheckRecord } from './types.ts';
	import { onMount } from 'svelte';
	import { Chart, LineController, LineElement, PointElement, LinearScale, TimeScale, Title, Tooltip, CategoryScale } from 'chart.js';
	import annotationPlugin from 'chartjs-plugin-annotation';

	Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Title, Tooltip, CategoryScale, annotationPlugin);

	interface Props {
		checks: UptimeCheckRecord[];
	}

	const { checks }: Props = $props();

	// Track which service charts are expanded
	let expandedCharts = $state(new Set<string>());
	// Track which tab is active for each service (derivative or uptime)
	let activeTab = $state(new Map<string, 'derivative' | 'uptime'>());

	// group checks by group name, then by region, then by service
	const groupedData = $derived(() => {
		const groups = new Map<string, Map<string, Map<string, UptimeCheckRecord[]>>>();

		for (const check of checks) {
			const groupName = check.value.groupName || 'ungrouped';
			const region = check.value.region || 'unknown';
			const serviceName = check.value.serviceName;

			if (!groups.has(groupName)) {
				groups.set(groupName, new Map<string, Map<string, UptimeCheckRecord[]>>());
			}

			const regionMap = groups.get(groupName)!;
			if (!regionMap.has(region)) {
				regionMap.set(region, new Map<string, UptimeCheckRecord[]>());
			}

			const serviceMap = regionMap.get(region)!;
			if (!serviceMap.has(serviceName)) {
				serviceMap.set(serviceName, []);
			}
			serviceMap.get(serviceName)!.push(check);
		}

		// sort checks within each service by time (newest first)
		for (const [, regionMap] of groups) {
			for (const [, serviceMap] of regionMap) {
				for (const [, serviceChecks] of serviceMap) {
					serviceChecks.sort((a, b) => b.indexedAt.getTime() - a.indexedAt.getTime());
				}
			}
		}

		return groups;
	});

	function calculateUptime(checks: UptimeCheckRecord[]): string {
		if (checks.length === 0) {
			return '0';
		}
		const upChecks = checks.filter((c) => c.value.status === 'up').length;
		return ((upChecks / checks.length) * 100).toFixed(2);
	}

	function formatResponseTime(ms: number): string {
		if (ms < 0) {
			return 'N/A';
		}
		if (ms < 1000) {
			return `${ms}ms`;
		}
		return `${(ms / 1000).toFixed(2)}s`;
	}

	function formatTimestamp(date: Date): string {
		return new Intl.DateTimeFormat('en-US', {
			dateStyle: 'short',
			timeStyle: 'short',
		}).format(date);
	}

	// Calculate derivative (change in response time between checks)
	function calculateDerivative(checks: UptimeCheckRecord[]): { labels: string[], values: number[], colors: string[], pointRadii: number[] } {
		if (checks.length < 2) {
			return { labels: [], values: [], colors: [], pointRadii: [] };
		}

		// Reverse to get oldest first for proper time ordering
		const sorted = [...checks].reverse();
		const labels: string[] = [];
		const values: number[] = [];
		const colors: string[] = [];

		for (let i = 1; i < sorted.length; i++) {
			const prev = sorted[i - 1];
			const curr = sorted[i];

			// Skip if either check is down
			if (prev.value.status !== 'up' || curr.value.status !== 'up') {
				continue;
			}

			const change = curr.value.responseTime - prev.value.responseTime;
			labels.push(formatTimestamp(curr.indexedAt));
			values.push(change);

			// Color code based on brightness: brighter = better (faster), darker = worse (slower)
			const absChange = Math.abs(change);
			const maxChange = 500; // Normalize around 500ms as reference
			const intensity = Math.min(absChange / maxChange, 1);

			if (change < 0) {
				// Faster (good) - brighter pink
				const brightness = 0.65 + (intensity * 0.25); // 0.65 to 0.9
				colors.push(`oklch(${brightness} 0.15 345)`);
			} else {
				// Slower (bad) - darker pink
				const brightness = 0.65 - (intensity * 0.35); // 0.65 to 0.3
				colors.push(`oklch(${brightness} 0.15 345)`);
			}
		}

		// Calculate standard deviation
		const mean = values.reduce((sum, v) => sum + Math.abs(v), 0) / values.length;
		const variance = values.reduce((sum, v) => sum + Math.pow(Math.abs(v) - mean, 2), 0) / values.length;
		const stdDev = Math.sqrt(variance);

		// Determine which points to show based on std deviation
		const pointRadii = values.map((value, index) => {
			const absValue = Math.abs(value);
			// Show point if it's a significant spike (> 1 std dev) OR every other point
			if (absValue > mean + stdDev) {
				return 5; // Larger dot for significant spikes
			} else if (index % 2 === 0) {
				return 3; // Show every other point at normal size
			} else {
				return 0; // Hide this point
			}
		});

		return { labels, values, colors, pointRadii };
	}

	function toggleChart(serviceKey: string) {
		const newSet = new Set(expandedCharts);
		if (newSet.has(serviceKey)) {
			newSet.delete(serviceKey);
		} else {
			newSet.add(serviceKey);
			// Default to uptime tab when opening
			if (!activeTab.has(serviceKey)) {
				const newTabMap = new Map(activeTab);
				newTabMap.set(serviceKey, 'uptime');
				activeTab = newTabMap;
			}
		}
		expandedCharts = newSet;
	}

	function setActiveTab(serviceKey: string, tab: 'derivative' | 'uptime') {
		const newTabMap = new Map(activeTab);
		newTabMap.set(serviceKey, tab);
		activeTab = newTabMap;
	}

	// Calculate uptime percentage in hourly rolling windows
	function calculateHourlyUptime(checks: UptimeCheckRecord[]): { labels: string[], values: number[], colors: string[] } {
		if (checks.length === 0) {
			return { labels: [], values: [], colors: [] };
		}

		const sorted = [...checks].reverse(); // Oldest first
		const hourlyBuckets = new Map<number, { total: number, up: number }>();

		// Group checks into hourly buckets
		for (const check of sorted) {
			const hourTimestamp = Math.floor(check.indexedAt.getTime() / (1000 * 60 * 60)); // Round to hour

			if (!hourlyBuckets.has(hourTimestamp)) {
				hourlyBuckets.set(hourTimestamp, { total: 0, up: 0 });
			}

			const bucket = hourlyBuckets.get(hourTimestamp)!;
			bucket.total++;
			if (check.value.status === 'up') {
				bucket.up++;
			}
		}

		// Convert to arrays sorted by time
		const sortedBuckets = Array.from(hourlyBuckets.entries()).sort((a, b) => a[0] - b[0]);

		const labels: string[] = [];
		const values: number[] = [];
		const colors: string[] = [];

		for (const [hourTimestamp, bucket] of sortedBuckets) {
			const date = new Date(hourTimestamp * 1000 * 60 * 60);
			const percentage = (bucket.up / bucket.total) * 100;

			labels.push(new Intl.DateTimeFormat('en-US', {
				month: 'short',
				day: 'numeric',
				hour: 'numeric'
			}).format(date));

			values.push(percentage);

			// Gradient: 100% = pink (good), <99% = purple-ish, <95% = dark blue (bad)
			if (percentage >= 99.5) {
				colors.push('oklch(0.78 0.15 345)'); // Pink - good!
			} else if (percentage >= 95) {
				colors.push('oklch(0.65 0.12 285)'); // Purple - concerning
			} else {
				colors.push('oklch(0.32 0.04 285)'); // Dark blue - bad
			}
		}

		return { labels, values, colors };
	}

	function createDerivativeChart(canvas: HTMLCanvasElement, checks: UptimeCheckRecord[]) {
		const derivative = calculateDerivative(checks);

		if (derivative.values.length === 0) {
			return;
		}

		// Get CSS variables for theme-aware colors
		const rootStyle = getComputedStyle(document.documentElement);
		const foregroundColor = rootStyle.getPropertyValue('--foreground').trim() || 'oklch(0.18 0.01 30)';
		const mutedForegroundColor = rootStyle.getPropertyValue('--muted-foreground').trim() || 'oklch(0.42 0.015 30)';
		const borderColor = rootStyle.getPropertyValue('--border').trim() || 'oklch(0.75 0.015 30)';

		// Calculate background annotations for problem areas
		const annotations: any = {};
		const threshold = 100; // 100ms change is "significant"

		derivative.values.forEach((value, index) => {
			if (value > threshold) {
				// Significant slowdown - add red background
				annotations[`problem-${index}`] = {
					type: 'box',
					xMin: index - 0.5,
					xMax: index + 0.5,
					backgroundColor: 'rgba(239, 68, 68, 0.15)', // Red with transparency
					borderWidth: 0
				};
			}
		});

		const chart = new Chart(canvas, {
			type: 'line',
			data: {
				labels: derivative.labels,
				datasets: [{
					label: 'Response Time Change (ms)',
					data: derivative.values,
					borderColor: 'oklch(0.78 0.15 345)', // Pink line
					backgroundColor: 'transparent',
					tension: 0.4,
					pointBackgroundColor: 'oklch(0.78 0.15 345)',
					pointBorderColor: 'oklch(0.78 0.15 345)',
					pointRadius: derivative.pointRadii,
					pointHoverRadius: 6,
					fill: false,
				}]
			},
			options: {
				responsive: true,
				maintainAspectRatio: false,
				plugins: {
					tooltip: {
						backgroundColor: 'oklch(0.20 0.02 285)',
						titleColor: 'oklch(0.98 0.00 285)',
						bodyColor: 'oklch(0.98 0.00 285)',
						borderColor: 'oklch(0.56 0.08 220)',
						borderWidth: 1,
						callbacks: {
							label: (context) => {
								const value = context.parsed.y;
								const direction = value > 0 ? 'slower' : 'faster';
								return `${Math.abs(value).toFixed(0)}ms ${direction}`;
							}
						}
					},
					title: {
						display: true,
						text: 'latency spikes (change in response time) • lower is better',
						color: foregroundColor,
						font: {
							size: 14,
							weight: 'normal'
						}
					},
					annotation: {
						annotations
					}
				},
				scales: {
					y: {
						ticks: {
							color: mutedForegroundColor,
							callback: (value) => `${value}ms`
						},
						grid: {
							color: borderColor
						}
					},
					x: {
						ticks: {
							color: mutedForegroundColor,
							maxRotation: 45,
							minRotation: 45,
							autoSkip: true,
							maxTicksLimit: 6,
							callback: function(value, index, ticks) {
								// Always show the last (latest) timestamp
								if (index === ticks.length - 1) {
									return this.getLabelForValue(value);
								}
								// Show evenly distributed labels
								const skipRate = Math.ceil(ticks.length / 6);
								return index % skipRate === 0 ? this.getLabelForValue(value) : '';
							}
						},
						grid: {
							color: borderColor
						}
					}
				}
			}
		});

		return {
			destroy() {
				chart.destroy();
			}
		};
	}

	function createUptimeChart(canvas: HTMLCanvasElement, checks: UptimeCheckRecord[]) {
		const uptimeData = calculateHourlyUptime(checks);

		if (uptimeData.values.length === 0) {
			return;
		}

		// Get CSS variables for theme-aware colors
		const rootStyle = getComputedStyle(document.documentElement);
		const foregroundColor = rootStyle.getPropertyValue('--foreground').trim() || 'oklch(0.18 0.01 30)';
		const mutedForegroundColor = rootStyle.getPropertyValue('--muted-foreground').trim() || 'oklch(0.42 0.015 30)';
		const borderColor = rootStyle.getPropertyValue('--border').trim() || 'oklch(0.75 0.015 30)';

		const chart = new Chart(canvas, {
			type: 'line',
			data: {
				labels: uptimeData.labels,
				datasets: [{
					label: 'Uptime %',
					data: uptimeData.values,
					borderColor: 'oklch(0.78 0.15 345)', // Pink
					backgroundColor: (context) => {
						const ctx = context.chart.ctx;
						const gradient = ctx.createLinearGradient(0, 0, 0, 250);
						gradient.addColorStop(0, 'oklch(0.78 0.15 345 / 0.4)'); // Pink at top (100%)
						gradient.addColorStop(0.5, 'oklch(0.65 0.12 285 / 0.2)'); // Purple middle (~97%)
						gradient.addColorStop(1, 'oklch(0.32 0.04 285 / 0.1)'); // Dark blue at bottom (<95%)
						return gradient;
					},
					tension: 0.4,
					pointBackgroundColor: uptimeData.colors,
					pointBorderColor: uptimeData.colors,
					pointRadius: 3,
					pointHoverRadius: 6,
					fill: true,
				}]
			},
			options: {
				responsive: true,
				maintainAspectRatio: false,
				plugins: {
					tooltip: {
						backgroundColor: 'oklch(0.20 0.02 285)',
						titleColor: 'oklch(0.98 0.00 285)',
						bodyColor: 'oklch(0.98 0.00 285)',
						borderColor: 'oklch(0.78 0.15 345)',
						borderWidth: 1,
						callbacks: {
							label: (context) => {
								const value = context.parsed.y;
								return `${value.toFixed(2)}% uptime`;
							}
						}
					},
					title: {
						display: true,
						text: 'uptime percentage (hourly)',
						color: foregroundColor,
						font: {
							size: 14,
							weight: 'normal'
						}
					}
				},
				scales: {
					y: {
						min: 0,
						max: 100,
						ticks: {
							color: mutedForegroundColor,
							callback: (value) => `${value}%`
						},
						grid: {
							color: borderColor
						}
					},
					x: {
						ticks: {
							color: mutedForegroundColor,
							maxRotation: 45,
							minRotation: 45,
							autoSkip: true,
							maxTicksLimit: 6,
							callback: function(value, index, ticks) {
								// Always show the last (latest) timestamp
								if (index === ticks.length - 1) {
									return this.getLabelForValue(value);
								}
								// Show evenly distributed labels
								const skipRate = Math.ceil(ticks.length / 6);
								return index % skipRate === 0 ? this.getLabelForValue(value) : '';
							}
						},
						grid: {
							color: borderColor
						}
					}
				}
			}
		});

		return {
			destroy() {
				chart.destroy();
			}
		};
	}
</script>

<div class="mt-8">
	<h2 class="text-2xl font-semibold mb-6">uptime statistics</h2>

	{#each [...groupedData()] as [groupName, regionMap]}
		<div class="mb-8">
			{#if groupName !== 'ungrouped'}
				<h2 class="text-3xl font-bold text-accent mb-4 pb-2 border-b-2 border-accent">{groupName}</h2>
			{/if}

			{#each [...regionMap] as [region, serviceMap]}
				<div class="mb-8">
					<h3 class="text-xl font-semibold text-foreground mb-4 pl-2 border-l-4 border-accent">{region}</h3>

					{#each [...serviceMap] as [serviceName, serviceChecks]}
						{@const serviceKey = `${groupName}-${region}-${serviceName}`}
						<div class="bg-card rounded-lg shadow-sm p-6 mb-6">
							<div class="flex justify-between items-center mb-2">
								<h4 class="text-lg font-medium">{serviceName}</h4>
								<div class="flex gap-2 items-center">
									<button
										onclick={() => toggleChart(serviceKey)}
										class="px-3 py-1 rounded-full text-sm font-medium transition-colors {expandedCharts.has(serviceKey) ? 'bg-chart-3 text-white' : 'bg-chart-3/20 text-chart-3 hover:bg-chart-3/30'}"
									>
										{expandedCharts.has(serviceKey) ? 'hide graph' : 'show graph'}
									</button>
									<div class="bg-accent text-accent-foreground px-3 py-1 rounded-full text-sm font-medium">
										{calculateUptime(serviceChecks)}% uptime
									</div>
								</div>
							</div>

							<div class="text-sm text-muted-foreground mb-4 break-all">
								{serviceChecks[0].value.serviceUrl}
							</div>

							<div class="flex gap-1 flex-wrap mb-4">
								{#each serviceChecks.slice(0, 20) as check}
									<div
										class="w-3 h-3 rounded-sm cursor-pointer transition-transform hover:scale-150 {check.value.status === 'up' ? 'bg-chart-4' : 'bg-destructive'}"
										title={`${check.value.status} - ${formatResponseTime(check.value.responseTime)} - ${formatTimestamp(check.indexedAt)}`}
									></div>
								{/each}
							</div>

							{#if expandedCharts.has(serviceKey)}
								<div class="mb-4 bg-background rounded-lg p-4 border border-border">
									<!-- Tabs -->
									<div class="flex gap-2 mb-4 border-b border-border">
										<button
											onclick={() => setActiveTab(serviceKey, 'uptime')}
											class="px-4 py-2 text-sm font-medium transition-colors border-b-2 {activeTab.get(serviceKey) === 'uptime' ? 'border-accent text-accent' : 'border-transparent text-muted-foreground hover:text-foreground'}"
										>
											uptime %
										</button>
										<button
											onclick={() => setActiveTab(serviceKey, 'derivative')}
											class="px-4 py-2 text-sm font-medium transition-colors border-b-2 {activeTab.get(serviceKey) === 'derivative' ? 'border-accent text-accent' : 'border-transparent text-muted-foreground hover:text-foreground'}"
										>
											latency spikes
										</button>
									</div>

									<!-- Chart -->
									<div style="height: 250px;">
										{#if activeTab.get(serviceKey) === 'uptime'}
											<canvas use:createUptimeChart={serviceChecks}></canvas>
										{:else}
											<canvas use:createDerivativeChart={serviceChecks}></canvas>
										{/if}
									</div>
								</div>
							{/if}

							<div class="flex flex-wrap gap-4 items-center pt-4 border-t border-border text-sm">
								<span class="px-2 py-1 rounded {serviceChecks[0].value.status === 'up' ? 'bg-chart-4/20 text-chart-4 font-semibold' : 'bg-destructive/20 text-destructive font-semibold'}">
									{serviceChecks[0].value.status}
								</span>
								{#if serviceChecks[0].value.status === 'up'}
									<span>response time: {formatResponseTime(serviceChecks[0].value.responseTime)}</span>
									{#if serviceChecks[0].value.httpStatus}
										<span>HTTP {serviceChecks[0].value.httpStatus}</span>
									{/if}
								{:else if serviceChecks[0].value.errorMessage}
									<span class="text-destructive">{serviceChecks[0].value.errorMessage}</span>
								{/if}
								<span class="text-muted-foreground ml-auto">checked {formatTimestamp(serviceChecks[0].indexedAt)}</span>
							</div>
						</div>
					{/each}
				</div>
			{/each}
		</div>
	{/each}
</div>

