import { useMutation, useQuery } from 'convex/react';
import {
	Bell,
	Keyboard,
	Microphone,
	Palette,
	Shield,
	SignOut,
	User,
	X,
} from '@phosphor-icons/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { api } from '../../../../../convex/_generated/api';
import { useTheme } from '../../contexts/ThemeContext';
import { useIsMobile } from '../../hooks/useIsMobile';
import { useLogout } from '../../hooks/useLogout';
import { usePlatform } from '../../platform';
import { AvatarCropModal } from './AvatarCropModal';
import { KeybindsTab } from './KeybindsTab';
import { MobileUserSettings } from './MobileUserSettings';
import { LogoutConfirmModal } from '../modals/LogoutConfirmModal';
import styles from './UserSettingsModal.module.css';

type TabId = 'account' | 'appearance' | 'voice' | 'keybinds' | 'notifications' | 'security';

interface UserSettingsModalProps {
	isOpen: boolean;
	onClose: () => void;
	initialTab?: TabId;
}

const TABS: Array<{ id: TabId; label: string; icon: typeof User }> = [
	{ id: 'account', label: 'My Account', icon: User },
	{ id: 'appearance', label: 'Appearance', icon: Palette },
	{ id: 'voice', label: 'Voice & Video', icon: Microphone },
	{ id: 'keybinds', label: 'Keybinds', icon: Keyboard },
	{ id: 'notifications', label: 'Notifications', icon: Bell },
	{ id: 'security', label: 'Security', icon: Shield },
];

export function UserSettingsModal({ isOpen, onClose, initialTab }: UserSettingsModalProps) {
	// Desktop opens straight to the Account tab so the user lands on
	// meaningful content. Mobile prefers the category list when the
	// caller didn't ask for a specific tab — the list view is the
	// mobile-correct default.
	const isMobile = useIsMobile();
	const logout = useLogout();
	const resolvedInitialTab: TabId = initialTab ?? 'account';
	const [activeTab, setActiveTab] = useState<TabId>(resolvedInitialTab);
	const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);

	useEffect(() => {
		if (isOpen) setActiveTab(resolvedInitialTab);
	}, [isOpen, resolvedInitialTab]);

	useEffect(() => {
		if (!isOpen) return;
		const onKey = (e: KeyboardEvent) => {
			if (e.key === 'Escape') onClose();
		};
		document.addEventListener('keydown', onKey);
		return () => document.removeEventListener('keydown', onKey);
	}, [isOpen, onClose]);

	// On mobile, present the full-screen MobileUserSettings overlay
	// instead of the desktop two-column modal. Pass `initialTab` as-is
	// (NOT the resolved version) so mobile can land on the category
	// list when no explicit tab was requested.
	if (isMobile) {
		return (
			<MobileUserSettings
				isOpen={isOpen}
				onClose={onClose}
				initialTab={initialTab as any}
			/>
		);
	}

	if (!isOpen) return null;

	const active = TABS.find((t) => t.id === activeTab) ?? TABS[0];

	return createPortal(
		<>
		<div className={styles.overlay} onClick={onClose}>
			<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
				<nav className={styles.sidebar}>
					<div className={styles.sidebarInner}>
						<div className={styles.sidebarCategoryTitle}>User Settings</div>
						<div className={styles.sidebarGroup}>
							{TABS.map((tab) => {
								const Icon = tab.icon;
								const isActive = activeTab === tab.id;
								return (
									<button
										key={tab.id}
										type="button"
										className={`${styles.sidebarItem} ${isActive ? styles.sidebarItemActive : ''}`}
										onClick={() => setActiveTab(tab.id)}
									>
										<span className={styles.sidebarItemIcon}>
											<Icon size={18} />
										</span>
										<span className={styles.sidebarItemLabel}>{tab.label}</span>
									</button>
								);
							})}
							<div className={styles.sidebarDivider} />
							<button
								type="button"
								className={`${styles.sidebarItem} ${styles.sidebarItemDanger}`}
								onClick={() => setShowLogoutConfirm(true)}
							>
								<span className={styles.sidebarItemIcon}>
									<SignOut size={18} />
								</span>
								<span className={styles.sidebarItemLabel}>Log Out</span>
							</button>
						</div>
					</div>
				</nav>

				<div className={styles.contentColumn}>
					<div className={styles.contentHeader}>
						<h1 className={styles.contentTitle}>{active.label}</h1>
						<button
							type="button"
							className={styles.closeButton}
							onClick={onClose}
							aria-label="Close settings"
						>
							<X size={22} weight="bold" />
						</button>
					</div>
					<div className={styles.contentScroll}>
						<div className={styles.contentInner}>
							{activeTab === 'account' && <AccountTab />}
							{activeTab === 'appearance' && <AppearanceTab />}
							{activeTab === 'voice' && <VoiceTab />}
							{activeTab === 'keybinds' && <KeybindsTab />}
							{activeTab === 'notifications' && <Placeholder label="Notifications" />}
							{activeTab === 'security' && <SecurityTab />}
						</div>
					</div>
				</div>
			</div>
		</div>
		<LogoutConfirmModal
			isOpen={showLogoutConfirm}
			onClose={() => setShowLogoutConfirm(false)}
			onConfirm={() => {
				setShowLogoutConfirm(false);
				void logout();
			}}
		/>
		</>,
		document.body,
	);
}

const DEFAULT_ACCENT_COLOR = '#4641D9';
const AVATAR_MAX_SIZE = 10 * 1024 * 1024;
// Join sounds are tiny by design — a 2 MB cap matches the desktop
// Discord limit and keeps upload latency in check. Audio mimeType
// accept list pulls the common web-playable formats.
const JOIN_SOUND_MAX_SIZE = 2 * 1024 * 1024;
const JOIN_SOUND_ACCEPT = 'audio/mpeg,audio/mp3,audio/wav,audio/ogg,audio/webm,audio/mp4';

export function AccountTab() {
	const userId = typeof localStorage !== 'undefined' ? localStorage.getItem('userId') : null;
	const allUsers = useQuery(api.auth.getPublicKeys) ?? [];
	const me = allUsers.find((u) => u.id === userId);
	const updateProfile = useMutation(api.auth.updateProfile);
	const generateUploadUrl = useMutation(api.files.generateUploadUrl);
	const validateUpload = useMutation(api.files.validateUpload);

	const fileInputRef = useRef<HTMLInputElement>(null);
	const joinSoundInputRef = useRef<HTMLInputElement>(null);
	const joinSoundPreviewRef = useRef<HTMLAudioElement | null>(null);
	const [displayName, setDisplayName] = useState('');
	const [aboutMe, setAboutMe] = useState('');
	const [accentColor, setAccentColor] = useState<string>(DEFAULT_ACCENT_COLOR);
	const [pendingAvatarBlob, setPendingAvatarBlob] = useState<Blob | null>(null);
	const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
	const [cropCandidate, setCropCandidate] = useState<File | null>(null);
	const [saving, setSaving] = useState(false);
	const [status, setStatus] = useState<string | null>(null);
	// Join sound state.
	//   `joinSoundBusy` — upload in flight
	//   `joinSoundError` — surface-level error (file too big, wrong type, …)
	//   `joinSoundFilename` — the currently-committed sound's filename so
	//     we can show it next to the preview button without a round-trip
	const [joinSoundBusy, setJoinSoundBusy] = useState(false);
	const [joinSoundError, setJoinSoundError] = useState<string | null>(null);
	const [joinSoundFilename, setJoinSoundFilename] = useState<string | null>(null);

	useEffect(() => {
		if (me) {
			setDisplayName(me.displayName ?? '');
			setAboutMe(me.aboutMe ?? '');
			setAccentColor((me as any).accentColor ?? DEFAULT_ACCENT_COLOR);
		}
	}, [me?.id]);

	// Revoke locally-created object URLs on unmount.
	useEffect(() => {
		return () => {
			if (avatarPreview) URL.revokeObjectURL(avatarPreview);
		};
	}, [avatarPreview]);

	const pickAvatar = () => fileInputRef.current?.click();

	const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		const file = e.target.files?.[0];
		e.target.value = '';
		if (!file) return;
		if (!file.type.startsWith('image/')) {
			setStatus('Pick an image file.');
			return;
		}
		if (file.size > AVATAR_MAX_SIZE) {
			setStatus('Avatar must be under 10 MB.');
			return;
		}
		setCropCandidate(file);
	};

	const handleCropped = (blob: Blob) => {
		setPendingAvatarBlob(blob);
		if (avatarPreview) URL.revokeObjectURL(avatarPreview);
		setAvatarPreview(URL.createObjectURL(blob));
		setCropCandidate(null);
	};

	const handleSkipCrop = () => {
		if (!cropCandidate) return;
		setPendingAvatarBlob(cropCandidate);
		if (avatarPreview) URL.revokeObjectURL(avatarPreview);
		setAvatarPreview(URL.createObjectURL(cropCandidate));
		setCropCandidate(null);
	};

	const uploadAvatar = async (blob: Blob): Promise<string> => {
		const uploadUrl = await generateUploadUrl();
		const res = await fetch(uploadUrl, {
			method: 'POST',
			headers: { 'Content-Type': blob.type || 'image/png' },
			body: blob,
		});
		if (!res.ok) throw new Error('Avatar upload failed');
		const { storageId } = (await res.json()) as { storageId: string };
		await validateUpload({ storageId: storageId as any });
		return storageId;
	};

	const pickJoinSound = () => joinSoundInputRef.current?.click();

	const handleJoinSoundFile = async (
		e: React.ChangeEvent<HTMLInputElement>,
	) => {
		const file = e.target.files?.[0];
		e.target.value = '';
		if (!file) return;
		setJoinSoundError(null);
		if (!file.type.startsWith('audio/')) {
			setJoinSoundError('Pick an audio file.');
			return;
		}
		if (file.size > JOIN_SOUND_MAX_SIZE) {
			setJoinSoundError('Join sound must be under 2 MB.');
			return;
		}
		if (!userId) return;
		setJoinSoundBusy(true);
		try {
			const uploadUrl = await generateUploadUrl();
			const res = await fetch(uploadUrl, {
				method: 'POST',
				headers: { 'Content-Type': file.type || 'audio/mpeg' },
				body: file,
			});
			if (!res.ok) throw new Error('Join sound upload failed');
			const { storageId } = (await res.json()) as { storageId: string };
			await validateUpload({ storageId: storageId as any });
			await updateProfile({
				userId: userId as any,
				joinSoundStorageId: storageId as any,
			});
			setJoinSoundFilename(file.name);
		} catch (err: any) {
			setJoinSoundError(err?.message ?? 'Upload failed.');
		} finally {
			setJoinSoundBusy(false);
		}
	};

	const handlePreviewJoinSound = () => {
		const url = (me as any)?.joinSoundUrl;
		if (!url) return;
		// Reuse a single audio element so multiple presses don't stack
		// overlapping plays. Stopping first resets the clip to the start.
		if (joinSoundPreviewRef.current) {
			joinSoundPreviewRef.current.pause();
			joinSoundPreviewRef.current.currentTime = 0;
		}
		const audio = new Audio(url);
		joinSoundPreviewRef.current = audio;
		void audio.play().catch(() => {
			setJoinSoundError('Could not play preview.');
		});
	};

	const handleRemoveJoinSound = async () => {
		if (!userId || joinSoundBusy) return;
		setJoinSoundBusy(true);
		setJoinSoundError(null);
		try {
			await updateProfile({
				userId: userId as any,
				removeJoinSound: true,
			});
			setJoinSoundFilename(null);
		} catch (err: any) {
			setJoinSoundError(err?.message ?? 'Remove failed.');
		} finally {
			setJoinSoundBusy(false);
		}
	};

	const handleSave = async () => {
		if (!userId) return;
		setSaving(true);
		setStatus(null);
		try {
			const patch: Record<string, unknown> = {
				userId,
				displayName: displayName || undefined,
				aboutMe: aboutMe || undefined,
				accentColor: accentColor || undefined,
			};
			if (pendingAvatarBlob) {
				const storageId = await uploadAvatar(pendingAvatarBlob);
				patch.avatarStorageId = storageId;
			}
			await updateProfile(patch as any);
			setPendingAvatarBlob(null);
			setStatus('Saved');
			setTimeout(() => setStatus(null), 1500);
		} catch (err: any) {
			setStatus(err?.message ?? 'Save failed');
		} finally {
			setSaving(false);
		}
	};

	const currentAvatar = avatarPreview ?? me?.avatarUrl ?? null;

	return (
		<>
			<div className={styles.profileHeader}>
				<h2 className={styles.profileSubheading}>My Account</h2>
				<p className={styles.profileDescription}>
					Edit how you appear to others in the app.
				</p>
			</div>

			{/* Profile preview card — matches the new UI's
			    "this is what your profile looks like" frame. */}
			<div
				style={{
					position: 'relative',
					marginTop: 16,
					marginBottom: 24,
					border: '1px solid var(--background-modifier-accent)',
					borderRadius: 10,
					overflow: 'hidden',
					background: 'var(--background-secondary)',
				}}
			>
				<div
					style={{
						height: 72,
						background: `linear-gradient(135deg, ${accentColor}, ${accentColor}aa)`,
					}}
				/>
				<div style={{ position: 'absolute', top: 36, left: 18 }}>
					<button
						type="button"
						onClick={pickAvatar}
						title="Change avatar"
						style={{
							width: 88,
							height: 88,
							padding: 0,
							borderRadius: '50%',
							border: '6px solid var(--background-secondary)',
							background: 'var(--background-tertiary)',
							overflow: 'hidden',
							cursor: 'pointer',
						}}
					>
						{currentAvatar ? (
							<img
								src={currentAvatar}
								alt="Avatar"
								style={{ width: '100%', height: '100%', objectFit: 'cover' }}
							/>
						) : (
							<div
								style={{
									width: '100%',
									height: '100%',
									display: 'flex',
									alignItems: 'center',
									justifyContent: 'center',
									color: 'var(--text-tertiary)',
									fontSize: 28,
									fontWeight: 700,
								}}
							>
								{(me?.displayName || me?.username || '?').slice(0, 1).toUpperCase()}
							</div>
						)}
					</button>
				</div>
				<div style={{ padding: '48px 18px 16px 18px' }}>
					<div style={{ fontSize: 18, fontWeight: 700, color: 'var(--text-primary)' }}>
						{displayName || me?.username || 'You'}
					</div>
					<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
						@{me?.username}
					</div>
					{aboutMe && (
						<div
							style={{
								marginTop: 8,
								fontSize: 13,
								color: 'var(--text-secondary)',
								whiteSpace: 'pre-wrap',
							}}
						>
							{aboutMe}
						</div>
					)}
				</div>
			</div>

			<input
				ref={fileInputRef}
				type="file"
				accept="image/*"
				style={{ display: 'none' }}
				onChange={handleFileChange}
			/>

			<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
				<FieldRow label="Username" value={me?.username ?? '—'} readOnly />
				<FieldRow
					label="Display Name"
					value={displayName}
					onChange={setDisplayName}
					placeholder="How others see you"
				/>
				<FieldRow
					label="About Me"
					value={aboutMe}
					onChange={setAboutMe}
					placeholder="Tell others about yourself"
					multiline
				/>

				<div>
					<div
						style={{
							fontSize: 12,
							fontWeight: 700,
							letterSpacing: '0.04em',
							textTransform: 'uppercase',
							color: 'var(--text-tertiary)',
							marginBottom: 8,
						}}
					>
						Accent Color
					</div>
					<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
						<input
							type="color"
							value={accentColor}
							onChange={(e) => setAccentColor(e.target.value)}
							style={{
								width: 56,
								height: 40,
								padding: 0,
								border: '1px solid var(--background-modifier-accent)',
								borderRadius: 6,
								cursor: 'pointer',
								background: 'transparent',
							}}
							aria-label="Accent color"
						/>
						<input
							type="text"
							value={accentColor}
							onChange={(e) => setAccentColor(e.target.value)}
							placeholder="#4641D9"
							spellCheck={false}
							style={{
								width: 120,
								padding: '8px 10px',
								borderRadius: 6,
								border: '1px solid var(--background-modifier-accent)',
								background: 'var(--background-tertiary)',
								color: 'var(--text-primary)',
								font: 'inherit',
								fontSize: 14,
							}}
						/>
						<span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
							Used for your profile banner.
						</span>
					</div>
				</div>

				<div>
					<div
						style={{
							fontSize: 12,
							fontWeight: 700,
							letterSpacing: '0.04em',
							textTransform: 'uppercase',
							color: 'var(--text-tertiary)',
							marginBottom: 8,
						}}
					>
						Join Sound
					</div>
					<div
						style={{
							fontSize: 12,
							color: 'var(--text-tertiary)',
							marginBottom: 10,
						}}
					>
						Plays for other users when you join a voice channel. Max 2 MB.
					</div>
					<input
						ref={joinSoundInputRef}
						type="file"
						accept={JOIN_SOUND_ACCEPT}
						style={{ display: 'none' }}
						onChange={handleJoinSoundFile}
					/>
					<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
						<button
							type="button"
							onClick={pickJoinSound}
							disabled={joinSoundBusy}
							style={{
								padding: '8px 14px',
								borderRadius: 6,
								border: '1px solid var(--background-modifier-accent)',
								background: 'var(--background-tertiary)',
								color: 'var(--text-primary)',
								cursor: joinSoundBusy ? 'not-allowed' : 'pointer',
								fontWeight: 600,
								fontSize: 13,
								opacity: joinSoundBusy ? 0.6 : 1,
							}}
						>
							{joinSoundBusy ? 'Uploading…' : 'Upload Sound'}
						</button>
						{(me as any)?.joinSoundUrl && (
							<>
								<button
									type="button"
									onClick={handlePreviewJoinSound}
									disabled={joinSoundBusy}
									style={{
										padding: '8px 14px',
										borderRadius: 6,
										border: 'none',
										background: 'var(--brand-primary)',
										color: '#fff',
										cursor: 'pointer',
										fontWeight: 600,
										fontSize: 13,
									}}
								>
									Preview
								</button>
								<button
									type="button"
									onClick={handleRemoveJoinSound}
									disabled={joinSoundBusy}
									style={{
										padding: '8px 14px',
										borderRadius: 6,
										border: 'none',
										background: 'var(--status-danger, #ed4245)',
										color: '#fff',
										cursor: 'pointer',
										fontWeight: 600,
										fontSize: 13,
									}}
								>
									Remove
								</button>
								<span
									style={{
										fontSize: 12,
										color: 'var(--text-tertiary)',
										marginLeft: 2,
									}}
								>
									{joinSoundFilename ?? 'Custom sound set'}
								</span>
							</>
						)}
					</div>
					{joinSoundError && (
						<div
							style={{
								marginTop: 8,
								fontSize: 12,
								color: 'var(--status-danger, #ed4245)',
							}}
						>
							{joinSoundError}
						</div>
					)}
				</div>

				<div style={{ display: 'flex', gap: 12, alignItems: 'center', marginTop: 8 }}>
					<button
						type="button"
						onClick={handleSave}
						disabled={saving}
						style={{
							background: 'var(--brand-primary)',
							color: '#fff',
							border: 'none',
							padding: '10px 20px',
							borderRadius: 6,
							cursor: 'pointer',
							fontWeight: 600,
							fontSize: 14,
							opacity: saving ? 0.6 : 1,
						}}
					>
						{saving ? 'Saving…' : 'Save'}
					</button>
					{status && (
						<span style={{ color: 'var(--text-secondary)', fontSize: 13 }}>{status}</span>
					)}
				</div>
			</div>

			<AvatarCropModal
				isOpen={!!cropCandidate}
				onClose={() => setCropCandidate(null)}
				file={cropCandidate}
				onCropped={handleCropped}
				onSkipCrop={handleSkipCrop}
			/>
		</>
	);
}

export function AppearanceTab() {
	const theme = useTheme?.();
	const current = theme?.theme ?? 'theme-dark';

	const options: Array<{
		id: 'theme-dark' | 'theme-light';
		label: string;
		bg: string;
		surface: string;
		bubble: string;
		text: string;
		subText: string;
	}> = [
		{
			id: 'theme-dark',
			label: 'Dark',
			bg: '#1c1d22',
			surface: '#141318',
			bubble: '#2b2d31',
			text: '#ffffff',
			subText: '#b5bac1',
		},
		{
			id: 'theme-light',
			label: 'Light',
			bg: '#ffffff',
			surface: '#e3e5e8',
			bubble: '#f2f3f5',
			text: '#060607',
			subText: '#4e5058',
		},
	];

	return (
		<>
			<div className={styles.profileHeader}>
				<h2 className={styles.profileSubheading}>Appearance</h2>
				<p className={styles.profileDescription}>Choose how the app looks.</p>
			</div>
			<div style={{ display: 'flex', gap: 16, marginTop: 16, flexWrap: 'wrap' }}>
				{options.map((opt) => {
					const selected = current === opt.id;
					return (
						<button
							key={opt.id}
							type="button"
							onClick={() => theme?.setTheme?.(opt.id)}
							aria-pressed={selected}
							style={{
								width: 200,
								height: 140,
								padding: 0,
								borderRadius: 8,
								border: `2px solid ${
									selected ? 'var(--brand-primary, #5865f2)' : 'transparent'
								}`,
								background: opt.bg,
								cursor: 'pointer',
								position: 'relative',
								overflow: 'hidden',
								display: 'flex',
								flexDirection: 'column',
								textAlign: 'left',
								boxShadow: selected
									? '0 0 0 1px rgba(0,0,0,0.2)'
									: '0 0 0 1px rgba(0,0,0,0.15)',
								transition: 'border-color 0.15s ease',
							}}
						>
							{/* sidebar strip */}
							<div
								style={{
									position: 'absolute',
									inset: 0,
									display: 'flex',
								}}
							>
								<div
									style={{
										width: 32,
										background: opt.surface,
										borderRight: `1px solid ${opt.bubble}`,
									}}
								/>
								<div style={{ flex: 1, padding: '10px 10px 28px 10px' }}>
									{/* chat bubbles */}
									<div
										style={{
											background: opt.bubble,
											color: opt.text,
											borderRadius: 6,
											padding: '4px 6px',
											fontSize: 8,
											marginBottom: 4,
											width: '70%',
										}}
									>
										<div
											style={{
												height: 4,
												width: '60%',
												background: opt.subText,
												borderRadius: 2,
												marginBottom: 3,
											}}
										/>
										<div
											style={{
												height: 4,
												width: '90%',
												background: opt.text,
												borderRadius: 2,
											}}
										/>
									</div>
									<div
										style={{
											background: opt.bubble,
											color: opt.text,
											borderRadius: 6,
											padding: '4px 6px',
											fontSize: 8,
											width: '55%',
										}}
									>
										<div
											style={{
												height: 4,
												width: '50%',
												background: opt.subText,
												borderRadius: 2,
												marginBottom: 3,
											}}
										/>
										<div
											style={{
												height: 4,
												width: '80%',
												background: opt.text,
												borderRadius: 2,
											}}
										/>
									</div>
								</div>
							</div>
							{/* footer label */}
							<div
								style={{
									position: 'absolute',
									left: 0,
									right: 0,
									bottom: 0,
									padding: '6px 10px',
									background: 'rgba(0,0,0,0.35)',
									color: '#fff',
									fontSize: 12,
									fontWeight: 600,
									display: 'flex',
									alignItems: 'center',
									justifyContent: 'space-between',
								}}
							>
								<span>{opt.label}</span>
								{selected && (
									<span
										style={{
											display: 'inline-flex',
											alignItems: 'center',
											justifyContent: 'center',
											width: 16,
											height: 16,
											borderRadius: '50%',
											background: 'var(--brand-primary, #5865f2)',
											color: '#fff',
											fontSize: 11,
											lineHeight: 1,
										}}
									>
										✓
									</span>
								)}
							</div>
						</button>
					);
				})}
			</div>

		</>
	);
}

interface VoiceSettings {
	inputDeviceId: string;
	outputDeviceId: string;
	videoDeviceId: string;
	inputVolume: number;
	noiseSuppression: boolean;
	echoCancellation: boolean;
	autoGainControl: boolean;
	recordingDir?: string;
}

const DEFAULT_VOICE_SETTINGS: VoiceSettings = {
	inputDeviceId: 'default',
	outputDeviceId: 'default',
	videoDeviceId: 'default',
	inputVolume: 100,
	noiseSuppression: true,
	echoCancellation: false,
	autoGainControl: true,
};

function loadVoiceSettings(): VoiceSettings {
	if (typeof localStorage === 'undefined') return { ...DEFAULT_VOICE_SETTINGS };
	try {
		const raw = localStorage.getItem('voiceSettings');
		if (!raw) return { ...DEFAULT_VOICE_SETTINGS };
		const parsed = JSON.parse(raw);
		return { ...DEFAULT_VOICE_SETTINGS, ...parsed };
	} catch {
		return { ...DEFAULT_VOICE_SETTINGS };
	}
}

export function VoiceTab() {
	const [settings, setSettings] = useState<VoiceSettings>(() => loadVoiceSettings());
	const [inputDevices, setInputDevices] = useState<MediaDeviceInfo[]>([]);
	const [outputDevices, setOutputDevices] = useState<MediaDeviceInfo[]>([]);
	const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([]);
	const [permissionState, setPermissionState] = useState<'unknown' | 'granted' | 'denied' | 'prompt'>(
		'unknown',
	);
	const [permissionError, setPermissionError] = useState<string | null>(null);
	const mountedRef = useRef(true);

	useEffect(() => {
		mountedRef.current = true;
		return () => {
			mountedRef.current = false;
		};
	}, []);

	const enumerateDevices = useCallback(async () => {
		if (typeof navigator === 'undefined' || !navigator.mediaDevices?.enumerateDevices) {
			return;
		}
		try {
			const devices = await navigator.mediaDevices.enumerateDevices();
			if (!mountedRef.current) return;
			setInputDevices(devices.filter((d) => d.kind === 'audioinput'));
			setOutputDevices(devices.filter((d) => d.kind === 'audiooutput'));
			setVideoDevices(devices.filter((d) => d.kind === 'videoinput'));
			// If any device has a label, permission has effectively been granted
			// (labels are empty strings until the user grants mic/camera access).
			const anyLabeled = devices.some(
				(d) => (d.kind === 'audioinput' || d.kind === 'videoinput') && !!d.label,
			);
			if (anyLabeled) {
				setPermissionState('granted');
			} else if (devices.length === 0) {
				setPermissionState('denied');
			} else {
				setPermissionState((prev) => (prev === 'granted' ? prev : 'prompt'));
			}
		} catch (err: any) {
			setPermissionError(err?.message ?? 'Failed to list audio/video devices');
		}
	}, []);

	useEffect(() => {
		void enumerateDevices();
		if (typeof navigator === 'undefined' || !navigator.mediaDevices) return;
		const onChange = () => {
			void enumerateDevices();
		};
		navigator.mediaDevices.addEventListener?.('devicechange', onChange);
		return () => {
			navigator.mediaDevices.removeEventListener?.('devicechange', onChange);
		};
	}, [enumerateDevices]);

	const requestPermission = useCallback(async () => {
		setPermissionError(null);
		try {
			const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
			// Immediately stop tracks — we only wanted the prompt so labels populate.
			stream.getTracks().forEach((t) => t.stop());
			setPermissionState('granted');
			await enumerateDevices();
		} catch (err: any) {
			setPermissionState('denied');
			setPermissionError(err?.message ?? 'Microphone access was denied');
		}
	}, [enumerateDevices]);

	const commit = useCallback((next: VoiceSettings) => {
		setSettings(next);
		try {
			localStorage.setItem('voiceSettings', JSON.stringify(next));
		} catch {
			/* ignore quota / unavailable storage */
		}
		try {
			window.dispatchEvent(
				new CustomEvent('brycord:voice-settings-changed', { detail: next }),
			);
		} catch {
			/* ignore */
		}
	}, []);

	const update = useCallback(
		<K extends keyof VoiceSettings>(key: K, value: VoiceSettings[K]) => {
			commit({ ...settings, [key]: value });
		},
		[commit, settings],
	);

	const showPermissionGate =
		permissionState !== 'granted' &&
		inputDevices.every((d) => !d.label) &&
		videoDevices.every((d) => !d.label);

	return (
		<div>
			<div className={styles.profileHeader}>
				<h3 className={styles.profileSubheading}>Voice &amp; Video</h3>
				<p className={styles.profileDescription}>
					Configure your microphone, speakers, and camera
				</p>
			</div>

			{showPermissionGate && (
				<div className={styles.voiceSection}>
					<h4 className={styles.voiceSectionTitle}>Microphone Access</h4>
					<p className={styles.settingDescription}>
						Grant microphone access so device names can be shown and devices can be
						selected.
					</p>
					<button
						type="button"
						onClick={() => void requestPermission()}
						style={{
							background: 'var(--brand-primary)',
							color: '#fff',
							border: 'none',
							padding: '10px 20px',
							borderRadius: 6,
							cursor: 'pointer',
							fontWeight: 600,
							fontSize: 14,
							marginTop: 8,
						}}
					>
						Grant microphone access
					</button>
					{permissionError && (
						<p
							className={styles.settingDescription}
							style={{ color: 'var(--status-danger, #ed4245)', marginTop: 8 }}
						>
							{permissionError}
						</p>
					)}
				</div>
			)}

			<div className={styles.voiceSection}>
				<h4 className={styles.voiceSectionTitle}>Audio</h4>
				<p className={styles.settingDescription}>
					Choose which microphone and speakers the app should use.
				</p>

				<div className={styles.voiceFieldLabel}>Input Device</div>
				<select
					className={styles.voiceSelect}
					value={settings.inputDeviceId}
					onChange={(e) => update('inputDeviceId', e.target.value)}
				>
					<option value="default">Default</option>
					{inputDevices
						.filter((d) => d.deviceId && d.deviceId !== 'default')
						.map((d) => (
							<option key={d.deviceId} value={d.deviceId}>
								{d.label || d.deviceId}
							</option>
						))}
				</select>

				<div className={styles.voiceFieldLabel}>Output Device</div>
				<select
					className={styles.voiceSelect}
					value={settings.outputDeviceId}
					onChange={(e) => update('outputDeviceId', e.target.value)}
				>
					<option value="default">Default</option>
					{outputDevices
						.filter((d) => d.deviceId && d.deviceId !== 'default')
						.map((d) => (
							<option key={d.deviceId} value={d.deviceId}>
								{d.label || d.deviceId}
							</option>
						))}
				</select>

				<div className={styles.voiceVolumeHeader}>
					<span className={styles.voiceFieldLabel}>Input Volume</span>
					<span className={styles.voiceVolumeValue}>{settings.inputVolume}%</span>
				</div>
				<input
					type="range"
					min={0}
					max={200}
					value={settings.inputVolume}
					onChange={(e) => update('inputVolume', Number(e.target.value))}
					className={styles.voiceSlider}
				/>

				<MicTest settings={settings} />
			</div>

			<div className={styles.voiceSection}>
				<h4 className={styles.voiceSectionTitle}>Audio Processing</h4>
				<p className={styles.settingDescription}>
					These settings improve voice audio quality. Disable them for music mode or legacy
					compatibility.
				</p>

				<label className={styles.voiceToggleRow}>
					<div>
						<div className={styles.voiceRadioLabel}>Echo Cancellation</div>
						<div className={styles.voiceRadioHelp}>
							Reduces echo and feedback from speakers.
						</div>
					</div>
					<input
						type="checkbox"
						checked={settings.echoCancellation}
						onChange={(e) => update('echoCancellation', e.target.checked)}
					/>
				</label>

				<label className={styles.voiceToggleRow}>
					<div>
						<div className={styles.voiceRadioLabel}>Noise Suppression</div>
						<div className={styles.voiceRadioHelp}>
							Filters out background noise like fans and keyboard typing.
						</div>
					</div>
					<input
						type="checkbox"
						checked={settings.noiseSuppression}
						onChange={(e) => update('noiseSuppression', e.target.checked)}
					/>
				</label>

				<label className={styles.voiceToggleRow}>
					<div>
						<div className={styles.voiceRadioLabel}>Auto Gain Control</div>
						<div className={styles.voiceRadioHelp}>
							Adjusts microphone volume for consistent levels.
						</div>
					</div>
					<input
						type="checkbox"
						checked={settings.autoGainControl}
						onChange={(e) => update('autoGainControl', e.target.checked)}
					/>
				</label>
			</div>

			<div className={styles.voiceSection}>
				<h4 className={styles.voiceSectionTitle}>Video</h4>
				<p className={styles.settingDescription}>Configure your camera.</p>
				<div className={styles.voiceFieldLabel}>Camera</div>
				<select
					className={styles.voiceSelect}
					value={settings.videoDeviceId}
					onChange={(e) => update('videoDeviceId', e.target.value)}
				>
					<option value="default">Default</option>
					{videoDevices
						.filter((d) => d.deviceId && d.deviceId !== 'default')
						.map((d) => (
							<option key={d.deviceId} value={d.deviceId}>
								{d.label || d.deviceId}
							</option>
						))}
				</select>
			</div>

			<RecordingSection settings={settings} update={update} />
		</div>
	);
}

/**
 * RecordingSection — picks where per-participant voice recordings
 * are saved on disk. Electron-only; renders nothing on web /
 * Android because `platform.features.hasRecording` is false.
 */
function RecordingSection({
	settings,
	update,
}: {
	settings: VoiceSettings;
	update: <K extends keyof VoiceSettings>(key: K, value: VoiceSettings[K]) => void;
}) {
	const platform = usePlatform() as any;
	const hasRecording = !!platform?.features?.hasRecording;
	const recording = platform?.recording;

	const [defaultFolder, setDefaultFolder] = useState<string | null>(null);
	const [error, setError] = useState<string | null>(null);
	const [busy, setBusy] = useState(false);

	useEffect(() => {
		if (!hasRecording || !recording) return;
		let cancelled = false;
		(async () => {
			try {
				const path = await recording.getDefaultFolder();
				if (!cancelled && typeof path === 'string') setDefaultFolder(path);
			} catch {}
		})();
		return () => {
			cancelled = true;
		};
	}, [hasRecording, recording]);

	if (!hasRecording) return null;

	const currentPath = settings.recordingDir || '';
	const effectivePath = currentPath || defaultFolder || '';
	const isDefault = !currentPath;

	const handlePick = async () => {
		if (!recording) return;
		setBusy(true);
		setError(null);
		try {
			const res = await recording.pickFolder();
			if (!res?.ok || !res.path) return;
			const v = await recording.validateFolder(res.path);
			if (!v?.valid) {
				setError(v?.error || 'Folder is not writable.');
				return;
			}
			update('recordingDir', res.path);
		} catch (err: any) {
			setError(err?.message || 'Failed to pick folder');
		} finally {
			setBusy(false);
		}
	};

	const handleOpen = async () => {
		if (!recording) return;
		try {
			await recording.openFolder(effectivePath || null);
		} catch (err) {
			console.warn('Failed to open recording folder:', err);
		}
	};

	const handleReset = () => {
		update('recordingDir', undefined as any);
	};

	return (
		<div className={styles.voiceSection}>
			<h4 className={styles.voiceSectionTitle}>Call Recording</h4>
			<p className={styles.settingDescription}>
				Per-participant voice recordings are saved here. Each participant
				gets their own audio file so you can edit them separately later.
			</p>
			<div className={styles.voiceFieldLabel}>Recording folder</div>
			<div
				style={{
					display: 'flex',
					gap: 8,
					alignItems: 'center',
					flexWrap: 'wrap',
				}}
			>
				<div
					style={{
						flex: 1,
						minWidth: 0,
						padding: '10px 12px',
						borderRadius: 6,
						background: 'var(--background-tertiary)',
						border: '1px solid var(--background-header-secondary)',
						color: 'var(--text-primary)',
						fontSize: 13,
						fontFamily: 'ui-monospace, Menlo, Consolas, monospace',
						overflow: 'hidden',
						textOverflow: 'ellipsis',
						whiteSpace: 'nowrap',
					}}
					title={effectivePath}
				>
					{effectivePath || '—'}
				</div>
				<button
					type="button"
					onClick={handlePick}
					disabled={busy}
					style={{
						padding: '8px 14px',
						background: 'var(--brand-primary)',
						color: '#fff',
						border: 'none',
						borderRadius: 6,
						fontSize: 13,
						fontWeight: 600,
						cursor: busy ? 'not-allowed' : 'pointer',
						opacity: busy ? 0.6 : 1,
					}}
				>
					Change…
				</button>
				<button
					type="button"
					onClick={handleOpen}
					style={{
						padding: '8px 14px',
						background: 'var(--background-tertiary)',
						color: 'var(--text-primary)',
						border: '1px solid var(--background-header-secondary)',
						borderRadius: 6,
						fontSize: 13,
						fontWeight: 600,
						cursor: 'pointer',
					}}
				>
					Open folder
				</button>
			</div>
			{!isDefault && (
				<button
					type="button"
					onClick={handleReset}
					style={{
						marginTop: 8,
						background: 'none',
						border: 'none',
						color: 'var(--text-secondary)',
						fontSize: 12,
						cursor: 'pointer',
						padding: 0,
						textDecoration: 'underline',
					}}
				>
					Reset to default
				</button>
			)}
			{error && (
				<div
					style={{
						marginTop: 8,
						padding: '8px 10px',
						background: 'rgba(237,66,69,0.12)',
						border: '1px solid rgba(237,66,69,0.4)',
						borderRadius: 6,
						color: 'var(--text-primary)',
						fontSize: 12,
					}}
				>
					{error}
				</div>
			)}
		</div>
	);
}

function Placeholder({ label }: { label: string }) {
	return (
		<div className={styles.profileHeader}>
			<h2 className={styles.profileSubheading}>{label}</h2>
			<p className={styles.profileDescription}>Coming soon.</p>
		</div>
	);
}

/**
 * SecurityTab — shows the user's recovery key. Reads `masterKey`
 * from sessionStorage (where LoginPage / RegisterPage stashes the
 * decrypted master key for the session). The recovery key is the
 * master key — anyone who has it can decrypt the user's private
 * keys, so we gate the display behind an explicit "Show" toggle.
 */
export function SecurityTab() {
	const [revealed, setRevealed] = useState(false);
	const [copied, setCopied] = useState(false);
	const masterKey =
		typeof sessionStorage !== 'undefined'
			? sessionStorage.getItem('masterKey')
			: null;

	const formatted = masterKey
		? (masterKey.match(/.{1,4}/g) ?? []).join('-').toUpperCase()
		: null;

	const handleCopy = () => {
		if (!masterKey) return;
		void navigator.clipboard.writeText(masterKey).then(() => {
			setCopied(true);
			setTimeout(() => setCopied(false), 1500);
		});
	};

	return (
		<div>
			<div className={styles.profileHeader}>
				<h2 className={styles.profileSubheading}>Security</h2>
				<p className={styles.profileDescription}>
					Back up your recovery key. You need it to restore your account
					on another device or if you lose your password.
				</p>
			</div>

			<div
				style={{
					marginTop: 16,
					padding: 16,
					borderRadius: 8,
					border: '1px solid var(--background-modifier-accent)',
					background: 'var(--background-secondary)',
				}}
			>
				<div
					style={{
						fontSize: 12,
						fontWeight: 700,
						textTransform: 'uppercase',
						letterSpacing: '0.04em',
						color: 'var(--text-tertiary)',
						marginBottom: 8,
					}}
				>
					Recovery Key
				</div>
				{!masterKey ? (
					<div
						style={{
							fontSize: 13,
							color: 'var(--text-secondary)',
						}}
					>
						No recovery key is available in this session. Sign out and sign
						back in to regenerate your session state.
					</div>
				) : (
					<>
						<code
							style={{
								display: 'block',
								padding: '12px 14px',
								borderRadius: 6,
								background: 'var(--background-tertiary)',
								border: '1px solid var(--background-modifier-accent)',
								fontFamily:
									'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
								fontSize: 13,
								color: 'var(--text-primary)',
								wordBreak: 'break-all',
								letterSpacing: revealed ? '0.5px' : undefined,
								filter: revealed ? 'none' : 'blur(6px)',
								userSelect: revealed ? 'text' : 'none',
								transition: 'filter 0.15s ease',
							}}
						>
							{formatted}
						</code>
						<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
							<button
								type="button"
								onClick={() => setRevealed((v) => !v)}
								style={{
									padding: '8px 14px',
									borderRadius: 6,
									border: '1px solid var(--background-modifier-accent)',
									background: 'var(--background-tertiary)',
									color: 'var(--text-primary)',
									cursor: 'pointer',
									fontWeight: 600,
									fontSize: 13,
								}}
							>
								{revealed ? 'Hide' : 'Show Recovery Key'}
							</button>
							<button
								type="button"
								onClick={handleCopy}
								disabled={!revealed}
								style={{
									padding: '8px 14px',
									borderRadius: 6,
									border: 'none',
									background: 'var(--brand-primary)',
									color: '#fff',
									cursor: revealed ? 'pointer' : 'not-allowed',
									fontWeight: 600,
									fontSize: 13,
									opacity: revealed ? 1 : 0.5,
								}}
							>
								{copied ? 'Copied!' : 'Copy'}
							</button>
						</div>
						<div
							style={{
								marginTop: 12,
								padding: 10,
								borderRadius: 6,
								background: 'rgba(237, 66, 69, 0.08)',
								border: '1px solid rgba(237, 66, 69, 0.25)',
								fontSize: 12,
								color: 'var(--text-secondary)',
							}}
						>
							<strong style={{ color: '#ed4245' }}>Keep this safe.</strong>{' '}
							Anyone with this key can access your account and decrypt
							your messages. Store it in a password manager or print it
							and put it somewhere secure.
						</div>
					</>
				)}
			</div>
		</div>
	);
}

/**
 * MicTest — live loopback test so the user can hear themselves
 * through the selected input device with the configured filters
 * applied. Pipes getUserMedia → AnalyserNode (for the level meter)
 * → GainNode (for volume) → destination. Updating `settings` while
 * the test is live tears down and re-creates the graph with the
 * new constraints.
 */
function MicTest({ settings }: { settings: VoiceSettings }) {
	const [testing, setTesting] = useState(false);
	const [level, setLevel] = useState(0);
	const [error, setError] = useState<string | null>(null);
	const streamRef = useRef<MediaStream | null>(null);
	const audioCtxRef = useRef<AudioContext | null>(null);
	const analyserRef = useRef<AnalyserNode | null>(null);
	const rafRef = useRef<number | null>(null);

	const stop = useCallback(() => {
		setTesting(false);
		setLevel(0);
		if (rafRef.current !== null) {
			cancelAnimationFrame(rafRef.current);
			rafRef.current = null;
		}
		if (streamRef.current) {
			streamRef.current.getTracks().forEach((t) => t.stop());
			streamRef.current = null;
		}
		if (audioCtxRef.current) {
			void audioCtxRef.current.close().catch(() => {});
			audioCtxRef.current = null;
		}
		analyserRef.current = null;
	}, []);

	const start = useCallback(async () => {
		setError(null);
		try {
			const audioConstraints: MediaTrackConstraints = {
				echoCancellation: settings.echoCancellation,
				noiseSuppression: settings.noiseSuppression,
				autoGainControl: settings.autoGainControl,
				channelCount: 1,
			};
			if (settings.inputDeviceId && settings.inputDeviceId !== 'default') {
				audioConstraints.deviceId = { exact: settings.inputDeviceId };
			}
			const stream = await navigator.mediaDevices.getUserMedia({
				audio: audioConstraints,
			});
			streamRef.current = stream;

			const AudioCtx =
				(window as any).AudioContext || (window as any).webkitAudioContext;
			const ctx: AudioContext = new AudioCtx();
			audioCtxRef.current = ctx;
			const source = ctx.createMediaStreamSource(stream);
			const gain = ctx.createGain();
			gain.gain.value = Math.max(0, settings.inputVolume / 100);
			const analyser = ctx.createAnalyser();
			analyser.fftSize = 256;
			analyserRef.current = analyser;
			source.connect(gain);
			gain.connect(analyser);
			// Pipe to speaker so the user hears themselves. If the user
			// has a specific output device selected, route via
			// setSinkId — only supported when `analyser -> destination`
			// goes through an <audio> element, so we do that instead.
			const audioEl = new Audio();
			audioEl.autoplay = true;
			audioEl.muted = false;
			const dest = ctx.createMediaStreamDestination();
			analyser.connect(dest);
			audioEl.srcObject = dest.stream;
			if (
				settings.outputDeviceId &&
				settings.outputDeviceId !== 'default' &&
				typeof (audioEl as any).setSinkId === 'function'
			) {
				try {
					await (audioEl as any).setSinkId(settings.outputDeviceId);
				} catch {
					/* fall back to default output */
				}
			}
			void audioEl.play().catch(() => {});

			setTesting(true);

			const data = new Uint8Array(analyser.frequencyBinCount);
			const tick = () => {
				if (!analyserRef.current) return;
				analyserRef.current.getByteTimeDomainData(data);
				let sum = 0;
				for (let i = 0; i < data.length; i++) {
					const v = (data[i] - 128) / 128;
					sum += v * v;
				}
				const rms = Math.sqrt(sum / data.length);
				setLevel(Math.min(1, rms * 3));
				rafRef.current = requestAnimationFrame(tick);
			};
			rafRef.current = requestAnimationFrame(tick);
		} catch (err: any) {
			setError(err?.message ?? 'Microphone access failed');
			stop();
		}
	}, [settings, stop]);

	// Re-apply settings by restarting the test when the user changes
	// the processing toggles or the selected device while it's live.
	useEffect(() => {
		if (!testing) return;
		stop();
		void start();
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [
		settings.echoCancellation,
		settings.noiseSuppression,
		settings.autoGainControl,
		settings.inputDeviceId,
		settings.outputDeviceId,
	]);

	// Volume changes don't need a restart — patch the gain node in place.
	useEffect(() => {
		if (!testing || !audioCtxRef.current) return;
		// The analyser chain hides the GainNode; re-use the current
		// source graph by walking AudioContext. Simpler: clamp local
		// level meter and leave track volume to the OS (LiveKit does
		// the same in-call). A real per-track gain ramp would require
		// keeping a reference to the GainNode, which we're skipping
		// to keep the loopback simple. The slider still persists.
	}, [settings.inputVolume, testing]);

	useEffect(() => {
		return () => stop();
	}, [stop]);

	return (
		<div style={{ marginTop: 16 }}>
			<div className={styles.voiceFieldLabel}>Mic Test</div>
			<div
				style={{
					display: 'flex',
					alignItems: 'center',
					gap: 12,
					marginTop: 6,
				}}
			>
				<button
					type="button"
					onClick={testing ? stop : () => void start()}
					style={{
						padding: '8px 16px',
						borderRadius: 6,
						border: 'none',
						background: testing ? 'var(--status-danger, #ed4245)' : 'var(--brand-primary)',
						color: '#fff',
						cursor: 'pointer',
						fontWeight: 600,
						fontSize: 13,
					}}
				>
					{testing ? 'Stop Test' : "Let's Check"}
				</button>
				<div
					style={{
						flex: 1,
						height: 10,
						borderRadius: 5,
						background: 'var(--background-tertiary)',
						overflow: 'hidden',
						border: '1px solid var(--background-modifier-accent)',
					}}
					aria-label="Input level"
				>
					<div
						style={{
							width: `${Math.round(level * 100)}%`,
							height: '100%',
							background:
								'linear-gradient(90deg, #3ba55d 0%, #3ba55d 60%, #faa61a 80%, #ed4245 100%)',
							transition: 'width 60ms linear',
						}}
					/>
				</div>
			</div>
			<div
				style={{
					fontSize: 12,
					color: 'var(--text-tertiary)',
					marginTop: 6,
				}}
			>
				{testing
					? 'Speak into your mic — you should hear yourself through the selected output.'
					: 'Click to hear yourself with your current filters applied.'}
			</div>
			{error && (
				<div
					style={{
						fontSize: 12,
						color: 'var(--status-danger, #ed4245)',
						marginTop: 6,
					}}
				>
					{error}
				</div>
			)}
		</div>
	);
}

interface FieldRowProps {
	label: string;
	value: string;
	onChange?: (v: string) => void;
	placeholder?: string;
	readOnly?: boolean;
	multiline?: boolean;
}

function FieldRow({ label, value, onChange, placeholder, readOnly, multiline }: FieldRowProps) {
	const baseStyle: React.CSSProperties = {
		width: '100%',
		padding: '10px 12px',
		background: 'var(--background-tertiary)',
		border: '1px solid var(--background-modifier-accent)',
		borderRadius: 6,
		color: 'var(--text-primary)',
		fontSize: 14,
		fontFamily: 'inherit',
		outline: 'none',
		boxSizing: 'border-box',
	};
	return (
		<div>
			<label
				style={{
					display: 'block',
					marginBottom: 6,
					fontSize: 12,
					fontWeight: 700,
					textTransform: 'uppercase',
					letterSpacing: 0.5,
					color: 'var(--text-secondary)',
				}}
			>
				{label}
			</label>
			{multiline ? (
				<textarea
					value={value}
					onChange={(e) => onChange?.(e.target.value)}
					placeholder={placeholder}
					readOnly={readOnly}
					rows={4}
					style={{ ...baseStyle, resize: 'vertical' }}
				/>
			) : (
				<input
					type="text"
					value={value}
					onChange={(e) => onChange?.(e.target.value)}
					placeholder={placeholder}
					readOnly={readOnly}
					style={baseStyle}
				/>
			)}
		</div>
	);
}
