<script lang="ts">
	import { page } from '$app/stores';
	import { onMount } from 'svelte';
	import * as d3 from 'd3';
	import { fetchGraph, createNode, createEdge, type GraphNode, type GraphEdge } from '$lib/api';

	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);

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

	let svgEl: SVGSVGElement | undefined = $state();
	let graphNodes = $state<GraphNode[]>([]);
	let graphEdges = $state<GraphEdge[]>([]);
	let loading = $state(true);
	let error = $state<string | null>(null);
	let focusId = $state<string | null>(null);
	let depth = $state(2);

	// Filters
	let filterKinds = $state<string[]>([]);
	let filterEdgeTypes = $state<string[]>([]);

	// Topic creation
	let showTopicForm = $state(false);
	let newTopicTitle = $state('');
	let isCreatingTopic = $state(false);

	// Mentions edge creation
	let showMentionsForm = $state(false);
	let mentionsSourceId = $state('');
	let mentionsTargetId = $state('');
	let isCreatingMention = $state(false);

	// Selected node for info panel
	let selectedNode = $state<GraphNode | null>(null);

	// =========================================================================
	// Node kind colors and labels
	// =========================================================================

	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'
	};

	const edgeTypeLabels: Record<string, string> = {
		mentions: 'Nevner',
		belongs_to: 'Tilhører',
		owner: 'Eier',
		member_of: 'Medlem av',
		has_media: 'Har media',
		scheduled: 'Planlagt',
		status: 'Status',
		part_of: 'Del av'
	};

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

	function nodeRadius(kind: string): number {
		if (kind === 'topic') return 12;
		if (kind === 'person' || kind === 'team') return 10;
		return 8;
	}

	// =========================================================================
	// Data loading
	// =========================================================================

	async function loadGraph() {
		if (!accessToken) return;
		loading = true;
		error = null;
		try {
			const params: Record<string, unknown> = { depth };
			if (focusId) params.focusId = focusId;
			if (filterKinds.length) params.nodeKinds = filterKinds;
			if (filterEdgeTypes.length) params.edgeTypes = filterEdgeTypes;

			const resp = await fetchGraph(accessToken, params as any);
			graphNodes = resp.nodes;
			graphEdges = resp.edges;
		} catch (e: any) {
			error = e.message;
		} finally {
			loading = false;
		}
	}

	// Load on mount and when filters change
	onMount(() => {
		// Check URL for focus parameter
		const url = new URL(window.location.href);
		const fid = url.searchParams.get('focus');
		if (fid) focusId = fid;

		loadGraph();
	});

	// =========================================================================
	// D3 Force Graph
	// =========================================================================

	interface SimNode extends d3.SimulationNodeDatum {
		id: string;
		node_kind: string;
		title: string | null;
		visibility: string;
	}

	interface SimLink extends d3.SimulationLinkDatum<SimNode> {
		id: string;
		edge_type: string;
	}

	let simulation: d3.Simulation<SimNode, SimLink> | null = null;

	function renderGraph() {
		if (!svgEl || graphNodes.length === 0) return;

		// Clear previous
		d3.select(svgEl).selectAll('*').remove();
		if (simulation) simulation.stop();

		const width = svgEl.clientWidth;
		const height = svgEl.clientHeight;

		const svg = d3.select(svgEl);

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

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

		svg.call(zoom);

		// Build data
		const nodeMap = new Map(graphNodes.map(n => [n.id, n]));
		const simNodes: SimNode[] = graphNodes.map(n => ({
			id: n.id,
			node_kind: n.node_kind,
			title: n.title,
			visibility: n.visibility
		}));

		const simLinks: SimLink[] = graphEdges
			.filter(e => nodeMap.has(e.source_id) && nodeMap.has(e.target_id))
			.map(e => ({
				id: e.id,
				source: e.source_id,
				target: e.target_id,
				edge_type: e.edge_type
			}));

		// Simulation
		simulation = d3.forceSimulation(simNodes)
			.force('link', d3.forceLink<SimNode, SimLink>(simLinks).id(d => d.id).distance(80))
			.force('charge', d3.forceManyBody().strength(-200))
			.force('center', d3.forceCenter(width / 2, height / 2))
			.force('collision', d3.forceCollide().radius(20));

		// Edge type colors
		function edgeColor(type: string): string {
			if (type === 'mentions') return '#8b5cf6';
			if (type === 'belongs_to') return '#9ca3af';
			if (type === 'owner') return '#3b82f6';
			if (type === 'member_of') return '#10b981';
			if (type === 'part_of') return '#f59e0b';
			return '#d1d5db';
		}

		// Draw edges
		const link = g.append('g')
			.selectAll('line')
			.data(simLinks)
			.join('line')
			.attr('stroke', d => edgeColor(d.edge_type))
			.attr('stroke-width', d => d.edge_type === 'mentions' ? 2.5 : 1.5)
			.attr('stroke-opacity', 0.6)
			.attr('stroke-dasharray', d => d.edge_type === 'mentions' ? '' : '4,2');

		// Edge labels
		const linkLabel = g.append('g')
			.selectAll('text')
			.data(simLinks)
			.join('text')
			.attr('font-size', '9px')
			.attr('fill', '#9ca3af')
			.attr('text-anchor', 'middle')
			.text(d => edgeTypeLabels[d.edge_type] ?? d.edge_type);

		// Draw nodes
		const node = g.append('g')
			.selectAll<SVGCircleElement, SimNode>('circle')
			.data(simNodes)
			.join('circle')
			.attr('r', d => nodeRadius(d.node_kind))
			.attr('fill', d => nodeColor(d.node_kind))
			.attr('stroke', d => focusId === d.id ? '#1f2937' : '#fff')
			.attr('stroke-width', d => focusId === d.id ? 3 : 2)
			.attr('cursor', 'pointer')
			.on('click', (_event, d) => {
				selectedNode = graphNodes.find(n => n.id === d.id) ?? null;
			})
			.on('dblclick', (_event, d) => {
				focusId = d.id;
				loadGraph();
			})
			.call(d3.drag<SVGCircleElement, SimNode>()
				.on('start', (event, d) => {
					if (!event.active) simulation!.alphaTarget(0.3).restart();
					d.fx = d.x;
					d.fy = d.y;
				})
				.on('drag', (event, d) => {
					d.fx = event.x;
					d.fy = event.y;
				})
				.on('end', (event, d) => {
					if (!event.active) simulation!.alphaTarget(0);
					d.fx = null;
					d.fy = null;
				})
			);

		// Node labels
		const label = g.append('g')
			.selectAll('text')
			.data(simNodes)
			.join('text')
			.attr('font-size', '11px')
			.attr('font-weight', d => d.node_kind === 'topic' ? '600' : '400')
			.attr('fill', '#374151')
			.attr('text-anchor', 'middle')
			.attr('dy', d => nodeRadius(d.node_kind) + 14)
			.text(d => {
				const t = d.title || 'Uten tittel';
				return t.length > 20 ? t.slice(0, 18) + '…' : t;
			});

		// Tick
		simulation.on('tick', () => {
			link
				.attr('x1', d => (d.source as SimNode).x!)
				.attr('y1', d => (d.source as SimNode).y!)
				.attr('x2', d => (d.target as SimNode).x!)
				.attr('y2', d => (d.target as SimNode).y!);

			linkLabel
				.attr('x', d => ((d.source as SimNode).x! + (d.target as SimNode).x!) / 2)
				.attr('y', d => ((d.source as SimNode).y! + (d.target as SimNode).y!) / 2 - 6);

			node
				.attr('cx', d => d.x!)
				.attr('cy', d => d.y!);

			label
				.attr('x', d => d.x!)
				.attr('y', d => d.y!);
		});

		// Auto-zoom to fit
		simulation.on('end', () => {
			const bounds = (g.node() as SVGGElement)?.getBBox();
			if (!bounds || bounds.width === 0) return;
			const padding = 40;
			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(500).call(
				zoom.transform,
				d3.zoomIdentity.translate(tx, ty).scale(scale)
			);
		});
	}

	// Re-render when data changes
	$effect(() => {
		if (graphNodes.length > 0 && svgEl) {
			renderGraph();
		}
	});

	// =========================================================================
	// Topic creation
	// =========================================================================

	async function handleCreateTopic() {
		if (!accessToken || !nodeId || !newTopicTitle.trim() || isCreatingTopic) return;
		isCreatingTopic = true;
		try {
			const { node_id } = await createNode(accessToken, {
				node_kind: 'topic',
				title: newTopicTitle.trim(),
				visibility: 'readable'
			});

			await createEdge(accessToken, {
				source_id: nodeId,
				target_id: node_id,
				edge_type: 'owner'
			});

			newTopicTitle = '';
			showTopicForm = false;
			await loadGraph();
		} catch (e: any) {
			error = e.message;
		} finally {
			isCreatingTopic = false;
		}
	}

	// =========================================================================
	// Mentions edge creation
	// =========================================================================

	async function handleCreateMention() {
		if (!accessToken || !mentionsSourceId || !mentionsTargetId || isCreatingMention) return;
		if (mentionsSourceId === mentionsTargetId) return;
		isCreatingMention = true;
		try {
			await createEdge(accessToken, {
				source_id: mentionsSourceId,
				target_id: mentionsTargetId,
				edge_type: 'mentions'
			});

			mentionsSourceId = '';
			mentionsTargetId = '';
			showMentionsForm = false;
			await loadGraph();
		} catch (e: any) {
			error = e.message;
		} finally {
			isCreatingMention = false;
		}
	}

	function resetFocus() {
		focusId = null;
		selectedNode = null;
		loadGraph();
	}

	// =========================================================================
	// Unique kinds/types in current data (for filters)
	// =========================================================================

	const availableKinds = $derived([...new Set(graphNodes.map(n => n.node_kind))].sort());
	const availableEdgeTypes = $derived([...new Set(graphEdges.map(e => e.edge_type))].sort());

	function toggleKindFilter(kind: string) {
		if (filterKinds.includes(kind)) {
			filterKinds = filterKinds.filter(k => k !== kind);
		} else {
			filterKinds = [...filterKinds, kind];
		}
		loadGraph();
	}

	function toggleEdgeTypeFilter(type: string) {
		if (filterEdgeTypes.includes(type)) {
			filterEdgeTypes = filterEdgeTypes.filter(t => t !== type);
		} else {
			filterEdgeTypes = [...filterEdgeTypes, type];
		}
		loadGraph();
	}
</script>

<div class="flex flex-col h-screen bg-gray-50">
	<!-- Header -->
	<header class="border-b border-gray-200 bg-white shrink-0">
		<div class="flex items-center justify-between px-4 py-3">
			<div class="flex items-center gap-3">
				<a href="/" class="text-sm text-gray-400 hover:text-gray-600">&larr; Arbeidsflaten</a>
				<h1 class="text-lg font-semibold text-gray-900">Kunnskapsgraf</h1>
			</div>
			<div class="flex items-center gap-2">
				{#if focusId}
					<button
						onclick={resetFocus}
						class="rounded bg-gray-100 px-2 py-1 text-xs text-gray-600 hover:bg-gray-200"
					>
						Vis alle
					</button>
				{/if}
				<span class="text-xs text-gray-400">
					{graphNodes.length} noder, {graphEdges.length} kanter
				</span>
			</div>
		</div>
	</header>

	<!-- Toolbar -->
	<div class="border-b border-gray-200 bg-white px-4 py-2 shrink-0">
		<div class="flex flex-wrap items-center gap-2">
			<!-- Depth control -->
			<label class="flex items-center gap-1 text-xs text-gray-500">
				Dybde:
				<select
					bind:value={depth}
					onchange={() => loadGraph()}
					class="rounded border border-gray-200 bg-white px-1 py-0.5 text-xs"
				>
					<option value={1}>1</option>
					<option value={2}>2</option>
					<option value={3}>3</option>
				</select>
			</label>

			<div class="h-4 w-px bg-gray-200"></div>

			<!-- Create topic -->
			<button
				onclick={() => { showTopicForm = !showTopicForm; showMentionsForm = false; }}
				class="rounded bg-violet-100 px-2 py-1 text-xs font-medium text-violet-700 hover:bg-violet-200"
			>
				+ Nytt tema
			</button>

			<!-- Create mention -->
			<button
				onclick={() => { showMentionsForm = !showMentionsForm; showTopicForm = false; }}
				class="rounded bg-purple-100 px-2 py-1 text-xs font-medium text-purple-700 hover:bg-purple-200"
			>
				+ Kobling
			</button>

			<button
				onclick={() => loadGraph()}
				class="rounded bg-gray-100 px-2 py-1 text-xs text-gray-600 hover:bg-gray-200"
			>
				Oppdater
			</button>
		</div>

		<!-- Topic creation form -->
		{#if showTopicForm}
			<div class="mt-2 flex items-center gap-2">
				<input
					type="text"
					bind:value={newTopicTitle}
					placeholder="Temanavn…"
					class="rounded border border-gray-200 px-2 py-1 text-sm focus:border-violet-400 focus:outline-none"
					onkeydown={(e) => e.key === 'Enter' && handleCreateTopic()}
				/>
				<button
					onclick={handleCreateTopic}
					disabled={isCreatingTopic || !newTopicTitle.trim()}
					class="rounded bg-violet-600 px-3 py-1 text-xs font-medium text-white hover:bg-violet-700 disabled:opacity-50"
				>
					{isCreatingTopic ? '…' : 'Opprett'}
				</button>
				<button
					onclick={() => { showTopicForm = false; newTopicTitle = ''; }}
					class="text-xs text-gray-500 hover:text-gray-700"
				>
					Avbryt
				</button>
			</div>
		{/if}

		<!-- Mentions edge creation form -->
		{#if showMentionsForm}
			<div class="mt-2 flex flex-wrap items-center gap-2">
				<select
					bind:value={mentionsSourceId}
					class="rounded border border-gray-200 px-2 py-1 text-xs focus:border-purple-400 focus:outline-none"
				>
					<option value="">Fra node…</option>
					{#each graphNodes.sort((a, b) => (a.title ?? '').localeCompare(b.title ?? '')) as n}
						<option value={n.id}>{n.title || n.id.slice(0, 8)} ({kindLabels[n.node_kind] ?? n.node_kind})</option>
					{/each}
				</select>
				<span class="text-xs text-gray-400">nevner</span>
				<select
					bind:value={mentionsTargetId}
					class="rounded border border-gray-200 px-2 py-1 text-xs focus:border-purple-400 focus:outline-none"
				>
					<option value="">Til node…</option>
					{#each graphNodes.filter(n => n.id !== mentionsSourceId).sort((a, b) => (a.title ?? '').localeCompare(b.title ?? '')) as n}
						<option value={n.id}>{n.title || n.id.slice(0, 8)} ({kindLabels[n.node_kind] ?? n.node_kind})</option>
					{/each}
				</select>
				<button
					onclick={handleCreateMention}
					disabled={isCreatingMention || !mentionsSourceId || !mentionsTargetId}
					class="rounded bg-purple-600 px-3 py-1 text-xs font-medium text-white hover:bg-purple-700 disabled:opacity-50"
				>
					{isCreatingMention ? '…' : 'Opprett'}
				</button>
				<button
					onclick={() => { showMentionsForm = false; mentionsSourceId = ''; mentionsTargetId = ''; }}
					class="text-xs text-gray-500 hover:text-gray-700"
				>
					Avbryt
				</button>
			</div>
		{/if}
	</div>

	<!-- Main content: graph + side panel -->
	<div class="flex flex-1 overflow-hidden">
		<!-- Graph SVG -->
		<div class="flex-1 relative">
			{#if loading}
				<div class="absolute inset-0 flex items-center justify-center">
					<p class="text-sm text-gray-400">Laster graf…</p>
				</div>
			{:else if error}
				<div class="absolute inset-0 flex items-center justify-center">
					<p class="text-sm text-red-500">{error}</p>
				</div>
			{:else if graphNodes.length === 0}
				<div class="absolute inset-0 flex items-center justify-center">
					<div class="text-center">
						<p class="text-sm text-gray-400">Ingen noder å vise.</p>
						<p class="mt-1 text-xs text-gray-300">Opprett et tema for å komme i gang.</p>
					</div>
				</div>
			{/if}
			<svg bind:this={svgEl} class="w-full h-full"></svg>

			<!-- Legend -->
			<div class="absolute bottom-3 left-3 rounded-lg border border-gray-200 bg-white/90 p-2 text-xs backdrop-blur">
				<div class="mb-1 font-medium text-gray-500">Nodetyper</div>
				<div class="flex flex-wrap gap-x-3 gap-y-1">
					{#each Object.entries(kindColors) as [kind, color]}
						<button
							onclick={() => toggleKindFilter(kind)}
							class="flex items-center gap-1 {filterKinds.includes(kind) ? 'opacity-100 font-medium' : filterKinds.length > 0 ? 'opacity-40' : 'opacity-80'}"
						>
							<span class="inline-block h-2.5 w-2.5 rounded-full" style="background:{color}"></span>
							{kindLabels[kind] ?? kind}
						</button>
					{/each}
				</div>
				<div class="mt-1.5 mb-1 font-medium text-gray-500">Kanttyper</div>
				<div class="flex flex-wrap gap-x-3 gap-y-1">
					<button
						onclick={() => toggleEdgeTypeFilter('mentions')}
						class="flex items-center gap-1 {filterEdgeTypes.includes('mentions') ? 'font-medium' : filterEdgeTypes.length > 0 ? 'opacity-40' : 'opacity-80'}"
					>
						<span class="inline-block h-0.5 w-4 bg-violet-500"></span>
						Nevner
					</button>
					<button
						onclick={() => toggleEdgeTypeFilter('belongs_to')}
						class="flex items-center gap-1 {filterEdgeTypes.includes('belongs_to') ? 'font-medium' : filterEdgeTypes.length > 0 ? 'opacity-40' : 'opacity-80'}"
					>
						<span class="inline-block h-0.5 w-4 bg-gray-400" style="border-top: 1px dashed"></span>
						Tilhører
					</button>
					<button
						onclick={() => toggleEdgeTypeFilter('part_of')}
						class="flex items-center gap-1 {filterEdgeTypes.includes('part_of') ? 'font-medium' : filterEdgeTypes.length > 0 ? 'opacity-40' : 'opacity-80'}"
					>
						<span class="inline-block h-0.5 w-4 bg-amber-500" style="border-top: 1px dashed"></span>
						Del av
					</button>
				</div>
				<p class="mt-1.5 text-gray-300">Dobbeltklikk node for å fokusere. Dra for å flytte.</p>
			</div>
		</div>

		<!-- Side panel: selected node info -->
		{#if selectedNode}
			<div class="w-72 shrink-0 border-l border-gray-200 bg-white p-4 overflow-y-auto">
				<div class="flex items-start justify-between">
					<h3 class="font-medium text-gray-900">{selectedNode.title || 'Uten tittel'}</h3>
					<button
						onclick={() => selectedNode = null}
						class="text-gray-400 hover:text-gray-600"
					>
						&times;
					</button>
				</div>
				<span
					class="mt-1 inline-block rounded-full px-2 py-0.5 text-xs text-white"
					style="background:{nodeColor(selectedNode.node_kind)}"
				>
					{kindLabels[selectedNode.node_kind] ?? selectedNode.node_kind}
				</span>

				<!-- Edges for this node -->
				{#if graphEdges.filter(e => e.source_id === selectedNode?.id || e.target_id === selectedNode?.id).length > 0}
				{@const nodeEdges = graphEdges.filter(e => e.source_id === selectedNode?.id || e.target_id === selectedNode?.id)}
					<div class="mt-4">
						<h4 class="text-xs font-medium text-gray-500">Koblinger ({nodeEdges.length})</h4>
						<ul class="mt-1 space-y-1">
							{#each nodeEdges as edge}
								{@const otherId = edge.source_id === selectedNode?.id ? edge.target_id : edge.source_id}
								{@const otherNode = graphNodes.find(n => n.id === otherId)}
								{@const direction = edge.source_id === selectedNode?.id ? '→' : '←'}
								<li class="text-xs text-gray-600">
									<span class="text-gray-400">{direction}</span>
									<span class="font-medium">{edgeTypeLabels[edge.edge_type] ?? edge.edge_type}</span>
									<button
										onclick={() => {
											focusId = otherId;
											loadGraph();
										}}
										class="ml-1 text-blue-600 hover:underline"
									>
										{otherNode?.title || otherId.slice(0, 8) + '…'}
									</button>
								</li>
							{/each}
						</ul>
					</div>
				{/if}

				<div class="mt-4 flex flex-col gap-1">
					<button
						onclick={() => { focusId = selectedNode?.id ?? null; loadGraph(); }}
						class="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700 hover:bg-gray-200"
					>
						Fokuser på denne
					</button>
					<button
						onclick={() => {
							showMentionsForm = true;
							showTopicForm = false;
							mentionsSourceId = selectedNode?.id ?? '';
						}}
						class="rounded bg-purple-100 px-2 py-1 text-xs text-purple-700 hover:bg-purple-200"
					>
						Opprett kobling fra denne
					</button>
				</div>

				<p class="mt-4 text-xs text-gray-300 break-all">ID: {selectedNode.id}</p>
			</div>
		{/if}
	</div>
</div>
