import { useState, useCallback, useRef } from 'react';
import { Node, Edge } from '@xyflow/react';

/**
 * State snapshot for undo/redo
 */
interface WorkflowState {
  nodes: Node[];
  edges: Edge[];
  timestamp: number;
}

/**
 * Return type for useUndoRedo hook
 */
interface UseUndoRedoReturn {
  /** Whether undo is available */
  canUndo: boolean;
  /** Whether redo is available */
  canRedo: boolean;
  /** Number of undo steps available */
  undoCount: number;
  /** Number of redo steps available */
  redoCount: number;
  /** Take a snapshot of current state for undo */
  takeSnapshot: (nodes: Node[], edges: Edge[]) => void;
  /** Undo to previous state */
  undo: () => WorkflowState | null;
  /** Redo to next state */
  redo: () => WorkflowState | null;
  /** Clear all history */
  clearHistory: () => void;
  /** Get history for display */
  getHistory: () => { past: WorkflowState[]; future: WorkflowState[] };
}

/**
 * Maximum number of history states to keep
 */
const MAX_HISTORY_LENGTH = 50;

/**
 * Minimum time between snapshots (ms) to prevent rapid-fire snapshots
 */
const MIN_SNAPSHOT_INTERVAL = 100;

/**
 * Base undo/redo hook - internal use only.
 * Use useUndoRedoWithState for the public API.
 *
 * Features:
 * - Tracks node and edge changes
 * - Supports multiple undo/redo steps
 * - Configurable history limit (50 states)
 * - Debounced snapshots to prevent rapid state changes
 */
function useUndoRedo(): UseUndoRedoReturn {
  // Past states (for undo)
  const [past, setPast] = useState<WorkflowState[]>([]);
  // Future states (for redo)
  const [future, setFuture] = useState<WorkflowState[]>([]);
  // Track last snapshot time to prevent rapid-fire snapshots
  const lastSnapshotTime = useRef<number>(0);

  /**
   * Take a snapshot of the current state
   * Called before making changes to enable undo
   */
  const takeSnapshot = useCallback((nodes: Node[], edges: Edge[]) => {
    const now = Date.now();

    // Debounce rapid snapshots
    if (now - lastSnapshotTime.current < MIN_SNAPSHOT_INTERVAL) {
      return;
    }
    lastSnapshotTime.current = now;

    const snapshot: WorkflowState = {
      nodes: JSON.parse(JSON.stringify(nodes)), // Deep clone
      edges: JSON.parse(JSON.stringify(edges)),
      timestamp: now,
    };

    setPast((prevPast) => {
      const newPast = [...prevPast, snapshot];
      // Limit history length
      if (newPast.length > MAX_HISTORY_LENGTH) {
        return newPast.slice(-MAX_HISTORY_LENGTH);
      }
      return newPast;
    });

    // Clear future when new action is taken
    setFuture([]);
  }, []);

  /**
   * Undo to the previous state
   * Returns the previous state or null if nothing to undo
   */
  const undo = useCallback((): WorkflowState | null => {
    if (past.length === 0) {
      return null;
    }

    const previousState = past[past.length - 1];
    const newPast = past.slice(0, -1);

    setPast(newPast);
    // Note: The current state should be pushed to future by the caller
    // This allows the hook to remain stateless about the "current" state

    return previousState;
  }, [past]);

  /**
   * Redo to the next state
   * Returns the next state or null if nothing to redo
   */
  const redo = useCallback((): WorkflowState | null => {
    if (future.length === 0) {
      return null;
    }

    const nextState = future[future.length - 1];
    const newFuture = future.slice(0, -1);

    setFuture(newFuture);

    return nextState;
  }, [future]);

  /**
   * Clear all history
   */
  const clearHistory = useCallback(() => {
    setPast([]);
    setFuture([]);
    lastSnapshotTime.current = 0;
  }, []);

  /**
   * Get history for display (e.g., in a dropdown)
   */
  const getHistory = useCallback(() => {
    return { past, future };
  }, [past, future]);

  return {
    canUndo: past.length > 0,
    canRedo: future.length > 0,
    undoCount: past.length,
    redoCount: future.length,
    takeSnapshot,
    undo,
    redo,
    clearHistory,
    getHistory,
  };
}

/**
 * Enhanced version of useUndoRedo with automatic state management
 * Integrates directly with React Flow's state
 */
export function useUndoRedoWithState<NodeType extends Node = Node>(
  nodes: NodeType[],
  edges: Edge[],
  setNodes: (nodes: NodeType[] | ((nodes: NodeType[]) => NodeType[])) => void,
  setEdges: (edges: Edge[] | ((edges: Edge[]) => Edge[])) => void,
) {
  const {
    canUndo,
    undoCount,
    takeSnapshot,
    undo: undoBase,
    clearHistory,
    getHistory,
  } = useUndoRedo();

  // Store current state ref for pushing to future on undo
  const currentStateRef = useRef<{ nodes: NodeType[]; edges: Edge[] }>({ nodes: [], edges: [] });

  // Keep current state ref updated
  currentStateRef.current = { nodes, edges };

  // Past states for redo (when undoing, current state goes here)
  const [futureForRedo, setFutureForRedo] = useState<WorkflowState[]>([]);

  /**
   * Undo with automatic state update
   */
  const undo = useCallback(() => {
    const previousState = undoBase();
    if (previousState) {
      // Save current state for redo
      setFutureForRedo((prev) => [
        ...prev,
        {
          nodes: JSON.parse(JSON.stringify(currentStateRef.current.nodes)),
          edges: JSON.parse(JSON.stringify(currentStateRef.current.edges)),
          timestamp: Date.now(),
        },
      ]);

      // Apply previous state (cast needed: JSON.parse deep clone loses subtype info)
      setNodes(previousState.nodes as NodeType[]);
      setEdges(previousState.edges);
    }
  }, [undoBase, setNodes, setEdges]);

  /**
   * Redo with automatic state update
   */
  const redo = useCallback(() => {
    if (futureForRedo.length === 0) return;

    const nextState = futureForRedo[futureForRedo.length - 1];

    // Save current state for undo
    takeSnapshot(currentStateRef.current.nodes, currentStateRef.current.edges);

    // Remove from future
    setFutureForRedo((prev) => prev.slice(0, -1));

    // Apply next state (cast needed: JSON.parse deep clone loses subtype info)
    setNodes(nextState.nodes as NodeType[]);
    setEdges(nextState.edges);
  }, [futureForRedo, takeSnapshot, setNodes, setEdges]);

  /**
   * Take snapshot - also clears redo stack
   */
  const takeSnapshotAndClearRedo = useCallback(
    (snapshotNodes: Node[], snapshotEdges: Edge[]) => {
      takeSnapshot(snapshotNodes, snapshotEdges);
      setFutureForRedo([]);
    },
    [takeSnapshot],
  );

  return {
    canUndo,
    canRedo: futureForRedo.length > 0,
    undoCount,
    redoCount: futureForRedo.length,
    takeSnapshot: takeSnapshotAndClearRedo,
    undo,
    redo,
    clearHistory: useCallback(() => {
      clearHistory();
      setFutureForRedo([]);
    }, [clearHistory]),
    getHistory,
  };
}
