<script lang="ts">
	import { page } from '$app/stores';
	import { getApiBaseUrl } from '$lib/api';
	import { authenticatedFetch, getAuthenticatedUserId } from '$lib/auth';
	import { savePendingGrades, getPendingGrades, clearPendingGrades } from '$lib/stores/gradeOfflineStore';
	import { savePendingAppreciation, getPendingAppreciations, clearPendingAppreciations } from '$lib/stores/appreciationOfflineStore';

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

	interface StudentRow {
		studentId: string;
		studentName: string;
		inputValue: string;
		value: number | null;
		status: string;
		gradeId: string | null;
		dirty: boolean;
		error: string | null;
		appreciation: string | null;
		appreciationDirty: boolean;
	}

	interface AppreciationTemplate {
		id: string;
		title: string;
		content: string;
		category: string | null;
		usageCount: number;
	}

	const MAX_APPRECIATION_LENGTH = 500;

	let evaluationId: string = $derived($page.params.id ?? '');
	let evaluation: Evaluation | null = $state(null);
	let students: StudentRow[] = $state([]);
	let isLoading = $state(true);
	let isPublishing = $state(false);
	let showPublishConfirm = $state(false);
	let error: string | null = $state(null);
	let saveStatus: string | null = $state(null);
	let saveTimer: number | null = $state(null);
	let isOnline = $state(typeof window !== 'undefined' ? window.navigator.onLine : true);
	let hasPendingOfflineGrades = $state(false);

	// Appreciation state
	let appreciationOpenIndex: number | null = $state(null);
	let appreciationSaveTimers: Map<number, number> = $state(new Map());
	let templates: AppreciationTemplate[] = $state([]);
	let showTemplateSelector: boolean = $state(false);
	let showTemplateManager: boolean = $state(false);
	let editingTemplate: AppreciationTemplate | null = $state(null);
	let newTemplateTitle = $state('');
	let newTemplateContent = $state('');
	let newTemplateCategory: string | null = $state(null);

	// Slash command hint state
	let activeSlashHint: string | null = $state(null);
	let activeRowIndex: number | null = $state(null);

	const SLASH_COMMANDS = [
		{ cmd: '/abs', desc: 'Absent (non noté)' },
		{ cmd: '/disp', desc: 'Dispensé (non comptabilisé)' },
	];

	$effect(() => {
		if (evaluationId) {
			loadAll().then(() => {
				restoreOfflineQueue();
				loadTemplates();
			});
		}
	});

	$effect(() => {
		return () => {
			for (const timerId of appreciationSaveTimers.values()) {
				window.clearTimeout(timerId);
			}
			if (saveTimer !== null) {
				window.clearTimeout(saveTimer);
			}
		};
	});

	$effect(() => {
		if (typeof window !== 'undefined') {
			const handleOnline = async () => {
				isOnline = true;
				const gradesOk = await syncOfflineGrades();
				const appreciationsOk = await syncOfflineAppreciations();
				if (gradesOk && appreciationsOk) {
					hasPendingOfflineGrades = false;
					saveStatus = 'Synchronisé';
					window.setTimeout(() => {
						if (saveStatus === 'Synchronisé') saveStatus = null;
					}, 2000);
				}
			};
			const handleOffline = () => {
				isOnline = false;
			};
			window.addEventListener('online', handleOnline);
			window.addEventListener('offline', handleOffline);
			return () => {
				window.removeEventListener('online', handleOnline);
				window.removeEventListener('offline', handleOffline);
			};
		}
	});

	async function restoreOfflineQueue() {
		if (!evaluationId) return;

		const pendingGrades = await getPendingGrades(evaluationId);
		const pendingAppreciations = await getPendingAppreciations(evaluationId);

		if (pendingGrades.length === 0 && pendingAppreciations.length === 0) return;

		hasPendingOfflineGrades = true;
		saveStatus = 'Non synchronisé';

		// Merge offline grade values into current grid
		for (const p of pendingGrades) {
			const idx = students.findIndex((s) => s.studentId === p.studentId);
			if (idx === -1) continue;
			const row = students[idx]!;
			const displayValue =
				p.status === 'absent' ? '/abs'
				: p.status === 'dispensed' ? '/disp'
				: p.value !== null ? String(p.value)
				: '';
			students[idx] = {
				...row,
				inputValue: displayValue,
				value: p.value,
				status: p.status, dirty: false, error: null,
			};
		}

		// Merge offline appreciations into current grid
		for (const p of pendingAppreciations) {
			const idx = students.findIndex((s) => s.studentId === p.studentId);
			if (idx === -1) continue;
			const row = students[idx]!;
			students[idx] = { ...row, appreciation: p.appreciation, appreciationDirty: true };
		}

		// If online, sync immediately
		if (isOnline) {
			await syncOfflineGrades();
			await syncOfflineAppreciations();
		}
	}

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

			// Load evaluation details
			const evalRes = await authenticatedFetch(`${apiUrl}/evaluations/${evaluationId}`);
			if (!evalRes.ok) throw new Error('Évaluation non trouvée');
			evaluation = await evalRes.json();

			if (!evaluation) return;

			// Load all students in the class with their existing grades (if any)
			const gradesRes = await authenticatedFetch(`${apiUrl}/evaluations/${evaluationId}/grades`);
			if (!gradesRes.ok) {
				throw new Error(`Erreur lors du chargement des notes (${gradesRes.status})`);
			}
			const gradesData = await gradesRes.json();
			const gradeRows: Array<{
				id: string | null;
				studentId: string;
				studentName: string | null;
				value: number | null;
				status: string | null;
				appreciation: string | null;
			}> = gradesData['hydra:member'] ?? gradesData['member'] ?? (Array.isArray(gradesData) ? gradesData : []);

			students = gradeRows.map((row) => {
				const val = row.value ?? null;
				const st = row.status ?? 'graded';
				const displayValue =
					st === 'absent' ? '/abs'
					: st === 'dispensed' ? '/disp'
					: val != null ? String(val)
					: '';
				return {
					studentId: row.studentId,
					studentName: row.studentName ?? '',
					inputValue: displayValue,
					value: val,
					status: st,
					gradeId: row.id ?? null,
					dirty: false,
					error: null,
					appreciation: row.appreciation ?? null,
					appreciationDirty: false,
				};
			});
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur inconnue';
		} finally {
			isLoading = false;
		}
	}

	function parseInput(input: string): { value: number | null; status: string; error: string | null } {
		const trimmed = input.trim();
		if (trimmed === '') {
			return { value: null, status: 'graded', error: null };
		}
		if (trimmed === '/abs') {
			return { value: null, status: 'absent', error: null };
		}
		if (trimmed === '/disp') {
			return { value: null, status: 'dispensed', error: null };
		}

		const num = parseFloat(trimmed);
		if (isNaN(num)) {
			return { value: null, status: 'graded', error: 'Valeur invalide' };
		}
		if (num < 0) {
			return { value: null, status: 'graded', error: 'La note ne peut pas être négative' };
		}
		if (evaluation && num > evaluation.gradeScale) {
			return { value: null, status: 'graded', error: `Maximum: ${evaluation.gradeScale}` };
		}
		return { value: num, status: 'graded', error: null };
	}

	function handleInput(index: number) {
		const row: StudentRow | undefined = students[index];
		if (!row) return;
		const input = row.inputValue;

		// Show slash command hints
		if (input.startsWith('/')) {
			activeSlashHint = input;
			activeRowIndex = index;
		} else {
			activeSlashHint = null;
			activeRowIndex = null;
		}

		const result = parseInput(input);

		if (input === '/abs' || input === '/disp') {
			students[index] = {
				...row,
				value: result.value, status: result.status, dirty: true, error: null,
			};
		} else {
			students[index] = {
				...row,
				value: result.value, status: result.status,
				dirty: result.value !== row.value || result.status !== row.status,
				error: result.error,
			};
		}

		scheduleAutoSave();
	}

	function handleKeydown(event: KeyboardEvent, index: number) {
		if (event.key === 'Enter') {
			event.preventDefault();
			activeSlashHint = null;
			focusRow(index + 1);
		} else if (event.key === 'Tab' && !event.shiftKey) {
			event.preventDefault();
			activeSlashHint = null;
			focusRow(index + 1);
		} else if (event.key === 'Tab' && event.shiftKey) {
			event.preventDefault();
			activeSlashHint = null;
			focusRow(index - 1);
		} else if (event.key === 'Escape') {
			activeSlashHint = null;
		}
	}

	function focusRow(index: number) {
		if (index < 0 || index >= students.length) return;
		const input = document.getElementById(`grade-input-${index}`);
		if (input) {
			(input as HTMLInputElement).focus();
			(input as HTMLInputElement).select();
		}
	}

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

	async function saveGrades() {
		const dirtyRows = students.filter((s) => s.dirty && s.error === null && s.inputValue.trim() !== '');
		if (dirtyRows.length === 0) return;

		if (!isOnline) {
			await savePendingGrades(
				dirtyRows.map((row) => ({
					evaluationId: evaluationId,
					studentId: row.studentId,
					value: row.value,
					status: row.status,
					savedAt: Date.now(),
				}))
			);
			hasPendingOfflineGrades = true;
			saveStatus = 'Non synchronisé';
			students = students.map((s) =>
				s.dirty ? { ...s, dirty: false } : s
			);
			return;
		}

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

			const grades = dirtyRows.map((row) => ({
				studentId: row.studentId,
				value: row.value,
				status: row.status,
			}));

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

			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)?.['detail'] ??
					(data as Record<string, string> | null)?.['message'] ??
					`Erreur lors de la sauvegarde (${response.status})`
				);
			}

			// Parse response to update gradeIds
			const savedData = await response.json().catch(() => null);
			const savedGrades: Array<{ id: string; studentId: string }> =
				savedData?.['hydra:member'] ?? savedData?.['member'] ?? (Array.isArray(savedData) ? savedData : []);

			const gradeIdByStudent = new Map<string, string>();
			for (const sg of savedGrades) {
				if (sg.studentId && sg.id) {
					gradeIdByStudent.set(sg.studentId, sg.id);
				}
			}

			// Mark saved rows as clean and update gradeIds
			students = students.map((s) =>
				dirtyRows.some((d) => d.studentId === s.studentId)
					? { ...s, dirty: false, gradeId: gradeIdByStudent.get(s.studentId) ?? s.gradeId }
					: s
			);

			// Flush buffered appreciations that were waiting for a gradeId
			for (let idx = 0; idx < students.length; idx++) {
				const s = students[idx]!;
				if (s.appreciationDirty && s.gradeId !== null && s.appreciation) {
					scheduleAppreciationAutoSave(idx);
				}
			}

			saveStatus = 'Sauvegardé';
			window.setTimeout(() => {
				if (saveStatus === 'Sauvegardé') saveStatus = null;
			}, 2000);
		} catch (e) {
			saveStatus = null;
			error = e instanceof Error ? e.message : 'Erreur de sauvegarde';
		}
	}

	async function syncOfflineGrades(): Promise<boolean> {
		const pending = await getPendingGrades(evaluationId);
		if (pending.length === 0) {
			return true;
		}

		try {
			const apiUrl = getApiBaseUrl();
			const grades = pending.map((p) => ({
				studentId: p.studentId,
				value: p.value,
				status: p.status,
			}));

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

			if (!response.ok) throw new Error('Sync échouée');

			// Update gradeIds from response so syncOfflineAppreciations can use them
			const savedData = await response.json().catch(() => null);
			const savedGrades: Array<{ id: string; studentId: string }> =
				savedData?.['hydra:member'] ?? savedData?.['member'] ?? (Array.isArray(savedData) ? savedData : []);

			for (const sg of savedGrades) {
				if (!sg.studentId || !sg.id) continue;
				const idx = students.findIndex((s) => s.studentId === sg.studentId);
				if (idx !== -1) {
					const row = students[idx]!;
					students[idx] = { ...row, gradeId: sg.id, dirty: false };
				}
			}

			await clearPendingGrades(evaluationId);
			return true;
		} catch {
			saveStatus = 'Non synchronisé';
			return false;
		}
	}

	async function syncOfflineAppreciations(): Promise<boolean> {
		const pending = await getPendingAppreciations(evaluationId);
		if (pending.length === 0) {
			return true;
		}

		const apiUrl = getApiBaseUrl();
		const remaining: typeof pending = [];
		let hadError = false;

		for (const p of pending) {
			const row = students.find((s) => s.studentId === p.studentId);
			if (!row || row.gradeId === null) {
				remaining.push(p);
				continue;
			}

			try {
				const response = await authenticatedFetch(
					`${apiUrl}/grades/${row.gradeId}/appreciation`,
					{
						method: 'PUT',
						headers: {
							'Content-Type': 'application/ld+json',
							'Accept': 'application/ld+json',
						},
						body: JSON.stringify({ appreciation: p.appreciation }),
					}
				);

				if (!response.ok) throw new Error('Sync appréciations échouée');
				// Successfully synced — do NOT add to remaining
			} catch {
				remaining.push(p);
				hadError = true;
			}
		}

		// Rewrite IndexedDB with only the items that were not synced
		await clearPendingAppreciations(evaluationId);
		for (const r of remaining) {
			await savePendingAppreciation(r);
		}

		if (hadError) {
			saveStatus = 'Non synchronisé';
		}

		return remaining.length === 0;
	}

	function confirmPublish() {
		showPublishConfirm = true;
	}

	async function handlePublish() {
		showPublishConfirm = false;
		if (!evaluation || evaluation.gradesPublishedAt) return;

		try {
			isPublishing = true;
			error = null;
			const apiUrl = getApiBaseUrl();

			const response = await authenticatedFetch(
				`${apiUrl}/evaluations/${evaluationId}/publish`,
				{
					method: 'POST',
					headers: {
						'Content-Type': 'application/ld+json',
						'Accept': 'application/ld+json',
					},
					body: JSON.stringify({}),
				}
			);

			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)?.['detail'] ??
					(data as Record<string, string> | null)?.['message'] ??
					`Erreur lors de la publication (${response.status})`
				);
			}

			const result = await response.json();
			if (evaluation) {
				evaluation = { ...evaluation, gradesPublishedAt: result.gradesPublishedAt };
			}
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur inconnue';
		} finally {
			isPublishing = false;
		}
	}

	let gradedCount = $derived(students.filter((s) => s.status === 'graded' && s.value !== null).length);
	let absentCount = $derived(students.filter((s) => s.status === 'absent').length);
	let dispensedCount = $derived(students.filter((s) => s.status === 'dispensed').length);
	let filledCount = $derived(gradedCount + absentCount + dispensedCount);
	let hasErrors = $derived(students.some((s) => s.error !== null));
	let isPublished = $derived.by(() => {
		const ev = evaluation;
		return ev !== null && ev.gradesPublishedAt != null;
	});

	function slashHintsForInput(input: string | null) {
		if (!input || !input.startsWith('/')) return [];
		return SLASH_COMMANDS.filter((c) => c.cmd.startsWith(input));
	}

	function applySlashCommand(index: number, cmd: string) {
		const row: StudentRow | undefined = students[index];
		if (!row) return;
		students[index] = {
			...row,
			inputValue: cmd,
		};
		handleInput(index);
		activeSlashHint = null;
		focusRow(index + 1);
	}

	// --- Appreciation functions ---

	function toggleAppreciation(index: number) {
		if (appreciationOpenIndex === index) {
			appreciationOpenIndex = null;
		} else {
			appreciationOpenIndex = index;
			showTemplateSelector = false;
		}
	}

	function handleAppreciationInput(index: number) {
		const row: StudentRow | undefined = students[index];
		if (!row) return;
		students[index] = { ...row, appreciationDirty: true };
		scheduleAppreciationAutoSave(index);
	}

	function scheduleAppreciationAutoSave(index: number) {
		const existing = appreciationSaveTimers.get(index);
		if (existing !== undefined) {
			window.clearTimeout(existing);
		}
		const timerId = window.setTimeout(() => {
			appreciationSaveTimers.delete(index);
			saveAppreciation(index);
		}, 1000) as unknown as number;
		appreciationSaveTimers.set(index, timerId);
	}

	async function saveAppreciation(index: number) {
		const row: StudentRow | undefined = students[index];
		if (!row || !row.appreciationDirty) return;

		// No gradeId yet — buffer offline so it can be sent once grade is created
		if (row.gradeId === null) {
			await savePendingAppreciation({
				evaluationId: evaluationId,
				studentId: row.studentId,
				appreciation: row.appreciation,
				savedAt: Date.now(),
			});
			students[index] = { ...row, appreciationDirty: false };
			hasPendingOfflineGrades = true;
			saveStatus = 'En attente de note';
			return;
		}

		if (!isOnline) {
			await savePendingAppreciation({
				evaluationId: evaluationId,
				studentId: row.studentId,
				appreciation: row.appreciation,
				savedAt: Date.now(),
			});
			students[index] = { ...row, appreciationDirty: false };
			saveStatus = 'Non synchronisé';
			return;
		}

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

			const response = await authenticatedFetch(
				`${apiUrl}/grades/${row.gradeId}/appreciation`,
				{
					method: 'PUT',
					headers: {
						'Content-Type': 'application/ld+json',
						'Accept': 'application/ld+json',
					},
					body: JSON.stringify({
						appreciation: row.appreciation,
					}),
				}
			);

			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)?.['detail'] ??
					`Erreur lors de la sauvegarde (${response.status})`
				);
			}

			students[index] = { ...row, appreciationDirty: false };
			saveStatus = 'Sauvegardé';
			window.setTimeout(() => {
				if (saveStatus === 'Sauvegardé') saveStatus = null;
			}, 2000);
		} catch (e) {
			saveStatus = null;
			error = e instanceof Error ? e.message : 'Erreur de sauvegarde appréciation';
			window.setTimeout(() => {
				if (error === (e instanceof Error ? e.message : 'Erreur de sauvegarde appréciation')) {
					error = null;
				}
			}, 5000);
		}
	}

	function applyTemplate(index: number, template: AppreciationTemplate) {
		const row: StudentRow | undefined = students[index];
		if (!row) return;
		students[index] = { ...row, appreciation: template.content, appreciationDirty: true };
		showTemplateSelector = false;
		scheduleAppreciationAutoSave(index);
	}

	async function loadTemplates() {
		try {
			const apiUrl = getApiBaseUrl();
			const res = await authenticatedFetch(`${apiUrl}/me/appreciation-templates`);
			if (!res.ok) return;
			const data = await res.json();
			templates = data['hydra:member'] ?? data['member'] ?? (Array.isArray(data) ? data : []);
		} catch {
			// Silently fail - templates are optional
		}
	}

	async function createTemplate() {
		if (!newTemplateTitle.trim() || !newTemplateContent.trim()) return;
		try {
			const apiUrl = getApiBaseUrl();
			const res = await authenticatedFetch(`${apiUrl}/me/appreciation-templates`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/ld+json', 'Accept': 'application/ld+json' },
				body: JSON.stringify({
					title: newTemplateTitle,
					content: newTemplateContent,
					category: newTemplateCategory,
				}),
			});
			if (!res.ok) throw new Error('Erreur création modèle');
			newTemplateTitle = '';
			newTemplateContent = '';
			newTemplateCategory = null;
			await loadTemplates();
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur création modèle';
		}
	}

	async function updateTemplate() {
		if (!editingTemplate || !newTemplateTitle.trim() || !newTemplateContent.trim()) return;
		try {
			const apiUrl = getApiBaseUrl();
			const res = await authenticatedFetch(`${apiUrl}/me/appreciation-templates/${editingTemplate.id}`, {
				method: 'PUT',
				headers: { 'Content-Type': 'application/ld+json', 'Accept': 'application/ld+json' },
				body: JSON.stringify({
					title: newTemplateTitle,
					content: newTemplateContent,
					category: newTemplateCategory,
				}),
			});
			if (!res.ok) throw new Error('Erreur modification modèle');
			editingTemplate = null;
			newTemplateTitle = '';
			newTemplateContent = '';
			newTemplateCategory = null;
			await loadTemplates();
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur modification modèle';
		}
	}

	async function deleteTemplate(templateId: string) {
		try {
			const apiUrl = getApiBaseUrl();
			const res = await authenticatedFetch(`${apiUrl}/me/appreciation-templates/${templateId}`, {
				method: 'DELETE',
			});
			if (!res.ok) throw new Error('Erreur suppression modèle');
			if (editingTemplate?.id === templateId) {
				cancelEditTemplate();
			}
			await loadTemplates();
		} catch (e) {
			error = e instanceof Error ? e.message : 'Erreur suppression modèle';
		}
	}

	function startEditTemplate(template: AppreciationTemplate) {
		editingTemplate = template;
		newTemplateTitle = template.title;
		newTemplateContent = template.content;
		newTemplateCategory = template.category;
	}

	function cancelEditTemplate() {
		editingTemplate = null;
		newTemplateTitle = '';
		newTemplateContent = '';
		newTemplateCategory = null;
	}

	function appreciationCharCount(index: number): number {
		const row: StudentRow | undefined = students[index];
		if (!row || !row.appreciation) return 0;
		return row.appreciation.length;
	}
</script>

<svelte:head>
	<title>{evaluation?.title ?? 'Saisie des notes'} - Classeo</title>
</svelte:head>

<div class="grades-page">
	<header class="page-header">
		<div class="header-left">
			<a href="/dashboard/teacher/evaluations" class="btn-back" aria-label="Retour aux évaluations">
				&#8592; Retour
			</a>
			{#if evaluation}
				<div class="evaluation-info">
					<h1>{evaluation.title}</h1>
					<div class="evaluation-meta">
						<span>{evaluation.className ?? ''}</span>
						<span>{evaluation.subjectName ?? ''}</span>
						<span>Barème: /{evaluation.gradeScale}</span>
					</div>
				</div>
			{/if}
		</div>
		<div class="header-right">
			{#if saveStatus}
				<span class="save-status" class:offline={saveStatus === 'Non synchronisé'}>
					{saveStatus}
				</span>
			{/if}
			{#if !isOnline}
				<span class="offline-badge">Hors ligne</span>
			{/if}
			{#if hasPendingOfflineGrades && isOnline}
				<span class="offline-badge">Sync en attente</span>
			{/if}
			{#if evaluation && !isPublished}
				<button
					class="btn-primary"
					onclick={confirmPublish}
					disabled={isPublishing || filledCount === 0 || hasErrors}
				>
					{isPublishing ? 'Publication...' : 'Publier les notes'}
				</button>
			{:else if isPublished}
				<span class="published-badge">Notes publiées</span>
			{/if}
		</div>
	</header>

	{#if error}
		<div class="alert alert-error">
			<span class="alert-icon">&#9888;</span>
			{error}
			<button class="alert-close" onclick={() => (error = null)}>&times;</button>
		</div>
	{/if}

	{#if isLoading}
		<div class="loading-state" aria-live="polite" role="status">
			<div class="spinner"></div>
			<p>Chargement de la grille...</p>
		</div>
	{:else if students.length === 0}
		<div class="empty-state">
			<h2>Aucun élève dans cette classe</h2>
			<p>Ajoutez des élèves à la classe pour saisir les notes.</p>
		</div>
	{:else}
		<div class="grade-summary">
			<span>{filledCount}/{students.length} remplis</span>
			{#if absentCount > 0}<span class="summary-absent">{absentCount} absent(s)</span>{/if}
			{#if dispensedCount > 0}<span class="summary-dispensed">{dispensedCount} dispensé(s)</span>{/if}
		</div>

		<div class="grade-grid" role="grid" aria-label="Grille de saisie des notes">
			<div class="grid-header" role="row">
				<div class="cell-name" role="columnheader">Élève</div>
				<div class="cell-grade" role="columnheader">Note /{evaluation?.gradeScale ?? 20}</div>
				<div class="cell-status" role="columnheader">Statut</div>
				<div class="cell-appreciation-toggle" role="columnheader">Appr.</div>
			</div>

			{#each students as row, i (row.studentId)}
				<div class="grid-row" role="row" class:row-absent={row.status === 'absent'} class:row-dispensed={row.status === 'dispensed'} class:row-error={row.error !== null}>
					<div class="cell-name" role="gridcell">
						<span class="student-name">{row.studentName}</span>
					</div>
					<div class="cell-grade" role="gridcell">
						<div class="input-wrapper">
							<input
								id="grade-input-{i}"
								type="text"
								class="grade-input"
								class:input-error={row.error !== null}
								class:input-absent={row.status === 'absent'}
								class:input-dispensed={row.status === 'dispensed'}
								bind:value={row.inputValue}
								oninput={() => handleInput(i)}
								onkeydown={(e) => handleKeydown(e, i)}
								onfocus={() => { activeRowIndex = i; }}
								onblur={() => { activeSlashHint = null; activeRowIndex = null; }}
								placeholder="—"
								autocomplete="off"
								aria-label="Note de {row.studentName}"
							/>
							{#if activeRowIndex === i && activeSlashHint}
								{@const hints = slashHintsForInput(activeSlashHint)}
								{#if hints.length > 0}
									<div class="slash-hints">
										{#each hints as hint (hint.cmd)}
											<!-- svelte-ignore a11y_no_static_element_interactions -->
											<div
												class="slash-hint-item"
												onmousedown={() => applySlashCommand(i, hint.cmd)}
											>
												<code>{hint.cmd}</code>
												<span>{hint.desc}</span>
											</div>
										{/each}
									</div>
								{/if}
							{/if}
						</div>
						{#if row.error}
							<span class="input-error-msg">{row.error}</span>
						{/if}
					</div>
					<div class="cell-status" role="gridcell">
						{#if row.status === 'absent'}
							<span class="status-badge status-absent">Absent</span>
						{:else if row.status === 'dispensed'}
							<span class="status-badge status-dispensed">Dispensé</span>
						{:else if row.value !== null}
							<span class="status-badge status-graded">{row.value}/{evaluation?.gradeScale ?? 20}</span>
						{:else}
							<span class="status-badge status-empty">—</span>
						{/if}
					</div>
					<div class="cell-appreciation-toggle" role="gridcell">
						<button
							class="btn-appreciation"
							class:has-appreciation={row.appreciation !== null && row.appreciation !== ''}
							onclick={() => toggleAppreciation(i)}
							aria-label="Appréciation de {row.studentName}"
							title={row.appreciation ? row.appreciation : 'Ajouter une appréciation'}
						>
							{#if row.appreciation}&#9998;{:else}+{/if}
						</button>
					</div>
				</div>
				{#if appreciationOpenIndex === i}
					<div class="appreciation-panel">
						<div class="appreciation-header">
							<span class="appreciation-label">Appréciation pour {row.studentName}</span>
							<span class="char-counter" class:char-limit={appreciationCharCount(i) >= MAX_APPRECIATION_LENGTH}>
								{appreciationCharCount(i)}/{MAX_APPRECIATION_LENGTH}
							</span>
						</div>
						<textarea
							class="appreciation-textarea"
							bind:value={row.appreciation}
							oninput={() => handleAppreciationInput(i)}
							placeholder="Saisir une appréciation..."
							maxlength={MAX_APPRECIATION_LENGTH}
							rows="3"
							aria-label="Appréciation de {row.studentName}"
						></textarea>
						<div class="appreciation-actions">
							<div class="template-actions">
								<button class="btn-template-select" onclick={() => { showTemplateSelector = !showTemplateSelector; }}>
									Modèles
								</button>
								<button class="btn-template-manage" onclick={() => { showTemplateManager = true; }}>
									Gérer
								</button>
							</div>
							{#if row.appreciationDirty}
								<span class="appreciation-saving">Auto-save...</span>
							{/if}
						</div>
						{#if showTemplateSelector && templates.length > 0}
							<div class="template-dropdown">
								{#each templates as tmpl (tmpl.id)}
									<button
										class="template-item"
										onclick={() => applyTemplate(i, tmpl)}
									>
										<strong>{tmpl.title}</strong>
										<span class="template-preview">{tmpl.content}</span>
									</button>
								{/each}
							</div>
						{:else if showTemplateSelector && templates.length === 0}
							<div class="template-dropdown">
								<div class="template-empty">Aucun modèle. Cliquez "Gérer" pour en créer.</div>
							</div>
						{/if}
					</div>
				{/if}
			{/each}
		</div>

		<div class="grade-footer">
			<div class="slash-commands-help">
				<strong>Raccourcis :</strong>
				{#each SLASH_COMMANDS as cmd (cmd.cmd)}
					<code>{cmd.cmd}</code> {cmd.desc}
					{#if cmd !== SLASH_COMMANDS[SLASH_COMMANDS.length - 1]}&nbsp;|&nbsp;{/if}
				{/each}
			</div>
			<div class="navigation-help">
				<strong>Navigation :</strong> Tab / Enter = suivant, Shift+Tab = précédent
			</div>
		</div>
	{/if}
</div>

{#if showTemplateManager}
	<!-- svelte-ignore a11y_no_static_element_interactions -->
	<div class="modal-overlay" onclick={() => { showTemplateManager = false; cancelEditTemplate(); }} role="presentation">
		<div
			class="modal modal-wide"
			role="dialog"
			aria-modal="true"
			aria-labelledby="template-manager-title"
			tabindex="-1"
			onclick={(e) => e.stopPropagation()}
			onkeydown={(e) => { if (e.key === 'Escape') { showTemplateManager = false; cancelEditTemplate(); } }}
		>
			<header class="modal-header">
				<h2 id="template-manager-title">Gérer les modèles d'appréciation</h2>
			</header>
			<div class="modal-body">
				<div class="template-form">
					<h3>{editingTemplate ? 'Modifier le modèle' : 'Nouveau modèle'}</h3>
					<input
						type="text"
						class="template-input"
						bind:value={newTemplateTitle}
						placeholder="Titre du modèle"
						maxlength={100}
					/>
					<textarea
						class="template-textarea"
						bind:value={newTemplateContent}
						placeholder="Contenu de l'appréciation"
						maxlength={MAX_APPRECIATION_LENGTH}
						rows="3"
					></textarea>
					<div class="template-category-select">
						<label>
							<input type="radio" bind:group={newTemplateCategory} value="positive" /> Positive
						</label>
						<label>
							<input type="radio" bind:group={newTemplateCategory} value="neutral" /> Neutre
						</label>
						<label>
							<input type="radio" bind:group={newTemplateCategory} value="improvement" /> Amélioration
						</label>
						<label>
							<input type="radio" bind:group={newTemplateCategory} value={null} /> Sans catégorie
						</label>
					</div>
					<div class="template-form-actions">
						{#if editingTemplate}
							<button class="btn-primary" onclick={updateTemplate} disabled={!newTemplateTitle.trim() || !newTemplateContent.trim()}>Modifier</button>
							<button class="btn-secondary" onclick={cancelEditTemplate}>Annuler</button>
						{:else}
							<button class="btn-primary" onclick={createTemplate} disabled={!newTemplateTitle.trim() || !newTemplateContent.trim()}>Créer</button>
						{/if}
					</div>
				</div>

				{#if templates.length > 0}
					<h3>Modèles existants</h3>
					<div class="template-list">
						{#each templates as tmpl (tmpl.id)}
							<div class="template-list-item">
								<div class="template-list-info">
									<strong>{tmpl.title}</strong>
									{#if tmpl.category}
										<span class="template-category-badge" class:cat-positive={tmpl.category === 'positive'} class:cat-neutral={tmpl.category === 'neutral'} class:cat-improvement={tmpl.category === 'improvement'}>
											{tmpl.category === 'positive' ? 'Positive' : tmpl.category === 'neutral' ? 'Neutre' : 'Amélioration'}
										</span>
									{/if}
									<p class="template-list-content">{tmpl.content}</p>
								</div>
								<div class="template-list-actions">
									<button class="btn-sm" onclick={() => startEditTemplate(tmpl)}>Modifier</button>
									<button class="btn-sm btn-danger" onclick={() => deleteTemplate(tmpl.id)}>Supprimer</button>
								</div>
							</div>
						{/each}
					</div>
				{/if}
			</div>
			<footer class="modal-footer">
				<button class="btn-secondary" onclick={() => { showTemplateManager = false; cancelEditTemplate(); }}>Fermer</button>
			</footer>
		</div>
	</div>
{/if}

{#if showPublishConfirm}
	<!-- svelte-ignore a11y_no_static_element_interactions -->
	<div class="modal-overlay" onclick={() => (showPublishConfirm = false)} role="presentation">
		<div
			class="modal"
			role="alertdialog"
			aria-modal="true"
			aria-labelledby="publish-confirm-title"
			aria-describedby="publish-confirm-desc"
			tabindex="-1"
			onclick={(e) => e.stopPropagation()}
			onkeydown={(e) => { if (e.key === 'Escape') showPublishConfirm = false; }}
		>
			<header class="modal-header">
				<h2 id="publish-confirm-title">Publier les notes ?</h2>
			</header>
			<div class="modal-body">
				<p id="publish-confirm-desc">
					Les notes seront visibles immédiatement par les élèves.
					Les parents pourront les consulter après un délai de 24 heures.
					<strong>Cette action est irréversible.</strong>
				</p>
				<p class="publish-summary">
					{gradedCount} note(s) saisie(s) sur {students.length} élèves
					{#if absentCount > 0}, {absentCount} absent(s){/if}
					{#if dispensedCount > 0}, {dispensedCount} dispensé(s){/if}
				</p>
			</div>
			<footer class="modal-footer">
				<button class="btn-secondary" onclick={() => (showPublishConfirm = false)}>Annuler</button>
				<button class="btn-primary" onclick={handlePublish}>Confirmer la publication</button>
			</footer>
		</div>
	</div>
{/if}

<style>
	.grades-page {
		max-width: 900px;
		margin: 0 auto;
		padding: 1.5rem;
	}

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

	.header-left {
		display: flex;
		align-items: flex-start;
		gap: 1rem;
	}

	.header-right {
		display: flex;
		align-items: center;
		gap: 0.75rem;
	}

	.btn-back {
		color: #6b7280;
		text-decoration: none;
		font-size: 0.875rem;
		white-space: nowrap;
		padding-top: 0.25rem;
	}

	.btn-back:hover {
		color: #3b82f6;
	}

	.evaluation-info h1 {
		font-size: 1.25rem;
		font-weight: 600;
		color: #1f2937;
		margin: 0;
	}

	.evaluation-meta {
		display: flex;
		gap: 1rem;
		font-size: 0.8rem;
		color: #6b7280;
		margin-top: 0.25rem;
	}

	.save-status {
		font-size: 0.8rem;
		color: #16a34a;
		font-weight: 500;
	}

	.save-status.offline {
		color: #f59e0b;
	}

	.offline-badge {
		font-size: 0.75rem;
		background: #fef3c7;
		color: #92400e;
		padding: 0.25rem 0.5rem;
		border-radius: 4px;
		font-weight: 500;
	}

	.published-badge {
		font-size: 0.8rem;
		background: #dcfce7;
		color: #166534;
		padding: 0.25rem 0.75rem;
		border-radius: 4px;
		font-weight: 500;
	}

	.btn-primary {
		background: #3b82f6;
		color: white;
		border: none;
		padding: 0.5rem 1rem;
		border-radius: 6px;
		font-size: 0.875rem;
		font-weight: 500;
		cursor: pointer;
		text-decoration: none;
		display: inline-block;
	}

	.btn-primary:hover:not(:disabled) {
		background: #2563eb;
	}

	.btn-primary:disabled {
		opacity: 0.5;
		cursor: not-allowed;
	}

	.alert {
		padding: 0.75rem 1rem;
		border-radius: 6px;
		margin-bottom: 1rem;
		display: flex;
		align-items: center;
		gap: 0.5rem;
	}

	.alert-error {
		background: #fef2f2;
		border: 1px solid #fecaca;
		color: #dc2626;
	}

	.alert-icon {
		font-size: 1.1rem;
	}

	.alert-close {
		margin-left: auto;
		background: none;
		border: none;
		color: inherit;
		font-size: 1.25rem;
		cursor: pointer;
		padding: 0 0.25rem;
	}

	.loading-state, .empty-state {
		text-align: center;
		padding: 3rem 1rem;
		color: #6b7280;
	}

	.spinner {
		width: 2rem;
		height: 2rem;
		border: 3px solid #e5e7eb;
		border-top-color: #3b82f6;
		border-radius: 50%;
		animation: spin 0.7s linear infinite;
		margin: 0 auto 1rem;
	}

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

	.grade-summary {
		display: flex;
		gap: 1rem;
		font-size: 0.875rem;
		color: #6b7280;
		margin-bottom: 0.75rem;
	}

	.summary-absent { color: #f59e0b; }
	.summary-dispensed { color: #8b5cf6; }

	.grade-grid {
		border: 1px solid #e5e7eb;
		border-radius: 8px;
		overflow: hidden;
	}

	.grid-header {
		display: grid;
		grid-template-columns: 1fr 160px 120px 50px;
		background: #f9fafb;
		border-bottom: 2px solid #e5e7eb;
		font-weight: 600;
		font-size: 0.8rem;
		color: #374151;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}

	.grid-row {
		display: grid;
		grid-template-columns: 1fr 160px 120px 50px;
		border-bottom: 1px solid #f3f4f6;
		transition: background 0.15s;
	}

	.grid-row:last-child {
		border-bottom: none;
	}

	.grid-row:hover {
		background: #f9fafb;
	}

	.grid-row.row-absent {
		background: #fffbeb;
	}

	.grid-row.row-dispensed {
		background: #f5f3ff;
	}

	.grid-row.row-error {
		background: #fef2f2;
	}

	.cell-name, .cell-grade, .cell-status {
		padding: 0.5rem 0.75rem;
		display: flex;
		align-items: center;
	}

	.student-name {
		font-size: 0.875rem;
		color: #1f2937;
	}

	.input-wrapper {
		position: relative;
		width: 100%;
	}

	.grade-input {
		width: 100%;
		padding: 0.375rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.9rem;
		text-align: center;
		background: white;
		transition: border-color 0.15s, box-shadow 0.15s;
	}

	.grade-input:focus {
		outline: none;
		border-color: #3b82f6;
		box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
	}

	.grade-input.input-error {
		border-color: #dc2626;
		box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.15);
	}

	.grade-input.input-absent {
		background: #fef3c7;
		color: #92400e;
		font-style: italic;
	}

	.grade-input.input-dispensed {
		background: #ede9fe;
		color: #5b21b6;
		font-style: italic;
	}

	.input-error-msg {
		font-size: 0.7rem;
		color: #dc2626;
		position: absolute;
		bottom: -1rem;
		left: 0;
	}

	.slash-hints {
		position: absolute;
		top: 100%;
		left: 0;
		right: 0;
		background: white;
		border: 1px solid #e5e7eb;
		border-radius: 4px;
		box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
		z-index: 10;
		overflow: hidden;
	}

	.slash-hint-item {
		display: flex;
		gap: 0.5rem;
		padding: 0.375rem 0.5rem;
		font-size: 0.8rem;
		cursor: pointer;
		align-items: center;
	}

	.slash-hint-item:hover {
		background: #f3f4f6;
	}

	.slash-hint-item code {
		background: #e5e7eb;
		padding: 0.125rem 0.375rem;
		border-radius: 3px;
		font-size: 0.75rem;
		font-weight: 600;
	}

	.status-badge {
		font-size: 0.75rem;
		padding: 0.125rem 0.5rem;
		border-radius: 999px;
		font-weight: 500;
	}

	.status-graded { background: #dcfce7; color: #166534; }
	.status-absent { background: #fef3c7; color: #92400e; }
	.status-dispensed { background: #ede9fe; color: #5b21b6; }
	.status-empty { color: #9ca3af; }

	.grade-footer {
		margin-top: 1rem;
		padding: 0.75rem;
		background: #f9fafb;
		border-radius: 6px;
		font-size: 0.775rem;
		color: #6b7280;
	}

	.grade-footer code {
		background: #e5e7eb;
		padding: 0.1rem 0.3rem;
		border-radius: 3px;
		font-size: 0.725rem;
	}

	.slash-commands-help, .navigation-help {
		margin-bottom: 0.25rem;
	}

	.navigation-help {
		margin-bottom: 0;
	}

	@media (max-width: 640px) {
		.grades-page {
			padding: 1rem;
		}

		.page-header {
			flex-direction: column;
		}

		.grid-header, .grid-row {
			grid-template-columns: 1fr 100px 80px 40px;
		}

		.cell-name, .cell-grade, .cell-status {
			padding: 0.375rem 0.5rem;
		}

		.grade-footer {
			font-size: 0.7rem;
		}
	}

	.modal-overlay {
		position: fixed;
		inset: 0;
		background: rgba(0, 0, 0, 0.5);
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 100;
	}

	.modal {
		background: white;
		border-radius: 8px;
		padding: 1.5rem;
		max-width: 480px;
		width: 90%;
		max-height: 90vh;
		display: flex;
		flex-direction: column;
		box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
	}

	.modal-header h2 {
		font-size: 1.1rem;
		font-weight: 600;
		color: #1f2937;
		margin: 0 0 1rem;
	}

	.modal-body {
		overflow-y: auto;
		flex: 1;
		min-height: 0;
	}

	.modal-body p {
		font-size: 0.875rem;
		color: #4b5563;
		line-height: 1.5;
		margin: 0 0 0.75rem;
	}

	.publish-summary {
		font-weight: 500;
		color: #1f2937 !important;
	}

	.modal-footer {
		display: flex;
		justify-content: flex-end;
		gap: 0.75rem;
		margin-top: 1.25rem;
	}

	.btn-secondary {
		background: #f3f4f6;
		color: #374151;
		border: 1px solid #d1d5db;
		padding: 0.5rem 1rem;
		border-radius: 6px;
		font-size: 0.875rem;
		cursor: pointer;
	}

	.btn-secondary:hover {
		background: #e5e7eb;
	}

	/* Appreciation styles */
	.cell-appreciation-toggle {
		padding: 0.5rem 0.25rem;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.btn-appreciation {
		width: 28px;
		height: 28px;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		background: white;
		cursor: pointer;
		font-size: 0.8rem;
		display: flex;
		align-items: center;
		justify-content: center;
		color: #6b7280;
		transition: all 0.15s;
	}

	.btn-appreciation:hover:not(:disabled) {
		border-color: #3b82f6;
		color: #3b82f6;
	}

	.btn-appreciation:disabled {
		opacity: 0.3;
		cursor: not-allowed;
	}

	.btn-appreciation.has-appreciation {
		background: #dbeafe;
		border-color: #3b82f6;
		color: #2563eb;
	}

	.appreciation-panel {
		grid-column: 1 / -1;
		padding: 0.75rem;
		background: #f0f9ff;
		border-bottom: 1px solid #bae6fd;
	}

	.appreciation-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 0.375rem;
	}

	.appreciation-label {
		font-size: 0.8rem;
		font-weight: 500;
		color: #1e40af;
	}

	.char-counter {
		font-size: 0.7rem;
		color: #6b7280;
	}

	.char-counter.char-limit {
		color: #dc2626;
		font-weight: 600;
	}

	.appreciation-textarea {
		width: 100%;
		padding: 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.875rem;
		resize: vertical;
		font-family: inherit;
		line-height: 1.4;
	}

	.appreciation-textarea:focus {
		outline: none;
		border-color: #3b82f6;
		box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
	}

	.appreciation-actions {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-top: 0.375rem;
	}

	.template-actions {
		display: flex;
		gap: 0.5rem;
	}

	.btn-template-select, .btn-template-manage {
		font-size: 0.75rem;
		padding: 0.25rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		background: white;
		cursor: pointer;
		color: #374151;
	}

	.btn-template-select:hover, .btn-template-manage:hover {
		background: #f3f4f6;
	}

	.appreciation-saving {
		font-size: 0.7rem;
		color: #f59e0b;
		font-style: italic;
	}

	.template-dropdown {
		margin-top: 0.5rem;
		border: 1px solid #e5e7eb;
		border-radius: 4px;
		background: white;
		max-height: 200px;
		overflow-y: auto;
	}

	.template-item {
		display: block;
		width: 100%;
		text-align: left;
		padding: 0.5rem;
		border: none;
		background: none;
		cursor: pointer;
		border-bottom: 1px solid #f3f4f6;
		font-size: 0.8rem;
	}

	.template-item:last-child {
		border-bottom: none;
	}

	.template-item:hover {
		background: #f0f9ff;
	}

	.template-item strong {
		display: block;
		color: #1f2937;
		margin-bottom: 0.125rem;
	}

	.template-preview {
		color: #6b7280;
		font-size: 0.75rem;
	}

	.template-empty {
		padding: 0.75rem;
		text-align: center;
		color: #9ca3af;
		font-size: 0.8rem;
	}

	/* Template manager modal */
	.modal-wide {
		max-width: 600px;
	}

	.template-form {
		margin-bottom: 1.5rem;
	}

	.template-form h3 {
		font-size: 0.9rem;
		font-weight: 600;
		color: #1f2937;
		margin: 0 0 0.5rem;
	}

	.template-input {
		width: 100%;
		padding: 0.375rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.875rem;
		margin-bottom: 0.5rem;
	}

	.template-input:focus {
		outline: none;
		border-color: #3b82f6;
		box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
	}

	.template-textarea {
		width: 100%;
		padding: 0.375rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.875rem;
		resize: vertical;
		font-family: inherit;
		margin-bottom: 0.5rem;
	}

	.template-textarea:focus {
		outline: none;
		border-color: #3b82f6;
		box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
	}

	.template-category-select {
		display: flex;
		gap: 1rem;
		margin-bottom: 0.75rem;
		font-size: 0.8rem;
		color: #374151;
		flex-wrap: wrap;
	}

	.template-category-select label {
		display: flex;
		align-items: center;
		gap: 0.25rem;
		cursor: pointer;
	}

	.template-form-actions {
		display: flex;
		gap: 0.5rem;
	}

	.template-list {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}

	.template-list h3 {
		font-size: 0.9rem;
		font-weight: 600;
		margin: 0 0 0.5rem;
	}

	.template-list-item {
		display: flex;
		justify-content: space-between;
		align-items: flex-start;
		padding: 0.5rem;
		border: 1px solid #e5e7eb;
		border-radius: 4px;
		gap: 0.75rem;
	}

	.template-list-info {
		flex: 1;
		min-width: 0;
	}

	.template-list-info strong {
		font-size: 0.85rem;
	}

	.template-list-content {
		font-size: 0.8rem;
		color: #6b7280;
		margin: 0.25rem 0 0;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.template-category-badge {
		font-size: 0.65rem;
		padding: 0.1rem 0.375rem;
		border-radius: 999px;
		margin-left: 0.375rem;
		font-weight: 500;
	}

	.cat-positive { background: #dcfce7; color: #166534; }
	.cat-neutral { background: #e0e7ff; color: #3730a3; }
	.cat-improvement { background: #fef3c7; color: #92400e; }

	.template-list-actions {
		display: flex;
		gap: 0.25rem;
		flex-shrink: 0;
	}

	.btn-sm {
		font-size: 0.7rem;
		padding: 0.2rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		background: white;
		cursor: pointer;
		color: #374151;
	}

	.btn-sm:hover {
		background: #f3f4f6;
	}

	.btn-danger {
		color: #dc2626;
		border-color: #fecaca;
	}

	.btn-danger:hover {
		background: #fef2f2;
	}
</style>
