<script lang="ts">
	import { base } from '$app/paths';
	import { goto } from '$app/navigation';
	import { page } from '$app/state';
	import {
		api,
		ApiError,
		type AdminDto,
		type App,
		type AppDomain,
		type AppMemberDto,
		type AppRole,
		type Script
	} from '$lib/api';
	import CodeEditor from '$lib/CodeEditor.svelte';
	import ConfirmModal from '$lib/ConfirmModal.svelte';
	import ActionMenu from '$lib/ActionMenu.svelte';
	import RoleChip from '$lib/RoleChip.svelte';
	import { currentUser } from '$lib/auth';
	import { canAdminApp, canWriteApp } from '$lib/capabilities';

	const me = $derived($currentUser);

	const SAMPLE_SOURCE =
		'#{\n  statusCode: 200,\n  body: #{ ok: true, echo: ctx.request.body }\n}';

	type Tab = 'scripts' | 'domains' | 'members' | 'settings';

	let slug = $derived(page.params.slug ?? '');
	let app = $state<App | null>(null);
	let myRole = $state<AppRole | null>(null);
	let loadError = $state<string | null>(null);
	let loading = $state(true);
	let activeTab = $state<Tab>('scripts');

	let scripts = $state<Script[]>([]);
	let domains = $state<AppDomain[]>([]);
	let members = $state<AppMemberDto[]>([]);

	// Derive UI gates from the capabilities helper so the rules stay
	// in lockstep with the backend's `can()`. canAdminApp also covers
	// the Members + Settings + Domains-mutation tabs; canWriteApp
	// covers New script.
	const canWrite = $derived(canWriteApp(me, myRole));
	const canAdmin = $derived(canAdminApp(me, myRole));

	// Script create
	let showCreateScript = $state(false);
	let createScriptName = $state('');
	let createScriptDescription = $state('');
	let createScriptSource = $state(SAMPLE_SOURCE);
	let creatingScript = $state(false);
	let createScriptError = $state<string | null>(null);

	// Domain create
	let createDomainPattern = $state('');
	let creatingDomain = $state(false);
	let createDomainError = $state<string | null>(null);

	// Settings
	let editName = $state('');
	let editDescription = $state('');
	let editSlug = $state('');
	let savingSettings = $state(false);
	let settingsError = $state<string | null>(null);
	let slugTakeoverNeeded = $state<App | null>(null);

	// Delete confirmations
	let confirmingDeleteApp = $state(false);
	let deletingApp = $state(false);
	let deleteAppError = $state<string | null>(null);
	let domainToRemove = $state<AppDomain | null>(null);
	let removingDomain = $state(false);
	let removeDomainError = $state<string | null>(null);

	// Members tab
	let eligibleUsers = $state<AdminDto[]>([]);
	let eligibleLoadError = $state<string | null>(null);
	let addMemberUserId = $state('');
	let addMemberRole = $state<AppRole>('viewer');
	let addingMember = $state(false);
	let addMemberError = $state<string | null>(null);
	let memberToRemove = $state<AppMemberDto | null>(null);
	let removingMember = $state(false);
	let removeMemberError = $state<string | null>(null);
	let roleChangeBusy = $state<string | null>(null);
	let memberActionError = $state<string | null>(null);

	async function loadApp() {
		loading = true;
		loadError = null;
		try {
			const fetched = await api.apps.get(slug);
			if (fetched.redirect_to && fetched.redirect_to !== slug) {
				await goto(`${base}/apps/${fetched.redirect_to}`, { replaceState: true });
				return;
			}
			app = {
				id: fetched.id,
				slug: fetched.slug,
				name: fetched.name,
				description: fetched.description,
				created_at: fetched.created_at,
				updated_at: fetched.updated_at
			};
			myRole = fetched.my_role;
			editName = app.name;
			editDescription = app.description ?? '';
			editSlug = app.slug;
			const loaders: Promise<unknown>[] = [loadScripts(app.id), loadDomains(app.id)];
			if (canAdmin) {
				loaders.push(loadMembers(app.id), loadEligibleUsers());
			}
			await Promise.all(loaders);
		} catch (e) {
			loadError = e instanceof Error ? e.message : String(e);
		} finally {
			loading = false;
		}
	}

	async function loadScripts(appId: string) {
		try {
			scripts = await api.scripts.list({ app: appId });
		} catch (e) {
			scripts = [];
			loadError = e instanceof Error ? e.message : String(e);
		}
	}

	async function loadDomains(appId: string) {
		try {
			domains = await api.domains.listForApp(appId);
		} catch (e) {
			domains = [];
			loadError = e instanceof Error ? e.message : String(e);
		}
	}

	async function loadMembers(appId: string) {
		try {
			members = await api.appMembers.list(appId);
		} catch (e) {
			members = [];
			memberActionError = e instanceof Error ? e.message : String(e);
		}
	}

	async function loadEligibleUsers() {
		eligibleLoadError = null;
		try {
			const all = await api.admins.list();
			// Only inactive=false members are valid invite targets — the
			// API rejects everyone else anyway, so filter upfront.
			eligibleUsers = all.filter(
				(u) => u.is_active && u.instance_role === 'member'
			);
		} catch (e) {
			eligibleUsers = [];
			// member-with-app_admin can hit /apps/.../members but cannot
			// browse /admins (gated on InstanceManageUsers). The add form
			// will render disabled with the explanatory message below.
			eligibleLoadError =
				e instanceof ApiError && e.status === 403
					? 'Only instance owners/admins can browse the user directory to invite new members.'
					: e instanceof Error
						? e.message
						: String(e);
		}
	}

	const eligibleAfterFilter = $derived(
		eligibleUsers.filter((u) => !members.some((m) => m.user_id === u.id))
	);

	async function submitCreateScript(event: Event) {
		event.preventDefault();
		if (!app) return;
		creatingScript = true;
		createScriptError = null;
		try {
			await api.scripts.create({
				app_id: app.id,
				name: createScriptName.trim(),
				description: createScriptDescription.trim() || null,
				source: createScriptSource
			});
			showCreateScript = false;
			createScriptName = '';
			createScriptDescription = '';
			createScriptSource = SAMPLE_SOURCE;
			await loadScripts(app.id);
		} catch (e) {
			createScriptError = e instanceof Error ? e.message : String(e);
			if (e instanceof ApiError && e.status === 422) {
				createScriptError = `Validation: ${createScriptError}`;
			}
		} finally {
			creatingScript = false;
		}
	}

	async function submitCreateDomain(event: Event) {
		event.preventDefault();
		if (!app) return;
		creatingDomain = true;
		createDomainError = null;
		try {
			await api.domains.create(app.id, createDomainPattern.trim());
			createDomainPattern = '';
			await loadDomains(app.id);
		} catch (e) {
			createDomainError = e instanceof Error ? e.message : String(e);
		} finally {
			creatingDomain = false;
		}
	}

	function askRemoveDomain(d: AppDomain) {
		removeDomainError = null;
		domainToRemove = d;
	}

	async function confirmRemoveDomain() {
		if (!app || !domainToRemove) return;
		removingDomain = true;
		removeDomainError = null;
		try {
			await api.domains.remove(app.id, domainToRemove.id);
			domainToRemove = null;
			await loadDomains(app.id);
		} catch (e) {
			removeDomainError = e instanceof Error ? e.message : String(e);
		} finally {
			removingDomain = false;
		}
	}

	async function saveSettings(event: Event, forceTakeover = false) {
		event.preventDefault();
		if (!app) return;
		savingSettings = true;
		settingsError = null;
		if (!forceTakeover) slugTakeoverNeeded = null;
		try {
			const slugChanged = editSlug.trim() !== app.slug;
			const updated = await api.apps.update(app.id, {
				name: editName.trim() !== app.name ? editName.trim() : undefined,
				description:
					editDescription !== (app.description ?? '')
						? editDescription || null
						: undefined,
				slug: slugChanged ? editSlug.trim() : undefined,
				force_takeover: forceTakeover || undefined
			});
			if (slugChanged) {
				await goto(`${base}/apps/${updated.slug}`, { replaceState: true });
				return;
			}
			app = updated;
		} catch (e) {
			if (e instanceof ApiError && e.status === 409 && e.body) {
				const body = e.body as { conflict_kind?: string; current_app?: App };
				if (body.conflict_kind === 'historical' && body.current_app) {
					slugTakeoverNeeded = body.current_app;
					settingsError = null;
					return;
				}
			}
			settingsError = e instanceof Error ? e.message : String(e);
		} finally {
			savingSettings = false;
		}
	}

	async function submitAddMember(event: Event) {
		event.preventDefault();
		if (!app || !addMemberUserId) return;
		addingMember = true;
		addMemberError = null;
		try {
			await api.appMembers.add(app.id, {
				user_id: addMemberUserId,
				role: addMemberRole
			});
			addMemberUserId = '';
			addMemberRole = 'viewer';
			await loadMembers(app.id);
		} catch (e) {
			addMemberError = e instanceof Error ? e.message : String(e);
		} finally {
			addingMember = false;
		}
	}

	async function changeMemberRole(member: AppMemberDto, role: AppRole) {
		if (!app || member.role === role) return;
		roleChangeBusy = member.user_id;
		memberActionError = null;
		try {
			await api.appMembers.setRole(app.id, member.user_id, role);
			await loadMembers(app.id);
		} catch (e) {
			memberActionError = e instanceof Error ? e.message : String(e);
		} finally {
			roleChangeBusy = null;
		}
	}

	function askRemoveMember(member: AppMemberDto) {
		removeMemberError = null;
		memberToRemove = member;
	}

	async function confirmRemoveMember() {
		if (!app || !memberToRemove) return;
		removingMember = true;
		removeMemberError = null;
		try {
			const removedSelf = !!me && memberToRemove.user_id === me.id;
			await api.appMembers.remove(app.id, memberToRemove.user_id);
			memberToRemove = null;
			if (removedSelf) {
				// We just revoked our own access to this app; the next
				// fetch of /apps/{slug} would 403. Bounce back to the
				// apps list rather than render a broken tab.
				await goto(`${base}/apps`);
				return;
			}
			await loadMembers(app.id);
		} catch (e) {
			removeMemberError = e instanceof Error ? e.message : String(e);
		} finally {
			removingMember = false;
		}
	}

	function shortDate(iso: string): string {
		try {
			return new Date(iso).toLocaleDateString();
		} catch {
			return iso;
		}
	}

	function askDeleteApp() {
		deleteAppError = null;
		confirmingDeleteApp = true;
	}

	async function confirmDeleteApp() {
		if (!app) return;
		deletingApp = true;
		deleteAppError = null;
		try {
			// force=true cascades scripts (and thereby their routes +
			// execution logs); domains and slug-history rows cascade off
			// the app row itself.
			await api.apps.remove(app.id, { force: true });
			await goto(`${base}/apps`);
		} catch (e) {
			deleteAppError = e instanceof Error ? e.message : String(e);
		} finally {
			deletingApp = false;
		}
	}

	$effect(() => {
		void loadApp();
	});

	// Defense-in-depth: a viewer / editor following a stale link to
	// the Settings or Members tab gets bounced back to Scripts. The
	// backend still 403s the underlying calls, but no point showing an
	// empty tab.
	$effect(() => {
		if (!canAdmin && (activeTab === 'settings' || activeTab === 'members')) {
			activeTab = 'scripts';
		}
	});
</script>

{#if loading && !app}
	<p class="muted">Loading…</p>
{:else if loadError && !app}
	<div class="error">
		<strong>Could not load app.</strong>
		<p>{loadError}</p>
		<a href="{base}/apps">Back to apps</a>
	</div>
{:else if app}
	<header class="page-header">
		<div>
			<div class="breadcrumb">
				<a href="{base}/apps">Apps</a> / <code>{app.slug}</code>
			</div>
			<h1>{app.name}</h1>
			{#if app.description}<p class="muted">{app.description}</p>{/if}
		</div>
	</header>

	<nav class="tabs">
		<button
			type="button"
			class:active={activeTab === 'scripts'}
			onclick={() => (activeTab = 'scripts')}>Scripts ({scripts.length})</button
		>
		<button
			type="button"
			class:active={activeTab === 'domains'}
			onclick={() => (activeTab = 'domains')}>Domains ({domains.length})</button
		>
		{#if canAdmin}
			<button
				type="button"
				class:active={activeTab === 'members'}
				onclick={() => (activeTab = 'members')}>Members ({members.length})</button
			>
			<button
				type="button"
				class:active={activeTab === 'settings'}
				onclick={() => (activeTab = 'settings')}>Settings</button
			>
		{/if}
	</nav>

	{#if activeTab === 'scripts'}
		<section>
			<div class="row">
				<h2>Scripts</h2>
				{#if canWrite}
					<button
						type="button"
						onclick={() => (showCreateScript = !showCreateScript)}
					>
						{showCreateScript ? 'Cancel' : 'New script'}
					</button>
				{/if}
			</div>

			{#if showCreateScript && canWrite}
				<form class="create-form" onsubmit={submitCreateScript}>
					<div class="row">
						<label>
							<span>Name</span>
							<input bind:value={createScriptName} required placeholder="echo" />
						</label>
						<label>
							<span>Description</span>
							<input bind:value={createScriptDescription} placeholder="optional" />
						</label>
					</div>
					<label class="full">
						<span>Source (Rhai)</span>
						<CodeEditor bind:value={createScriptSource} language="rhai" minHeight="14rem" />
					</label>
					{#if createScriptError}
						<div class="error">{createScriptError}</div>
					{/if}
					<div class="actions">
						<button type="submit" disabled={creatingScript}>
							{creatingScript ? 'Creating…' : 'Create script'}
						</button>
					</div>
				</form>
			{/if}

			{#if scripts.length === 0}
				<p class="muted">No scripts in this app yet.</p>
			{:else}
				<ul class="list">
					{#each scripts as script (script.id)}
						<li>
							<a href="{base}/scripts/{script.id}">
								<div class="primary">
									<strong>{script.name}</strong>
									<span class="muted">v{script.version}</span>
								</div>
								<div class="secondary muted">{script.description ?? '—'}</div>
							</a>
						</li>
					{/each}
				</ul>
			{/if}
		</section>
	{:else if activeTab === 'domains'}
		<section>
			<h2>Domain claims</h2>
			<p class="muted">
				Hosts this app answers on. Routes inside this app can only bind to
				these. Use <code>app.example.com</code> for exact, <code>*.example.com</code> for
				wildcard, or <code>{'{'}tenant{'}'}.example.com</code> to bind a capture.
			</p>
			{#if canAdmin}
				<form class="create-form inline" onsubmit={submitCreateDomain}>
					<input
						bind:value={createDomainPattern}
						required
						placeholder="app.example.com"
					/>
					<button type="submit" disabled={creatingDomain}>
						{creatingDomain ? 'Adding…' : 'Add domain'}
					</button>
				</form>
				{#if createDomainError}
					<div class="error">{createDomainError}</div>
				{/if}
			{/if}
			{#if domains.length === 0}
				<p class="muted">No domain claims yet.</p>
			{:else}
				<ul class="list">
					{#each domains as d (d.id)}
						<li class="domain-row">
							<div>
								<code>{d.pattern}</code>
								<span class="muted">— {d.shape}</span>
							</div>
							{#if canAdmin}
								<button
									type="button"
									class="secondary danger"
									onclick={() => askRemoveDomain(d)}
								>
									Delete
								</button>
							{/if}
						</li>
					{/each}
				</ul>
			{/if}
		</section>
	{:else if activeTab === 'members' && canAdmin}
		<section>
			<h2>Members</h2>
			<p class="muted">
				Users with explicit access to this app. Instance owners and admins
				already have implicit access — they are not listed here. Use the Users
				page to invite a <code>member</code> first, then grant them app access
				below.
			</p>

			<form class="create-form" onsubmit={submitAddMember}>
				<div class="row">
					<label class="grow">
						<span>User</span>
						<select
							bind:value={addMemberUserId}
							disabled={!!eligibleLoadError || eligibleAfterFilter.length === 0}
							required
						>
							<option value="" disabled>Pick a member to invite…</option>
							{#each eligibleAfterFilter as u (u.id)}
								<option value={u.id}>{u.username}{u.email ? ` (${u.email})` : ''}</option>
							{/each}
						</select>
					</label>
					<label>
						<span>Role</span>
						<select bind:value={addMemberRole} disabled={!!eligibleLoadError}>
							<option value="viewer">viewer</option>
							<option value="editor">editor</option>
							<option value="app_admin">app admin</option>
						</select>
					</label>
				</div>
				{#if eligibleLoadError}
					<p class="muted">{eligibleLoadError}</p>
				{:else if eligibleAfterFilter.length === 0}
					<p class="muted">
						No eligible users to invite. Create a <code>member</code> on the Users
						page first.
					</p>
				{/if}
				{#if addMemberError}
					<div class="error">{addMemberError}</div>
				{/if}
				<div class="actions">
					<button
						type="submit"
						disabled={addingMember || !addMemberUserId || !!eligibleLoadError}
					>
						{addingMember ? 'Adding…' : 'Add member'}
					</button>
				</div>
			</form>

			{#if memberActionError}
				<div class="error">{memberActionError}</div>
			{/if}

			{#if members.length === 0}
				<p class="muted">No explicit members yet.</p>
			{:else}
				<div class="table">
					<div class="row head-row">
						<div>User</div>
						<div>Instance</div>
						<div>App role</div>
						<div>Joined</div>
						<div class="actions-col"></div>
					</div>
					{#each members as m (m.user_id)}
						<div class="row member-row" class:inactive={!m.is_active}>
							<div>
								<strong>{m.username}</strong>
								{#if m.email}<span class="muted">{m.email}</span>{/if}
								{#if !m.is_active}<span class="muted">(inactive)</span>{/if}
							</div>
							<div><RoleChip role={m.instance_role} size="sm" /></div>
							<div><RoleChip appRole={m.role} size="sm" /></div>
							<div>{shortDate(m.created_at)}</div>
							<div class="actions-col">
								<ActionMenu
									label="Member actions for {m.username}"
									items={[
										{
											label: 'Make app admin',
											disabled:
												m.role === 'app_admin' || roleChangeBusy === m.user_id,
											onClick: () => changeMemberRole(m, 'app_admin')
										},
										{
											label: 'Make editor',
											disabled:
												m.role === 'editor' || roleChangeBusy === m.user_id,
											onClick: () => changeMemberRole(m, 'editor')
										},
										{
											label: 'Make viewer',
											disabled:
												m.role === 'viewer' || roleChangeBusy === m.user_id,
											onClick: () => changeMemberRole(m, 'viewer')
										},
										{
											label: 'Remove from app',
											danger: true,
											onClick: () => askRemoveMember(m)
										}
									]}
								/>
							</div>
						</div>
					{/each}
				</div>
			{/if}
		</section>
	{:else if activeTab === 'settings' && canAdmin}
		<section>
			<h2>Settings</h2>
			<form class="create-form" onsubmit={(e) => saveSettings(e)}>
				<label>
					<span>Name</span>
					<input bind:value={editName} required />
				</label>
				<label>
					<span>Description</span>
					<input bind:value={editDescription} />
				</label>
				<label>
					<span>Slug</span>
					<input
						bind:value={editSlug}
						required
						pattern="[a-z0-9][a-z0-9-]*"
					/>
					<small class="muted">
						Renaming records the old slug as a permanent 301 redirect.
					</small>
				</label>
				{#if slugTakeoverNeeded}
					<div class="warning">
						<strong>Slug previously redirected.</strong>
						<p>
							<code>{editSlug}</code> currently redirects to
							<code>{slugTakeoverNeeded.slug}</code>. Renaming to it will break old
							links.
						</p>
						<div class="actions">
							<button
								type="button"
								class="secondary"
								onclick={() => (slugTakeoverNeeded = null)}
							>
								Cancel
							</button>
							<button
								type="button"
								onclick={(e) => saveSettings(e, true)}
								disabled={savingSettings}
							>
								{savingSettings ? 'Renaming…' : 'Rename anyway'}
							</button>
						</div>
					</div>
				{:else if settingsError}
					<div class="error">{settingsError}</div>
				{/if}
				{#if !slugTakeoverNeeded}
					<div class="actions">
						<button type="submit" disabled={savingSettings}>
							{savingSettings ? 'Saving…' : 'Save changes'}
						</button>
					</div>
				{/if}
			</form>

			<div class="danger-zone">
				<h3>Delete app</h3>
				<p class="muted">
					Permanently removes the app along with all its scripts, routes,
					execution logs, and domain claims.
				</p>
				<button type="button" class="danger" onclick={askDeleteApp}>Delete app</button>
			</div>
		</section>
	{/if}

	{#if confirmingDeleteApp}
		<ConfirmModal
			title="Delete app “{app.name}”"
			variant="danger"
			confirmLabel="Delete app"
			busyLabel="Deleting…"
			confirmPhrase={app.slug}
			confirmPhrasePrompt="Type the app slug to confirm:"
			busy={deletingApp}
			onConfirm={confirmDeleteApp}
			onCancel={() => (confirmingDeleteApp = false)}
		>
			<p>
				This will <strong>permanently delete</strong> everything inside
				<strong>{app.name}</strong>. There is no undo.
			</p>
			<ul class="impact-list">
				<li>
					<span>Scripts</span><strong>{scripts.length}</strong>
				</li>
				<li>
					<span>Domain claims</span><strong>{domains.length}</strong>
				</li>
				<li>
					<span>Routes &amp; execution logs</span><strong>all</strong>
				</li>
			</ul>
			{#if domains.length > 0}
				<p>The following hosts will stop pointing at this app:</p>
				<ul class="impact-list">
					{#each domains as d (d.id)}
						<li>
							<code>{d.pattern}</code><span class="muted">{d.shape}</span>
						</li>
					{/each}
				</ul>
			{/if}
			{#if deleteAppError}
				<p class="modal-error">{deleteAppError}</p>
			{/if}
		</ConfirmModal>
	{/if}

	{#if domainToRemove}
		<ConfirmModal
			title="Delete domain claim"
			variant="danger"
			confirmLabel="Delete claim"
			busyLabel="Deleting…"
			busy={removingDomain}
			onConfirm={confirmRemoveDomain}
			onCancel={() => (domainToRemove = null)}
		>
			<p>
				<strong>{app.name}</strong> will stop answering on
				<code>{domainToRemove.pattern}</code>.
			</p>
			<p class="muted">
				Routes already bound to this host are blocked from deletion by the
				API; if so, you’ll see an error here.
			</p>
			{#if removeDomainError}
				<p class="modal-error">{removeDomainError}</p>
			{/if}
		</ConfirmModal>
	{/if}

	{#if memberToRemove}
		<ConfirmModal
			title="Remove {memberToRemove.username} from {app.name}"
			variant="danger"
			confirmLabel="Remove member"
			busyLabel="Removing…"
			busy={removingMember}
			onConfirm={confirmRemoveMember}
			onCancel={() => (memberToRemove = null)}
		>
			<p>
				<strong>{memberToRemove.username}</strong> will lose access to this
				app. Their other app memberships and account are untouched.
			</p>
			{#if removeMemberError}
				<p class="modal-error">{removeMemberError}</p>
			{/if}
		</ConfirmModal>
	{/if}
{/if}

<style>
	.page-header {
		margin-bottom: 1rem;
	}

	.breadcrumb {
		font-size: 0.875rem;
		color: #64748b;
		margin-bottom: 0.25rem;
	}

	.breadcrumb a {
		color: #94a3b8;
		text-decoration: none;
	}

	.breadcrumb a:hover {
		color: #e2e8f0;
	}

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

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

	h2 {
		font-size: 1.125rem;
		margin: 0 0 1rem;
	}

	h3 {
		font-size: 1rem;
		margin: 0 0 0.5rem;
	}

	.tabs {
		display: flex;
		gap: 0.25rem;
		border-bottom: 1px solid #1e293b;
		margin-bottom: 1.25rem;
	}

	.tabs button {
		background: transparent;
		color: #94a3b8;
		border: none;
		padding: 0.6rem 1rem;
		font: inherit;
		cursor: pointer;
		border-bottom: 2px solid transparent;
	}

	.tabs button:hover {
		color: #e2e8f0;
	}

	.tabs button.active {
		color: #38bdf8;
		border-bottom-color: #38bdf8;
	}

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

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

	button.danger {
		background: #7f1d1d;
		color: #fecaca;
	}

	button.secondary.danger {
		background: transparent;
		color: #fca5a5;
		border-color: #7f1d1d;
	}

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

	.muted {
		color: #64748b;
	}

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

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

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

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

	.create-form.inline {
		flex-direction: row;
		align-items: center;
		gap: 0.5rem;
	}

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

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

	.create-form label.full {
		grid-column: 1 / -1;
	}

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

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

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

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

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

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

	.domain-row {
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding: 0.85rem 1rem;
		background: #1e293b;
		border-radius: 0.375rem;
	}

	.domain-row code {
		background: #0b1220;
		padding: 0.15rem 0.4rem;
		border-radius: 0.25rem;
	}

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

	.secondary {
		font-size: 0.875rem;
	}

	.danger-zone {
		margin-top: 2rem;
		padding: 1rem;
		border: 1px solid #7f1d1d;
		border-radius: 0.5rem;
		background: #1e0a0a;
	}

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

	.create-form .row > label.grow {
		grid-column: span 2;
	}

	.table {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
	}

	.table .row {
		display: grid;
		grid-template-columns: 2fr 1fr 1fr 1fr 3rem;
		gap: 0.75rem;
		padding: 0.85rem 1rem;
		background: #1e293b;
		border-radius: 0.375rem;
		align-items: center;
		margin: 0;
	}

	.table .head-row {
		background: transparent;
		padding: 0.25rem 1rem;
		color: #64748b;
		font-size: 0.75rem;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}

	.table .member-row.inactive {
		opacity: 0.55;
	}

	.table .member-row strong {
		margin-right: 0.4rem;
	}

	.table .member-row .muted {
		font-size: 0.8rem;
	}

	.table .actions-col {
		display: flex;
		justify-content: flex-end;
	}
</style>
