/**
 * Transfer service — drag-and-drop compatibility and execution between tool panels.
 *
 * Centralizes compatibility checking, transfer mode resolution, and
 * transfer execution for the universal transfer system (universell overføring).
 *
 * Two modes:
 * - **Innholdstransfer**: Creates new node + source_material edge (content transformation)
 * - **Lettvekts-triage**: Adds edge/placement to existing node (context addition)
 *
 * Default mode is determined by the source→target tool pair.
 * Shift-modifier overrides to always create a new node (innholdstransfer).
 *
 * Ref: docs/features/universell_overfoering.md § 1, 3
 * Ref: docs/retninger/arbeidsflaten.md § Kompatibilitetsmatrise
 */

import { createNode, createEdge, type CreateNodeResponse } from '$lib/api.js';
import type { PlacementIntent } from '$lib/components/blockshell/types.js';

export type ToolType = 'ai_tool' | 'editor' | 'chat' | 'kanban' | 'calendar' | 'studio';

export type TransferMode = 'innholdstransfer' | 'lettvekts-triage';

export interface DragPayload {
	nodeId: string;
	nodeKind: string;
	/** Optional: AI preset ID when dragging from AI tool panel */
	presetId?: string;
	sourcePanel: ToolType;
}

export interface CompatResult {
	compatible: boolean;
	/** Human-readable reason when incompatible */
	reason?: string;
	/** Suggested alternative action */
	suggestion?: string;
}

const MIME_TYPE = 'application/x-synops-transfer';

/** Encode drag payload into dataTransfer */
export function setDragPayload(dt: DataTransfer, payload: DragPayload): void {
	dt.setData('text/plain', payload.nodeId);
	dt.setData(MIME_TYPE, JSON.stringify(payload));
	dt.effectAllowed = 'copy';
}

/** Decode drag payload from dataTransfer. Falls back to plain text node ID. */
export function getDragPayload(dt: DataTransfer): DragPayload | null {
	const json = dt.getData(MIME_TYPE);
	if (json) {
		try {
			return JSON.parse(json) as DragPayload;
		} catch { /* fall through */ }
	}
	const nodeId = dt.getData('text/plain');
	if (nodeId) {
		return { nodeId, nodeKind: 'unknown', sourcePanel: 'editor' as ToolType };
	}
	return null;
}

/** Media node kinds that are incompatible with text-only tools */
const MEDIA_KINDS = new Set(['media', 'audio', 'image', 'video']);

/** Check if a node kind represents media content */
function isMediaKind(nodeKind: string): boolean {
	return MEDIA_KINDS.has(nodeKind);
}

/** Check if a node kind represents audio */
function isAudioKind(nodeKind: string): boolean {
	return nodeKind === 'audio' || nodeKind === 'media'; // media is often audio in this context
}

/**
 * Check compatibility for dropping a node onto the AI tool panel.
 */
export function checkAiToolCompat(nodeKind: string, hasContent: boolean): CompatResult {
	if (isMediaKind(nodeKind)) {
		return {
			compatible: false,
			reason: 'AI-verktøyet behandler kun tekst.',
			suggestion: nodeKind === 'audio' || nodeKind === 'media'
				? 'Transkriber lydfilen først, og dra transkripsjonen hit.'
				: 'Kun tekstnoder kan prosesseres av AI-verktøyet.'
		};
	}
	if (nodeKind !== 'content' && nodeKind !== 'communication') {
		return {
			compatible: false,
			reason: `Nodetypen «${nodeKind}» støttes ikke av AI-verktøyet.`,
			suggestion: 'Kun innholds- og kommunikasjonsnoder med tekst kan prosesseres.'
		};
	}
	if (!hasContent) {
		return {
			compatible: false,
			reason: 'Noden har ikke noe tekstinnhold å prosessere.'
		};
	}
	return { compatible: true };
}

/**
 * Check compatibility for dropping an AI preset onto a content node
 * (tool_to_node direction — in-place revision).
 */
export function checkToolToNodeCompat(nodeKind: string, hasContent: boolean): CompatResult {
	if (isMediaKind(nodeKind)) {
		return {
			compatible: false,
			reason: 'AI-verktøyet kan ikke revidere mediefiler.',
			suggestion: 'Kun tekstnoder kan revideres in-place.'
		};
	}
	if (nodeKind !== 'content') {
		return {
			compatible: false,
			reason: `Kun innholdsnoder kan revideres in-place.`
		};
	}
	if (!hasContent) {
		return {
			compatible: false,
			reason: 'Noden har ikke noe tekstinnhold å revidere.'
		};
	}
	return { compatible: true };
}

// --- Per-tool compatibility checks ---
// Ref: docs/retninger/arbeidsflaten.md § Kompatibilitetsmatrise

/**
 * Check compatibility for dropping a node onto the Chat panel.
 * Chat accepts practically anything — it wraps the node as a message with mentions.
 */
export function checkChatCompat(payload: DragPayload): CompatResult {
	if (payload.sourcePanel === 'chat') {
		return {
			compatible: false,
			reason: 'Noden er allerede i denne chatten.',
			suggestion: 'Dra til en annen kontekst for å dele.'
		};
	}
	return { compatible: true };
}

/**
 * Check compatibility for dropping a node onto the Kanban panel.
 * Kanban accepts text-based nodes (communication, content) as new tasks.
 * Media and images can't become tasks directly.
 */
export function checkKanbanCompat(payload: DragPayload): CompatResult {
	if (payload.sourcePanel === 'kanban') {
		// Same board — handled as column move, not a new drop
		return { compatible: true };
	}
	if (isMediaKind(payload.nodeKind)) {
		return {
			compatible: false,
			reason: 'Mediefiler kan ikke bli oppgaver direkte.',
			suggestion: 'Dra til Chat for å dele, eller til Artikkelverktøyet for å sette inn.'
		};
	}
	if (payload.nodeKind === 'communication' || payload.nodeKind === 'content') {
		return { compatible: true };
	}
	return {
		compatible: false,
		reason: `Nodetypen «${payload.nodeKind}» kan ikke legges til som oppgave.`
	};
}

/**
 * Check compatibility for dropping a node onto the Calendar panel.
 * Calendar accepts text nodes as events, and kanban cards for scheduling.
 */
export function checkCalendarCompat(payload: DragPayload): CompatResult {
	if (isMediaKind(payload.nodeKind)) {
		return {
			compatible: false,
			reason: 'Mediefiler kan ikke planlegges i kalenderen.',
			suggestion: 'Opprett en oppgave først, og planlegg den.'
		};
	}
	if (payload.nodeKind === 'communication' || payload.nodeKind === 'content') {
		return { compatible: true };
	}
	return {
		compatible: false,
		reason: `Nodetypen «${payload.nodeKind}» kan ikke legges til i kalenderen.`
	};
}

/**
 * Check compatibility for dropping a node onto the Editor (Artikkelverktøy) panel.
 * Editor accepts most node types — text as source_material, media as embedded content.
 */
export function checkEditorCompat(payload: DragPayload): CompatResult {
	if (payload.nodeKind === 'communication' || payload.nodeKind === 'content') {
		return { compatible: true };
	}
	if (isMediaKind(payload.nodeKind)) {
		// Media (audio, image) can be embedded
		return { compatible: true };
	}
	// Agent, person, collection etc. don't make sense in editor
	return {
		compatible: false,
		reason: `Nodetypen «${payload.nodeKind}» kan ikke settes inn i artikkelverktøyet.`
	};
}

/**
 * Check compatibility for dropping a node onto the Studio panel.
 * Studio only accepts audio files.
 */
export function checkStudioCompat(payload: DragPayload): CompatResult {
	if (payload.sourcePanel === 'studio') {
		return {
			compatible: false,
			reason: 'Lydfiler kan ikke slås sammen direkte.',
			suggestion: 'Åpne i Studioet for å redigere.'
		};
	}
	if (isAudioKind(payload.nodeKind)) {
		return { compatible: true };
	}
	if (isMediaKind(payload.nodeKind)) {
		return {
			compatible: false,
			reason: 'Studioet behandler kun lydfiler.',
			suggestion: payload.nodeKind === 'image'
				? 'Bilder kan dras til Artikkelverktøyet.'
				: undefined
		};
	}
	return {
		compatible: false,
		reason: 'Studioet behandler kun lydfiler.',
		suggestion: 'Dra lydfiler hit for å åpne dem i studioet.'
	};
}

/** Map of tool type to its compatibility check function */
const TOOL_COMPAT_CHECKS: Record<ToolType, (payload: DragPayload) => CompatResult> = {
	chat: checkChatCompat,
	kanban: checkKanbanCompat,
	calendar: checkCalendarCompat,
	editor: checkEditorCompat,
	studio: checkStudioCompat,
	ai_tool: (payload) => checkAiToolCompat(payload.nodeKind, true), // hasContent not knowable from payload alone
};

/**
 * Create a BlockReceiver for a given tool type.
 * Used by workspace/BlockShell to validate drops.
 */
export function createBlockReceiver(toolType: ToolType): { canReceive: (payload: DragPayload) => CompatResult } {
	const checkFn = TOOL_COMPAT_CHECKS[toolType];
	return {
		canReceive(payload: DragPayload): CompatResult {
			return checkFn(payload);
		}
	};
}

// =============================================================================
// Transfer mode resolution
// Ref: docs/features/universell_overfoering.md § 1.1
// =============================================================================

/**
 * Default transfer mode matrix: source → target → mode.
 *
 * Innholdstransfer (new node): content is *transformed* for a new context.
 *   e.g. chat → editor: chatboble becomes source material for an article.
 *
 * Lettvekts-triage (existing node): content *appears* in an additional context.
 *   e.g. chat → kanban: same node gets a board placement.
 *
 * Rule of thumb: if the target tool creates a *new kind* of content from
 * the source, it's innholdstransfer. If it just shows the same node in
 * a different view, it's lettvekts-triage.
 */
const DEFAULT_TRANSFER_MODE: Partial<Record<ToolType, Partial<Record<ToolType, TransferMode>>>> = {
	chat: {
		kanban:   'lettvekts-triage',   // Same node appears on board
		calendar: 'lettvekts-triage',   // Same node gets scheduled
		editor:   'innholdstransfer',   // New article from chat content
		studio:   'lettvekts-triage',   // Open audio in studio
		ai_tool:  'innholdstransfer',   // AI creates new content from source
		chat:     'lettvekts-triage',   // Cross-chat share
	},
	kanban: {
		chat:     'lettvekts-triage',   // Share task in chat
		calendar: 'lettvekts-triage',   // Schedule existing task
		editor:   'innholdstransfer',   // Task becomes article source
		studio:   'lettvekts-triage',   // Open in studio
		ai_tool:  'innholdstransfer',   // AI processes task
		kanban:   'lettvekts-triage',   // Cross-board move
	},
	editor: {
		chat:     'lettvekts-triage',   // Share article in chat
		kanban:   'lettvekts-triage',   // Track article as task
		calendar: 'lettvekts-triage',   // Schedule article
		studio:   'lettvekts-triage',   // Open media in studio
		ai_tool:  'innholdstransfer',   // AI revises article
		editor:   'innholdstransfer',   // Article → new article (fork)
	},
	calendar: {
		chat:     'lettvekts-triage',   // Share event in chat
		kanban:   'lettvekts-triage',   // Track event as task
		editor:   'innholdstransfer',   // Event becomes article source
		studio:   'lettvekts-triage',   // Open in studio
		ai_tool:  'innholdstransfer',   // AI processes event
		calendar: 'lettvekts-triage',   // Cross-calendar
	},
	studio: {
		chat:     'lettvekts-triage',   // Share audio in chat
		kanban:   'lettvekts-triage',   // Track audio as task
		calendar: 'lettvekts-triage',   // Schedule audio
		editor:   'innholdstransfer',   // Audio → article source
		ai_tool:  'innholdstransfer',   // AI processes audio
		studio:   'lettvekts-triage',   // Cross-studio
	},
	ai_tool: {
		chat:     'lettvekts-triage',   // Share AI result in chat
		kanban:   'lettvekts-triage',   // Track result as task
		calendar: 'lettvekts-triage',   // Schedule result
		editor:   'innholdstransfer',   // AI result → article source
		studio:   'lettvekts-triage',   // Open in studio
		ai_tool:  'innholdstransfer',   // Chain AI processing
	},
};

/**
 * Resolve the transfer mode for a source→target tool pair.
 *
 * - Default mode comes from the tool-pair matrix above.
 * - Shift-modifier forces innholdstransfer (always create new node).
 *
 * @param source - Source tool type (where content was dragged from)
 * @param target - Target tool type (where content was dropped)
 * @param shiftKey - Whether Shift was held during drop (forces new node)
 * @returns The resolved transfer mode
 */
export function resolveTransferMode(
	source: ToolType,
	target: ToolType,
	shiftKey: boolean = false,
): TransferMode {
	// Shift override: always create a new node
	if (shiftKey) return 'innholdstransfer';

	// Look up default from matrix
	const sourceMap = DEFAULT_TRANSFER_MODE[source];
	if (sourceMap) {
		const mode = sourceMap[target];
		if (mode) return mode;
	}

	// Fallback: if the target creates new content (editor, ai_tool), use innholdstransfer.
	// Otherwise default to lettvekts-triage (most common case).
	if (target === 'editor' || target === 'ai_tool') return 'innholdstransfer';
	return 'lettvekts-triage';
}

// =============================================================================
// Transfer execution
// Ref: docs/features/universell_overfoering.md § 1, 3, 4
// =============================================================================

/** Result of a completed transfer */
export interface TransferResult {
	mode: TransferMode;
	/** The node that ended up in the target (original for triage, new for innholdstransfer) */
	nodeId: string;
	/** Edge ID for the source_material edge (only for innholdstransfer) */
	sourceEdgeId?: string;
	/** Edge ID for the placement/context edge (for lettvekts-triage) */
	placementEdgeId?: string;
}

/**
 * Execute a transfer: either create a new node with source_material edge
 * (innholdstransfer) or add an edge/placement to the existing node
 * (lettvekts-triage).
 *
 * @param accessToken - Auth token for API calls
 * @param payload - The drag payload (source node info)
 * @param intent - The placement intent from the receiver (target info)
 * @param shiftKey - Whether Shift was held (overrides mode to innholdstransfer)
 */
export async function executeTransfer(
	accessToken: string,
	payload: DragPayload,
	intent: PlacementIntent,
	shiftKey: boolean = false,
): Promise<TransferResult> {
	const mode = resolveTransferMode(payload.sourcePanel, intent.contextType as ToolType, shiftKey);

	if (mode === 'innholdstransfer') {
		return executeInnholdstransfer(accessToken, payload, intent);
	} else {
		return executeLettvektsTriage(accessToken, payload, intent);
	}
}

/**
 * Innholdstransfer: Create a new node and link it back to the source
 * with a source_material edge.
 *
 * Flow:
 * 1. Create new content node (inherits title from source, empty content)
 * 2. Create source_material edge: new node → source node
 * 3. If target has a context, create belongs_to edge to context
 */
async function executeInnholdstransfer(
	accessToken: string,
	payload: DragPayload,
	intent: PlacementIntent,
): Promise<TransferResult> {
	// 1. Create a new content node in the target context
	const nodeResp: CreateNodeResponse = await createNode(accessToken, {
		node_kind: 'content',
		visibility: 'hidden',
		metadata: {
			transfer_source: payload.sourcePanel,
			transfer_target: intent.contextType,
		},
	});

	// 2. Create source_material edge: new node → original source
	const edgeResp = await createEdge(accessToken, {
		source_id: nodeResp.node_id,
		target_id: payload.nodeId,
		edge_type: 'source_material',
		metadata: {
			context: 'referenced',
			excerpt: '',   // Will be populated by UI or AI later
		},
	});

	// 3. Create belongs_to edge to target context if contextId is set
	let placementEdgeId: string | undefined;
	if (intent.contextId) {
		const belongsResp = await createEdge(accessToken, {
			source_id: nodeResp.node_id,
			target_id: intent.contextId,
			edge_type: 'belongs_to',
			metadata: intent.position ? { placement: intent.position } : {},
		});
		placementEdgeId = belongsResp.edge_id;
	}

	return {
		mode: 'innholdstransfer',
		nodeId: nodeResp.node_id,
		sourceEdgeId: edgeResp.edge_id,
		placementEdgeId,
	};
}

/**
 * Lettvekts-triage: Add the existing node to a new context via edge.
 *
 * Flow:
 * 1. Create belongs_to edge from source node to target context
 *    (with position metadata for kanban column, calendar date, etc.)
 */
async function executeLettvektsTriage(
	accessToken: string,
	payload: DragPayload,
	intent: PlacementIntent,
): Promise<TransferResult> {
	const edgeResp = await createEdge(accessToken, {
		source_id: payload.nodeId,
		target_id: intent.contextId,
		edge_type: 'belongs_to',
		metadata: intent.position ? { placement: intent.position } : {},
	});

	return {
		mode: 'lettvekts-triage',
		nodeId: payload.nodeId,
		placementEdgeId: edgeResp.edge_id,
	};
}
