/**
 * Hook for managing node resize behavior
 */

import { useAtomValue, useSetAtom } from 'jotai';
import { useRef, useState, useEffect } from 'react';
import { flushSync } from 'react-dom';
import type { NodeId, NodePosition, GraphNodeAttributes, UINodeProperties } from '../core/types';
import { graphAtom, currentGraphIdAtom, graphUpdateVersionAtom } from '../core/graph-store';
import { nodePositionUpdateCounterAtom } from '../core/graph-position';
import { zoomAtom } from '../core/viewport-store';
import { startMutationAtom, completeMutationAtom } from '../core/sync-store';
import { createDebug } from '../utils/debug';

const debug = createDebug('resize');

export type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';

export interface UseNodeResizeOptions {
	/** Callback to persist node size/position to database */
	onPersist?: (nodeId: NodeId, graphId: string, uiProperties: UINodeProperties) => Promise<void>;
	/** Callback when persist fails */
	onPersistError?: (nodeId: NodeId, error: Error) => void;
	/** Minimum node width */
	minWidth?: number;
	/** Minimum node height */
	minHeight?: number;
}

export interface UseNodeResizeProps {
	id: NodeId;
	nodeData: { width?: number; height?: number };
	updateNodePositions: (updates: { id: NodeId; pos: NodePosition }[]) => void;
	options?: UseNodeResizeOptions;
}

/**
 * Hook for node resize behavior
 */
export function useNodeResize({ id, nodeData, updateNodePositions, options = {} }: UseNodeResizeProps) {
	const { onPersist, onPersistError, minWidth = 200, minHeight = 150 } = options;

	const [localWidth, setLocalWidth] = useState(nodeData.width || 500);
	const [localHeight, setLocalHeight] = useState(nodeData.height || 500);
	const [isResizing, setIsResizing] = useState(false);

	const resizeStartRef = useRef<{
		width: number;
		height: number;
		startX: number;
		startY: number;
		startNodeX: number;
		startNodeY: number;
		direction: ResizeDirection;
	} | null>(null);

	const graph = useAtomValue(graphAtom);
	const currentZoom = useAtomValue(zoomAtom);
	const currentGraphId = useAtomValue(currentGraphIdAtom);
	const startMutation = useSetAtom(startMutationAtom);
	const completeMutation = useSetAtom(completeMutationAtom);
	const setGraphUpdateVersion = useSetAtom(graphUpdateVersionAtom);
	const setNodePositionUpdateCounter = useSetAtom(nodePositionUpdateCounterAtom);

	// Sync local state with node data when not resizing
	useEffect(() => {
		if (!isResizing) {
			setLocalWidth(nodeData.width || 500);
			setLocalHeight(nodeData.height || 500);
		}
	}, [nodeData.width, nodeData.height, isResizing]);

	const createResizeStart = (direction: ResizeDirection) => (e: React.PointerEvent) => {
		e.stopPropagation();
		e.preventDefault();
		setIsResizing(true);

		const nodeAttrs = graph.hasNode(id) ? (graph.getNodeAttributes(id) as { x: number; y: number }) : { x: 0, y: 0 };
		resizeStartRef.current = {
			width: localWidth,
			height: localHeight,
			startX: e.clientX,
			startY: e.clientY,
			startNodeX: nodeAttrs.x,
			startNodeY: nodeAttrs.y,
			direction,
		};
		(e.target as HTMLElement).setPointerCapture(e.pointerId);
	};

	const handleResizeMove = (e: React.PointerEvent) => {
		if (!resizeStartRef.current) return;
		e.stopPropagation();
		e.preventDefault();

		const deltaX = (e.clientX - resizeStartRef.current.startX) / currentZoom;
		const deltaY = (e.clientY - resizeStartRef.current.startY) / currentZoom;
		const { direction, width: startWidth, height: startHeight, startNodeX, startNodeY } = resizeStartRef.current;

		let newWidth = startWidth;
		let newHeight = startHeight;
		let newX = startNodeX;
		let newY = startNodeY;

		if (direction.includes('e')) {
			newWidth = Math.max(minWidth, startWidth + deltaX);
		}
		if (direction.includes('w')) {
			newWidth = Math.max(minWidth, startWidth - deltaX);
			newX = startNodeX + (startWidth - newWidth);
		}
		if (direction.includes('s')) {
			newHeight = Math.max(minHeight, startHeight + deltaY);
		}
		if (direction.includes('n')) {
			newHeight = Math.max(minHeight, startHeight - deltaY);
			newY = startNodeY + (startHeight - newHeight);
		}

		if (graph.hasNode(id)) {
			graph.setNodeAttribute(id, 'width', newWidth);
			graph.setNodeAttribute(id, 'height', newHeight);
			graph.setNodeAttribute(id, 'x', newX);
			graph.setNodeAttribute(id, 'y', newY);
		}

		flushSync(() => {
			setLocalWidth(newWidth);
			setLocalHeight(newHeight);
			setGraphUpdateVersion((v) => v + 1);
		});

		if (direction.includes('n') || direction.includes('w')) {
			updateNodePositions([{ id, pos: { x: newX, y: newY } }]);
		} else {
			setNodePositionUpdateCounter((c) => c + 1);
		}
	};

	const handleResizeEnd = (e: React.PointerEvent) => {
		if (!resizeStartRef.current) return;
		e.stopPropagation();
		(e.target as HTMLElement).releasePointerCapture(e.pointerId);
		setIsResizing(false);

		if (!currentGraphId || !resizeStartRef.current) {
			resizeStartRef.current = null;
			return;
		}

		const finalAttrs = graph.hasNode(id) ? (graph.getNodeAttributes(id) as GraphNodeAttributes) : null;
		if (!finalAttrs) {
			resizeStartRef.current = null;
			return;
		}

		const finalWidth = finalAttrs.width || localWidth;
		const finalHeight = finalAttrs.height || localHeight;
		const finalX = finalAttrs.x;
		const finalY = finalAttrs.y;

		setLocalWidth(finalWidth);
		setLocalHeight(finalHeight);

		// If no persist callback, just finish
		if (!onPersist) {
			resizeStartRef.current = null;
			return;
		}

		const existingDbUiProps: Record<string, unknown> =
			typeof finalAttrs.dbData.ui_properties === 'object' &&
			finalAttrs.dbData.ui_properties !== null &&
			!Array.isArray(finalAttrs.dbData.ui_properties)
				? (finalAttrs.dbData.ui_properties as Record<string, unknown>)
				: {};

		const newUiProperties = {
			...existingDbUiProps,
			width: finalWidth,
			height: finalHeight,
			x: finalX,
			y: finalY,
		};

		startMutation();
		onPersist(id, currentGraphId, newUiProperties)
			.then(() => {
				completeMutation(true);
			})
			.catch((error) => {
				completeMutation(false);
				// Rollback on error
				if (resizeStartRef.current) {
					setLocalWidth(resizeStartRef.current.width);
					setLocalHeight(resizeStartRef.current.height);
				}
				onPersistError?.(id, error);
			})
			.finally(() => {
				resizeStartRef.current = null;
			});
	};

	return {
		localWidth,
		localHeight,
		isResizing,
		createResizeStart,
		handleResizeMove,
		handleResizeEnd,
	};
}
