/**
 * Selection Path Store
 *
 * Manages the active lasso/rect selection path during drag operations.
 * The gesture resolver already returns 'lasso-select' and 'rect-select' intents;
 * this store tracks the drawn path and resolves which nodes fall inside it.
 */

import { atom } from 'jotai';
import { uiNodesAtom } from './graph-derived';
import { selectedNodeIdsAtom } from './selection-store';
import type { NodeId } from './types';

// --- Types ---

export type SelectionPathType = 'lasso' | 'rect';

export interface SelectionPathPoint {
	x: number;
	y: number;
}

export interface SelectionPath {
	type: SelectionPathType;
	/** All points in the path (world coordinates) */
	points: SelectionPathPoint[];
}

// --- Core Atoms ---

/** The active selection path, or null when not selecting */
export const selectionPathAtom = atom<SelectionPath | null>(null);

/** Whether a selection drag is currently in progress */
export const isSelectingAtom = atom((get) => get(selectionPathAtom) !== null);

// --- Actions ---

/** Start a new selection path */
export const startSelectionAtom = atom(
	null,
	(_get, set, { type, point }: { type: SelectionPathType; point: SelectionPathPoint }) => {
		set(selectionPathAtom, { type, points: [point] });
	},
);

/** Add a point to the current selection path */
export const updateSelectionAtom = atom(null, (get, set, point: SelectionPathPoint) => {
	const current = get(selectionPathAtom);
	if (!current) return;

	if (current.type === 'rect') {
		// For rect, keep only start point + current point
		set(selectionPathAtom, { ...current, points: [current.points[0], point] });
	} else {
		// For lasso, append the point
		set(selectionPathAtom, { ...current, points: [...current.points, point] });
	}
});

/** Cancel the active selection drag without changing selection. */
export const cancelSelectionAtom = atom(null, (_get, set) => {
	set(selectionPathAtom, null);
});

/** End selection: compute intersecting nodes, update selection, clear path */
export const endSelectionAtom = atom(null, (get, set) => {
	const path = get(selectionPathAtom);
	if (!path || path.points.length < 2) {
		set(selectionPathAtom, null);
		return;
	}

	const nodes = get(uiNodesAtom);
	const selectedIds: NodeId[] = [];

	if (path.type === 'rect') {
		const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
		const minX = Math.min(p1.x, p2.x);
		const maxX = Math.max(p1.x, p2.x);
		const minY = Math.min(p1.y, p2.y);
		const maxY = Math.max(p1.y, p2.y);

		for (const node of nodes) {
			// AABB intersection: node overlaps selection rect
			const nodeRight = node.position.x + (node.width ?? 200);
			const nodeBottom = node.position.y + (node.height ?? 100);

			if (
				node.position.x < maxX &&
				nodeRight > minX &&
				node.position.y < maxY &&
				nodeBottom > minY
			) {
				selectedIds.push(node.id);
			}
		}
	} else {
		// Lasso: point-in-polygon test on node center
		const polygon = path.points;
		for (const node of nodes) {
			const cx = node.position.x + (node.width ?? 200) / 2;
			const cy = node.position.y + (node.height ?? 100) / 2;
			if (pointInPolygon(cx, cy, polygon)) {
				selectedIds.push(node.id);
			}
		}
	}

	// Set selection to the intersecting nodes
	set(selectedNodeIdsAtom, new Set(selectedIds));
	set(selectionPathAtom, null);
});

// --- Derived: selection rect bounds (for rendering) ---

export interface SelectionRect {
	x: number;
	y: number;
	width: number;
	height: number;
}

/** For rect mode, compute the bounding box */
export const selectionRectAtom = atom((get): SelectionRect | null => {
	const path = get(selectionPathAtom);
	if (!path || path.type !== 'rect' || path.points.length < 2) return null;

	const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
	return {
		x: Math.min(p1.x, p2.x),
		y: Math.min(p1.y, p2.y),
		width: Math.abs(p2.x - p1.x),
		height: Math.abs(p2.y - p1.y),
	};
});

// --- Utilities ---

/**
 * Ray-casting point-in-polygon test.
 * Returns true if (px, py) is inside the polygon defined by points.
 */
export function pointInPolygon(
	px: number,
	py: number,
	polygon: SelectionPathPoint[],
): boolean {
	let inside = false;
	const n = polygon.length;
	for (let i = 0, j = n - 1; i < n; j = i++) {
		const xi = polygon[i].x;
		const yi = polygon[i].y;
		const xj = polygon[j].x;
		const yj = polygon[j].y;

		if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
			inside = !inside;
		}
	}
	return inside;
}
