/**
 * History Actions — Pure delta functions
 *
 * applyDelta, invertDelta, createSnapshot extracted from history-store.ts.
 * These are pure functions operating on Graphology graphs and delta objects.
 */

import Graph from 'graphology';
import type { GraphNodeAttributes, GraphEdgeAttributes } from './types';
import type { HistoryDelta, GraphSnapshot } from './history-types';

// =============================================================================
// Delta Application
// =============================================================================

/**
 * Apply a delta to the graph in-place.
 * Returns true if graph structure changed (needs version bump).
 */
export function applyDelta(
    graph: Graph<GraphNodeAttributes, GraphEdgeAttributes>,
    delta: HistoryDelta,
): boolean {
    switch (delta.type) {
        case 'move-node': {
            if (!graph.hasNode(delta.nodeId)) return false;
            graph.setNodeAttribute(delta.nodeId, 'x', delta.to.x);
            graph.setNodeAttribute(delta.nodeId, 'y', delta.to.y);
            return false; // Position change, not structural
        }

        case 'resize-node': {
            if (!graph.hasNode(delta.nodeId)) return false;
            graph.setNodeAttribute(delta.nodeId, 'width', delta.to.width);
            graph.setNodeAttribute(delta.nodeId, 'height', delta.to.height);
            return false;
        }

        case 'add-node': {
            if (graph.hasNode(delta.nodeId)) return false;
            graph.addNode(delta.nodeId, delta.attributes);
            return true;
        }

        case 'remove-node': {
            if (!graph.hasNode(delta.nodeId)) return false;
            graph.dropNode(delta.nodeId); // Also removes connected edges
            return true;
        }

        case 'add-edge': {
            if (graph.hasEdge(delta.edgeId)) return false;
            if (!graph.hasNode(delta.source) || !graph.hasNode(delta.target)) return false;
            graph.addEdgeWithKey(delta.edgeId, delta.source, delta.target, delta.attributes);
            return true;
        }

        case 'remove-edge': {
            if (!graph.hasEdge(delta.edgeId)) return false;
            graph.dropEdge(delta.edgeId);
            return true;
        }

        case 'update-node-attr': {
            if (!graph.hasNode(delta.nodeId)) return false;
            graph.setNodeAttribute(delta.nodeId, delta.key as keyof GraphNodeAttributes, delta.to as GraphNodeAttributes[keyof GraphNodeAttributes]);
            return false;
        }

        case 'batch': {
            let structuralChange = false;
            for (const d of delta.deltas) {
                if (applyDelta(graph, d)) structuralChange = true;
            }
            return structuralChange;
        }

        case 'full-snapshot': {
            // Full graph restore — clear and rebuild
            graph.clear();
            for (const node of delta.nodes) {
                graph.addNode(node.id, node.attributes);
            }
            for (const edge of delta.edges) {
                if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
                    graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge.attributes);
                }
            }
            return true;
        }
    }
}

/**
 * Invert a delta (for creating reverse operations).
 */
export function invertDelta(delta: HistoryDelta): HistoryDelta {
    switch (delta.type) {
        case 'move-node':
            return { ...delta, from: delta.to, to: delta.from };

        case 'resize-node':
            return { ...delta, from: delta.to, to: delta.from };

        case 'add-node':
            return { type: 'remove-node', nodeId: delta.nodeId, attributes: delta.attributes, connectedEdges: [] };

        case 'remove-node': {
            // Restore node AND its connected edges
            const batch: HistoryDelta[] = [
                { type: 'add-node', nodeId: delta.nodeId, attributes: delta.attributes },
                ...delta.connectedEdges.map((e) => ({
                    type: 'add-edge' as const,
                    edgeId: e.id,
                    source: e.source,
                    target: e.target,
                    attributes: e.attributes,
                })),
            ];
            return batch.length === 1 ? batch[0] : { type: 'batch', deltas: batch };
        }

        case 'add-edge':
            return { type: 'remove-edge', edgeId: delta.edgeId, source: delta.source, target: delta.target, attributes: delta.attributes };

        case 'remove-edge':
            return { type: 'add-edge', edgeId: delta.edgeId, source: delta.source, target: delta.target, attributes: delta.attributes };

        case 'update-node-attr':
            return { ...delta, from: delta.to, to: delta.from };

        case 'batch':
            return { type: 'batch', deltas: delta.deltas.map(invertDelta).reverse() };

        case 'full-snapshot':
            // Can't invert a full snapshot — caller handles this
            return delta;
    }
}

// =============================================================================
// Helper: Create full snapshot (for complex operations)
// =============================================================================

export function createSnapshot(graph: Graph<GraphNodeAttributes, GraphEdgeAttributes>, label?: string): GraphSnapshot {
    const nodes: GraphSnapshot['nodes'] = [];
    const edges: GraphSnapshot['edges'] = [];

    graph.forEachNode((nodeId, attributes) => {
        nodes.push({ id: nodeId, attributes: { ...attributes } });
    });

    graph.forEachEdge((edgeId, attributes, source, target) => {
        edges.push({ id: edgeId, source, target, attributes: { ...attributes } });
    });

    return { timestamp: Date.now(), label, nodes, edges };
}
