<script lang="ts">
	import { page } from '$app/stores';
	import { getApiBaseUrl } from '$lib/api';
	import { authenticatedFetch, getAuthenticatedUserId } from '$lib/auth';

	interface Evaluation {
		id: string;
		title: string;
		classId: string;
		className: string | null;
		subjectName: string | null;
		evaluationDate: string;
		gradesPublishedAt: string | null;
	}

	interface CompetencyLevel {
		code: string;
		name: string;
		color: string | null;
		sortOrder: number;
	}

	interface CompetencyEvaluation {
		id: string;
		competencyId: string;
		competencyCode: string;
		competencyName: string;
	}

	interface ResultCell {
		studentId: string;
		studentName: string;
		competencyEvaluationId: string;
		competencyCode: string;
		competencyName: string;
		levelCode: string | null;
		dirty: boolean;
	}

	let evaluationId: string = $derived($page.params.id ?? '');
	let evaluation: Evaluation | null = $state(null);
	let levels: CompetencyLevel[] = $state([]);
	let competencyEvaluations: CompetencyEvaluation[] = $state([]);
	let results: ResultCell[] = $state([]);
	let isLoading = $state(true);
	let error: string | null = $state(null);
	let saveStatus: string | null = $state(null);
	let saveTimer: number | null = $state(null);

	// Derived: unique students
	let students = $derived.by(() => {
		const seen = new Map<string, string>();
		for (const r of results) {
			if (!seen.has(r.studentId)) {
				seen.set(r.studentId, r.studentName);
			}
		}
		return [...seen.entries()].map(([id, name]) => ({ id, name }));
	});

	// Derived: unique competencies (in order)
	let competencies = $derived.by(() => {
		const seen = new Map<string, { code: string; name: string; ceId: string }>();
		for (const ce of competencyEvaluations) {
			if (!seen.has(ce.competencyId)) {
				seen.set(ce.competencyId, {
					code: ce.competencyCode,
					name: ce.competencyName,
					ceId: ce.id,
				});
			}
		}
		return [...seen.entries()].map(([id, v]) => ({ id, ...v }));
	});

	// Derived: result lookup by studentId-ceId for reactive template bindings
	let resultMap = $derived.by(() => {
		const map = new Map<string, ResultCell>();
		for (const r of results) {
			map.set(`${r.studentId}-${r.competencyEvaluationId}`, r);
		}
		return map;
	});

	// Keyboard shortcut map: 1=first level, 2=second, etc. (based on array index)
	let levelByShortcut = $derived.by(() => {
		const map = new Map<string, string>();
		for (let i = 0; i < levels.length; i++) {
			map.set(String(i + 1), levels[i]!.code);
		}
		return map;
	});

	$effect(() => {
		if (evaluationId) {
			loadAll();
		}
	});

	$effect(() => {
		return () => {
			if (saveTimer !== null) {
				window.clearTimeout(saveTimer);
			}
		};
	});

	async function loadAll() {
		try {
			isLoading = true;
			error = null;
			const apiUrl = getApiBaseUrl();
			await getAuthenticatedUserId();

			// Load evaluation, levels, competency evaluations, and results in parallel
			const [evalRes, levelsRes, ceRes, resultsRes] = await Promise.all([
				authenticatedFetch(`${apiUrl}/evaluations/${evaluationId}`),
				authenticatedFetch(`${apiUrl}/competency-levels`),
				authenticatedFetch(`${apiUrl}/evaluations/${evaluationId}/competencies`),
				authenticatedFetch(`${apiUrl}/evaluations/${evaluationId}/competency-results`),
			]);

			if (!evalRes.ok) throw new Error('Évaluation non trouvée');
			if (!levelsRes.ok) throw new Error('Erreur lors du chargement des niveaux');
			if (!ceRes.ok) throw new Error('Erreur lors du chargement des compétences');
			if (!resultsRes.ok) throw new Error('Erreur lors du chargement des résultats');

			evaluation = await evalRes.json();

			const levelsData = await levelsRes.json();
			levels = (levelsData['hydra:member'] ?? levelsData['member'] ?? (Array.isArray(levelsData) ? levelsData : [])) as CompetencyLevel[];

			const ceData = await ceRes.json();
			competencyEvaluations = (ceData['hydra:member'] ?? ceData['member'] ?? (Array.isArray(ceData) ? ceData : [])) as CompetencyEvaluation[];

			const resultsData = await resultsRes.json();
			const rawResults: Array<{
				id: string | null;
				competencyEvaluationId: string;
				competencyId: string;
				competencyCode: string;
				competencyName: string;
				studentId: string;
				studentName: string | null;
				levelCode: string | null;
			}> = resultsData['hydra:member'] ?? resultsData['member'] ?? (Array.isArray(resultsData) ? resultsData : []);

			results = rawResults.map((r) => ({
				studentId: r.studentId,
				studentName: r.studentName ?? '',
				competencyEvaluationId: r.competencyEvaluationId,
				competencyCode: r.competencyCode ?? '',
				competencyName: r.competencyName ?? '',
				levelCode: r.levelCode,
				dirty: false,
			}));
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur inconnue';
		} finally {
			isLoading = false;
		}
	}

	function setLevel(studentId: string, ceId: string, levelCode: string) {
		const idx = results.findIndex(
			(r) => r.studentId === studentId && r.competencyEvaluationId === ceId,
		);
		if (idx === -1) return;

		const current = results[idx]!;
		// Toggle: if already same level, clear it
		const newLevel = current.levelCode === levelCode ? null : levelCode;
		results[idx] = { ...current, levelCode: newLevel, dirty: true };
		scheduleAutoSave();
	}

	function handleCellKeydown(event: KeyboardEvent, studentId: string, ceId: string) {
		const levelCode = levelByShortcut.get(event.key);
		if (levelCode) {
			event.preventDefault();
			setLevel(studentId, ceId, levelCode);
			return;
		}

		const cellIndex = getCellIndex(studentId, ceId);

		if (event.key === 'Tab' && !event.shiftKey) {
			event.preventDefault();
			focusNextCell(cellIndex);
		} else if (event.key === 'Tab' && event.shiftKey) {
			event.preventDefault();
			focusPrevCell(cellIndex);
		} else if (event.key === 'ArrowRight') {
			event.preventDefault();
			focusNextCell(cellIndex);
		} else if (event.key === 'ArrowLeft') {
			event.preventDefault();
			focusPrevCell(cellIndex);
		} else if (event.key === 'ArrowDown') {
			event.preventDefault();
			focusCellBelow(studentId, ceId);
		} else if (event.key === 'ArrowUp') {
			event.preventDefault();
			focusCellAbove(studentId, ceId);
		}
	}

	function getCellIndex(studentId: string, ceId: string): number {
		const studentIdx = students.findIndex((s) => s.id === studentId);
		const compIdx = competencies.findIndex((c) => c.ceId === ceId);
		return studentIdx * competencies.length + compIdx;
	}

	function focusNextCell(currentIndex: number) {
		const nextIndex = currentIndex + 1;
		const totalCells = students.length * competencies.length;
		if (nextIndex < totalCells) {
			focusCellByIndex(nextIndex);
		}
	}

	function focusPrevCell(currentIndex: number) {
		const prevIndex = currentIndex - 1;
		if (prevIndex >= 0) {
			focusCellByIndex(prevIndex);
		}
	}

	function focusCellBelow(studentId: string, ceId: string) {
		const studentIdx = students.findIndex((s) => s.id === studentId);
		if (studentIdx < students.length - 1) {
			const nextStudent = students[studentIdx + 1]!;
			const el = document.getElementById(`cell-${nextStudent.id}-${ceId}`);
			el?.focus();
		}
	}

	function focusCellAbove(studentId: string, ceId: string) {
		const studentIdx = students.findIndex((s) => s.id === studentId);
		if (studentIdx > 0) {
			const prevStudent = students[studentIdx - 1]!;
			const el = document.getElementById(`cell-${prevStudent.id}-${ceId}`);
			el?.focus();
		}
	}

	function focusCellByIndex(index: number) {
		const studentIdx = Math.floor(index / competencies.length);
		const compIdx = index % competencies.length;
		const student = students[studentIdx];
		const comp = competencies[compIdx];
		if (student && comp) {
			const el = document.getElementById(`cell-${student.id}-${comp.ceId}`);
			el?.focus();
		}
	}

	function scheduleAutoSave() {
		if (saveTimer !== null) {
			window.clearTimeout(saveTimer);
		}
		saveTimer = window.setTimeout(() => {
			saveResults();
		}, 500) as unknown as number;
	}

	async function saveResults() {
		const dirtyResults = results.filter((r) => r.dirty);
		if (dirtyResults.length === 0) return;

		// Snapshot dirty IDs before async operation to avoid race condition
		const dirtyKeys = new Set(dirtyResults.map((r) => `${r.studentId}-${r.competencyEvaluationId}`));

		try {
			saveStatus = 'Sauvegarde...';
			const apiUrl = getApiBaseUrl();

			const payload = dirtyResults.map((r) => ({
				studentId: r.studentId,
				competencyEvaluationId: r.competencyEvaluationId,
				levelCode: r.levelCode,
			}));

			const response = await authenticatedFetch(
				`${apiUrl}/evaluations/${evaluationId}/competency-results`,
				{
					method: 'PUT',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({ results: payload }),
				},
			);

			if (!response.ok) {
				const data = await response.json().catch(() => null);
				throw new Error(
					(data as Record<string, string> | null)?.['hydra:description'] ??
					(data as Record<string, string> | null)?.['message'] ??
					'Erreur lors de la sauvegarde',
				);
			}

			// Only mark saved the results that were actually sent
			results = results.map((r) => {
				const key = `${r.studentId}-${r.competencyEvaluationId}`;
				return r.dirty && dirtyKeys.has(key) ? { ...r, dirty: false } : r;
			});
			saveStatus = 'Sauvegardé';
			window.setTimeout(() => {
				if (saveStatus === 'Sauvegardé') saveStatus = null;
			}, 2000);
		} catch (e) {
			saveStatus = null;
			error = e instanceof Error ? e.message : 'Erreur inconnue';
		}
	}

	function getLevelName(levelCode: string | null): string {
		if (!levelCode) return '';
		const level = levels.find((l) => l.code === levelCode);
		return level?.name ?? levelCode;
	}

	function formatDate(dateStr: string | null): string {
		if (!dateStr) return '-';
		try {
			return new Intl.DateTimeFormat('fr-FR', {
				day: 'numeric',
				month: 'long',
				year: 'numeric',
			}).format(new Date(dateStr));
		} catch {
			return dateStr;
		}
	}
</script>

<svelte:head>
	<title>{evaluation?.title ?? 'Compétences'} - Évaluation par compétences</title>
</svelte:head>

<div class="competency-page">
	<header class="page-header">
		<div class="header-left">
			<a href="/dashboard/teacher/evaluations" class="back-link">&larr; Retour aux évaluations</a>
			{#if evaluation}
				<h1>{evaluation.title}</h1>
				<div class="header-meta">
					{#if evaluation.className}
						<span class="meta-tag">{evaluation.className}</span>
					{/if}
					{#if evaluation.subjectName}
						<span class="meta-tag">{evaluation.subjectName}</span>
					{/if}
					<span class="meta-tag">{formatDate(evaluation.evaluationDate)}</span>
				</div>
			{/if}
		</div>
		{#if saveStatus}
			<div class="save-indicator" class:saving={saveStatus === 'Sauvegarde...'}>
				{saveStatus}
			</div>
		{/if}
	</header>

	{#if error}
		<div class="error-banner" role="alert">
			<p>{error}</p>
			<button onclick={() => (error = null)}>Fermer</button>
		</div>
	{/if}

	{#if isLoading}
		<div class="loading-state" aria-live="polite" role="status">
			<div class="spinner"></div>
			<p>Chargement de la grille de compétences...</p>
		</div>
	{:else if competencies.length === 0}
		<div class="empty-state">
			<h2>Aucune compétence associée</h2>
			<p>Cette évaluation n'a pas encore de compétences associées.</p>
		</div>
	{:else}
		<!-- Level legend -->
		<div class="level-legend">
			<span class="legend-label">Niveaux :</span>
			{#each levels as level, i (level.code)}
				<span class="legend-item" style="--level-color: {level.color}">
					<span class="legend-key">{i + 1}</span>
					<span class="legend-dot" style="background-color: {level.color}"></span>
					{level.name}
				</span>
			{/each}
		</div>

		<!-- Competency grid -->
		<div class="grid-container" role="grid" aria-label="Grille de compétences">
			<table class="competency-grid">
				<thead>
					<tr>
						<th class="student-col" scope="col">Élève</th>
						{#each competencies as comp (comp.ceId)}
							<th class="competency-col" scope="col" title={comp.name}>
								<span class="comp-code">{comp.code}</span>
								<span class="comp-name">{comp.name}</span>
							</th>
						{/each}
					</tr>
				</thead>
				<tbody>
					{#each students as student (student.id)}
						<tr>
							<td class="student-name">{student.name}</td>
							{#each competencies as comp (comp.ceId)}
								{@const cellKey = `${student.id}-${comp.ceId}`}
								<td
									class="level-cell"
									class:dirty={resultMap.get(cellKey)?.dirty}
									id="cell-{student.id}-{comp.ceId}"
									tabindex="0"
									role="gridcell"
									aria-label="{student.name} - {comp.name}: {getLevelName(resultMap.get(cellKey)?.levelCode ?? null)}"
									onkeydown={(e) => handleCellKeydown(e, student.id, comp.ceId)}
								>
									<div class="level-buttons">
										{#each levels as level, levelIdx (level.code)}
											<button
												class="level-btn"
												class:active={resultMap.get(cellKey)?.levelCode === level.code}
												style="--btn-color: {level.color}"
												title="{level.name} (Raccourci: {levelIdx + 1})"
												onclick={() => setLevel(student.id, comp.ceId, level.code)}
											>
												{levelIdx + 1}
											</button>
										{/each}
									</div>
								</td>
							{/each}
						</tr>
					{/each}
				</tbody>
			</table>
		</div>
	{/if}
</div>

<style>
	.competency-page {
		padding: 1.5rem;
		max-width: 100%;
		overflow-x: auto;
	}

	.page-header {
		display: flex;
		justify-content: space-between;
		align-items: flex-start;
		margin-bottom: 1.5rem;
		gap: 1rem;
	}

	.back-link {
		color: var(--color-primary, #3b82f6);
		text-decoration: none;
		font-size: 0.875rem;
		margin-bottom: 0.5rem;
		display: inline-block;
	}

	.back-link:hover {
		text-decoration: underline;
	}

	.page-header h1 {
		margin: 0;
		font-size: 1.5rem;
		font-weight: 600;
	}

	.header-meta {
		display: flex;
		gap: 0.5rem;
		margin-top: 0.5rem;
	}

	.meta-tag {
		background: var(--color-surface-alt, #f1f5f9);
		padding: 0.25rem 0.75rem;
		border-radius: 1rem;
		font-size: 0.8rem;
		color: var(--color-text-secondary, #64748b);
	}

	.save-indicator {
		padding: 0.5rem 1rem;
		border-radius: 0.5rem;
		background: var(--color-success-bg, #dcfce7);
		color: var(--color-success, #16a34a);
		font-size: 0.875rem;
		font-weight: 500;
		white-space: nowrap;
	}

	.save-indicator.saving {
		background: var(--color-warning-bg, #fef9c3);
		color: var(--color-warning, #ca8a04);
	}

	.error-banner {
		background: var(--color-error-bg, #fee2e2);
		color: var(--color-error, #dc2626);
		padding: 0.75rem 1rem;
		border-radius: 0.5rem;
		margin-bottom: 1rem;
		display: flex;
		justify-content: space-between;
		align-items: center;
	}

	.error-banner p {
		margin: 0;
	}

	.error-banner button {
		background: none;
		border: none;
		color: var(--color-error, #dc2626);
		cursor: pointer;
		font-weight: 600;
	}

	.loading-state,
	.empty-state {
		text-align: center;
		padding: 3rem;
		color: var(--color-text-secondary, #64748b);
	}

	.spinner {
		width: 2rem;
		height: 2rem;
		border: 3px solid var(--color-border, #e2e8f0);
		border-top-color: var(--color-primary, #3b82f6);
		border-radius: 50%;
		animation: spin 0.8s linear infinite;
		margin: 0 auto 1rem;
	}

	@keyframes spin {
		to {
			transform: rotate(360deg);
		}
	}

	.level-legend {
		display: flex;
		align-items: center;
		gap: 1rem;
		padding: 0.75rem 1rem;
		background: var(--color-surface-alt, #f8fafc);
		border-radius: 0.5rem;
		margin-bottom: 1rem;
		flex-wrap: wrap;
		font-size: 0.875rem;
	}

	.legend-label {
		font-weight: 600;
		color: var(--color-text, #1e293b);
	}

	.legend-item {
		display: flex;
		align-items: center;
		gap: 0.375rem;
		color: var(--color-text-secondary, #64748b);
	}

	.legend-key {
		background: var(--color-surface, #fff);
		border: 1px solid var(--color-border, #e2e8f0);
		border-radius: 0.25rem;
		width: 1.25rem;
		height: 1.25rem;
		display: flex;
		align-items: center;
		justify-content: center;
		font-size: 0.75rem;
		font-weight: 600;
	}

	.legend-dot {
		width: 0.75rem;
		height: 0.75rem;
		border-radius: 50%;
		flex-shrink: 0;
	}

	.grid-container {
		overflow-x: auto;
		border-radius: 0.5rem;
		border: 1px solid var(--color-border, #e2e8f0);
	}

	.competency-grid {
		width: 100%;
		border-collapse: collapse;
		font-size: 0.875rem;
	}

	.competency-grid th {
		background: var(--color-surface-alt, #f8fafc);
		padding: 0.75rem 0.5rem;
		font-weight: 600;
		text-align: center;
		border-bottom: 2px solid var(--color-border, #e2e8f0);
		white-space: nowrap;
	}

	.student-col {
		text-align: left !important;
		min-width: 180px;
		position: sticky;
		left: 0;
		z-index: 1;
	}

	.competency-col {
		min-width: 120px;
	}

	.comp-code {
		display: block;
		font-weight: 700;
		font-size: 0.8rem;
	}

	.comp-name {
		display: block;
		font-weight: 400;
		font-size: 0.7rem;
		color: var(--color-text-secondary, #64748b);
		max-width: 120px;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.competency-grid td {
		padding: 0.375rem;
		border-bottom: 1px solid var(--color-border, #e2e8f0);
	}

	.student-name {
		font-weight: 500;
		padding-left: 0.75rem !important;
		background: var(--color-surface, #fff);
		position: sticky;
		left: 0;
		z-index: 1;
	}

	.level-cell {
		text-align: center;
		outline: none;
	}

	.level-cell:focus-within {
		box-shadow: inset 0 0 0 2px var(--color-primary, #3b82f6);
		border-radius: 0.25rem;
	}

	.level-cell.dirty {
		background: var(--color-warning-bg, #fefce8);
	}

	.level-buttons {
		display: flex;
		gap: 0.25rem;
		justify-content: center;
	}

	.level-btn {
		width: 1.75rem;
		height: 1.75rem;
		border-radius: 0.375rem;
		border: 2px solid var(--color-border, #e2e8f0);
		background: var(--color-surface, #fff);
		cursor: pointer;
		font-size: 0.75rem;
		font-weight: 600;
		color: var(--color-text-secondary, #94a3b8);
		transition: all 0.15s ease;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.level-btn:hover {
		border-color: var(--btn-color, #3b82f6);
		color: var(--btn-color, #3b82f6);
	}

	.level-btn.active {
		background: var(--btn-color, #3b82f6);
		border-color: var(--btn-color, #3b82f6);
		color: #fff;
	}
</style>
