/**
 * Canvas Storage Adapter
 *
 * Backend-agnostic interface for persisting canvas data.
 * The default Supabase implementation lives in `./supabase-adapter.ts`.
 * Users can implement this interface for any backend (REST, GraphQL, localStorage, etc.).
 *
 * Note: The core canvas works WITHOUT any adapter —
 * use `onNodePersist` / `onEdgeCreate` callback props for simple cases.
 * The adapter is for users who want the full `CanvasDbProvider` experience
 * with a non-Supabase backend.
 */

import { atom } from 'jotai';
import type { DBGraphNode, DBGraphEdge, DBGraph, Json } from '../core/types';

// =============================================================================
// Adapter Types
// =============================================================================

/** Type aliases for backend-agnostic naming */
export type CanvasNode = DBGraphNode;
export type CanvasEdge = DBGraphEdge;
export type CanvasGraph = DBGraph;

/** Change event for realtime subscriptions */
export interface CanvasChangeEvent {
    type: 'INSERT' | 'UPDATE' | 'DELETE';
    table: 'nodes' | 'edges';
    /** The new record (for INSERT/UPDATE) */
    new?: CanvasNode | CanvasEdge;
    /** The old record (for DELETE/UPDATE) */
    old?: Partial<CanvasNode | CanvasEdge>;
}

// =============================================================================
// Adapter Interface
// =============================================================================

/**
 * Storage adapter for canvas persistence.
 *
 * Implement this interface to use any backend with `CanvasDbProvider`.
 * All methods return Promises for async-first design.
 */
export interface CanvasStorageAdapter {
    // --- Node Operations ---

    /** Fetch all nodes for a graph */
    fetchNodes(graphId: string): Promise<CanvasNode[]>;

    /** Create a new node */
    createNode(graphId: string, node: Partial<CanvasNode>): Promise<CanvasNode>;

    /** Update an existing node */
    updateNode(nodeId: string, updates: Partial<CanvasNode>): Promise<CanvasNode>;

    /** Delete a node */
    deleteNode(nodeId: string): Promise<void>;

    // --- Edge Operations ---

    /** Fetch all edges for a graph */
    fetchEdges(graphId: string): Promise<CanvasEdge[]>;

    /** Create a new edge */
    createEdge(graphId: string, edge: Partial<CanvasEdge>): Promise<CanvasEdge>;

    /** Update an existing edge */
    updateEdge(edgeId: string, updates: Partial<CanvasEdge>): Promise<CanvasEdge>;

    /** Delete an edge */
    deleteEdge(edgeId: string): Promise<void>;

    // --- Graph Operations (optional) ---

    /** Fetch graph metadata */
    fetchGraph?(graphId: string): Promise<CanvasGraph | null>;

    /** Update graph metadata */
    updateGraph?(graphId: string, updates: Partial<CanvasGraph>): Promise<CanvasGraph>;

    // --- Realtime (optional) ---

    /**
     * Subscribe to realtime changes for a graph.
     * Returns an unsubscribe function.
     *
     * If not implemented, the canvas will work in poll/callback mode only.
     */
    subscribe?(graphId: string, onChange: (event: CanvasChangeEvent) => void): () => void;

    // --- Batch Operations (optional) ---

    /** Create multiple nodes in a single operation */
    createNodes?(graphId: string, nodes: Partial<CanvasNode>[]): Promise<CanvasNode[]>;

    /** Delete multiple nodes in a single operation */
    deleteNodes?(nodeIds: string[]): Promise<void>;

    /** Create multiple edges in a single operation */
    createEdges?(graphId: string, edges: Partial<CanvasEdge>[]): Promise<CanvasEdge[]>;

    /** Delete multiple edges in a single operation */
    deleteEdges?(edgeIds: string[]): Promise<void>;
}

// =============================================================================
// In-Memory Adapter (for testing / local-only use)
// =============================================================================

/**
 * Simple in-memory adapter for testing and local-only canvases.
 * Data is lost on page reload.
 */
export class InMemoryStorageAdapter implements CanvasStorageAdapter {
    private nodes: Map<string, CanvasNode> = new Map();
    private edges: Map<string, CanvasEdge> = new Map();

    async fetchNodes(graphId: string): Promise<CanvasNode[]> {
        return Array.from(this.nodes.values()).filter((n) => n.graph_id === graphId);
    }

    async createNode(graphId: string, node: Partial<CanvasNode>): Promise<CanvasNode> {
        const now = new Date().toISOString();
        const fullNode: CanvasNode = {
            id: node.id ?? crypto.randomUUID(),
            graph_id: graphId,
            label: node.label ?? null,
            node_type: node.node_type ?? null,
            configuration: node.configuration ?? null,
            ui_properties: node.ui_properties ?? null,
            data: node.data ?? null,
            created_at: now,
            updated_at: now,
        };
        this.nodes.set(fullNode.id, fullNode);
        return fullNode;
    }

    async updateNode(nodeId: string, updates: Partial<CanvasNode>): Promise<CanvasNode> {
        const existing = this.nodes.get(nodeId);
        if (!existing) throw new Error(`Node ${nodeId} not found`);
        const updated = { ...existing, ...updates, updated_at: new Date().toISOString() };
        this.nodes.set(nodeId, updated);
        return updated;
    }

    async deleteNode(nodeId: string): Promise<void> {
        this.nodes.delete(nodeId);
        // Also delete connected edges
        for (const [edgeId, edge] of this.edges) {
            if (edge.source_node_id === nodeId || edge.target_node_id === nodeId) {
                this.edges.delete(edgeId);
            }
        }
    }

    async fetchEdges(graphId: string): Promise<CanvasEdge[]> {
        return Array.from(this.edges.values()).filter((e) => e.graph_id === graphId);
    }

    async createEdge(graphId: string, edge: Partial<CanvasEdge>): Promise<CanvasEdge> {
        const now = new Date().toISOString();
        const fullEdge: CanvasEdge = {
            id: edge.id ?? crypto.randomUUID(),
            graph_id: graphId,
            source_node_id: edge.source_node_id ?? '',
            target_node_id: edge.target_node_id ?? '',
            edge_type: edge.edge_type ?? null,
            filter_condition: edge.filter_condition ?? null,
            ui_properties: edge.ui_properties ?? null,
            data: edge.data ?? null,
            created_at: now,
            updated_at: now,
        };
        this.edges.set(fullEdge.id, fullEdge);
        return fullEdge;
    }

    async updateEdge(edgeId: string, updates: Partial<CanvasEdge>): Promise<CanvasEdge> {
        const existing = this.edges.get(edgeId);
        if (!existing) throw new Error(`Edge ${edgeId} not found`);
        const updated = { ...existing, ...updates, updated_at: new Date().toISOString() };
        this.edges.set(edgeId, updated);
        return updated;
    }

    async deleteEdge(edgeId: string): Promise<void> {
        this.edges.delete(edgeId);
    }

    // --- Batch Operations ---

    async createNodes(graphId: string, nodes: Partial<CanvasNode>[]): Promise<CanvasNode[]> {
        return Promise.all(nodes.map(n => this.createNode(graphId, n)));
    }

    async deleteNodes(nodeIds: string[]): Promise<void> {
        for (const id of nodeIds) {
            await this.deleteNode(id);
        }
    }

    async createEdges(graphId: string, edges: Partial<CanvasEdge>[]): Promise<CanvasEdge[]> {
        return Promise.all(edges.map(e => this.createEdge(graphId, e)));
    }

    async deleteEdges(edgeIds: string[]): Promise<void> {
        for (const id of edgeIds) {
            await this.deleteEdge(id);
        }
    }
}

// =============================================================================
// Global Adapter Atom
// =============================================================================

/**
 * Global storage adapter atom.
 * Set by `CanvasDbProvider` so that any store/hook can access the adapter.
 */
export const storageAdapterAtom = atom<CanvasStorageAdapter | null>(null);
