<script lang="ts">
	import { nodeStore, edgeStore, nodeVisibility } from '$lib/realtime';
	import type { Node, Edge } from '$lib/realtime';
	import * as d3 from 'd3';

	interface Props {
		collection?: Node;
		config: Record<string, unknown>;
		userId?: string;
		accessToken?: string;
	}

	let { collection, config, userId, accessToken }: Props = $props();

	// =========================================================================
	// Config
	// =========================================================================

	const defaultDepth = $derived((config.default_depth as number) ?? 2);
	const layoutMode = $derived((config.layout as string) ?? 'radial');
	const allowedEdgeTypes = $derived(
		(config.edge_types as string[]) ?? ['belongs_to', 'mentions', 'source_material', 'member_of', 'owner', 'part_of']
	);

	// =========================================================================
	// State
	// =========================================================================

	let svgEl: SVGSVGElement | undefined = $state();
	let containerEl: HTMLDivElement | undefined = $state();
	let rootId = $state<string | null>(null);
	let depth = $state(2);
	let hoveredNodeId = $state<string | null>(null);

	// Initialize depth from config
	$effect(() => {
		depth = defaultDepth;
	});

	// Set initial root to collection id
	$effect(() => {
		if (collection && !rootId) {
			rootId = collection.id;
		}
	});

	// =========================================================================
	// Node kind colors (consistent with graph page)
	// =========================================================================

	const kindColors: Record<string, string> = {
		topic: '#8b5cf6',
		person: '#3b82f6',
		team: '#10b981',
		content: '#f59e0b',
		communication: '#06b6d4',
		collection: '#84cc16',
		media: '#ec4899',
		agent: '#6b7280',
	};

	const kindLabels: Record<string, string> = {
		topic: 'Tema',
		person: 'Person',
		team: 'Team',
		content: 'Innhold',
		communication: 'Samtale',
		collection: 'Samling',
		media: 'Media',
		agent: 'Agent',
	};

	function nodeColor(kind: string): string {
		return kindColors[kind] ?? '#9ca3af';
	}

	// =========================================================================
	// Build tree data from graph stores
	// =========================================================================

	interface TreeNode {
		id: string;
		node: Node;
		children: TreeNode[];
		edgeType?: string;
	}

	const treeData = $derived.by((): TreeNode | null => {
		if (!rootId) return null;
		const rootNode = nodeStore.get(rootId);
		if (!rootNode) return null;

		const visited = new Set<string>();
		return buildTree(rootNode, visited, 0);
	});

	function buildTree(node: Node, visited: Set<string>, currentDepth: number): TreeNode {
		visited.add(node.id);
		const treeNode: TreeNode = { id: node.id, node, children: [] };

		if (currentDepth >= depth) return treeNode;

		// Get all edges connected to this node (both directions)
		const outEdges = edgeStore.bySource(node.id);
		const inEdges = edgeStore.byTarget(node.id);
		const allEdges = [...outEdges, ...inEdges];

		for (const edge of allEdges) {
			if (!allowedEdgeTypes.includes(edge.edgeType)) continue;

			const otherId = edge.sourceId === node.id ? edge.targetId : edge.sourceId;
			if (visited.has(otherId)) continue;

			const otherNode = nodeStore.get(otherId);
			if (!otherNode) continue;

			// Check visibility
			const vis = nodeVisibility(otherNode, userId);
			if (vis === 'hidden') continue;

			const child = buildTree(otherNode, visited, currentDepth + 1);
			child.edgeType = edge.edgeType;
			treeNode.children.push(child);
		}

		// Sort children by title for stable layout
		treeNode.children.sort((a, b) =>
			(a.node.title ?? '').localeCompare(b.node.title ?? '')
		);

		return treeNode;
	}

	// Count connected edges for tooltip
	function edgeCount(nodeId: string): number {
		return edgeStore.bySource(nodeId).length + edgeStore.byTarget(nodeId).length;
	}

	// =========================================================================
	// D3 layout + SVG rendering
	// =========================================================================

	let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown> | null = null;

	function renderMindMap() {
		if (!svgEl || !treeData) return;

		d3.select(svgEl).selectAll('*').remove();

		const width = svgEl.clientWidth || 600;
		const height = svgEl.clientHeight || 400;
		const svg = d3.select(svgEl);

		// Zoom container
		const g = svg.append('g');

		zoomBehavior = d3.zoom<SVGSVGElement, unknown>()
			.scaleExtent([0.1, 4])
			.on('zoom', (event) => {
				g.attr('transform', event.transform);
			});

		svg.call(zoomBehavior);

		// Build d3 hierarchy
		const root = d3.hierarchy(treeData, d => d.children);

		if (layoutMode === 'radial') {
			renderRadial(g, root, width, height);
		} else {
			renderTree(g, root, width, height);
		}

		// Auto-fit after layout
		requestAnimationFrame(() => {
			const bounds = (g.node() as SVGGElement)?.getBBox();
			if (!bounds || bounds.width === 0) return;
			const padding = 60;
			const scale = Math.min(
				width / (bounds.width + padding * 2),
				height / (bounds.height + padding * 2),
				1.5
			);
			const tx = width / 2 - (bounds.x + bounds.width / 2) * scale;
			const ty = height / 2 - (bounds.y + bounds.height / 2) * scale;
			svg.transition().duration(400).call(
				zoomBehavior!.transform,
				d3.zoomIdentity.translate(tx, ty).scale(scale)
			);
		});
	}

	function renderRadial(
		g: d3.Selection<SVGGElement, unknown, null, undefined>,
		root: d3.HierarchyNode<TreeNode>,
		width: number,
		height: number,
	) {
		const nodeCount = root.descendants().length;
		const radius = Math.max(120, Math.min(300, nodeCount * 30));

		const treeLayout = d3.tree<TreeNode>()
			.size([2 * Math.PI, radius])
			.separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth || 1);

		treeLayout(root);

		// Convert polar to cartesian
		function radialPoint(d: d3.HierarchyPointNode<TreeNode>): [number, number] {
			const angle = d.x - Math.PI / 2; // rotate so root is at top
			const r = d.y;
			return [r * Math.cos(angle), r * Math.sin(angle)];
		}

		// Draw edges
		g.append('g')
			.attr('fill', 'none')
			.selectAll('path')
			.data(root.links())
			.join('path')
			.attr('stroke', d => edgeStroke(d.target.data.edgeType))
			.attr('stroke-width', 1.5)
			.attr('stroke-opacity', 0.5)
			.attr('stroke-dasharray', d => edgeDash(d.target.data.edgeType))
			.attr('d', d => {
				const [sx, sy] = radialPoint(d.source as d3.HierarchyPointNode<TreeNode>);
				const [tx, ty] = radialPoint(d.target as d3.HierarchyPointNode<TreeNode>);
				return `M${sx},${sy}L${tx},${ty}`;
			});

		// Draw nodes
		const nodeGroups = g.append('g')
			.selectAll<SVGGElement, d3.HierarchyPointNode<TreeNode>>('g')
			.data(root.descendants() as d3.HierarchyPointNode<TreeNode>[])
			.join('g')
			.attr('transform', d => {
				const [x, y] = radialPoint(d);
				return `translate(${x},${y})`;
			})
			.attr('cursor', 'pointer');

		addNodeElements(nodeGroups);
	}

	function renderTree(
		g: d3.Selection<SVGGElement, unknown, null, undefined>,
		root: d3.HierarchyNode<TreeNode>,
		width: number,
		height: number,
	) {
		const nodeCount = root.descendants().length;
		const treeWidth = Math.max(300, nodeCount * 40);
		const treeHeight = Math.max(200, root.height * 120);

		const treeLayout = d3.tree<TreeNode>()
			.size([treeWidth, treeHeight]);

		treeLayout(root);

		// Draw edges
		g.append('g')
			.attr('fill', 'none')
			.selectAll('path')
			.data(root.links())
			.join('path')
			.attr('stroke', d => edgeStroke(d.target.data.edgeType))
			.attr('stroke-width', 1.5)
			.attr('stroke-opacity', 0.5)
			.attr('stroke-dasharray', d => edgeDash(d.target.data.edgeType))
			.attr('d', d => {
				const s = d.source as d3.HierarchyPointNode<TreeNode>;
				const t = d.target as d3.HierarchyPointNode<TreeNode>;
				return `M${s.x},${s.y}C${s.x},${(s.y + t.y) / 2} ${t.x},${(s.y + t.y) / 2} ${t.x},${t.y}`;
			});

		// Draw nodes
		const nodeGroups = g.append('g')
			.selectAll<SVGGElement, d3.HierarchyPointNode<TreeNode>>('g')
			.data(root.descendants() as d3.HierarchyPointNode<TreeNode>[])
			.join('g')
			.attr('transform', d => `translate(${(d as d3.HierarchyPointNode<TreeNode>).x},${(d as d3.HierarchyPointNode<TreeNode>).y})`)
			.attr('cursor', 'pointer');

		addNodeElements(nodeGroups);
	}

	function addNodeElements(
		nodeGroups: d3.Selection<SVGGElement, d3.HierarchyPointNode<TreeNode>, SVGGElement, unknown>
	) {
		// Circle
		nodeGroups.append('circle')
			.attr('r', d => d.depth === 0 ? 14 : 9)
			.attr('fill', d => nodeColor(d.data.node.nodeKind))
			.attr('stroke', d => d.depth === 0 ? '#1f2937' : '#fff')
			.attr('stroke-width', d => d.depth === 0 ? 3 : 2);

		// Privacy marker
		nodeGroups.filter(d => d.data.node.visibility === 'private')
			.append('text')
			.attr('text-anchor', 'middle')
			.attr('dy', '-1.2em')
			.attr('font-size', '10px')
			.text('\uD83D\uDD12');

		// Label
		nodeGroups.append('text')
			.attr('dy', d => d.depth === 0 ? 28 : 22)
			.attr('text-anchor', 'middle')
			.attr('font-size', d => d.depth === 0 ? '13px' : '11px')
			.attr('font-weight', d => d.depth === 0 ? '600' : '400')
			.attr('fill', '#374151')
			.text(d => {
				const t = d.data.node.title || 'Uten tittel';
				return t.length > 22 ? t.slice(0, 20) + '\u2026' : t;
			});

		// Interaction
		nodeGroups
			.on('click', (event, d) => {
				event.stopPropagation();
				// Single click = new root
				rootId = d.data.id;
			})
			.on('dblclick', (event, d) => {
				event.stopPropagation();
				// Double click = open in editor (navigate)
				window.location.href = `/node/${d.data.id}`;
			})
			.on('mouseenter', (_event, d) => {
				hoveredNodeId = d.data.id;
			})
			.on('mouseleave', () => {
				hoveredNodeId = null;
			});

		// Tooltip (title element)
		nodeGroups.append('title')
			.text(d => {
				const n = d.data.node;
				const kind = kindLabels[n.nodeKind] ?? n.nodeKind;
				const edges = edgeCount(n.id);
				return `${n.title || 'Uten tittel'}\n${kind} \u00b7 ${edges} koblinger`;
			});
	}

	// =========================================================================
	// Edge styling
	// =========================================================================

	function edgeStroke(edgeType?: string): string {
		if (edgeType === 'mentions') return '#8b5cf6';
		if (edgeType === 'belongs_to') return '#9ca3af';
		if (edgeType === 'owner') return '#3b82f6';
		if (edgeType === 'member_of') return '#10b981';
		if (edgeType === 'source_material') return '#f59e0b';
		return '#d1d5db';
	}

	function edgeDash(edgeType?: string): string {
		if (edgeType === 'mentions') return '6,3';
		if (edgeType === 'source_material') return '4,2';
		return '';
	}

	// =========================================================================
	// Re-render on data/state change
	// =========================================================================

	$effect(() => {
		if (treeData && svgEl) {
			renderMindMap();
		}
	});

	// Handle resize
	$effect(() => {
		if (!containerEl) return;
		const observer = new ResizeObserver(() => {
			if (treeData && svgEl) renderMindMap();
		});
		observer.observe(containerEl);
		return () => observer.disconnect();
	});

	// =========================================================================
	// Navigation
	// =========================================================================

	let history = $state<string[]>([]);

	function goBack() {
		if (history.length === 0) return;
		const prev = history[history.length - 1];
		history = history.slice(0, -1);
		rootId = prev;
	}

	// Track root changes for back navigation
	let previousRoot = $state<string | null>(null);
	$effect(() => {
		if (rootId && previousRoot && rootId !== previousRoot) {
			history = [...history, previousRoot];
		}
		previousRoot = rootId;
	});

	function resetRoot() {
		if (collection) {
			rootId = collection.id;
			history = [];
		}
	}

	// =========================================================================
	// Layout toggle
	// =========================================================================

	let currentLayout = $state<'radial' | 'tree'>('radial');
	$effect(() => {
		currentLayout = layoutMode === 'tree' ? 'tree' : 'radial';
	});

	// Override layoutMode for rendering
	const effectiveLayout = $derived(currentLayout);

	// Node count for display
	const nodeCount = $derived(treeData ? countNodes(treeData) : 0);

	function countNodes(tree: TreeNode): number {
		let count = 1;
		for (const child of tree.children) {
			count += countNodes(child);
		}
		return count;
	}

	// Root node title
	const rootTitle = $derived(treeData?.node.title ?? 'Tankekart');
</script>

<div class="mindmap-container" bind:this={containerEl}>
	<!-- Toolbar -->
	<div class="mindmap-toolbar">
		<div class="mindmap-toolbar-left">
			{#if history.length > 0}
				<button class="mindmap-btn" onclick={goBack} title="Tilbake">
					&larr;
				</button>
			{/if}
			{#if rootId !== collection?.id}
				<button class="mindmap-btn" onclick={resetRoot} title="Tilbake til rot">
					Rot
				</button>
			{/if}
			<span class="mindmap-title">{rootTitle}</span>
		</div>
		<div class="mindmap-toolbar-right">
			<span class="mindmap-count">{nodeCount} noder</span>
			<label class="mindmap-depth-label">
				Dybde:
				<select bind:value={depth} class="mindmap-depth-select">
					<option value={1}>1</option>
					<option value={2}>2</option>
					<option value={3}>3</option>
				</select>
			</label>
			<button
				class="mindmap-btn {currentLayout === 'radial' ? 'mindmap-btn-active' : ''}"
				onclick={() => { currentLayout = 'radial'; }}
				title="Radial layout"
			>
				Radial
			</button>
			<button
				class="mindmap-btn {currentLayout === 'tree' ? 'mindmap-btn-active' : ''}"
				onclick={() => { currentLayout = 'tree'; }}
				title="Tre-layout"
			>
				Tre
			</button>
		</div>
	</div>

	<!-- SVG canvas -->
	<div class="mindmap-svg-wrap">
		{#if !treeData}
			<div class="mindmap-empty">
				<p>Ingen data tilgjengelig.</p>
				<p class="mindmap-empty-sub">Velg en node som rot for tankekartet.</p>
			</div>
		{/if}
		<svg bind:this={svgEl} class="mindmap-svg"></svg>
	</div>

	<!-- Legend -->
	<div class="mindmap-legend">
		<span class="mindmap-legend-hint">Klikk = ny rot &middot; Dobbeltklikk = åpne</span>
	</div>
</div>

<style>
	.mindmap-container {
		display: flex;
		flex-direction: column;
		width: 100%;
		height: 100%;
		min-height: 200px;
		background: var(--color-bg);
		overflow: hidden;
	}

	.mindmap-toolbar {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 8px;
		padding: 6px 10px;
		border-bottom: 1px solid var(--color-border);
		background: var(--color-surface);
		flex-shrink: 0;
		flex-wrap: wrap;
	}

	.mindmap-toolbar-left,
	.mindmap-toolbar-right {
		display: flex;
		align-items: center;
		gap: 6px;
	}

	.mindmap-title {
		font-size: 13px;
		font-weight: 600;
		color: var(--color-text-muted);
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
		max-width: 180px;
	}

	.mindmap-count {
		font-size: 11px;
		color: var(--color-text-dim);
	}

	.mindmap-btn {
		padding: 3px 8px;
		border: 1px solid var(--color-border);
		border-radius: 4px;
		background: var(--color-surface);
		font-size: 11px;
		color: var(--color-text-muted);
		cursor: pointer;
	}

	.mindmap-btn:hover {
		background: var(--color-surface-hover);
	}

	.mindmap-btn-active {
		background: var(--color-surface);
		border-color: var(--color-accent);
		color: var(--color-accent);
	}

	.mindmap-depth-label {
		font-size: 11px;
		color: var(--color-text-muted);
		display: flex;
		align-items: center;
		gap: 3px;
	}

	.mindmap-depth-select {
		padding: 1px 4px;
		border: 1px solid var(--color-border);
		border-radius: 3px;
		background: var(--color-surface);
		font-size: 11px;
	}

	.mindmap-svg-wrap {
		flex: 1;
		position: relative;
		min-height: 0;
	}

	.mindmap-svg {
		width: 100%;
		height: 100%;
		display: block;
	}

	.mindmap-empty {
		position: absolute;
		inset: 0;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		font-size: 13px;
		color: var(--color-text-dim);
	}

	.mindmap-empty-sub {
		font-size: 11px;
		color: var(--color-text-muted);
		margin-top: 4px;
	}

	.mindmap-legend {
		padding: 4px 10px;
		border-top: 1px solid var(--color-border);
		background: var(--color-surface);
		flex-shrink: 0;
	}

	.mindmap-legend-hint {
		font-size: 10px;
		color: var(--color-text-dim);
	}

	/* Mobile: simplify */
	@media (max-width: 768px) {
		.mindmap-toolbar {
			padding: 4px 8px;
		}

		.mindmap-toolbar-right {
			flex-wrap: wrap;
		}

		.mindmap-title {
			max-width: 120px;
			font-size: 12px;
		}
	}
</style>
