/**
 * Canvas History Store — v2
 *
 * Delta-based undo/redo — replaces full-graph JSON.stringify snapshots.
 *
 * Performance improvement:
 *   - Before: O(N) per operation (deep-clone all nodes + edges)
 *   - After:  O(1) for moves, O(K) for batch operations
 *
 * Each history entry stores forward + reverse deltas, not full snapshots.
 * Falls back to full snapshot for complex operations (graph import).
 *
 * Pure delta functions are in ./history-actions.ts
 */

import { atom } from 'jotai';
import type { GraphNodeAttributes, GraphEdgeAttributes } from './types';
import { graphAtom, graphUpdateVersionAtom, graphOptions } from './graph-store';
import { nodePositionUpdateCounterAtom } from './graph-position';
import { createDebug } from '../utils/debug';

// Re-export types for backward compat
export type { HistoryDelta, HistoryEntry, HistoryState, GraphSnapshot } from './history-types';
import type { HistoryDelta, HistoryEntry, HistoryState, GraphSnapshot } from './history-types';

// Re-export pure functions for backward compat
export { applyDelta, invertDelta, createSnapshot } from './history-actions';
import { applyDelta, invertDelta, createSnapshot } from './history-actions';

const debug = createDebug('history');

// =============================================================================
// Configuration
// =============================================================================

/** Maximum number of history entries to keep */
const MAX_HISTORY_SIZE = 50;


// =============================================================================
// Atoms
// =============================================================================

export const historyStateAtom = atom<HistoryState>({
    past: [],
    future: [],
    isApplying: false,
});

export const canUndoAtom = atom((get) => {
    const history = get(historyStateAtom);
    return history.past.length > 0 && !history.isApplying;
});

export const canRedoAtom = atom((get) => {
    const history = get(historyStateAtom);
    return history.future.length > 0 && !history.isApplying;
});

export const undoCountAtom = atom((get) => get(historyStateAtom).past.length);
export const redoCountAtom = atom((get) => get(historyStateAtom).future.length);

// =============================================================================
// Push Delta (replaces pushHistoryAtom's full-snapshot approach)
// =============================================================================

/**
 * Push a delta to history. Call AFTER making the change.
 * The delta describes what changed; the reverse is auto-computed.
 */
export const pushDeltaAtom = atom(null, (get, set, delta: HistoryDelta & { label?: string }) => {
    const history = get(historyStateAtom);
    if (history.isApplying) return;

    const { label, ...cleanDelta } = delta;
    const entry: HistoryEntry = {
        forward: cleanDelta,
        reverse: invertDelta(cleanDelta),
        timestamp: Date.now(),
        label,
    };

    const newPast = [...history.past, entry];
    if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();

    set(historyStateAtom, {
        past: newPast,
        future: [], // Clear redo stack
        isApplying: false,
    });

    debug('Pushed delta: %s (past: %d)', label || delta.type, newPast.length);
});

/**
 * Legacy: Push a full-graph snapshot to history.
 * Use for complex operations where computing deltas is impractical.
 * Call BEFORE making changes.
 */
export const pushHistoryAtom = atom(null, (get, set, label?: string) => {
    const history = get(historyStateAtom);
    if (history.isApplying) return;

    const graph = get(graphAtom);
    const snapshot = createSnapshot(graph, label);

    // Store as a full-snapshot delta
    const forward: HistoryDelta = {
        type: 'full-snapshot',
        nodes: snapshot.nodes,
        edges: snapshot.edges,
    };

    const entry: HistoryEntry = {
        forward,
        reverse: forward, // For full snapshots, reverse IS the current state
        timestamp: Date.now(),
        label,
    };

    const newPast = [...history.past, entry];
    if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();

    set(historyStateAtom, {
        past: newPast,
        future: [],
        isApplying: false,
    });

    debug('Pushed snapshot: %s (past: %d)', label || 'unnamed', newPast.length);
});

// =============================================================================
// Undo / Redo
// =============================================================================

export const undoAtom = atom(null, (get, set) => {
    const history = get(historyStateAtom);
    if (history.past.length === 0 || history.isApplying) return false;

    set(historyStateAtom, { ...history, isApplying: true });

    try {
        const graph = get(graphAtom);
        const newPast = [...history.past];
        const entry = newPast.pop()!;

        // For full-snapshot entries, save current state before restoring
        let forwardForRedo = entry.forward;
        if (entry.reverse.type === 'full-snapshot') {
            const currentSnapshot = createSnapshot(graph, 'current');
            forwardForRedo = {
                type: 'full-snapshot',
                nodes: currentSnapshot.nodes,
                edges: currentSnapshot.edges,
            };
        }

        const structuralChange = applyDelta(graph, entry.reverse);
        if (structuralChange) {
            set(graphAtom, graph);
            set(graphUpdateVersionAtom, (v) => v + 1);
        }
        set(nodePositionUpdateCounterAtom, (c) => c + 1);

        const redoEntry: HistoryEntry = {
            forward: forwardForRedo,
            reverse: entry.reverse,
            timestamp: entry.timestamp,
            label: entry.label,
        };

        set(historyStateAtom, {
            past: newPast,
            future: [redoEntry, ...history.future],
            isApplying: false,
        });

        debug('Undo: %s (past: %d, future: %d)', entry.label, newPast.length, history.future.length + 1);
        return true;
    } catch (error) {
        debug.error('Undo failed: %O', error);
        set(historyStateAtom, { ...history, isApplying: false });
        return false;
    }
});

export const redoAtom = atom(null, (get, set) => {
    const history = get(historyStateAtom);
    if (history.future.length === 0 || history.isApplying) return false;

    set(historyStateAtom, { ...history, isApplying: true });

    try {
        const graph = get(graphAtom);
        const newFuture = [...history.future];
        const entry = newFuture.shift()!;

        // For full-snapshot entries, save current state before restoring
        let reverseForUndo = entry.reverse;
        if (entry.forward.type === 'full-snapshot') {
            const currentSnapshot = createSnapshot(graph, 'current');
            reverseForUndo = {
                type: 'full-snapshot',
                nodes: currentSnapshot.nodes,
                edges: currentSnapshot.edges,
            };
        }

        const structuralChange = applyDelta(graph, entry.forward);
        if (structuralChange) {
            set(graphAtom, graph);
            set(graphUpdateVersionAtom, (v) => v + 1);
        }
        set(nodePositionUpdateCounterAtom, (c) => c + 1);

        const undoEntry: HistoryEntry = {
            forward: entry.forward,
            reverse: reverseForUndo,
            timestamp: entry.timestamp,
            label: entry.label,
        };

        set(historyStateAtom, {
            past: [...history.past, undoEntry],
            future: newFuture,
            isApplying: false,
        });

        debug('Redo: %s (past: %d, future: %d)', entry.label, history.past.length + 1, newFuture.length);
        return true;
    } catch (error) {
        debug.error('Redo failed: %O', error);
        set(historyStateAtom, { ...history, isApplying: false });
        return false;
    }
});

export const clearHistoryAtom = atom(null, (_get, set) => {
    set(historyStateAtom, { past: [], future: [], isApplying: false });
    debug('History cleared');
});

export const historyLabelsAtom = atom((get) => {
    const history = get(historyStateAtom);
    return {
        past: history.past.map((e) => e.label || 'Unnamed'),
        future: history.future.map((e) => e.label || 'Unnamed'),
    };
});
