import { type KeyboardEvent, useCallback, useState } from 'react';

import { isActorIdentifier } from '@atcute/lexicons/syntax';

import { login, type SessionAccount, switchAccount, useSession } from '#/state/session';

import { AccountList } from '#/components/AccountList';
import * as Dialog from '#/components/Dialog';
import { signinDialogHandle, type SigninDialogPayload } from '#/components/dialogs/handles';
import * as css from '#/components/dialogs/Signin.css';
import { Spinner } from '#/components/Spinner';
import { Stack } from '#/components/Stack';
import { Text } from '#/components/Text';
import * as TextField from '#/components/TextField';
import * as Toast from '#/components/Toast';
import { Button, ButtonIcon, ButtonText } from '#/components/web/Button';

import AtIcon from '#/icons/central/At_round_outlined_radius1_stroke2.svg';
import ChevronLeftIcon from '#/icons/central/ChevronLeft_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';

export function SigninDialog() {
	return (
		<Dialog.Root handle={signinDialogHandle}>
			{({ payload }: { payload: SigninDialogPayload | undefined }) =>
				payload ? (
					<Dialog.Popup size="narrow">
						<SigninDialogInner close={() => signinDialogHandle.close()} payload={payload} />
					</Dialog.Popup>
				) : null
			}
		</Dialog.Root>
	);
}

function SigninDialogInner({ close, payload }: { close: () => void; payload: SigninDialogPayload }) {
	const { accounts } = useSession();
	const requestedAccount = payload.requestedAccount;
	const showStoredAccounts = payload.showStoredAccounts ?? true;
	const hasStoredAccounts = showStoredAccounts && accounts.length > 0;

	// The render-prop subtree remounts on each open, so the entry screen is derived once from the
	// payload: stored accounts get the chooser, everything else the new-account form.
	const [screen, setScreen] = useState<'choose' | 'new'>(() =>
		hasStoredAccounts && !requestedAccount ? 'choose' : 'new',
	);

	if (screen === 'choose') {
		return (
			<ChooseAccountScreen
				close={close}
				intent={payload.intent ?? 'signin'}
				onSelectOther={() => setScreen('new')}
			/>
		);
	}
	return (
		<NewAccountScreen
			initialHandle={requestedAccount?.handle ?? ''}
			onBack={hasStoredAccounts ? () => setScreen('choose') : undefined}
		/>
	);
}

function ChooseAccountScreen({
	close,
	intent,
	onSelectOther,
}: {
	close: () => void;
	intent: 'signin' | 'switch';
	onSelectOther: () => void;
}) {
	const { currentAccount } = useSession();
	const [pendingDid, setPendingDid] = useState<string | null>(null);

	const onSelectAccount = useCallback(
		async (account: SessionAccount) => {
			if (pendingDid) {
				return;
			}
			if (account.did === currentAccount?.did) {
				close();
				Toast.show(m['components.dialogs.account.alreadySignedIn']({ handle: account.handle }));
				return;
			}
			try {
				setPendingDid(account.did);
				await switchAccount(account);
			} catch (e) {
				console.error('sign in dialog: resume account failed', e);
				setPendingDid(null);
				await login({ identifier: account.did });
				return;
			}
			setPendingDid(null);
		},
		[close, currentAccount?.did, pendingDid],
	);

	return (
		<Stack gap="lg">
			<Stack gap="xs">
				<Dialog.TitleRow>
					<Dialog.Title>
						{intent === 'switch' ? m['common.account.action.switch']() : m['common.session.action.signIn']()}
					</Dialog.Title>
					<Dialog.Close />
				</Dialog.TitleRow>

				{intent !== 'switch' && (
					<Text color="textContrastMedium">{m['components.dialogs.account.chooseDescription']()}</Text>
				)}
			</Stack>

			<AccountList
				onSelectAccount={(account) => void onSelectAccount(account)}
				onSelectOther={onSelectOther}
				otherLabel={m['components.dialogs.account.signInAnother']()}
				pendingDid={pendingDid}
			/>
		</Stack>
	);
}

function NewAccountScreen({ initialHandle, onBack }: { initialHandle: string; onBack?: () => void }) {
	const [identifier, setIdentifier] = useState(initialHandle);
	const [isSubmitting, setIsSubmitting] = useState(false);
	const [error, setError] = useState('');

	const onSubmit = async () => {
		// people habitually type the leading `@`, and the field renders one as a prefix icon
		const trimmed = identifier.trim().replace(/^@/, '');
		if (!isActorIdentifier(trimmed)) {
			setError(m['components.dialogs.account.handle.description']());
			return;
		}

		setError('');
		setIsSubmitting(true);
		try {
			await login({ identifier: trimmed });
		} catch (e) {
			console.error('sign in dialog: OAuth start failed', e);
			setError(m['components.dialogs.signin.startError']());
			setIsSubmitting(false);
		}
	};

	const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
		if (e.key === 'Enter') {
			e.preventDefault();
			void onSubmit();
		}
	};

	return (
		<Stack gap="xl">
			<Stack gap="xs">
				<Dialog.TitleRow>
					<Dialog.Title>{m['common.session.action.signIn']()}</Dialog.Title>
					<Dialog.Close />
				</Dialog.TitleRow>

				<Text color="textContrastMedium">{m['components.dialogs.signin.description']()}</Text>
			</Stack>
			<TextField.Root isInvalid={!!error}>
				<TextField.LabelText>{m['components.dialogs.account.handle.label']()}</TextField.LabelText>
				<div className={css.field}>
					<AtIcon className={css.fieldIcon} />
					<TextField.Input
						autoCapitalize="none"
						autoFocus
						className={css.fieldInput}
						label={m['components.dialogs.account.handle.label']()}
						onChangeText={setIdentifier}
						onKeyDown={onKeyDown}
						placeholder={m['components.dialogs.account.handle.placeholder']()}
						value={identifier}
					/>
				</div>

				{error && (
					<Text className={css.error} color="textContrastMedium" size="sm">
						{error}
					</Text>
				)}
			</TextField.Root>
			<Dialog.Actions direction="column">
				<Button
					color="primary"
					disabled={isSubmitting}
					label={m['common.session.action.signIn']()}
					onClick={() => void onSubmit()}
					variant="solid"
					size="large"
				>
					{isSubmitting ? (
						<Spinner color="white" label={m['common.status.loading']()} size="sm" />
					) : (
						<ButtonText>{m['common.session.action.signIn']()}</ButtonText>
					)}
				</Button>

				{onBack && (
					<Button
						color="secondary"
						disabled={isSubmitting}
						label={m['components.dialogs.account.back']()}
						onClick={onBack}
						variant="ghost"
						size="large"
					>
						<ButtonIcon icon={ChevronLeftIcon} />
						<ButtonText>{m['components.dialogs.account.back']()}</ButtonText>
					</Button>
				)}
			</Dialog.Actions>
		</Stack>
	);
}
