<script lang="ts">
	import { base } from '$app/paths';
	import { api, ApiError, type App, type Group } from '$lib/api';
	import { slugify, SLUG_MAX } from '$lib/slugify';
	import { canCreateApp } from '$lib/capabilities';
	import { currentUser } from '$lib/auth';

	const me = $derived($currentUser);
	const canCreate = $derived(canCreateApp(me));

	let apps = $state<App[] | null>(null);
	let listError = $state<string | null>(null);
	let loading = $state(true);

	/// Unresolved-dead-letter count per app (v1.1.1). Loaded in
	/// parallel after the app list. Failures here are non-fatal —
	/// missing counts just don't render a badge.
	let unresolvedDl = $state<Record<string, number>>({});
	async function loadDlCounts(appList: App[]) {
		const results = await Promise.all(
			appList.map(async (a) => {
				try {
					const r = await api.deadLetters.count(a.id);
					return [a.id, r.unresolved] as const;
				} catch {
					return [a.id, 0] as const;
				}
			})
		);
		const next: Record<string, number> = {};
		for (const [id, count] of results) next[id] = count;
		unresolvedDl = next;
	}

	let showCreate = $state(false);
	let createSlug = $state('');
	let createName = $state('');
	let createDescription = $state('');
	// Optional parent group for the new app (defaults to root). Loaded
	// lazily; failure leaves the picker empty (root-only).
	let createGroup = $state('');
	let groups = $state<Group[]>([]);
	async function loadGroups() {
		try {
			groups = await api.groups.list();
		} catch {
			groups = [];
		}
	}
	// Auto-derive slug from name until the user takes manual control of
	// the slug field. Clearing the slug input releases the lock so the
	// auto-derive resumes — matches the GitLab project-create UX.
	let slugTouched = $state(false);
	let creating = $state(false);
	let createError = $state<string | null>(null);
	let createHistoricalConflict = $state<App | null>(null);

	function onNameInput(event: Event) {
		const value = (event.target as HTMLInputElement).value;
		createName = value;
		if (!slugTouched) {
			createSlug = slugify(value);
		}
	}

	function onSlugInput(event: Event) {
		const raw = (event.target as HTMLInputElement).value;
		const normalized = slugify(raw);
		createSlug = normalized;
		// Re-sync the input element so a paste of "Hello World!" shows
		// "hello-world" immediately, not the raw value.
		if (raw !== normalized) {
			(event.target as HTMLInputElement).value = normalized;
		}
		slugTouched = normalized.length > 0;
	}

	async function load() {
		loading = true;
		listError = null;
		try {
			apps = await api.apps.list();
			void loadGroups();
			if (apps && apps.length > 0) {
				void loadDlCounts(apps);
			}
		} catch (e) {
			listError = e instanceof Error ? e.message : String(e);
			apps = null;
		} finally {
			loading = false;
		}
	}

	function resetCreate() {
		createSlug = '';
		createName = '';
		createDescription = '';
		createGroup = '';
		createError = null;
		createHistoricalConflict = null;
		slugTouched = false;
	}

	async function submitCreate(event: Event, forceTakeover = false) {
		event.preventDefault();
		creating = true;
		createError = null;
		if (!forceTakeover) createHistoricalConflict = null;
		try {
			await api.apps.create({
				slug: createSlug.trim(),
				name: createName.trim(),
				description: createDescription.trim() || null,
				force_takeover: forceTakeover || undefined,
				group: createGroup.trim() || undefined
			});
			showCreate = false;
			resetCreate();
			await load();
		} catch (e) {
			if (e instanceof ApiError && e.status === 409 && e.body) {
				const body = e.body as { conflict_kind?: string; current_app?: App };
				if (body.conflict_kind === 'historical' && body.current_app) {
					createHistoricalConflict = body.current_app;
					createError = null;
					return;
				}
			}
			createError = e instanceof Error ? e.message : String(e);
		} finally {
			creating = false;
		}
	}

	$effect(() => {
		void load();
	});
</script>

<section>
	<header class="page-header">
		<h1>Apps</h1>
		{#if canCreate}
			<button
				type="button"
				onclick={() => {
					showCreate = !showCreate;
					if (!showCreate) resetCreate();
				}}
			>
				{showCreate ? 'Cancel' : 'New app'}
			</button>
		{/if}
	</header>

	{#if showCreate && canCreate}
		<form class="create-form" onsubmit={(e) => submitCreate(e)}>
			<div class="row">
				<label>
					<span>Name</span>
					<input
						value={createName}
						oninput={onNameInput}
						required
						placeholder="My App"
					/>
				</label>
				<label>
					<span>Slug</span>
					<input
						value={createSlug}
						oninput={onSlugInput}
						required
						pattern="[a-z0-9][a-z0-9-]*"
						maxlength={SLUG_MAX}
						placeholder="my-app"
						autocomplete="off"
						autocapitalize="off"
						autocorrect="off"
						spellcheck="false"
					/>
				</label>
			</div>
			<label>
				<span>Description</span>
				<input bind:value={createDescription} placeholder="optional" />
			</label>
			<label>
				<span>Group (optional)</span>
				<select bind:value={createGroup}>
					<option value="">— root —</option>
					{#each groups as g (g.id)}
						<option value={g.slug}>{g.name} (/{g.slug})</option>
					{/each}
				</select>
			</label>
			{#if createHistoricalConflict}
				<div class="warning">
					<strong>Slug previously redirected.</strong>
					<p>
						<code>{createSlug}</code> currently redirects to
						<code>{createHistoricalConflict.slug}</code>. Using it here will break any
						external links that still target the old slug.
					</p>
					<div class="actions">
						<button type="button" class="secondary" onclick={() => (createHistoricalConflict = null)}>
							Cancel
						</button>
						<button
							type="button"
							onclick={(e) => submitCreate(e, true)}
							disabled={creating}
						>
							{creating ? 'Claiming…' : 'Claim slug anyway'}
						</button>
					</div>
				</div>
			{:else if createError}
				<div class="error">{createError}</div>
			{/if}
			{#if !createHistoricalConflict}
				<div class="actions">
					<button type="submit" disabled={creating}>
						{creating ? 'Creating…' : 'Create app'}
					</button>
				</div>
			{/if}
		</form>
	{/if}

	{#if loading}
		<p class="muted">Loading…</p>
	{:else if listError}
		<div class="error">
			<strong>Could not load apps.</strong>
			<p>{listError}</p>
			<button type="button" onclick={() => void load()}>Retry</button>
		</div>
	{:else if apps && apps.length === 0}
		<p class="muted">No apps yet. Create one above to get started.</p>
	{:else if apps}
		<ul class="list">
			{#each apps as app (app.id)}
				<li>
					<a href="{base}/apps/{app.slug}">
						<div class="primary">
							<strong>{app.name}</strong>
							<span class="muted">/{app.slug}</span>
							{#if unresolvedDl[app.id] > 0}
								<span
									class="dl-badge"
									title="Unresolved dead letters in this app"
								>{unresolvedDl[app.id]}</span>
							{/if}
						</div>
						<div class="secondary muted">
							{app.description ?? '—'}
						</div>
					</a>
				</li>
			{/each}
		</ul>
	{/if}
</section>

<style>
	.page-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 1.5rem;
	}

	h1 {
		margin: 0;
		font-size: 1.5rem;
	}

	button {
		background: #38bdf8;
		color: #0b1220;
		border: none;
		padding: 0.5rem 1rem;
		border-radius: 0.375rem;
		font-weight: 600;
		cursor: pointer;
	}

	button.secondary {
		background: transparent;
		color: #94a3b8;
		border: 1px solid #334155;
	}

	button:disabled {
		opacity: 0.5;
		cursor: not-allowed;
	}

	.dl-badge {
		display: inline-block;
		min-width: 1.25rem;
		padding: 0.1rem 0.4rem;
		background: #ef4444;
		color: #fff;
		border-radius: 999px;
		font-size: 0.75rem;
		font-weight: 600;
		text-align: center;
		margin-left: 0.5rem;
	}

	.muted {
		color: #64748b;
	}

	.error {
		border: 1px solid #b91c1c;
		background: #450a0a;
		color: #fecaca;
		padding: 1rem;
		border-radius: 0.5rem;
		margin: 1rem 0;
	}

	.warning {
		border: 1px solid #ca8a04;
		background: #3f2e07;
		color: #fde68a;
		padding: 1rem;
		border-radius: 0.5rem;
		margin: 1rem 0;
	}

	.warning code {
		background: #1e293b;
		padding: 0.1rem 0.3rem;
		border-radius: 0.25rem;
	}

	.create-form {
		background: #1e293b;
		border-radius: 0.5rem;
		padding: 1.25rem;
		margin-bottom: 1.5rem;
		display: flex;
		flex-direction: column;
		gap: 0.75rem;
	}

	.create-form .row {
		display: grid;
		grid-template-columns: 1fr 2fr;
		gap: 0.75rem;
	}

	.create-form label {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
		font-size: 0.85rem;
		color: #cbd5e1;
	}

	.create-form input {
		background: #0b1220;
		color: #e2e8f0;
		border: 1px solid #334155;
		border-radius: 0.375rem;
		padding: 0.5rem 0.75rem;
		font: inherit;
	}

	.actions {
		display: flex;
		justify-content: flex-end;
		gap: 0.5rem;
	}

	.list {
		list-style: none;
		padding: 0;
		margin: 0;
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}

	.list a {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
		padding: 0.85rem 1rem;
		background: #1e293b;
		border-radius: 0.375rem;
		text-decoration: none;
		color: inherit;
	}

	.list a:hover {
		background: #283549;
	}

	.primary {
		display: flex;
		gap: 0.5rem;
		align-items: baseline;
	}

	.secondary {
		font-size: 0.875rem;
	}
</style>
