<script lang="ts">
	import { addUser, deleteUser, listRoles, listUsers } from '$lib/api/admin-users';
	import { errorReporter } from '$lib/api/errors';
	import type { ApplicationUserDTO } from '$lib/api/schema-helpers';
	import { requireAdmin } from '$lib/auth/guard.svelte';
	import { fullName } from '$lib/format';
	import { session } from '$lib/stores/session.svelte';
	import { alertError, alertNotice, card, cardHeading, danger, field, ghost, label, primary } from '$lib/ui/classes';

	let users = $state<ApplicationUserDTO[]>([]);
	let roles = $state<string[]>([]);

	let username = $state('');
	let password = $state('');
	let firstName = $state('');
	let lastName = $state('');
	let selectedRoles = $state<string[]>([]);

	let loading = $state(false);
	let creating = $state(false);
	let deletingId = $state<number | null>(null);
	let notice = $state<string | null>(null);
	let error = $state<string | null>(null);

	const canCreate = $derived(username.trim() !== '' && password !== '' && !creating);

	// The page is admin-only on the server too; this just avoids showing a shell that
	// can only produce 403s.
	const guard = requireAdmin(() => void refresh());

	const report = errorReporter((message) => (error = message));

	async function refresh() {
		loading = true;
		error = null;
		try {
			// Roles are reference data; fetch them alongside the list on first load.
			[users, roles] = await Promise.all([listUsers(), listRoles()]);
		} catch (cause) {
			report(cause, 'Could not load the accounts.');
		} finally {
			loading = false;
		}
	}

	function toggleRole(role: string) {
		selectedRoles = selectedRoles.includes(role)
			? selectedRoles.filter((selected) => selected !== role)
			: [...selectedRoles, role];
	}

	async function create() {
		if (!canCreate) return;

		creating = true;
		error = null;
		notice = null;
		const name = username.trim();
		try {
			await addUser({
				username: name,
				password,
				firstName: firstName.trim() || null,
				lastName: lastName.trim() || null,
				roles: selectedRoles
			});
			notice = `Created "${name}".`;
			username = '';
			password = '';
			firstName = '';
			lastName = '';
			selectedRoles = [];
			await refresh();
		} catch (cause) {
			// A 400 carries the reason: username taken, missing password, unknown role.
			report(cause, `Could not create "${name}".`);
		} finally {
			creating = false;
		}
	}

	async function remove(user: ApplicationUserDTO) {
		if (user.id === undefined || deletingId !== null) return;
		if (!confirm(`Delete "${user.username}"? This cannot be undone.`)) return;

		deletingId = user.id;
		error = null;
		notice = null;
		try {
			await deleteUser(user.id);
			notice = `Deleted "${user.username}".`;
			await refresh();
		} catch (cause) {
			report(cause, `Could not delete "${user.username}".`);
		} finally {
			deletingId = null;
		}
	}

	function displayName(user: ApplicationUserDTO): string {
		return fullName(user.firstName, user.lastName);
	}
</script>

<svelte:head>
	<title>Users · LaDOSE</title>
</svelte:head>

<main id="main" class="mx-auto max-w-5xl px-4 py-10">
	{#if guard.ready}
	<header class="mb-8">
		<h1 class="text-3xl font-semibold tracking-tight">Users</h1>
		<p class="mt-1 text-sm text-muted">
			Accounts that can sign in. Admins may also manage this list.
		</p>
	</header>

	{#if error}
		<p role="alert" class="{alertError} mb-4">
			{error}
		</p>
	{/if}
	{#if notice}
		<p class="{alertNotice} mb-4">{notice}</p>
	{/if}

	<section class={card}>
		<div class="flex items-baseline justify-between gap-3">
			<h2 class={cardHeading}>
				Accounts
				<span class="ml-1 font-normal text-muted normal-case">({users.length})</span>
			</h2>
			<button class={ghost} onclick={refresh} disabled={loading}>
				{loading ? 'Loading…' : 'Refresh'}
			</button>
		</div>

		<div class="mt-4 overflow-x-auto">
			<table class="w-full min-w-max text-sm">
				<thead class="text-left text-xs tracking-wide text-muted uppercase">
					<tr class="border-b border-line">
						<th class="py-2 pr-4 font-medium">Username</th>
						<th class="py-2 pr-4 font-medium">Name</th>
						<th class="py-2 pr-4 font-medium">Roles</th>
						<th class="py-2 pl-2"></th>
					</tr>
				</thead>
				<tbody>
					{#each users as user (user.id)}
						<tr class="border-b border-line/60 last:border-0">
							<td class="py-2 pr-4 font-medium">
								{user.username}
								{#if user.id === session.user?.id}
									<span class="ml-1 text-xs font-normal text-subtle">(you)</span>
								{/if}
							</td>
							<td class="py-2 pr-4 text-muted">{displayName(user) || '—'}</td>
							<td class="py-2 pr-4">
								{#if user.roles?.length}
									{#each user.roles as role (role)}
										<span
											class="mr-1 rounded-full px-2 py-0.5 text-xs {role.toLowerCase() === 'admin'
												? 'bg-accent/20 text-accent'
												: 'bg-ink/5 text-muted'}"
										>
											{role}
										</span>
									{/each}
								{:else}
									<span class="text-subtle">none</span>
								{/if}
							</td>
							<td class="py-2 pl-2 text-right">
								{#if user.id === session.user?.id}
									<!-- The API refuses this, which is what keeps an admin in place. -->
									<span class="text-xs text-subtle">can't delete yourself</span>
								{:else}
									<button
										class={danger}
										onclick={() => remove(user)}
										disabled={deletingId !== null}
									>
										{deletingId === user.id ? 'Deleting…' : 'Delete'}
									</button>
								{/if}
							</td>
						</tr>
					{:else}
						<tr>
							<td colspan="4" class="py-6 text-center text-subtle">
								{loading ? 'Loading…' : 'No account.'}
							</td>
						</tr>
					{/each}
				</tbody>
			</table>
		</div>
	</section>

	<section class="{card} mt-6">
		<h2 class={cardHeading}>Add a user</h2>

		<form
			class="mt-5 grid gap-4 sm:grid-cols-2"
			onsubmit={(event) => {
				event.preventDefault();
				void create();
			}}
		>
			<div class="space-y-1.5">
				<label class={label} for="new-username">Username</label>
				<input
					id="new-username"
					bind:value={username}
					class={field}
					autocomplete="off"
					required
				/>
			</div>

			<div class="space-y-1.5">
				<label class={label} for="new-password">Password</label>
				<input
					id="new-password"
					type="password"
					bind:value={password}
					class={field}
					autocomplete="new-password"
					required
				/>
			</div>

			<div class="space-y-1.5">
				<label class={label} for="new-firstname">First name</label>
				<input id="new-firstname" bind:value={firstName} class={field} autocomplete="off" />
			</div>

			<div class="space-y-1.5">
				<label class={label} for="new-lastname">Last name</label>
				<input id="new-lastname" bind:value={lastName} class={field} autocomplete="off" />
			</div>

			<fieldset class="space-y-2 sm:col-span-2">
				<legend class={label}>Roles</legend>
				{#if roles.length}
					<div class="flex flex-wrap gap-3">
						{#each roles as role (role)}
							<label
								class="flex cursor-pointer items-center gap-2 rounded-lg border border-line px-3 py-1.5 text-sm transition hover:bg-ink/5"
							>
								<input
									type="checkbox"
									checked={selectedRoles.includes(role)}
									onchange={() => toggleRole(role)}
									class="size-4 accent-accent"
								/>
								{role}
							</label>
						{/each}
					</div>
					<p class="text-xs text-subtle">
						No role still signs in and uses everything else — only this page needs Admin.
					</p>
				{:else}
					<p class="text-xs text-warning">
						No role exists in the database yet. Run <code>Sql/2026-08-05_roles.sql</code> to seed
						Admin and User.
					</p>
				{/if}
			</fieldset>

			<div class="border-t border-line pt-4 sm:col-span-2">
				<button type="submit" class={primary} disabled={!canCreate}>
					{creating ? 'Creating…' : 'Create user'}
				</button>
			</div>
		</form>
	</section>
	{/if}
</main>
