<script lang="ts">
	import { goto } from "$app/navigation";
	import {
		AuthError,
		confirmEmailCode,
		sendEmailCode,
	} from "../../api/auth";
	import { GATEWAY_BASE_URL } from "$lib/env";
	import { i18n, SUPPORTED_LOCALES } from "$lib/i18n/index.svelte";
	import { session } from "$lib/session-store.svelte";

	type Step = "email" | "code";

	let step: Step = $state("email");
	let email = $state("");
	let code = $state("");
	let challengeId: string | null = $state(null);
	let pending = $state(false);
	let error: string | null = $state(null);

	// Safari ignores `autocomplete="off"` on type=email / login-shaped
	// fields and pops the Keychain suggester regardless. The classic
	// workaround is to render the input as `readonly` initially —
	// Safari does not autofill readonly fields — and drop the
	// attribute on the first user focus so typing still works. Once
	// dropped, the flag stays false for the rest of the page life.
	let emailReadonly = $state(true);
	let codeReadonly = $state(true);

	function describe(err: unknown): string {
		if (err instanceof AuthError) {
			return err.message;
		}
		if (err instanceof Error) {
			return err.message;
		}
		return "request failed";
	}

	async function submitEmail(event: Event): Promise<void> {
		event.preventDefault();
		if (pending) return;
		const trimmed = email.trim();
		if (trimmed.length === 0) {
			error = i18n.t("login.email_required");
			return;
		}
		pending = true;
		error = null;
		try {
			const result = await sendEmailCode(GATEWAY_BASE_URL, trimmed, {
				locale: i18n.locale,
			});
			challengeId = result.challengeId;
			code = "";
			step = "code";
		} catch (err) {
			error = describe(err);
		} finally {
			pending = false;
		}
	}

	async function submitCode(event: Event): Promise<void> {
		event.preventDefault();
		if (pending) return;
		const trimmedCode = code.trim();
		if (trimmedCode.length === 0) {
			error = i18n.t("login.code_required");
			return;
		}
		if (challengeId === null) {
			error = i18n.t("login.challenge_expired");
			step = "email";
			return;
		}
		if (session.keypair === null) {
			error = i18n.t("login.device_key_not_ready");
			return;
		}
		pending = true;
		error = null;
		try {
			const result = await confirmEmailCode(GATEWAY_BASE_URL, {
				challengeId,
				code: trimmedCode,
				publicKey: session.keypair.publicKey,
				timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
			});
			await session.signIn(result.deviceSessionId);
			void goto("/lobby", { replaceState: true });
		} catch (err) {
			if (err instanceof AuthError && err.code === "invalid_request") {
				challengeId = null;
				code = "";
				step = "email";
				error = i18n.t("login.code_expired_or_used");
			} else {
				error = describe(err);
			}
		} finally {
			pending = false;
		}
	}

	async function resend(): Promise<void> {
		if (pending) return;
		const trimmed = email.trim();
		if (trimmed.length === 0) {
			step = "email";
			return;
		}
		pending = true;
		error = null;
		try {
			const result = await sendEmailCode(GATEWAY_BASE_URL, trimmed, {
				locale: i18n.locale,
			});
			challengeId = result.challengeId;
			code = "";
		} catch (err) {
			error = describe(err);
		} finally {
			pending = false;
		}
	}

	function changeEmail(): void {
		challengeId = null;
		code = "";
		error = null;
		step = "email";
	}
</script>

<main>
	<header>
		<h1>{i18n.t("login.title")}</h1>
		<div class="language-picker">
			<svg
				class="globe"
				viewBox="0 0 24 24"
				width="20"
				height="20"
				aria-hidden="true"
				focusable="false"
			>
				<circle
					cx="12"
					cy="12"
					r="9"
					fill="none"
					stroke="currentColor"
					stroke-width="1.5"
				/>
				<path
					d="M3 12h18"
					fill="none"
					stroke="currentColor"
					stroke-width="1.5"
				/>
				<path
					d="M12 3a13 13 0 0 1 0 18M12 3a13 13 0 0 0 0 18"
					fill="none"
					stroke="currentColor"
					stroke-width="1.5"
				/>
			</svg>
			<label class="visually-hidden" for="login-language-select">
				{i18n.t("common.language")}
			</label>
			<select
				id="login-language-select"
				data-testid="login-language-select"
				bind:value={i18n.locale}
			>
				{#each SUPPORTED_LOCALES as locale (locale.code)}
					<option value={locale.code}>{locale.nativeName}</option>
				{/each}
			</select>
		</div>
	</header>

	{#if step === "email"}
		<form
			onsubmit={submitEmail}
			aria-busy={pending}
			autocomplete="off"
		>
			<label>
				{i18n.t("login.email_label")}
				<input
					type="email"
					name="galaxy-login-email"
					autocomplete="new-password"
					autocorrect="off"
					autocapitalize="off"
					spellcheck="false"
					readonly={emailReadonly}
					onfocus={() => (emailReadonly = false)}
					bind:value={email}
					disabled={pending}
					required
					data-testid="login-email-input"
				/>
			</label>
			<button
				type="submit"
				disabled={pending}
				data-testid="login-email-submit"
			>
				{pending ? i18n.t("login.sending") : i18n.t("login.send_code")}
			</button>
		</form>
	{:else}
		<form
			onsubmit={submitCode}
			aria-busy={pending}
			autocomplete="off"
		>
			<p data-testid="login-code-target">
				{i18n.t("login.code_sent_to", { email })}
			</p>
			<label>
				{i18n.t("login.code_label")}
				<input
					type="text"
					name="galaxy-login-code"
					inputmode="numeric"
					autocomplete="new-password"
					autocorrect="off"
					autocapitalize="off"
					spellcheck="false"
					readonly={codeReadonly}
					onfocus={() => (codeReadonly = false)}
					bind:value={code}
					disabled={pending}
					required
					data-testid="login-code-input"
				/>
			</label>
			<button type="submit" disabled={pending} data-testid="login-code-submit">
				{pending ? i18n.t("login.verifying") : i18n.t("login.verify")}
			</button>
			<div class="secondary">
				<button
					type="button"
					onclick={resend}
					disabled={pending}
					data-testid="login-resend"
				>
					{i18n.t("login.send_new_code")}
				</button>
				<button
					type="button"
					onclick={changeEmail}
					disabled={pending}
					data-testid="login-change-email"
				>
					{i18n.t("login.change_email")}
				</button>
			</div>
		</form>
	{/if}

	{#if error !== null}
		<p role="alert" data-testid="login-error">{error}</p>
	{/if}
</main>

<style>
	main {
		padding: 2rem;
		font-family: system-ui, sans-serif;
		max-width: 32rem;
	}

	header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 1rem;
		margin-bottom: 1rem;
	}

	header h1 {
		margin: 0;
	}

	.language-picker {
		display: inline-flex;
		align-items: center;
		gap: 0.5rem;
		opacity: 0.85;
	}

	.globe {
		flex: none;
	}

	.language-picker select {
		font-size: 0.875rem;
		padding: 0.25rem 0.5rem;
	}

	.visually-hidden {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		margin: -1px;
		overflow: hidden;
		clip: rect(0, 0, 0, 0);
		white-space: nowrap;
		border: 0;
	}

	form {
		display: flex;
		flex-direction: column;
		gap: 1rem;
		margin-top: 1.5rem;
	}

	form > label {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
		font-size: 0.875rem;
	}

	input {
		font-size: 1rem;
		padding: 0.5rem;
	}

	button {
		font-size: 1rem;
		padding: 0.5rem 1rem;
	}

	.secondary {
		display: flex;
		gap: 0.5rem;
	}

	.secondary button {
		font-size: 0.875rem;
		padding: 0.25rem 0.75rem;
	}

	[role="alert"] {
		margin-top: 1rem;
		color: var(--color-danger);
	}
</style>
