import {
	For,
	Show,
	createSignal,
	createEffect,
	createMemo,
	createResource,
	type Component,
	type JSX,
} from 'solid-js';
import { Portal } from 'solid-js/web';
import { A, useNavigate, useParams, useLocation, type RouteSectionProps } from '@solidjs/router';
import { createQuery, useQueryClient } from '@tanstack/solid-query';
import {
	Settings,
	User,
	Key,
	Mail,
	Bell,
	HardDrive,
	Volleyball,
	Spool,
	Plus,
	Trash2,
	RotateCw,
	RotateCcw,
	X,
	Book,
	ShieldCheck,
	BookMarked,
	LoaderCircle,
	Save,
} from 'lucide-solid';
import { useAuth } from '../lib/auth';
import { formatRelativeTime, getErrorMessage } from '../lib/repo-utils';
import { Avatar, buttonStyles, inputStyles, textareaStyles } from '../components/common';
import { clearApiCaches, resolveActor } from '../lib/api/identity';
import { listRepoRecords } from '../lib/api/repos';
import { useAppSettings } from '../lib/settings';
import {
	listPublicKeys,
	addPublicKey,
	deletePublicKey,
	listKnots,
	registerKnot,
	unregisterKnot,
	listSpindles,
	registerSpindle,
	unregisterSpindle,
	type PublicKeyRecord,
} from '../lib/api/settings';

// Helper to compute SSH Fingerprint using SubtleCrypto
export const getSshFingerprint = async (key: string): Promise<string> => {
	try {
		const parts = key.trim().split(/\s+/);
		if (parts.length < 2) return 'Invalid SSH Key format';
		const base64Data = parts[1];
		const binaryString = atob(base64Data);
		const bytes = new Uint8Array(binaryString.length);
		for (let i = 0; i < binaryString.length; i++) {
			bytes[i] = binaryString.charCodeAt(i);
		}
		const hashBuffer = await window.crypto.subtle.digest('SHA-256', bytes);
		const hashArray = Array.from(new Uint8Array(hashBuffer));
		const hashString = hashArray.map(b => String.fromCharCode(b)).join('');
		const base64Hash = btoa(hashString).replace(/=+$/, '');
		return `SHA256:${base64Hash}`;
	} catch (e) {
		console.error('Fingerprint generation failed:', e);
		return 'Error generating fingerprint';
	}
};

// Reusable Modal Component matching design system
const Modal: Component<{
	isOpen: boolean;
	onClose: () => void;
	title: string;
	children: JSX.Element;
}> = (props) => {
	return (
		<Show when={props.isOpen}>
			<Portal>
				<div
					class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
					onClick={props.onClose}
				>
					<div
						class="bg-white dark:bg-gray-800 w-full max-w-md p-6 rounded border border-gray-200 dark:border-gray-700 shadow-xl text-gray-900 dark:text-gray-100 flex flex-col gap-5 animate-in fade-in zoom-in-95 duration-150"
						onClick={(event) => event.stopPropagation()}
					>
						<div class="flex justify-between items-center pb-1">
							<h2 class="text-sm font-bold uppercase tracking-wider">{props.title}</h2>
							<button onClick={props.onClose} class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
								<X class="size-5" />
							</button>
						</div>
						<div>{props.children}</div>
					</div>
				</div>
			</Portal>
		</Show>
	);
};

// Sidebar active/inactive styling matching upstream template
const UserSettingsTabs = [
	{ name: 'app', label: 'app', icon: Settings, path: '/settings/app' },
	{ name: 'profile', label: 'profile', icon: User, path: '/settings/profile' },
	{ name: 'keys', label: 'keys', icon: Key, path: '/settings/keys' },
	{ name: 'emails', label: 'emails', icon: Mail, path: '/settings/emails' },
	{ name: 'notifications', label: 'notifications', icon: Bell, path: '/settings/notifications' },
	{ name: 'knots', label: 'knots', icon: Volleyball, path: '/settings/knots' },
	{ name: 'spindles', label: 'spindles', icon: Spool, path: '/settings/spindles' },
];

const settingsButtonClass = 'btn flex items-center gap-2';
const settingsDangerButtonClass =
	'btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group';
const settingsRegisterButtonClass =
	'btn rounded flex items-center dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 group';
const verifiedBadgeClass =
	'min-h-8 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm';
const dashboardVerifiedBadgeClass =
	'min-h-8 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm';

export const SettingsLayout: Component<RouteSectionProps> = (props) => {
	const auth = useAuth();
	const location = useLocation();

	const isTabActive = (tabName: string, path: string) => {
		if (tabName === 'knots' && location.pathname.startsWith('/settings/knots')) {
			return true;
		}
		if (tabName === 'spindles' && location.pathname.startsWith('/settings/spindles')) {
			return true;
		}
		return location.pathname === path;
	};

	return (
		<Show
			when={auth.currentDid()}
			fallback={
				<div class="p-6 text-center">
					<p class="text-xl font-bold dark:text-white mb-4">settings</p>
					<div class="bg-white dark:bg-gray-800 p-6 rounded max-w-md mx-auto border border-gray-200 dark:border-gray-700 text-gray-500">
						Please sign in to access settings.
					</div>
				</div>
			}
		>
			<div class="p-6">
				<h1 class="text-xl font-bold dark:text-white">settings</h1>
			</div>
			<div class="bg-white dark:bg-gray-800 p-6 rounded w-full mx-auto drop-shadow-sm dark:text-white">
				<section class="untangled-settings-grid">
					{/* Sidebar Navigation */}
					<div>
						<div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner">
							<For each={UserSettingsTabs}>
								{(tab) => {
									const active = () => isTabActive(tab.name, tab.path);
									return (
										<A
											href={tab.path}
											class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"
										>
											<div
												class={`flex gap-3 items-center p-2 transition-colors ${
													active()
														? 'bg-white dark:bg-gray-700 drop-shadow-sm'
														: 'bg-gray-100 dark:bg-gray-800'
												}`}
											>
												<tab.icon class="size-4" />
												{tab.label}
											</div>
										</A>
									);
								}}
							</For>
						</div>
					</div>

					{/* Tab Content */}
					<div class="min-w-0 flex flex-col gap-6">
						{props.children}
					</div>
				</section>
			</div>
		</Show>
	);
};

export const AppSettingsTab: Component = () => {
	const settings = useAppSettings();
	const queryClient = useQueryClient();
	const [draftAppviewUrl, setDraftAppviewUrl] = createSignal(settings.appviewUrl());
	const [draftRouteKnotRequestsThroughAppview, setDraftRouteKnotRequestsThroughAppview] = createSignal(
		settings.routeKnotRequestsThroughAppview(),
	);
	const [draftForcePds, setDraftForcePds] = createSignal(settings.forcePds());
	const [error, setError] = createSignal<string | null>(null);
	const [saved, setSaved] = createSignal(false);

	createEffect(() => {
		setDraftAppviewUrl(settings.appviewUrl());
		setDraftRouteKnotRequestsThroughAppview(settings.routeKnotRequestsThroughAppview());
		setDraftForcePds(settings.forcePds());
	});

	const reloadAppviewBackedQueries = () => {
		clearApiCaches();
		void queryClient.invalidateQueries();
	};

	const applyAppviewUrl = () => {
		try {
			if (draftAppviewUrl().trim() === settings.appviewUrl()) {
				return;
			}
			settings.setAppviewUrl(draftAppviewUrl());
			setError(null);
			setSaved(true);
			reloadAppviewBackedQueries();
		} catch (cause) {
			setSaved(false);
			setError(getErrorMessage(cause));
		}
	};

	const updateRouteKnotRequestsThroughAppview = (enabled: boolean) => {
		setDraftRouteKnotRequestsThroughAppview(enabled);
		try {
			settings.setRouteKnotRequestsThroughAppview(enabled);
			setSaved(true);
			setError(null);
			reloadAppviewBackedQueries();
		} catch (cause) {
			setError(getErrorMessage(cause));
		}
	};

	const updateForcePds = (enabled: boolean) => {
		setDraftForcePds(enabled);
		try {
			settings.setForcePds(enabled);
			setSaved(true);
			setError(null);
			reloadAppviewBackedQueries();
		} catch (cause) {
			setError(getErrorMessage(cause));
		}
	};

	const onReset = () => {
		settings.resetAppviewSettings();
		setDraftAppviewUrl(settings.defaultAppviewUrl);
		setDraftRouteKnotRequestsThroughAppview(false);
		setDraftForcePds(false);
		setError(null);
		setSaved(true);
		reloadAppviewBackedQueries();
	};

	return (
		<div class="flex flex-col gap-6">
			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">APP</h2>
				<p class="text-gray-500 dark:text-gray-400 pb-2">
					Configure the local app settings. Changes save automatically.
				</p>
			</div>

			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">APPVIEW URL</h2>
				<p class="text-gray-500 dark:text-gray-400 pb-2">
					Set the appview used by the local client.
				</p>
				<div class="max-w-xl">
					<input
						id="appview-url"
						value={draftAppviewUrl()}
						onInput={(event) => {
							setDraftAppviewUrl(event.currentTarget.value);
							setSaved(false);
							setError(null);
						}}
						onBlur={applyAppviewUrl}
						onKeyDown={(event) => {
							if (event.key === 'Enter') {
								event.preventDefault();
								applyAppviewUrl();
								event.currentTarget.blur();
							}
						}}
						placeholder={settings.defaultAppviewUrl}
						class={inputStyles()}
					/>
				</div>
			</div>

			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">REQUEST ROUTING</h2>
				<p class="text-gray-500 dark:text-gray-400 pb-2">
					Control how this client routes repository requests.
				</p>
				<div class="flex flex-col gap-1.5">
					<div class="flex items-center gap-2">
						<input
							type="checkbox"
							id="routeKnotRequestsThroughAppview"
							checked={draftRouteKnotRequestsThroughAppview()}
							onChange={(event) => {
								setSaved(false);
								setError(null);
								updateRouteKnotRequestsThroughAppview(event.currentTarget.checked);
							}}
							class="my-0 py-0 rounded border-gray-300 dark:border-gray-600 text-blue-600 size-4 p-0 m-0 untangled-checkbox"
						/>
						<label
							for="routeKnotRequestsThroughAppview"
							class="my-0 py-0 normal-case font-normal text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
						>
							route knot requests through appview
						</label>
					</div>

					<div class="flex items-center gap-2">
						<input
							type="checkbox"
							id="forcePds"
							checked={draftForcePds()}
							onChange={(event) => {
								setSaved(false);
								setError(null);
								updateForcePds(event.currentTarget.checked);
							}}
							class="my-0 py-0 rounded border-gray-300 dark:border-gray-600 text-blue-600 size-4 p-0 m-0 untangled-checkbox"
						/>
						<label
							for="forcePds"
							class="my-0 py-0 normal-case font-normal text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
						>
							force requests through PDS (bypass appview)
						</label>
					</div>
				</div>
			</div>

			<div class="flex items-center justify-between gap-3 pt-1 max-w-xl">
				<button type="button" class={buttonStyles()} onClick={onReset}>
					<RotateCcw class="size-4" />
					reset
				</button>
				<Show when={saved() && !error()}>
					<p class="text-sm text-gray-500 dark:text-gray-400">saved</p>
				</Show>
			</div>

			<Show when={error()}>
				<p class="text-sm text-red-500">{error()}</p>
			</Show>
		</div>
	);
};

// Profile settings tab implementation
export const ProfileSettingsTab: Component = () => {
	const auth = useAuth();
	const [punchcardHideMine, setPunchcardHideMine] = createSignal(false);
	const [punchcardHideOthers, setPunchcardHideOthers] = createSignal(false);

	// Load punchcard preference from localStorage
	createEffect(() => {
		const did = auth.currentDid();
		if (did) {
			const mine = localStorage.getItem(`untangled.punchcard.hideMine.${did}`) === 'true';
			const others = localStorage.getItem(`untangled.punchcard.hideOthers.${did}`) === 'true';
			setPunchcardHideMine(mine);
			setPunchcardHideOthers(others);
		}
	});

	// Save punchcard preferences
	const handlePunchcardChange = (field: 'mine' | 'others', value: boolean) => {
		const did = auth.currentDid();
		if (!did) return;
		if (field === 'mine') {
			setPunchcardHideMine(value);
			localStorage.setItem(`untangled.punchcard.hideMine.${did}`, String(value));
		} else {
			setPunchcardHideOthers(value);
			localStorage.setItem(`untangled.punchcard.hideOthers.${did}`, String(value));
		}
	};

	const actorQuery = createQuery(() => ({
		queryKey: ['actor', auth.currentDid()],
		queryFn: async () => resolveActor(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	return (
		<div class="flex flex-col gap-6">
			{/* Profile Info Section */}
			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">PROFILE</h2>
				<p class="text-gray-500 dark:text-gray-400 pb-2">
					Your account information from your AT Protocol identity.
				</p>
				<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
					<div class="flex flex-col gap-1 p-4">
						<span class="text-sm text-gray-500 dark:text-gray-400">Handle</span>
						<div class="flex items-center gap-2">
							<span class="font-bold">
								{actorQuery.isLoading ? 'Loading...' : actorQuery.data?.handle || auth.currentDid()}
							</span>
						</div>
					</div>
					<div class="flex flex-col gap-1 p-4">
						<span class="text-sm text-gray-500 dark:text-gray-400">Decentralized Identifier (DID)</span>
						<span class="font-mono font-bold break-all">{auth.currentDid()}</span>
					</div>
					<div class="flex flex-col gap-1 p-4">
						<span class="text-sm text-gray-500 dark:text-gray-400">Personal Data Server (PDS)</span>
						<span class="font-bold">{actorQuery.isLoading ? 'Loading...' : actorQuery.data?.pds || 'Unknown PDS'}</span>
					</div>
				</div>
			</div>

			{/* Punchcard Preferences Section */}
			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">PUNCHCARD</h2>
				<p class="text-gray-500 dark:text-gray-400 pb-2">
					Configure punchcard visibility and preferences.
				</p>
				<div class="flex flex-col gap-1.5">
					<div class="flex items-center gap-2">
						<input
							type="checkbox"
							id="hideMine"
							checked={punchcardHideMine()}
							onChange={(e) => handlePunchcardChange('mine', e.currentTarget.checked)}
							class="my-0 py-0 rounded border-gray-300 dark:border-gray-600 text-blue-600 size-4 p-0 m-0 untangled-checkbox"
						/>
						<label for="hideMine" class="my-0 py-0 normal-case font-normal text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
							hide mine
						</label>
					</div>
					<div class="flex items-center gap-2">
						<input
							type="checkbox"
							id="hideOthers"
							checked={punchcardHideOthers()}
							onChange={(e) => handlePunchcardChange('others', e.currentTarget.checked)}
							class="my-0 py-0 rounded border-gray-300 dark:border-gray-600 text-blue-600 size-4 p-0 m-0 untangled-checkbox"
						/>
						<label for="hideOthers" class="my-0 py-0 normal-case font-normal text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
							hide others from me
						</label>
					</div>
				</div>
			</div>
		</div>
	);
};

// SSH Keys settings tab implementation
const SshKeyRow: Component<{
	keyRecord: PublicKeyRecord;
	onDelete: () => void;
}> = (props) => {
	const [fingerprint] = createResource(() => getSshFingerprint(props.keyRecord.key));

	return (
		<div class="flex items-center justify-between p-2">
			<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]">
				<div class="flex items-center gap-2 text-gray-900 dark:text-gray-100">
					<Key class="w-4 h-4 text-gray-500" />
					<span class="font-bold break-all">{props.keyRecord.name}</span>
				</div>
				<span class="font-mono text-sm text-gray-500 dark:text-gray-400 break-all select-all">
					{fingerprint.loading ? 'Calculating fingerprint...' : fingerprint()}
				</span>
				<div class="flex flex-wrap text-sm items-center gap-1 text-gray-500 dark:text-gray-400">
					<span>added {formatRelativeTime(props.keyRecord.createdAt)}</span>
				</div>
			</div>
			<button
				class={`${settingsDangerButtonClass} shrink-0`}
				title="Delete key"
				onClick={() => {
					if (confirm(`Are you sure you want to delete the key "${props.keyRecord.name}"?`)) {
						props.onDelete();
					}
				}}
			>
				<Trash2 class="w-5 h-5" />
				<span class="hidden md:inline">delete</span>
			</button>
		</div>
	);
};

export const KeysSettingsTab: Component = () => {
	const auth = useAuth();
	const queryClient = useQueryClient();
	const [isOpen, setIsOpen] = createSignal(false);
	const [keyName, setKeyName] = createSignal('');
	const [keyValue, setKeyValue] = createSignal('');
	const [loading, setLoading] = createSignal(false);
	const [error, setError] = createSignal('');

	const keysQuery = createQuery(() => ({
		queryKey: ['settings', 'keys', auth.currentDid()],
		queryFn: async () => listPublicKeys(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const handleAddKey = async (e: Event) => {
		e.preventDefault();
		setError('');
		setLoading(true);

		try {
			const agent = auth.agent();
			if (!agent) throw new Error('Not authenticated.');
			if (!keyValue().trim().startsWith('ssh-')) {
				throw new Error("Invalid key format. SSH public keys usually start with 'ssh-'.");
			}

			await addPublicKey(agent, keyName().trim(), keyValue().trim());
			await queryClient.refetchQueries({ queryKey: ['settings', 'keys', auth.currentDid()] });
			setKeyName('');
			setKeyValue('');
			setIsOpen(false);
		} catch (err: unknown) {
			console.error(err);
			setError(getErrorMessage(err));
		} finally {
			setLoading(false);
		}
	};

	const handleDeleteKey = async (rkey: string) => {
		try {
			const agent = auth.agent();
			if (!agent) throw new Error('Not authenticated.');

			await deletePublicKey(agent, rkey);
			await queryClient.refetchQueries({ queryKey: ['settings', 'keys', auth.currentDid()] });
		} catch (err: any) {
			console.error(err);
			alert(err.message || 'Failed to delete SSH key.');
		}
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
				<div class="col-span-1 md:col-span-2">
					<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">SSH KEYS</h2>
					<p class="text-gray-500 dark:text-gray-400">
						SSH public keys added here will be broadcasted to knots that you are a member of, allowing you to push to repositories there.
					</p>
				</div>
				<div class="col-span-1 md:col-span-1 md:justify-self-end">
					<button onClick={() => setIsOpen(true)} class={settingsButtonClass}>
						<Plus class="size-4" /> add key
					</button>
				</div>
			</div>

			<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
				<Show
					when={!keysQuery.isLoading}
					fallback={<div class="p-4 text-center text-gray-500">Loading SSH keys...</div>}
				>
					<For
						each={keysQuery.data}
						fallback={
							<div class="flex items-center justify-center p-4 text-gray-500">
								No keys added yet
							</div>
						}
					>
						{(k) => <SshKeyRow keyRecord={k} onDelete={() => handleDeleteKey(k.rkey)} />}
					</For>
				</Show>
			</div>

			<Modal isOpen={isOpen()} onClose={() => setIsOpen(false)} title="add ssh key">
				<form onSubmit={handleAddKey} class="flex flex-col gap-3">
					<p class="text-sm text-gray-500">SSH keys allow you to push to repositories in knots you're a member of.</p>
					<div class="flex flex-col gap-1">
						<label class="text-sm text-gray-500 font-medium">Key name</label>
						<input
							type="text"
							placeholder="Key name"
							value={keyName()}
							onInput={(e) => setKeyName(e.currentTarget.value)}
							class={inputStyles()}
							required
							disabled={loading()}
						/>
					</div>
					<div class="flex flex-col gap-1">
						<label class="text-sm text-gray-500 font-medium">Public key string</label>
						<textarea
							placeholder="ssh-rsa AAAAB3NzaC1yc2E..."
							value={keyValue()}
							onInput={(e) => setKeyValue(e.currentTarget.value)}
							class={textareaStyles()}
							rows="5"
							required
							disabled={loading()}
						/>
					</div>

					<Show when={error()}>
						<p class="text-sm text-red-500">{error()}</p>
					</Show>

					<div class="flex gap-2 pt-2">
						<button
							type="button"
							onClick={() => setIsOpen(false)}
							class={`${settingsDangerButtonClass} w-1/2 justify-center`}
							disabled={loading()}
						>
							<X class="size-4" /> cancel
						</button>
						<button
							type="submit"
							class={`${settingsButtonClass} w-1/2 justify-center`}
							disabled={loading()}
						>
							<Show when={loading()} fallback={<><Plus class="size-4" /> add</>}>
								<LoaderCircle class="size-4 animate-spin mr-1" /> adding...
							</Show>
						</button>
					</div>
				</form>
			</Modal>
		</div>
	);
};

// Emails settings tab implementation
interface EmailItem {
	address: string;
	verified: boolean;
	primary: boolean;
	createdAt: string;
}

export const EmailsSettingsTab: Component = () => {
	const auth = useAuth();
	const [emails, setEmails] = createSignal<EmailItem[]>([]);
	const [isOpen, setIsOpen] = createSignal(false);
	const [newEmail, setNewEmail] = createSignal('');

	const storageKey = createMemo(() => {
		const did = auth.currentDid();
		return did ? `untangled.emails.${did}` : null;
	});

	// Load emails from localStorage
	createEffect(() => {
		const key = storageKey();
		if (!key) return;
		const saved = localStorage.getItem(key);
		if (saved) {
			try {
				setEmails(JSON.parse(saved));
			} catch {
				setEmails([]);
			}
		} else {
			// Prepopulate with a default primary email
			const resolved = actorQuery.data;
			const prepopulated = [
				{
					address: `${resolved?.handle || 'user'}@tangled.sh`,
					verified: true,
					primary: true,
					createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
				},
			];
			setEmails(prepopulated);
			localStorage.setItem(key, JSON.stringify(prepopulated));
		}
	});

	const actorQuery = createQuery(() => ({
		queryKey: ['actor', auth.currentDid()],
		queryFn: async () => resolveActor(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const saveEmails = (list: EmailItem[]) => {
		setEmails(list);
		const key = storageKey();
		if (key) {
			localStorage.setItem(key, JSON.stringify(list));
		}
	};

	const handleAddEmail = (e: Event) => {
		e.preventDefault();
		const address = newEmail().trim();
		if (!address) return;
		if (emails().some((em) => em.address.toLowerCase() === address.toLowerCase())) {
			alert('Email address already added.');
			return;
		}

		const updated = [
			...emails(),
			{
				address,
				verified: false,
				primary: false,
				createdAt: new Date().toISOString(),
			},
		];
		saveEmails(updated);
		setNewEmail('');
		setIsOpen(false);
	};

	const handleDeleteEmail = (address: string) => {
		const list = emails();
		const target = list.find((em) => em.address === address);
		if (target?.primary) {
			alert('You cannot delete your primary email.');
			return;
		}

		if (confirm(`Are you sure you want to delete the email "${address}"?`)) {
			saveEmails(list.filter((em) => em.address !== address));
		}
	};

	const handleVerifyEmail = (address: string) => {
		const updated = emails().map((em) => {
			if (em.address === address) {
				return { ...em, verified: true };
			}
			return em;
		});
		saveEmails(updated);
		alert(`Email address ${address} successfully verified!`);
	};

	const handleResendVerification = (address: string) => {
		alert(`Verification email resent to ${address}. Click "Verify" to simulate confirming the code.`);
	};

	const handleSetPrimary = (address: string) => {
		const updated = emails().map((em) => {
			if (em.address === address) {
				return { ...em, primary: true };
			}
			if (em.primary) {
				return { ...em, primary: false };
			}
			return em;
		});
		saveEmails(updated);
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
				<div class="col-span-1 md:col-span-2">
					<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">EMAIL ADDRESSES</h2>
					<p class="text-gray-500 dark:text-gray-400">
						Commits authored using emails listed here will be associated with your Tangled profile.
					</p>
				</div>
				<div class="col-span-1 md:col-span-1 md:justify-self-end">
					<button onClick={() => setIsOpen(true)} class={settingsButtonClass}>
						<Plus class="size-4" /> add email
					</button>
				</div>
			</div>

			<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
				<For
					each={emails()}
					fallback={
						<div class="flex items-center justify-center p-4 text-gray-500">
							No emails added yet
						</div>
					}
				>
					{(email) => (
						<div class="flex items-center justify-between p-2">
							<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[70%]">
								<div class="flex items-center gap-2 text-gray-900 dark:text-gray-100 flex-wrap">
									<Mail class="w-4 h-4 text-gray-500 shrink-0" />
									<span class="font-bold break-all">{email.address}</span>
									<div class="inline-flex items-center gap-1 shrink-0">
										<Show
											when={email.verified}
											fallback={
												<span class="text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 px-2 py-1 rounded">
													unverified
												</span>
											}
										>
											<span class="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">
												verified
											</span>
										</Show>
										<Show when={email.primary}>
											<span class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded">
												primary
											</span>
										</Show>
									</div>
								</div>
								<div class="flex text-sm flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
									<span>added {formatRelativeTime(email.createdAt)}</span>
								</div>
							</div>
							<div class="flex gap-2 items-center shrink-0">
								<Show when={!email.verified}>
									<button
										onClick={() => handleVerifyEmail(email.address)}
										class="btn text-sm px-2 py-1"
										title="Verify email address"
									>
										verify
									</button>
									<button
										onClick={() => handleResendVerification(email.address)}
										class="btn flex gap-2 text-sm px-2 py-1"
									>
										<RotateCw class="w-4 h-4" />
										<span class="hidden md:inline">resend</span>
									</button>
								</Show>
								<Show when={!email.primary && email.verified}>
									<button
										onClick={() => handleSetPrimary(email.address)}
										class="btn text-sm px-2 py-1 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
									>
										set as primary
									</button>
								</Show>
								<Show when={!email.primary}>
									<button
										onClick={() => handleDeleteEmail(email.address)}
										class={settingsDangerButtonClass}
										title="Delete email"
									>
										<Trash2 class="w-5 h-5" />
										<span class="hidden md:inline">delete</span>
									</button>
								</Show>
							</div>
						</div>
					)}
				</For>
			</div>

			<Modal isOpen={isOpen()} onClose={() => setIsOpen(false)} title="add email">
				<form onSubmit={handleAddEmail} class="flex flex-col gap-3">
					<p class="text-sm text-gray-500">Commits using this email will be associated with your profile.</p>
					<div class="flex flex-col gap-1">
						<label class="text-sm text-gray-500 font-medium">Email address</label>
						<input
							type="email"
							placeholder="your@email.com"
							value={newEmail()}
							onInput={(e) => setNewEmail(e.currentTarget.value)}
							class={inputStyles()}
							required
						/>
					</div>

					<div class="flex gap-2 pt-2">
						<button
							type="button"
							onClick={() => setIsOpen(false)}
							class={`${settingsDangerButtonClass} w-1/2 justify-center`}
						>
							<X class="size-4" /> cancel
						</button>
						<button type="submit" class={`${settingsButtonClass} w-1/2 justify-center`}>
							<Plus class="size-4" /> add
						</button>
					</div>
				</form>
			</Modal>
		</div>
	);
};

// Notifications settings tab implementation
export const NotificationsSettingsTab: Component = () => {
	const auth = useAuth();
	const [starred, setStarred] = createSignal(true);
	const [newIssues, setNewIssues] = createSignal(true);
	const [issueComments, setIssueComments] = createSignal(true);
	const [issueClosed, setIssueClosed] = createSignal(true);
	const [newPulls, setNewPulls] = createSignal(true);
	const [pullComments, setPullComments] = createSignal(true);
	const [pullMerged, setPullMerged] = createSignal(true);
	const [followers, setFollowers] = createSignal(true);
	const [mentions, setMentions] = createSignal(true);
	const [emailNotif, setEmailNotif] = createSignal(false);
	const [successMsg, setSuccessMsg] = createSignal('');

	const storageKey = createMemo(() => {
		const did = auth.currentDid();
		return did ? `untangled.notifications.${did}` : null;
	});

	createEffect(() => {
		const key = storageKey();
		if (!key) return;
		const saved = localStorage.getItem(key);
		if (saved) {
			try {
				const val = JSON.parse(saved);
				setStarred(val.starred !== false);
				setNewIssues(val.newIssues !== false);
				setIssueComments(val.issueComments !== false);
				setIssueClosed(val.issueClosed !== false);
				setNewPulls(val.newPulls !== false);
				setPullComments(val.pullComments !== false);
				setPullMerged(val.pullMerged !== false);
				setFollowers(val.followers !== false);
				setMentions(val.mentions !== false);
				setEmailNotif(val.emailNotif === true);
			} catch {
				// use defaults
			}
		}
	});

	const handleSave = (e: Event) => {
		e.preventDefault();
		const key = storageKey();
		if (!key) return;

		const prefs = {
			starred: starred(),
			newIssues: newIssues(),
			issueComments: issueComments(),
			issueClosed: issueClosed(),
			newPulls: newPulls(),
			pullComments: pullComments(),
			pullMerged: pullMerged(),
			followers: followers(),
			mentions: mentions(),
			emailNotif: emailNotif(),
		};

		localStorage.setItem(key, JSON.stringify(prefs));
		setSuccessMsg('Notification preferences saved successfully.');
		setTimeout(() => setSuccessMsg(''), 4000);
	};

	return (
		<div class="flex flex-col gap-6">
			<div>
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">NOTIFICATION PREFERENCES</h2>
				<p class="text-gray-500 dark:text-gray-400">
					Choose which notifications you want to receive when activity happens on your repositories and profile.
				</p>
			</div>

			<form onSubmit={handleSave} class="flex flex-col gap-6">
				<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
					{/* Preference rows matching upstream exactly */}
					{[
						{ label: 'Repository starred', desc: 'When someone stars your repository.', state: starred, set: setStarred },
						{ label: 'New issues', desc: 'When someone creates an issue on your repository.', state: newIssues, set: setNewIssues },
						{ label: 'Issue comments', desc: "When someone comments on an issue you're involved with.", state: issueComments, set: setIssueComments },
						{ label: 'Issue closed', desc: 'When an issue on your repository is closed.', state: issueClosed, set: setIssueClosed },
						{ label: 'New pull requests', desc: 'When someone creates a pull request on your repository.', state: newPulls, set: setNewPulls },
						{ label: 'Pull request comments', desc: "When someone comments on a pull request you're involved with.", state: pullComments, set: setPullComments },
						{ label: 'Pull request merged', desc: 'When your pull request is merged.', state: pullMerged, set: setPullMerged },
						{ label: 'New followers', desc: 'When someone follows you.', state: followers, set: setFollowers },
						{ label: 'Mentions', desc: 'When someone mentions you.', state: mentions, set: setMentions },
						{ label: 'Email notifications', desc: 'Receive notifications via email in addition to in-app notifications.', state: emailNotif, set: setEmailNotif },
					].map((pref) => (
						<div class="flex items-center justify-between p-2">
							<div class="flex flex-col gap-1 pr-4">
								<span class="font-bold text-gray-900 dark:text-gray-100">{pref.label}</span>
								<span class="text-sm text-gray-500 dark:text-gray-400">{pref.desc}</span>
							</div>
							<input
								type="checkbox"
								checked={pref.state()}
								onChange={(e) => pref.set(e.currentTarget.checked)}
								class="my-0 py-0 rounded border-gray-300 dark:border-gray-600 text-blue-600 size-4 p-0 m-0 untangled-checkbox"
							/>
						</div>
					))}
				</div>

				<div class="flex justify-between items-center pt-2">
					<span class="text-sm text-green-500 font-semibold">{successMsg()}</span>
					<button type="submit" class="btn-create flex items-center gap-2 group">
						<Save class="w-4 h-4" />
						save
					</button>
				</div>
			</form>
		</div>
	);
};

// Knots settings tab implementation
export const KnotsSettingsTab: Component = () => {
	const auth = useAuth();
	const queryClient = useQueryClient();
	const [domain, setDomain] = createSignal('');
	const [loading, setLoading] = createSignal(false);
	const [error, setError] = createSignal('');

	const knotsQuery = createQuery(() => ({
		queryKey: ['settings', 'knots', auth.currentDid()],
		queryFn: async () => listKnots(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const handleRegister = async (e: Event) => {
		e.preventDefault();
		setError('');
		setLoading(true);

		const knotDomain = domain().trim();
		if (!knotDomain) return;

		try {
			const agent = auth.agent();
			if (!agent) throw new Error('Not authenticated.');
			await registerKnot(agent, knotDomain);
			await queryClient.refetchQueries({ queryKey: ['settings', 'knots', auth.currentDid()] });
			setDomain('');
		} catch (err: any) {
			console.error(err);
			setError(err.message || 'Failed to register knot.');
		} finally {
			setLoading(false);
		}
	};

	const handleDelete = async (knotDomain: string) => {
		if (
			confirm(
				`Are you sure you want to delete the knot "${knotDomain}"? All git data will stay on the knot, but repositories on it will disappear from Tangled.`
			)
		) {
			try {
				const agent = auth.agent();
				if (!agent) throw new Error('Not authenticated.');
				await unregisterKnot(agent, knotDomain);
				await queryClient.refetchQueries({ queryKey: ['settings', 'knots', auth.currentDid()] });
			} catch (err: any) {
				console.error(err);
				alert(err.message || 'Failed to delete knot.');
			}
		}
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
				<div class="col-span-1 md:col-span-2">
					<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">KNOTS</h2>
					<p class="text-gray-500 dark:text-gray-400">
						Knots are lightweight headless servers that enable users to host Git repositories with ease.
						When creating a repository, you can choose a knot to store it on.
					</p>
				</div>
				<div class="col-span-1 md:col-span-1 md:justify-self-end">
					<a
						class={settingsButtonClass}
						href="https://docs.tangled.org/knot-self-hosting-guide.html#knot-self-hosting-guide"
						target="_blank"
						rel="noopener noreferrer"
					>
						<Book class="size-4" /> docs
					</a>
				</div>
			</div>

			{/* Your Knots List */}
			<section class="rounded w-full flex flex-col gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">YOUR KNOTS</h2>
				<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 w-full divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
					<Show
						when={!knotsQuery.isLoading}
						fallback={<div class="p-4 text-center text-gray-500">Loading knots...</div>}
					>
						<For
							each={knotsQuery.data}
							fallback={
								<div class="flex items-center justify-center p-4 text-gray-500">
									No knots registered yet
								</div>
							}
						>
							{(knot) => (
								<div class="flex items-center justify-between p-2">
									<A
										href={`/settings/knots/${knot.domain}`}
										class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]"
									>
										<HardDrive class="w-4 h-4 text-gray-500 shrink-0" />
										<span class="hover:underline text-gray-900 dark:text-gray-100 break-all">
											{knot.domain}
										</span>
										<span class="text-gray-500 shrink-0">
											{formatRelativeTime(knot.createdAt)}
										</span>
									</A>
									<div class="flex gap-2 items-center shrink-0">
										<span class={verifiedBadgeClass}>
											<ShieldCheck class="w-4 h-4" /> verified
										</span>
										<button
											onClick={() => handleDelete(knot.domain)}
											class={settingsDangerButtonClass}
											title="Delete knot"
										>
											<Trash2 class="w-5 h-5" />
											<span class="hidden md:inline">delete</span>
										</button>
									</div>
								</div>
							)}
						</For>
					</Show>
				</div>
			</section>

			{/* Register a knot */}
			<section class="rounded w-full lg:w-fit flex flex-col gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">REGISTER A KNOT</h2>
				<p class="mb-2 dark:text-gray-300">Enter the hostname of your knot to get started.</p>
				<form onSubmit={handleRegister} class="max-w-2xl mb-2">
					<div class="flex gap-2">
						<input
							type="text"
							placeholder="knot.example.com"
							value={domain()}
							onInput={(e) => setDomain(e.currentTarget.value)}
							class={inputStyles()}
							required
							disabled={loading()}
						/>
						<button type="submit" class={settingsRegisterButtonClass} disabled={loading()}>
							<span class={loading() ? 'hidden' : 'inline-flex items-center gap-2 px-4'}>
								<Plus class="w-4 h-4" />
								register
							</span>
							<span class={loading() ? 'inline-flex items-center gap-2 px-4' : 'hidden'}>
								<LoaderCircle class="w-4 h-4 animate-spin" />
								registering...
							</span>
						</button>
					</div>
				</form>
				<Show when={error()}>
					<p class="text-sm text-red-500 mt-1">{error()}</p>
				</Show>
			</section>
		</div>
	);
};

// Knot Details Dashboard implementation
export const KnotDashboardTab: Component = () => {
	const params = useParams();
	const auth = useAuth();
	const navigate = useNavigate();
	const queryClient = useQueryClient();

	const reposQuery = createQuery(() => ({
		queryKey: ['repos', auth.currentDid()],
		queryFn: async () => listRepoRecords(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const knotRepos = createMemo(() => {
		const list = reposQuery.data;
		return list?.filter((r) => r.value.knot === params.domain) || [];
	});

	const actorQuery = createQuery(() => ({
		queryKey: ['actor', auth.currentDid()],
		queryFn: async () => resolveActor(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const handleDelete = async () => {
		const knotDomain = params.domain || '';
		if (!knotDomain) return;
		if (
			confirm(
				`Are you sure you want to delete the knot "${knotDomain}"? All git data will stay on the knot, but repositories on it will disappear from Tangled.`
			)
		) {
			try {
				const agent = auth.agent();
				if (!agent) throw new Error('Not authenticated.');
				await unregisterKnot(agent, knotDomain);
				await queryClient.refetchQueries({ queryKey: ['settings', 'knots', auth.currentDid()] });
				navigate('/settings/knots');
			} catch (err: any) {
				console.error(err);
				alert(err.message || 'Failed to delete knot.');
			}
		}
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="flex justify-between items-center flex-wrap gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">KNOT &middot; {params.domain}</h2>
				<div class="flex gap-2 items-center">
					<span class={dashboardVerifiedBadgeClass}>
						<ShieldCheck class="w-4 h-4" /> verified
					</span>
					<button
						onClick={handleDelete}
						class={settingsDangerButtonClass}
					>
						<Trash2 class="w-5 h-5" />
						<span class="hidden md:inline">delete</span>
					</button>
				</div>
			</div>

			<section class="bg-white dark:bg-gray-800 rounded relative w-full mx-auto border border-gray-200 dark:border-gray-700 p-4 dark:text-white flex flex-col gap-4">
				<h3 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 border-b border-gray-100 dark:border-gray-700 pb-2 mb-2">KNOT MEMBERS</h3>
				<div class="flex flex-col gap-4">
					<div>
						<div class="flex justify-between items-center">
							<div class="flex items-center gap-2">
								<Avatar did={auth.currentDid() || ''} size="size-7" />
								<span class="font-semibold text-gray-900 dark:text-gray-100">
									{actorQuery.data?.handle || auth.currentDid()}
								</span>
								<span class="text-gray-500 text-xs">(Owner)</span>
							</div>
						</div>
						<div class="ml-4 pl-4 pt-2 border-l border-gray-200 dark:border-gray-700 flex flex-col gap-2">
							<Show
								when={!reposQuery.isLoading}
								fallback={<p class="text-gray-500 text-sm">Loading repositories...</p>}
							>
								<For
									each={knotRepos()}
									fallback={
										<div class="text-gray-500 text-sm italic">
											No repositories configured yet.
										</div>
									}
								>
									{(repo) => (
										<div class="flex gap-2 items-center">
											<BookMarked class="size-4 text-gray-400" />
											<A href={`/${actorQuery.data?.handle || auth.currentDid()}/${repo.rkey}`} class="text-blue-600 dark:text-blue-400 hover:underline">
												{repo.value.name}
											</A>
										</div>
									)}
								</For>
							</Show>
						</div>
					</div>
				</div>
			</section>
		</div>
	);
};

// Spindles settings tab implementation
export const SpindlesSettingsTab: Component = () => {
	const auth = useAuth();
	const queryClient = useQueryClient();
	const [instance, setInstance] = createSignal('');
	const [loading, setLoading] = createSignal(false);
	const [error, setError] = createSignal('');

	const spindlesQuery = createQuery(() => ({
		queryKey: ['settings', 'spindles', auth.currentDid()],
		queryFn: async () => listSpindles(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const handleRegister = async (e: Event) => {
		e.preventDefault();
		setError('');
		setLoading(true);

		const spindleDomain = instance().trim();
		if (!spindleDomain) return;

		try {
			const agent = auth.agent();
			if (!agent) throw new Error('Not authenticated.');
			await registerSpindle(agent, spindleDomain);
			await queryClient.refetchQueries({ queryKey: ['settings', 'spindles', auth.currentDid()] });
			setInstance('');
		} catch (err: any) {
			console.error(err);
			setError(err.message || 'Failed to register spindle.');
		} finally {
			setLoading(false);
		}
	};

	const handleDelete = async (spindleDomain: string) => {
		if (confirm(`Are you sure you want to delete the spindle "${spindleDomain}"?`)) {
			try {
				const agent = auth.agent();
				if (!agent) throw new Error('Not authenticated.');
				await unregisterSpindle(agent, spindleDomain);
				await queryClient.refetchQueries({ queryKey: ['settings', 'spindles', auth.currentDid()] });
			} catch (err: any) {
				console.error(err);
				alert(err.message || 'Failed to delete spindle.');
			}
		}
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
				<div class="col-span-1 md:col-span-2">
					<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">SPINDLE</h2>
					<p class="text-gray-500 dark:text-gray-400">
						Spindles are small CI runners.
					</p>
				</div>
				<div class="col-span-1 md:col-span-1 md:justify-self-end">
					<a
						class={settingsButtonClass}
						href="https://docs.tangled.org/spindles.html#self-hosting-guide"
						target="_blank"
						rel="noopener noreferrer"
					>
						<Book class="size-4" /> docs
					</a>
				</div>
			</div>

			{/* Your Spindles List */}
			<section class="rounded w-full flex flex-col gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">YOUR SPINDLES</h2>
				<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 w-full divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
					<Show
						when={!spindlesQuery.isLoading}
						fallback={<div class="p-4 text-center text-gray-500">Loading spindles...</div>}
					>
						<For
							each={spindlesQuery.data}
							fallback={
								<div class="flex items-center justify-center p-4 text-gray-500">
									No spindles registered yet
								</div>
							}
						>
							{(spindle) => (
								<div class="flex items-center justify-between p-2">
									<A
										href={`/settings/spindles/${spindle.instance}`}
										class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]"
									>
										<HardDrive class="w-4 h-4 text-gray-500 shrink-0" />
										<span class="hover:underline text-gray-900 dark:text-gray-100 break-all">
											{spindle.instance}
										</span>
										<span class="text-gray-500 shrink-0">
											{formatRelativeTime(spindle.createdAt)}
										</span>
									</A>
									<div class="flex gap-2 items-center shrink-0">
										<span class={verifiedBadgeClass}>
											<ShieldCheck class="w-4 h-4" /> verified
										</span>
										<button
											onClick={() => handleDelete(spindle.instance)}
											class={settingsDangerButtonClass}
											title="Delete spindle"
										>
											<Trash2 class="w-5 h-5" />
											<span class="hidden md:inline">delete</span>
										</button>
									</div>
								</div>
							)}
						</For>
					</Show>
				</div>
			</section>

			{/* Register a spindle */}
			<section class="rounded w-full lg:w-fit flex flex-col gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">REGISTER A SPINDLE</h2>
				<p class="mb-2 dark:text-gray-300">Enter the hostname of your spindle to get started.</p>
				<form onSubmit={handleRegister} class="max-w-2xl mb-2">
					<div class="flex gap-2">
						<input
							type="text"
							placeholder="spindle.example.com"
							value={instance()}
							onInput={(e) => setInstance(e.currentTarget.value)}
							class={inputStyles()}
							required
							disabled={loading()}
						/>
						<button type="submit" class={settingsRegisterButtonClass} disabled={loading()}>
							<span class={loading() ? 'hidden' : 'inline-flex items-center gap-2 px-4'}>
								<Plus class="w-4 h-4" />
								register
							</span>
							<span class={loading() ? 'inline-flex items-center gap-2 px-4' : 'hidden'}>
								<LoaderCircle class="w-4 h-4 animate-spin" />
								registering...
							</span>
						</button>
					</div>
				</form>
				<Show when={error()}>
					<p class="text-sm text-red-500 mt-1">{error()}</p>
				</Show>
			</section>
		</div>
	);
};

// Spindle Details Dashboard implementation
export const SpindleDashboardTab: Component = () => {
	const params = useParams();
	const auth = useAuth();
	const navigate = useNavigate();
	const queryClient = useQueryClient();

	const actorQuery = createQuery(() => ({
		queryKey: ['actor', auth.currentDid()],
		queryFn: async () => resolveActor(auth.currentDid()!),
		enabled: Boolean(auth.currentDid()),
	}));

	const handleDelete = async () => {
		const spindleDomain = params.domain || '';
		if (!spindleDomain) return;
		if (confirm(`Are you sure you want to delete the spindle "${spindleDomain}"?`)) {
			try {
				const agent = auth.agent();
				if (!agent) throw new Error('Not authenticated.');
				await unregisterSpindle(agent, spindleDomain);
				await queryClient.refetchQueries({ queryKey: ['settings', 'spindles', auth.currentDid()] });
				navigate('/settings/spindles');
			} catch (err: any) {
				console.error(err);
				alert(err.message || 'Failed to delete spindle.');
			}
		}
	};

	return (
		<div class="flex flex-col gap-6">
			<div class="flex justify-between items-center flex-wrap gap-2">
				<h2 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-gray-100 mb-2">SPINDLE &middot; {params.domain}</h2>
				<div class="flex gap-2 items-center">
					<span class={dashboardVerifiedBadgeClass}>
						<ShieldCheck class="w-4 h-4" /> verified
					</span>
					<button
						onClick={handleDelete}
						class={settingsDangerButtonClass}
					>
						<Trash2 class="w-5 h-5" />
						<span class="hidden md:inline">delete</span>
					</button>
				</div>
			</div>

			<section class="bg-white dark:bg-gray-800 rounded relative w-full mx-auto border border-gray-200 dark:border-gray-700 p-4 dark:text-white flex flex-col gap-4">
				<h3 class="text-sm font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 border-b border-gray-100 dark:border-gray-700 pb-2 mb-2">SPINDLE MEMBERS</h3>
				<div class="flex flex-col gap-4">
					<div>
						<div class="flex justify-between items-center">
							<div class="flex items-center gap-2">
								<Avatar did={auth.currentDid() || ''} size="size-7" />
								<span class="font-semibold text-gray-900 dark:text-gray-100">
									{actorQuery.data?.handle || auth.currentDid()}
								</span>
								<span class="text-gray-500 text-xs">(Owner)</span>
							</div>
						</div>
						<div class="ml-4 pl-4 pt-2 border-l border-gray-200 dark:border-gray-700 flex flex-col gap-2">
							<div class="text-gray-500 text-sm italic">
								No repositories configured yet.
							</div>
						</div>
					</div>
				</div>
			</section>
		</div>
	);
};
