<script lang="ts">
	import { page } from '$app/stores';
	import { goto } from '$app/navigation';
	import { connectionState, nodeStore, edgeStore, nodeAccessStore, nodeVisibility } from '$lib/realtime';
	import type { Node } from '$lib/realtime';
	import { fetchMyWorkspace, updateNode, createNode, createEdge } from '$lib/api';
	import { signOut } from '@auth/sveltekit/client';

	// Canvas + BlockShell
	import Canvas from '$lib/components/canvas/Canvas.svelte';
	import type { CanvasObject, Camera } from '$lib/components/canvas/types.js';
	import BlockShell from '$lib/components/blockshell/BlockShell.svelte';

	// Workspace layout
	import {
		type PanelLayout,
		type WorkspaceLayout,
		getPanelInfo,
		TRAIT_PANEL_INFO,
		PANEL_HEADER_HEIGHT,
	} from '$lib/workspace/types.js';

	// Recency tracking for context selector
	import { getRankedNodeIds } from '$lib/workspace/recency.js';

	// Trait components (reuse from collection page)
	import EditorTrait from '$lib/components/traits/EditorTrait.svelte';
	import ChatTrait from '$lib/components/traits/ChatTrait.svelte';
	import KanbanTrait from '$lib/components/traits/KanbanTrait.svelte';
	import PodcastTrait from '$lib/components/traits/PodcastTrait.svelte';
	import PublishingTrait from '$lib/components/traits/PublishingTrait.svelte';
	import RssTrait from '$lib/components/traits/RssTrait.svelte';
	import CalendarTrait from '$lib/components/traits/CalendarTrait.svelte';
	import RecordingTrait from '$lib/components/traits/RecordingTrait.svelte';
	import TranscriptionTrait from '$lib/components/traits/TranscriptionTrait.svelte';
	import StudioTrait from '$lib/components/traits/StudioTrait.svelte';
	import MixerTrait from '$lib/components/traits/MixerTrait.svelte';
	import MindMapTrait from '$lib/components/traits/MindMapTrait.svelte';
	import GenericTrait from '$lib/components/traits/GenericTrait.svelte';
	import AiToolPanel from '$lib/components/AiToolPanel.svelte';
	import NodeUsage from '$lib/components/NodeUsage.svelte';

	import { createBlockReceiver, executeTransfer, resolveTransferMode, type DragPayload } from '$lib/transfer';
	import type { BlockReceiver } from '$lib/components/blockshell/types';

	const session = $derived($page.data.session as Record<string, unknown> | undefined);
	const nodeId = $derived(session?.nodeId as string | undefined);
	const accessToken = $derived(session?.accessToken as string | undefined);
	const connected = $derived(connectionState.current === 'connected');

	// =========================================================================
	// Workspace node (fetched from backend)
	// =========================================================================

	let workspaceNodeId = $state<string | undefined>(undefined);
	let workspaceLoading = $state(true);
	let workspaceError = $state<string | undefined>(undefined);

	// Fetch or create workspace on load
	$effect(() => {
		if (!accessToken) return;
		workspaceLoading = true;
		fetchMyWorkspace(accessToken)
			.then((res) => {
				workspaceNodeId = res.node_id;
				// Initialize layout from backend metadata if we don't have one yet
				if (!layoutInitialized && res.metadata?.workspace_layout) {
					const saved = res.metadata.workspace_layout as WorkspaceLayout;
					if (saved.panels && Array.isArray(saved.panels) && saved.panels.length > 0) {
						layout = saved;
					}
					layoutInitialized = true;
				} else if (!layoutInitialized) {
					layoutInitialized = true;
				}
				// Load theme preferences
				if (res.metadata) {
					loadThemeFromMetadata(res.metadata as Record<string, unknown>);
					// Load saved camera position
					const meta = res.metadata as Record<string, unknown>;
					const cam = meta.camera as Camera | undefined;
					if (cam && typeof cam.x === 'number') {
						savedCamera = cam;
					}
					if (typeof meta.gridEnabled === 'boolean') {
						gridEnabled = meta.gridEnabled;
					}
				}
			})
			.catch((err) => {
				workspaceError = err.message;
			})
			.finally(() => {
				workspaceLoading = false;
			});
	});

	// Also try to read layout from WS node store (for real-time sync)
	const workspaceNode = $derived(
		workspaceNodeId && connected ? nodeStore.get(workspaceNodeId) : undefined
	);

	// =========================================================================
	// Layout state
	// =========================================================================

	let layout = $state<WorkspaceLayout>({ panels: [] });
	let layoutInitialized = $state(false);
	let savedCamera = $state<Camera>({ x: 0, y: 0, zoom: 1.0 });
	let gridEnabled = $state(false);

	// When workspace node appears in store (after creation), load its layout
	$effect(() => {
		if (!workspaceNode || layoutInitialized) return;
		try {
			const meta = JSON.parse(workspaceNode.metadata ?? '{}');
			if (meta.workspace_layout?.panels?.length > 0) {
				layout = meta.workspace_layout as WorkspaceLayout;
			}
		} catch { /* ignore */ }
		layoutInitialized = true;
	});

	/** Convert layout panels to CanvasObjects */
	const canvasObjects = $derived<CanvasObject[]>(
		layout.panels.map(p => ({
			id: p.trait,
			x: p.x,
			y: p.y,
			width: p.width,
			height: p.minimized ? PANEL_HEADER_HEIGHT : p.height,
		}))
	);

	// =========================================================================
	// Layout persistence (debounced save to workspace node metadata)
	// =========================================================================

	let saveTimeout: ReturnType<typeof setTimeout> | undefined;

	/** Persist all workspace metadata (layout + preferences) */
	function persistMetadata() {
		if (!accessToken || !workspaceNodeId) return;
		clearTimeout(saveTimeout);
		saveTimeout = setTimeout(async () => {
			try {
				const currentMeta = workspaceNode
					? JSON.parse(workspaceNode.metadata ?? '{}')
					: {};
				await updateNode(accessToken!, {
					node_id: workspaceNodeId!,
					metadata: {
						...currentMeta,
						workspace_layout: layout,
						camera: savedCamera,
						gridEnabled,
						preferences: {
							...(currentMeta.preferences ?? {}),
							theme: { hueBg: themeHueBg, hueSurface: themeHueSurface, hueAccent: themeHueAccent },
						},
					},
				});
			} catch (err) {
				console.warn('Failed to persist workspace metadata:', err);
			}
		}, 1000);
	}

	// Keep old name as alias for callers
	function persistLayout() { persistMetadata(); }

	function handleObjectMove(id: string, x: number, y: number) {
		const idx = layout.panels.findIndex(p => p.trait === id);
		if (idx >= 0) {
			layout.panels[idx] = { ...layout.panels[idx], x, y };
			layout = { ...layout };
			persistLayout();
		}
	}

	function handlePanelResize(trait: string, width: number, height: number, dx?: number, dy?: number) {
		const idx = layout.panels.findIndex(p => p.trait === trait);
		if (idx >= 0) {
			const panel = layout.panels[idx];
			layout.panels[idx] = {
				...panel,
				width,
				height,
				x: panel.x + (dx ?? 0),
				y: panel.y + (dy ?? 0),
			};
			layout = { ...layout };
			persistLayout();
		}
	}

	function handlePanelClose(trait: string) {
		layout = {
			panels: layout.panels.filter(p => p.trait !== trait),
		};
		persistLayout();
	}

	function handlePanelMinimize(trait: string, isMinimized: boolean) {
		const idx = layout.panels.findIndex(p => p.trait === trait);
		if (idx >= 0) {
			layout.panels[idx] = { ...layout.panels[idx], minimized: isMinimized };
			layout = { ...layout };
			persistLayout();
		}
	}

	function handleAddPanel(trait: string) {
		if (layout.panels.some(p => p.trait === trait)) return;
		const info = getPanelInfo(trait);
		const maxY = layout.panels.length > 0
			? Math.max(...layout.panels.map(p => p.y + p.height))
			: 0;
		layout = {
			panels: [
				...layout.panels,
				{
					trait,
					x: 30,
					y: maxY + 30,
					width: info.defaultWidth,
					height: info.defaultHeight,
				},
			],
		};
		persistLayout();
	}

	const activeLayoutTraits = $derived(layout.panels.map(p => p.trait));

	// =========================================================================
	// Context selector (navigate to collections)
	// =========================================================================

	let selectorOpen = $state(false);
	let searchQuery = $state('');
	let searchInput = $state<HTMLInputElement | undefined>(undefined);

	const collectionNodes = $derived.by((): Node[] => {
		if (!connected || !nodeId) return [];
		const accessibleIds = new Set<string>();
		for (const edge of edgeStore.bySource(nodeId)) {
			if (!edge.system) accessibleIds.add(edge.targetId);
		}
		for (const id of nodeAccessStore.objectsForSubject(nodeId)) {
			accessibleIds.add(id);
		}
		const collections: Node[] = [];
		for (const id of accessibleIds) {
			const node = nodeStore.get(id);
			if (node && node.nodeKind === 'collection' && nodeVisibility(node, nodeId) !== 'hidden') {
				collections.push(node);
			}
		}
		const ranked = getRankedNodeIds();
		const rankMap = new Map(ranked.map((id, i) => [id, i]));
		collections.sort((a, b) => {
			const ra = rankMap.get(a.id) ?? 999;
			const rb = rankMap.get(b.id) ?? 999;
			if (ra !== rb) return ra - rb;
			return (a.title ?? '').localeCompare(b.title ?? '');
		});
		return collections;
	});

	const filteredCollections = $derived.by(() => {
		if (!searchQuery.trim()) return collectionNodes;
		const q = searchQuery.toLowerCase().trim();
		return collectionNodes.filter(n => (n.title ?? '').toLowerCase().includes(q));
	});

	const myCollections = $derived(filteredCollections.filter(n => n.createdBy === nodeId));
	const sharedCollections = $derived(filteredCollections.filter(n => n.createdBy !== nodeId));

	let renamingId = $state<string | undefined>(undefined);
	let renameValue = $state('');

	function startRename(node: Node) {
		renamingId = node.id;
		renameValue = node.title || '';
	}

	async function commitRename() {
		if (!renamingId || !accessToken) return;
		const id = renamingId;
		renamingId = undefined;
		if (renameValue.trim()) {
			try {
				await updateNode(accessToken, { node_id: id, title: renameValue.trim() });
			} catch (e) {
				console.error('Feil ved omdøping:', e);
			}
		}
	}

	function handleRenameKeydown(e: KeyboardEvent) {
		if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
		if (e.key === 'Escape') { renamingId = undefined; }
	}

	let isCreatingWorkspace = $state(false);

	async function createNewWorkspace() {
		if (!accessToken || !nodeId || isCreatingWorkspace) return;
		isCreatingWorkspace = true;
		selectorOpen = false;
		try {
			const { node_id } = await createNode(accessToken, {
				node_kind: 'collection',
				title: 'Ny arbeidsflate',
				visibility: 'hidden',
				metadata: {
					traits: {
						chat: {},
						editor: {},
						kanban: {},
						calendar: {},
					}
				},
			});
			await createEdge(accessToken, {
				source_id: nodeId,
				target_id: node_id,
				edge_type: 'owner',
			});
			goto(`/collection/${node_id}`);
		} catch (e) {
			console.error('Feil ved oppretting av arbeidsflate:', e);
		} finally {
			isCreatingWorkspace = false;
		}
	}

	function toggleSelector() {
		selectorOpen = !selectorOpen;
		toolMenuOpen = false;
		settingsOpen = false;
		if (selectorOpen) {
			searchQuery = '';
			renamingId = undefined;
			requestAnimationFrame(() => searchInput?.focus());
		}
	}

	function selectCollection(id: string) {
		selectorOpen = false;
		goto(`/collection/${id}`);
	}

	// =========================================================================
	// Tool menu
	// =========================================================================

	let toolMenuOpen = $state(false);

	const availableTools = $derived.by(() => {
		return Object.entries(TRAIT_PANEL_INFO).map(([key, info]) => ({
			key,
			...info,
			active: activeLayoutTraits.includes(key),
		}));
	});

	function toggleToolMenu() {
		toolMenuOpen = !toolMenuOpen;
		selectorOpen = false;
		settingsOpen = false;
	}

	function addTool(trait: string) {
		handleAddPanel(trait);
		toolMenuOpen = false;
	}

	// =========================================================================
	// Settings menu (theme + sign out)
	// =========================================================================

	let settingsOpen = $state(false);
	let themeHueBg = $state(0);
	let themeHueSurface = $state(0);
	let themeHueAccent = $state(240);  // default indigo ≈ 240

	function toggleSettings() {
		settingsOpen = !settingsOpen;
		selectorOpen = false;
		toolMenuOpen = false;
	}

	function hslColor(hue: number, sat: number, light: number): string {
		return `hsl(${hue}, ${sat}%, ${light}%)`;
	}

	function applyTheme() {
		const root = document.documentElement;
		root.style.setProperty('--color-bg', hslColor(themeHueBg, themeHueBg ? 10 : 0, 4));
		root.style.setProperty('--color-surface', hslColor(themeHueSurface, themeHueSurface ? 8 : 0, 12));
		root.style.setProperty('--color-surface-hover', hslColor(themeHueSurface, themeHueSurface ? 6 : 0, 15));
		root.style.setProperty('--color-border', hslColor(themeHueSurface, themeHueSurface ? 5 : 0, 18));
		root.style.setProperty('--color-accent', hslColor(themeHueAccent, 70, 60));
		root.style.setProperty('--color-accent-hover', hslColor(themeHueAccent, 70, 65));
		root.style.setProperty('--color-accent-glow', `hsla(${themeHueAccent}, 70%, 60%, 0.15)`);
		persistMetadata();
	}

	function loadThemeFromMetadata(meta: Record<string, unknown>) {
		const prefs = meta.preferences as Record<string, unknown> | undefined;
		const theme = prefs?.theme as Record<string, number> | undefined;
		if (theme) {
			themeHueBg = theme.hueBg ?? 0;
			themeHueSurface = theme.hueSurface ?? 0;
			themeHueAccent = theme.hueAccent ?? 240;
			applyTheme();
		}
	}

	// =========================================================================
	// Mobile detection
	// =========================================================================

	let windowWidth = $state(typeof window !== 'undefined' ? window.innerWidth : 1024);
	const isMobile = $derived(windowWidth < 768);
	let activeTab = $state(0);

	$effect(() => {
		function onResize() { windowWidth = window.innerWidth; }
		window.addEventListener('resize', onResize);
		return () => window.removeEventListener('resize', onResize);
	});

	$effect(() => {
		if (activeTab >= layout.panels.length && layout.panels.length > 0) {
			activeTab = 0;
		}
	});

	// =========================================================================
	// Click outside
	// =========================================================================

	function handleClickOutside(e: MouseEvent) {
		const target = e.target as HTMLElement;
		if (selectorOpen && !target.closest('.context-selector')) {
			selectorOpen = false;
		}
		if (toolMenuOpen && !target.closest('.tool-menu')) {
			toolMenuOpen = false;
		}
		if (settingsOpen && !target.closest('.settings-menu')) {
			settingsOpen = false;
		}
	}

	function handleKeydown(e: KeyboardEvent) {
		if (e.key === 'Escape') {
			selectorOpen = false;
			toolMenuOpen = false;
			settingsOpen = false;
		}
	}

	/** Trait components that have dedicated implementations */
	const knownTraits = new Set([
		'editor', 'chat', 'kanban', 'podcast', 'publishing',
		'rss', 'calendar', 'recording', 'transcription', 'studio', 'mixer', 'mindmap',
		'ai', 'usage'
	]);

	// =========================================================================
	// BlockReceiver per tool type — wired into BlockShell for drop compatibility
	// Ref: docs/features/universell_overfoering.md § 4
	// =========================================================================

	/** Traits that support BlockReceiver drops */
	const RECEIVER_TRAITS = new Set(['editor', 'chat', 'kanban', 'calendar', 'studio']);

	/** Cache receivers by trait name — one per type */
	const receiverCache = new Map<string, BlockReceiver>();

	function getReceiverForTrait(trait: string): BlockReceiver | undefined {
		if (!RECEIVER_TRAITS.has(trait)) return undefined;
		if (!receiverCache.has(trait)) {
			const toolType = trait as import('$lib/transfer').ToolType;
			receiverCache.set(trait, createBlockReceiver(toolType));
		}
		return receiverCache.get(trait);
	}

	function handlePanelDrop(trait: string, payload: DragPayload, shiftKey: boolean = false) {
		const receiver = getReceiverForTrait(trait);
		if (!receiver?.receive) {
			console.warn(`[universell-overføring] Ingen mottaker for ${trait}`);
			return;
		}

		const intent = receiver.receive(payload);
		const mode = resolveTransferMode(
			payload.sourcePanel,
			trait as import('$lib/transfer').ToolType,
			shiftKey,
		);
		// Override intent mode with centrally resolved mode
		intent.mode = mode;

		if (!accessToken) {
			console.error('[universell-overføring] Mangler accessToken — kan ikke utføre overføring');
			return;
		}

		console.log(`[universell-overføring] ${payload.sourcePanel} → ${trait} (${mode}${shiftKey ? ', shift' : ''}):`, intent);

		executeTransfer(accessToken, payload, intent, shiftKey)
			.then((result) => {
				console.log(`[universell-overføring] Fullført:`, result);
			})
			.catch((err) => {
				console.error(`[universell-overføring] Feil:`, err);
			});
	}
</script>

<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />

<div class="workspace-page">
	<!-- Header -->
	<header class="context-header">
		<div class="context-header-inner">
			<div class="context-header-left">
				<div class="context-selector">
					<button
						class="context-selector-trigger"
						onclick={toggleSelector}
						title="Bytt kontekst"
					>
						<span class="context-selector-title">Min arbeidsflate</span>
						<span class="context-selector-chevron" class:open={selectorOpen}>&#9662;</span>
					</button>

					{#if selectorOpen}
						<div class="context-selector-dropdown">
							<div class="context-selector-search">
								<input
									bind:this={searchInput}
									bind:value={searchQuery}
									type="text"
									placeholder="Søk arbeidsflater..."
									class="context-selector-search-input"
									onclick={(e) => e.stopPropagation()}
								/>
							</div>
							<div class="context-selector-list">
								{#if myCollections.length > 0}
									<div class="context-selector-group-label">Mine flater</div>
									{#each myCollections as node (node.id)}
										<div class="context-selector-item-row">
											{#if renamingId === node.id}
												<input
													class="context-selector-rename-input"
													bind:value={renameValue}
													onblur={commitRename}
													onkeydown={handleRenameKeydown}
													onclick={(e) => e.stopPropagation()}
												/>
											{:else}
												<button
													class="context-selector-item"
													onclick={() => selectCollection(node.id)}
												>
													<span class="context-selector-item-title">{node.title || 'Uten tittel'}</span>
												</button>
												<button
													class="context-selector-rename-btn"
													onclick={(e) => { e.stopPropagation(); startRename(node); }}
													title="Gi nytt navn"
												>✏️</button>
											{/if}
										</div>
									{/each}
								{/if}
								{#if sharedCollections.length > 0}
									<div class="context-selector-group-label">Delte flater</div>
									{#each sharedCollections as node (node.id)}
										<button
											class="context-selector-item"
											onclick={() => selectCollection(node.id)}
										>
											<span class="context-selector-item-title">{node.title || 'Uten tittel'}</span>
										</button>
									{/each}
								{/if}
								{#if myCollections.length === 0 && sharedCollections.length === 0}
									<div class="context-selector-empty">
										{searchQuery ? 'Ingen treff' : 'Ingen arbeidsflater'}
									</div>
								{/if}
							</div>
							<div class="context-selector-footer">
								<button
									class="context-selector-new-btn"
									onclick={createNewWorkspace}
									disabled={isCreatingWorkspace}
								>
									+ Ny arbeidsflate
								</button>
							</div>
						</div>
					{/if}
				</div>
			</div>

			<div class="context-header-right">
				<div class="tool-menu">
					<button
						class="context-btn tool-menu-trigger"
						onclick={toggleToolMenu}
						title="Legg til verktøy-panel"
					>
						+ Verktøy
					</button>

					{#if toolMenuOpen}
						<div class="tool-menu-dropdown">
							<div class="tool-menu-title">Legg til panel</div>
							{#each availableTools as tool (tool.key)}
								<button
									class="tool-menu-item"
									class:tool-menu-item-active={tool.active}
									onclick={() => addTool(tool.key)}
									disabled={tool.active}
									title={tool.active ? 'Allerede på flaten' : `Legg til ${tool.title}`}
								>
									<span class="tool-menu-item-icon">{tool.icon}</span>
									<span class="tool-menu-item-label">{tool.title}</span>
									{#if tool.active}
										<span class="tool-menu-item-badge">aktiv</span>
									{/if}
								</button>
							{/each}
						</div>
					{/if}
				</div>

				{#if connected}
					<span class="context-status context-status-ok" title="Tilkoblet sanntid">&#9679;</span>
				{:else}
					<span class="context-status" title="{connectionState.current}">&#9679;</span>
				{/if}

				<div class="settings-menu">
					<button
						class="context-btn settings-trigger"
						onclick={toggleSettings}
						title="Innstillinger"
					>
						&#9881;
					</button>

					{#if settingsOpen}
						<div class="settings-dropdown">
							<div class="settings-title">Tema</div>
							<label class="settings-slider">
								<span class="settings-slider-label">Bakgrunn</span>
								<input type="range" min="0" max="360" bind:value={themeHueBg} oninput={applyTheme} />
							</label>
							<label class="settings-slider">
								<span class="settings-slider-label">Overflate</span>
								<input type="range" min="0" max="360" bind:value={themeHueSurface} oninput={applyTheme} />
							</label>
							<label class="settings-slider">
								<span class="settings-slider-label">Aksent</span>
								<input type="range" min="0" max="360" bind:value={themeHueAccent} oninput={applyTheme} />
							</label>
							<div class="settings-divider"></div>
							{#if $page.data.session?.user}
								<div class="settings-user">{$page.data.session.user.name}</div>
							{/if}
							<button
								class="settings-signout"
								onclick={() => signOut()}
							>
								Logg ut
							</button>
						</div>
					{/if}
				</div>
			</div>
		</div>
	</header>

	<!-- Main content -->
	{#if workspaceLoading}
		<div class="workspace-message">
			<p>Laster arbeidsflate...</p>
		</div>
	{:else if workspaceError}
		<div class="workspace-message workspace-message-warn">
			<p class="workspace-message-title">Kunne ikke laste arbeidsflate</p>
			<p>{workspaceError}</p>
		</div>
	{:else if !connected}
		<div class="workspace-message">
			<p>Venter på tilkobling...</p>
		</div>
	{:else if layout.panels.length === 0}
		<div class="workspace-empty">
			<div class="workspace-empty-inner">
				<p class="workspace-empty-title">Din personlige arbeidsflate</p>
				<p class="workspace-empty-desc">
					Legg til verktøy-paneler for å bygge opp arbeidsflaten din.
					Arrangementet huskes mellom besøk.
				</p>
				<div class="workspace-empty-tools">
					{#each Object.entries(TRAIT_PANEL_INFO) as [key, info] (key)}
						<button
							class="workspace-empty-tool"
							onclick={() => handleAddPanel(key)}
						>
							<span class="workspace-empty-tool-icon">{info.icon}</span>
							<span class="workspace-empty-tool-label">{info.title}</span>
						</button>
					{/each}
				</div>
			</div>
		</div>
	{:else if isMobile}
		<!-- MOBILE: Stacked tabs -->
		<div class="mobile-tabs">
			{#each layout.panels as panel, i (panel.trait)}
				<button
					class="mobile-tab {activeTab === i ? 'mobile-tab-active' : ''}"
					onclick={() => { activeTab = i; }}
				>
					{getPanelInfo(panel.trait).icon}
					<span class="mobile-tab-label">{getPanelInfo(panel.trait).title}</span>
				</button>
			{/each}
		</div>

		<div class="mobile-panel">
			{#each layout.panels as panel, i (panel.trait)}
				{#if activeTab === i}
					<div class="mobile-panel-content">
						{#if knownTraits.has(panel.trait)}
							{#if panel.trait === 'editor'}
								<EditorTrait collection={undefined} config={{}} userId={nodeId} {accessToken} collectionMetadata={{}} />
							{:else if panel.trait === 'chat'}
								<ChatTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if panel.trait === 'kanban'}
								<KanbanTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if panel.trait === 'calendar'}
								<CalendarTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if panel.trait === 'podcast'}
								<PodcastTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if panel.trait === 'publishing'}
								<PublishingTrait collection={undefined} config={{}} />
							{:else if panel.trait === 'rss'}
								<RssTrait collection={undefined} config={{}} />
							{:else if panel.trait === 'recording'}
								<RecordingTrait collection={undefined} config={{}} {accessToken} />
							{:else if panel.trait === 'transcription'}
								<TranscriptionTrait collection={undefined} config={{}} />
							{:else if panel.trait === 'studio'}
								<StudioTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if panel.trait === 'mixer'}
								<MixerTrait collection={undefined} config={{}} {accessToken} />
							{:else if panel.trait === 'mindmap'}
								<MindMapTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if panel.trait === 'ai'}
								<AiToolPanel {accessToken} userId={nodeId} />
							{:else if panel.trait === 'usage'}
								{#if nodeId && accessToken}
									<NodeUsage nodeId={nodeId} {accessToken} />
								{/if}
							{/if}
						{:else}
							<GenericTrait name={panel.trait} config={{}} />
						{/if}
					</div>
				{/if}
			{/each}
		</div>
	{:else}
		<!-- DESKTOP: Spatial Canvas -->
		<div class="workspace-canvas">
			<Canvas
				objects={canvasObjects}
				onObjectMove={handleObjectMove}
				grid={{ enabled: gridEnabled, size: 20 }}
				initialCamera={savedCamera}
				onCameraChange={(cam) => { savedCamera = cam; persistMetadata(); }}
				onGridChange={(enabled) => { gridEnabled = enabled; persistMetadata(); }}
			>
				{#snippet renderObject(obj)}
					{@const trait = obj.id}
					{@const info = getPanelInfo(trait)}
					{@const panel = layout.panels.find(p => p.trait === trait)}
					<BlockShell
						title={info.title}
						icon={info.icon}
						width={panel?.width ?? obj.width}
						height={panel?.height ?? obj.height}
						minimized={panel?.minimized ?? false}
						receiver={getReceiverForTrait(trait)}
						onResize={(w, h, dx, dy) => handlePanelResize(trait, w, h, dx, dy)}
						onClose={() => handlePanelClose(trait)}
						onMinimizeChange={(m) => handlePanelMinimize(trait, m)}
						onDrop={(payload, shiftKey) => handlePanelDrop(trait, payload, shiftKey)}
					>
						{#if knownTraits.has(trait)}
							{#if trait === 'editor'}
								<EditorTrait collection={undefined} config={{}} userId={nodeId} {accessToken} collectionMetadata={{}} />
							{:else if trait === 'chat'}
								<ChatTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if trait === 'kanban'}
								<KanbanTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if trait === 'calendar'}
								<CalendarTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if trait === 'podcast'}
								<PodcastTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if trait === 'publishing'}
								<PublishingTrait collection={undefined} config={{}} />
							{:else if trait === 'rss'}
								<RssTrait collection={undefined} config={{}} />
							{:else if trait === 'recording'}
								<RecordingTrait collection={undefined} config={{}} {accessToken} />
							{:else if trait === 'transcription'}
								<TranscriptionTrait collection={undefined} config={{}} />
							{:else if trait === 'studio'}
								<StudioTrait collection={undefined} config={{}} userId={nodeId} />
							{:else if trait === 'mixer'}
								<MixerTrait collection={undefined} config={{}} {accessToken} />
							{:else if trait === 'mindmap'}
								<MindMapTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
							{:else if trait === 'ai'}
								<AiToolPanel {accessToken} userId={nodeId} />
							{:else if trait === 'usage'}
								{#if nodeId && accessToken}
									<NodeUsage nodeId={nodeId} {accessToken} />
								{/if}
							{/if}
						{:else}
							<GenericTrait name={trait} config={{}} />
						{/if}
					</BlockShell>
				{/snippet}
			</Canvas>
		</div>
	{/if}

</div>

<style>
	.workspace-page {
		display: flex;
		flex-direction: column;
		height: 100vh;
		background: var(--color-bg, #0a0a0b);
	}

	/* ================================================================= */
	/* Context header (inline — personal workspace variant)              */
	/* ================================================================= */
	.context-header {
		border-bottom: 1px solid var(--color-border, #2a2a2e);
		background: var(--color-surface, #1c1c20);
		flex-shrink: 0;
		z-index: 30;
		position: relative;
	}

	.context-header-inner {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 6px 16px;
		max-width: 100%;
		min-height: 44px;
	}

	.context-header-left {
		display: flex;
		align-items: center;
		gap: 8px;
		min-width: 0;
		flex: 1;
	}

	.context-header-right {
		display: flex;
		align-items: center;
		gap: 10px;
		flex-shrink: 0;
	}

	.context-back {
		font-size: 16px;
		color: #5a5a66;
		text-decoration: none;
		flex-shrink: 0;
		padding: 4px;
		line-height: 1;
	}

	.context-back:hover {
		color: #4b5563;
	}

	/* ================================================================= */
	/* Context Selector                                                  */
	/* ================================================================= */
	.context-selector {
		position: relative;
		min-width: 0;
	}

	.context-selector-trigger {
		display: flex;
		align-items: center;
		gap: 6px;
		padding: 4px 10px;
		border: 1px solid transparent;
		border-radius: 6px;
		background: transparent;
		cursor: pointer;
		font-size: 15px;
		font-weight: 600;
		color: #e8e8ec;
		max-width: 300px;
		transition: background 0.12s, border-color 0.12s;
	}

	.context-selector-trigger:hover {
		background: var(--color-surface-hover, #242428);
		border-color: #e5e7eb;
	}

	.context-selector-title {
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.context-selector-chevron {
		font-size: 10px;
		color: #5a5a66;
		transition: transform 0.15s;
		flex-shrink: 0;
	}

	.context-selector-chevron.open {
		transform: rotate(180deg);
	}

	.context-selector-dropdown {
		position: absolute;
		top: calc(100% + 4px);
		left: 0;
		min-width: 260px;
		max-width: 360px;
		background: var(--color-surface, #1c1c20);
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 8px;
		box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
		z-index: 50;
		overflow: hidden;
	}

	.context-selector-search {
		padding: 8px;
		border-bottom: 1px solid #f3f4f6;
	}

	.context-selector-search-input {
		width: 100%;
		padding: 6px 10px;
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 6px;
		font-size: 13px;
		outline: none;
		background: var(--color-bg, #141416);
	}

	.context-selector-search-input:focus {
		border-color: var(--color-accent, #4f46e5);
		background: var(--color-surface, #1c1c20);
	}

	.context-selector-list {
		max-height: 280px;
		overflow-y: auto;
		padding: 4px;
	}

	.context-selector-item {
		display: flex;
		align-items: center;
		justify-content: space-between;
		width: 100%;
		padding: 8px 10px;
		border: none;
		border-radius: 6px;
		background: transparent;
		cursor: pointer;
		font-size: 13px;
		color: #8a8a96;
		text-align: left;
		transition: background 0.1s;
	}

	.context-selector-item:hover {
		background: var(--color-surface-hover, #242428);
	}

	.context-selector-item-title {
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.context-selector-empty {
		padding: 16px;
		text-align: center;
		font-size: 12px;
		color: #5a5a66;
	}

	/* ================================================================= */
	/* Tool Menu                                                         */
	/* ================================================================= */
	.tool-menu {
		position: relative;
	}

	.tool-menu-trigger {
		font-weight: 500;
	}

	.tool-menu-dropdown {
		position: absolute;
		top: calc(100% + 4px);
		right: 0;
		min-width: 200px;
		background: var(--color-surface, #1c1c20);
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 8px;
		box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
		z-index: 50;
		overflow: hidden;
		padding: 4px;
	}

	.tool-menu-title {
		padding: 6px 10px 4px;
		font-size: 11px;
		font-weight: 600;
		color: #5a5a66;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}

	.tool-menu-item {
		display: flex;
		align-items: center;
		gap: 8px;
		width: 100%;
		padding: 7px 10px;
		border: none;
		border-radius: 6px;
		background: transparent;
		cursor: pointer;
		font-size: 13px;
		color: #8a8a96;
		text-align: left;
		transition: background 0.1s;
	}

	.tool-menu-item:hover:not(:disabled) {
		background: var(--color-surface-hover, #242428);
	}

	.tool-menu-item:disabled {
		cursor: default;
		opacity: 0.5;
	}

	.tool-menu-item-active {
		color: #5a5a66;
	}

	.tool-menu-item-icon {
		font-size: 15px;
		flex-shrink: 0;
	}

	.tool-menu-item-label {
		flex: 1;
	}

	.tool-menu-item-badge {
		font-size: 10px;
		color: #5a5a66;
		background: var(--color-surface-hover, #242428);
		padding: 1px 6px;
		border-radius: 4px;
	}

	/* ================================================================= */
	/* Status + buttons                                                  */
	/* ================================================================= */
	.context-status {
		font-size: 8px;
		color: #d1d5db;
	}

	.context-status-ok {
		color: #16a34a;
	}

	.context-btn {
		padding: 4px 10px;
		border: none;
		border-radius: 6px;
		font-size: 12px;
		font-weight: 500;
		cursor: pointer;
		background: var(--color-surface-hover, #242428);
		color: #4b5563;
		transition: background 0.12s;
	}

	.context-btn:hover {
		background: #e5e7eb;
	}

	/* ================================================================= */
	/* Messages                                                          */
	/* ================================================================= */
	.workspace-message {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		flex: 1;
		padding: 24px;
		text-align: center;
		font-size: 14px;
		color: #8a8a96;
	}

	.workspace-message-warn {
		color: #92400e;
		background: #fffbeb;
	}

	.workspace-message-title {
		font-weight: 600;
		margin-bottom: 4px;
	}

	/* ================================================================= */
	/* Empty state (no panels)                                           */
	/* ================================================================= */
	.workspace-empty {
		display: flex;
		align-items: center;
		justify-content: center;
		flex: 1;
		padding: 24px;
	}

	.workspace-empty-inner {
		text-align: center;
		max-width: 480px;
	}

	.workspace-empty-title {
		font-size: 18px;
		font-weight: 600;
		color: #e8e8ec;
		margin-bottom: 8px;
	}

	.workspace-empty-desc {
		font-size: 14px;
		color: #8a8a96;
		margin-bottom: 24px;
	}

	.workspace-empty-tools {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		gap: 8px;
	}

	.workspace-empty-tool {
		display: flex;
		align-items: center;
		gap: 6px;
		padding: 8px 14px;
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 8px;
		background: var(--color-surface, #1c1c20);
		cursor: pointer;
		font-size: 13px;
		color: #8a8a96;
		transition: border-color 0.12s, box-shadow 0.12s;
	}

	.workspace-empty-tool:hover {
		border-color: var(--color-accent, #4f46e5);
		box-shadow: 0 2px 8px rgba(79, 70, 229, 0.1);
	}

	.workspace-empty-tool-icon {
		font-size: 16px;
	}

	.workspace-empty-tool-label {
		font-weight: 500;
	}

	/* ================================================================= */
	/* Canvas                                                            */
	/* ================================================================= */
	.workspace-canvas {
		flex: 1;
		min-height: 0;
	}

	/* ================================================================= */
	/* Mobile tabs                                                       */
	/* ================================================================= */
	.mobile-tabs {
		display: flex;
		overflow-x: auto;
		gap: 0;
		background: var(--color-surface, #1c1c20);
		border-bottom: 1px solid var(--color-border, #2a2a2e);
		flex-shrink: 0;
		-webkit-overflow-scrolling: touch;
	}

	.mobile-tab {
		display: flex;
		align-items: center;
		gap: 4px;
		padding: 10px 14px;
		border: none;
		background: transparent;
		font-size: 13px;
		color: #8a8a96;
		cursor: pointer;
		white-space: nowrap;
		border-bottom: 2px solid transparent;
		transition: color 0.12s, border-color 0.12s;
	}

	.mobile-tab:hover {
		color: #8a8a96;
	}

	.mobile-tab-active {
		color: var(--color-accent, #4f46e5);
		border-bottom-color: var(--color-accent, #4f46e5);
		font-weight: 500;
	}

	.mobile-tab-label {
		font-size: 12px;
	}

	.mobile-panel {
		flex: 1;
		overflow: auto;
		min-height: 0;
	}

	.mobile-panel-content {
		min-height: 100%;
	}

	/* ================================================================= */
	/* Context selector — groups, rename, new                            */
	/* ================================================================= */
	.context-selector-group-label {
		padding: 8px 10px 4px;
		font-size: 10px;
		font-weight: 600;
		color: #5a5a66;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}

	.context-selector-item-row {
		display: flex;
		align-items: center;
		gap: 2px;
	}

	.context-selector-item-row .context-selector-item {
		flex: 1;
		min-width: 0;
	}

	.context-selector-rename-btn {
		flex-shrink: 0;
		padding: 4px 6px;
		border: none;
		background: transparent;
		cursor: pointer;
		font-size: 12px;
		opacity: 0;
		transition: opacity 0.1s;
		border-radius: 4px;
	}

	.context-selector-item-row:hover .context-selector-rename-btn {
		opacity: 0.6;
	}

	.context-selector-rename-btn:hover {
		opacity: 1 !important;
		background: var(--color-surface-hover, #242428);
	}

	.context-selector-rename-input {
		flex: 1;
		padding: 6px 10px;
		border: 1px solid #6366f1 !important;
		border-radius: 6px;
		font-size: 13px;
		outline: none;
		background: #141416 !important;
		color: #e8e8ec !important;
		margin: 2px 4px;
	}

	.context-selector-footer {
		border-top: 1px solid var(--color-border, #2a2a2e);
		padding: 6px;
	}

	.context-selector-new-btn {
		width: 100%;
		padding: 8px 10px;
		border: 1px dashed var(--color-border, #2a2a2e);
		border-radius: 6px;
		background: transparent;
		cursor: pointer;
		font-size: 13px;
		color: #5a5a66;
		transition: border-color 0.1s, color 0.1s;
	}

	.context-selector-new-btn:hover:not(:disabled) {
		border-color: var(--color-accent, #6366f1);
		color: #8a8a96;
	}

	.context-selector-new-btn:disabled {
		opacity: 0.5;
		cursor: default;
	}

	/* ================================================================= */
	/* Settings menu                                                     */
	/* ================================================================= */
	.settings-menu {
		position: relative;
	}

	.settings-trigger {
		font-size: 14px;
	}

	.settings-dropdown {
		position: absolute;
		top: calc(100% + 4px);
		right: 0;
		min-width: 220px;
		background: var(--color-surface, #1c1c20);
		background: var(--color-surface, #1c1c20);
		border: 1px solid var(--color-border, #2a2a2e);
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 8px;
		box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
		z-index: 50;
		padding: 12px;
	}

	.settings-title {
		font-size: 11px;
		font-weight: 600;
		color: #5a5a66;
		text-transform: uppercase;
		letter-spacing: 0.05em;
		margin-bottom: 8px;
	}

	.settings-slider {
		display: flex;
		align-items: center;
		gap: 8px;
		margin-bottom: 6px;
		cursor: pointer;
	}

	.settings-slider-label {
		font-size: 12px;
		color: #8a8a96;
		min-width: 65px;
	}

	.settings-slider input[type="range"] {
		flex: 1;
		height: 4px;
		-webkit-appearance: none;
		appearance: none;
		background: #2a2a2e !important;
		border: none !important;
		border-radius: 2px;
		outline: none;
		padding: 0 !important;
	}

	.settings-slider input[type="range"]::-webkit-slider-thumb {
		-webkit-appearance: none;
		width: 14px;
		height: 14px;
		border-radius: 50%;
		background: var(--color-accent, #6366f1);
		cursor: pointer;
	}

	.settings-divider {
		height: 1px;
		background: #2a2a2e;
		margin: 10px 0;
	}

	.settings-user {
		font-size: 13px;
		color: #8a8a96;
		padding: 4px 0 8px;
	}

	.settings-signout {
		width: 100%;
		padding: 6px 10px;
		border: none;
		border-radius: 6px;
		background: transparent;
		cursor: pointer;
		font-size: 13px;
		color: #8a8a96;
		text-align: left;
		transition: background 0.1s;
	}

	.settings-signout:hover {
		background: var(--color-surface-hover, #242428);
		color: #e8e8ec;
	}

	/* ================================================================= */
	/* Responsive                                                        */
	/* ================================================================= */
	@media (max-width: 768px) {
		.context-header-inner {
			padding: 6px 12px;
		}

		.context-selector-trigger {
			font-size: 14px;
			max-width: 200px;
		}

		.context-selector-dropdown {
			min-width: 220px;
			max-width: calc(100vw - 24px);
		}

		.tool-menu-dropdown,
		.settings-dropdown {
			max-width: calc(100vw - 24px);
		}

		.context-header-right {
			gap: 6px;
		}

		.workspace-empty-tools {
			gap: 6px;
		}

		.workspace-empty-tool {
			padding: 6px 10px;
			font-size: 12px;
		}
	}
</style>
