'use client';

import * as React from 'react';
import {
  ReactFlow,
  ReactFlowProvider,
  Background,
  Controls,
  type NodeTypes,
  type EdgeTypes,
  type Connection,
} from '@xyflow/react';
import { cn } from '../../lib/utils';
import { WorkflowNode } from './WorkflowNode';
import { WorkflowEdge } from './WorkflowEdge';
import { useCanvasGraph } from './hooks/useCanvasGraph';
import { useNodeStatusOverlay } from './hooks/useNodeStatusOverlay';
import { connectionToEdge } from './graph-ops';
import type { WorkflowGraph, WfEdge, WfExecutionView, NodeTypeDef } from './types';
import type { LayoutDirection } from './layout/auto-layout';

const nodeTypes: NodeTypes = { workflowNode: WorkflowNode };
const edgeTypes: EdgeTypes = { workflowEdge: WorkflowEdge };

export interface WorkflowCanvasProps {
  /** The graph to render. */
  graph: WorkflowGraph;
  /** Node-type catalog (enriches nodes with category + ports). */
  nodeTypes?: NodeTypeDef[];
  /**
   * Optional run overlay. When present, node status badges and edge
   * animations reflect the live execution. Pass a fresh snapshot per poll.
   */
  execution?: WfExecutionView;
  /** Layout direction for positionless nodes. Default 'LR'. */
  direction?: LayoutDirection;
  /** Inline-rename callback. */
  onRename?: (nodeId: string, name: string) => void;
  /** Fired when a node is selected on the canvas. */
  onSelectNode?: (nodeId: string | null) => void;
  /** Fired when an edge is selected on the canvas. */
  onSelectEdge?: (edgeId: string | null) => void;
  /**
   * Currently-selected node id. Threaded onto xyflow's `selected` flag so the
   * controlled canvas knows what's selected — required for keyboard delete.
   */
  selectedNodeId?: string | null;
  /** Currently-selected edge id (same reason as {@link selectedNodeId}). */
  selectedEdgeId?: string | null;
  /** Drop handler for palette drag-and-drop (slug + client coords). */
  onDropNodeType?: (slug: string, clientX: number, clientY: number) => void;
  /** Fired when the user draws a connection between two node handles. */
  onConnectEdge?: (edge: WfEdge) => void;
  /** Fired with the ids of nodes removed via select+Delete/Backspace. */
  onDeleteNodes?: (nodeIds: string[]) => void;
  /** Fired with the edges removed via select+Delete/Backspace. */
  onDeleteEdges?: (edges: WfEdge[]) => void;
  className?: string;
}

function CanvasInner({
  graph,
  nodeTypes: catalog,
  execution,
  direction = 'LR',
  onRename,
  onSelectNode,
  onSelectEdge,
  selectedNodeId,
  selectedEdgeId,
  onDropNodeType,
  onConnectEdge,
  onDeleteNodes,
  onDeleteEdges,
  className,
}: WorkflowCanvasProps) {
  const { nodes: baseNodes, edges: baseEdges } = useCanvasGraph(graph, {
    nodeTypes: catalog,
    direction,
    onRename,
  });
  const { nodes: overlaidNodes, edges: overlaidEdges } = useNodeStatusOverlay(
    baseNodes,
    baseEdges,
    execution
  );
  // Thread our selection into xyflow's `selected` flag. The canvas is fully
  // controlled (no onNodesChange), so without this xyflow never marks a
  // clicked node selected and keyboard delete has nothing to remove.
  const nodes = React.useMemo(
    () =>
      selectedNodeId == null
        ? overlaidNodes
        : overlaidNodes.map((n) =>
            n.id === selectedNodeId ? { ...n, selected: true } : n
          ),
    [overlaidNodes, selectedNodeId]
  );
  const edges = React.useMemo(
    () =>
      selectedEdgeId == null
        ? overlaidEdges
        : overlaidEdges.map((e) =>
            e.id === selectedEdgeId ? { ...e, selected: true } : e
          ),
    [overlaidEdges, selectedEdgeId]
  );

  const handleDragOver = React.useCallback((e: React.DragEvent) => {
    if (!onDropNodeType) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  }, [onDropNodeType]);

  const handleDrop = React.useCallback(
    (e: React.DragEvent) => {
      if (!onDropNodeType) return;
      e.preventDefault();
      const slug = e.dataTransfer.getData('application/x-workflow-node');
      if (slug) onDropNodeType(slug, e.clientX, e.clientY);
    },
    [onDropNodeType]
  );

  const handleConnect = React.useCallback(
    (conn: Connection) => {
      if (!onConnectEdge || !conn.source || !conn.target) return;
      onConnectEdge(connectionToEdge(conn));
    },
    [onConnectEdge]
  );

  // Only enable keyboard delete when a delete handler is wired.
  const deletable = !!onDeleteNodes || !!onDeleteEdges;

  return (
    <div
      className={cn('workflow-canvas h-full w-full', className)}
      onDragOver={handleDragOver}
      onDrop={handleDrop}
    >
      <ReactFlow
        nodes={nodes as never}
        edges={edges as never}
        nodeTypes={nodeTypes}
        edgeTypes={edgeTypes}
        fitView
        proOptions={{ hideAttribution: true }}
        deleteKeyCode={deletable ? ['Backspace', 'Delete'] : null}
        onConnect={onConnectEdge ? handleConnect : undefined}
        onNodesDelete={
          onDeleteNodes
            ? (removed) => onDeleteNodes(removed.map((n) => n.id))
            : undefined
        }
        onEdgesDelete={
          onDeleteEdges
            ? (removed) =>
                onDeleteEdges(
                  removed.map((e) =>
                    connectionToEdge({
                      source: e.source,
                      target: e.target,
                      sourceHandle: e.sourceHandle,
                      targetHandle: e.targetHandle,
                    })
                  )
                )
            : undefined
        }
        onNodeClick={(_, n) => {
          onSelectNode?.(n.id);
          onSelectEdge?.(null);
        }}
        onEdgeClick={(_, e) => {
          onSelectEdge?.(e.id);
          onSelectNode?.(null);
        }}
        onPaneClick={() => {
          onSelectNode?.(null);
          onSelectEdge?.(null);
        }}
      >
        <Background gap={16} />
        <Controls showInteractive={false} />
      </ReactFlow>
    </div>
  );
}

/**
 * Renders a {@link WorkflowGraph} on an interactive xyflow canvas with custom
 * workflow nodes/edges, auto-layout, fit-view, and controls. When an
 * `execution` overlay is supplied, node statuses + edge animations reflect the
 * live run. Wraps its own {@link ReactFlowProvider} so it can be dropped in
 * anywhere.
 */
export function WorkflowCanvas(props: WorkflowCanvasProps) {
  return (
    <ReactFlowProvider>
      <CanvasInner {...props} />
    </ReactFlowProvider>
  );
}
