import React, { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
import {
  ReactFlow,
  ReactFlowProvider,
  Background,
  Controls,
  MiniMap,
  ConnectionMode,
  useReactFlow,
  type Node,
  type Edge,
  type Connection,
  type NodeChange,
  type EdgeChange,
  applyNodeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { CustomNode } from './CustomNode';
import { ExecutionStatusNode } from './ExecutionStatusNode';
import type { WorkflowDSL, WorkflowNodeType } from '../types';

interface NodeExecution {
  id: string;
  nodeId: string;
  nodeType: string;
  nodeLabel: string | null;
  status: string;
  input: any;
  output: any;
  error: any;
  startedAt: string | null;
  finishedAt: string | null;
}

const nodeTypes = { custom: CustomNode, executionStatus: ExecutionStatusNode };

interface EnhancedFlowRendererProps {
  dsl: WorkflowDSL | null;
  nodeTypeMap?: Record<string, { category: string; icon: string }>;
  selectedNodeId?: string | null;
  readonly?: boolean;
  onNodeClick?: (nodeId: string) => void;
  onDslChange?: (dsl: WorkflowDSL) => void;
  // New props for execution status
  nodeExecutions?: NodeExecution[];
  showExecutionStatus?: boolean;
}

function formatDuration(
  startedAt: string | null,
  finishedAt: string | null,
): string {
  if (!startedAt) return '';
  const endTime = finishedAt ? new Date(finishedAt).getTime() : Date.now();
  const ms = endTime - new Date(startedAt).getTime();
  if (ms < 1000) return `${ms}ms`;
  const s = ms / 1000;
  if (s < 60) return `${s.toFixed(1)}s`;
  const m = Math.floor(s / 60);
  const rem = s % 60;
  return rem > 0 ? `${m}m ${rem.toFixed(0)}s` : `${m}m`;
}

function EnhancedFlowCanvas({
  dsl,
  nodeTypeMap,
  selectedNodeId,
  readonly,
  onNodeClick,
  onDslChange,
  nodeExecutions,
  showExecutionStatus,
}: EnhancedFlowRendererProps) {
  const reactFlowInstance = useReactFlow();
  const dslRef = useRef(dsl);
  dslRef.current = dsl;
  const containerRef = useRef<HTMLDivElement>(null);
  const hasSetViewport = useRef(false);

  // Build execution map: nodeId -> execution record
  const executionMap = useMemo(() => {
    if (!nodeExecutions) return {};
    const map: Record<string, NodeExecution> = {};
    nodeExecutions.forEach((exec) => {
      map[exec.nodeId] = exec;
    });
    return map;
  }, [nodeExecutions]);

  const { nodes, edges } = useMemo(() => {
    if (!dsl) return { nodes: [] as Node[], edges: [] as Edge[] };

    const rfNodes: Node[] = dsl.nodes.map((n) => {
      const category = nodeTypeMap?.[n.type]?.category || 'ACTION';
      // Transpose x/y for vertical (top-to-bottom) layout
      // Scale y down to compress vertical spacing (original x-step=250 → 120)
      // Add y offset so top node isn't clipped
      const position = { x: n.position.y * 1.5, y: n.position.x * 0.5 + 20 };

      // Get execution status for this node
      const execution = executionMap[n.id];
      const nodeType = showExecutionStatus ? 'executionStatus' : 'custom';

      const nodeData: any = {
        label: n.label,
        category,
        isSelected: n.id === selectedNodeId,
        readonly: !!readonly,
      };

      // Add execution status data — show NOT_EXECUTED for nodes without records
      if (showExecutionStatus) {
        if (execution) {
          nodeData.executionStatus = execution.status;
          nodeData.duration = formatDuration(
            execution.startedAt,
            execution.finishedAt,
          );
          nodeData.error = execution.error;
        } else {
          nodeData.executionStatus = 'NOT_EXECUTED';
        }
      }

      return {
        id: n.id,
        type: nodeType,
        position,
        measured: { width: 140, height: 40 },
        selected: n.id === selectedNodeId,
        data: nodeData,
      };
    });

    const rfEdges: Edge[] = dsl.edges.map((e) => ({
      id: e.id,
      source: e.source,
      target: e.target,
      sourceHandle: e.sourceHandle,
      label: e.label,
      type: 'smoothstep',
      animated: showExecutionStatus ? false : true, // Disable animation when showing execution
    }));

    return { nodes: rfNodes, edges: rfEdges };
  }, [dsl, nodeTypeMap, selectedNodeId, executionMap, showExecutionStatus]);

  // Set viewport once before first paint: horizontal center + vertical top padding
  useLayoutEffect(() => {
    if (!hasSetViewport.current && containerRef.current && nodes.length > 0) {
      const containerWidth = containerRef.current.clientWidth;
      let minX = Infinity,
        maxX = -Infinity;
      nodes.forEach((n) => {
        const w = n.measured?.width || 140;
        minX = Math.min(minX, n.position.x);
        maxX = Math.max(maxX, n.position.x + w);
      });
      const contentWidth = maxX - minX;
      const x = (containerWidth - contentWidth) / 2 - minX;
      reactFlowInstance.setViewport({ x, y: 0, zoom: 1 });
      hasSetViewport.current = true;
    }
  }, [nodes, reactFlowInstance]);

  const onNodesChange = useCallback(
    (changes: NodeChange[]) => {
      const currentDsl = dslRef.current;
      if (!currentDsl || !onDslChange) return;

      // Apply changes to get new node positions
      const updatedRfNodes = applyNodeChanges(changes, nodes);

      // Check if any position actually changed
      const positionChanged = changes.some(
        (c) => c.type === 'position' && c.position,
      );
      const removeChanges = changes.filter((c) => c.type === 'remove');

      if (removeChanges.length > 0) {
        const removedIds = new Set(
          removeChanges.map((c) => (c as any).id as string),
        );
        const newNodes = currentDsl.nodes.filter((n) => !removedIds.has(n.id));
        const newEdges = currentDsl.edges.filter(
          (e) => !removedIds.has(e.source) && !removedIds.has(e.target),
        );
        onDslChange({ nodes: newNodes, edges: newEdges });
      } else if (positionChanged) {
        const newNodes = currentDsl.nodes.map((n) => {
          const rfNode = updatedRfNodes.find((rn) => rn.id === n.id);
          if (rfNode && rfNode.position) {
            // Reverse transpose: display (x,y) → stored (y,x) with scale reversal
            return {
              ...n,
              position: {
                x: (rfNode.position.y - 20) / 0.5,
                y: rfNode.position.x / 1.5,
              },
            };
          }
          return n;
        });
        onDslChange({ ...currentDsl, nodes: newNodes });
      }
    },
    [nodes, onDslChange],
  );

  const onEdgesChange = useCallback(
    (changes: EdgeChange[]) => {
      const currentDsl = dslRef.current;
      if (!currentDsl || !onDslChange) return;

      const removeChanges = changes.filter((c) => c.type === 'remove');
      if (removeChanges.length > 0) {
        const removedIds = new Set(
          removeChanges.map((c) => (c as any).id as string),
        );
        const newEdges = currentDsl.edges.filter((e) => !removedIds.has(e.id));
        onDslChange({ ...currentDsl, edges: newEdges });
      }
    },
    [onDslChange],
  );

  const onConnect = useCallback(
    (connection: Connection) => {
      const currentDsl = dslRef.current;
      if (!currentDsl || !onDslChange) return;

      const newEdge = {
        id: `e-${connection.source}-${connection.target}-${Date.now()}`,
        source: connection.source!,
        target: connection.target!,
        sourceHandle: connection.sourceHandle || undefined,
        label: undefined,
      };
      onDslChange({
        ...currentDsl,
        edges: [...currentDsl.edges, newEdge],
      });
    },
    [onDslChange],
  );

  const onDragOver = useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  const onDrop = useCallback(
    (event: React.DragEvent) => {
      event.preventDefault();
      const currentDsl = dslRef.current;
      if (!onDslChange) return;

      const data = event.dataTransfer.getData('application/workflow-node-type');
      if (!data) return;

      let nodeType: WorkflowNodeType;
      try {
        nodeType = JSON.parse(data);
      } catch {
        return;
      }

      const flowPosition = reactFlowInstance.screenToFlowPosition({
        x: event.clientX,
        y: event.clientY,
      });
      // Reverse transpose: display (x,y) → stored (y,x) with scale reversal
      const position = {
        x: (flowPosition.y - 20) / 0.5,
        y: flowPosition.x / 1.5,
      };

      // Initialize config with default values from configSchema
      const initialConfig: Record<string, any> = {};
      const schema = nodeType.configSchema as any;
      if (schema?.properties) {
        for (const [key, prop] of Object.entries<any>(schema.properties)) {
          if (prop.type === 'string') initialConfig[key] = '';
          else if (prop.type === 'number') initialConfig[key] = undefined;
          else if (prop.type === 'boolean') initialConfig[key] = false;
          else if (prop.type === 'array') initialConfig[key] = [];
          else if (prop.type === 'object') initialConfig[key] = {};
        }
      }

      const newNode = {
        id: crypto.randomUUID(),
        type: nodeType.type,
        label: nodeType.label,
        position,
        config: initialConfig,
      };

      const newDsl: WorkflowDSL = currentDsl
        ? { ...currentDsl, nodes: [...currentDsl.nodes, newNode] }
        : { nodes: [newNode], edges: [] };

      onDslChange(newDsl);
    },
    [reactFlowInstance, onDslChange],
  );

  if (!dsl || dsl.nodes.length === 0) {
    return (
      <div
        style={{ width: '100%', height: '100%' }}
        onDragOver={onDragOver}
        onDrop={onDrop}
      >
        <ReactFlow
          nodes={[]}
          edges={[]}
          nodeTypes={nodeTypes}
          fitView
          onDragOver={onDragOver}
          onDrop={onDrop}
        >
          <Background />
          <Controls showInteractive={false} />
        </ReactFlow>
      </div>
    );
  }

  return (
    <div ref={containerRef} style={{ width: '100%', height: '100%' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        nodeTypes={nodeTypes}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onDragOver={onDragOver}
        onDrop={onDrop}
        nodesDraggable={!showExecutionStatus}
        nodesConnectable={!showExecutionStatus}
        elementsSelectable={true}
        connectionMode={ConnectionMode.Loose}
        deleteKeyCode={['Backspace', 'Delete']}
        panOnScroll={true}
        zoomOnScroll={false}
        zoomOnPinch={false}
        zoomOnDoubleClick={false}
        onNodeClick={(_event, node) => {
          onNodeClick?.(node.id);
        }}
        onPaneClick={() => {
          onNodeClick?.('');
        }}
      >
        <Background />
        <Controls showInteractive={false} />
        <MiniMap
          nodeColor={(node) => {
            const category = (node.data as any)?.category || 'ACTION';
            const colors: Record<string, string> = {
              TRIGGER: '#91caff',
              ACTION: '#b7eb8f',
              CONDITION: '#ffd591',
              LOOP: '#d3adf7',
              APPROVAL: '#ffa39e',
              SUB_WORKFLOW: '#adc6ff',
            };
            return colors[category] || '#b7eb8f';
          }}
          nodeStrokeWidth={3}
          maskColor="rgba(240, 240, 240, 0.6)"
          zoomable
          pannable
        />
      </ReactFlow>
    </div>
  );
}

export function EnhancedFlowRenderer(props: EnhancedFlowRendererProps) {
  return (
    <ReactFlowProvider>
      <EnhancedFlowCanvas {...props} />
    </ReactFlowProvider>
  );
}
