'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  Background,
  ConnectionLineType,
  Controls,
  Handle,
  MarkerType,
  MiniMap,
  Position,
  ReactFlow,
  ReactFlowProvider,
  useReactFlow,
  type Connection,
  type Edge,
  type EdgeChange,
  type Node as FlowNode,
  type NodeChange,
  type NodeProps,
  type Rect,
  type ReactFlowInstance,
  type Viewport as FlowViewport,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Brush, FileImage, Image as ImageIcon, Layers, Link2, Loader2, Maximize2, Move, Type } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { CanvasConnection, CanvasLayer, CanvasNode, CanvasViewport } from '@/lib/canvas-types';

type NodePositionCommit = { id: string; x: number; y: number };

type CanvasFlowNodeData = {
  node: CanvasNode;
  selected: boolean;
  connecting: boolean;
  connections: CanvasConnection[];
  allNodes: CanvasNode[];
  layerCanvasSize: number;
  layerColors: Record<CanvasLayer['type'], string>;
  onSelect: (id: string, additive: boolean) => void;
  onStartConnect: (id: string) => void;
  onRemoveConnection: (id: string) => void;
};

type CanvasFlowNode = FlowNode<CanvasFlowNodeData, 'canvasNode'>;

type ReactFlowCanvasProps = {
  nodes: CanvasNode[];
  connections: CanvasConnection[];
  viewport: CanvasViewport;
  selectedNodeIds: string[];
  connectingFromId: string | null;
  editable: boolean;
  minZoom: number;
  maxZoom: number;
  layerCanvasSize: number;
  layerColors: Record<CanvasLayer['type'], string>;
  onSelectNode: (id: string, additive: boolean) => void;
  onSelectionChange: (ids: string[]) => void;
  onStartConnect: (id: string) => void;
  onConnect: (sourceId: string, targetId: string) => void;
  onRemoveConnection: (id: string) => void;
  onRemoveConnections: (ids: string[]) => void;
  onNodesCommit: (positions: NodePositionCommit[], options?: { history?: boolean; dirty?: boolean }) => void;
  onViewportCommit: (viewport: CanvasViewport) => void;
  onReady?: (controls: CanvasFlowControls | null) => void;
  onPaneClick: (point: { x: number; y: number }, event: MouseEvent | React.MouseEvent) => void;
  onPaneDoubleClick: (point: { x: number; y: number }) => void;
};

export type CanvasFlowControls = {
  setViewport: (viewport: CanvasViewport) => Promise<boolean>;
  zoomTo: (zoom: number) => Promise<boolean>;
  fitBounds: (bounds: Rect, options?: { padding?: number; duration?: number }) => Promise<boolean>;
  getViewport: () => CanvasViewport;
};

const CONNECTION_COLORS = ['#22c55e', '#06b6d4', '#8b5cf6', '#f59e0b', '#ef4444', '#14b8a6'];
const SNAP_GRID: [number, number] = [20, 20];
const MULTI_SELECTION_KEYS = ['Meta', 'Control', 'Shift'];
const FLOW_FIT_VIEW_OPTIONS = { padding: 0.2 };
const CANVAS_NODE_TYPES = { canvasNode: CanvasNodeCard };

function getNodeImageUrl(node?: CanvasNode | null) {
  if (!node) return '';
  if (node.type === 'image') return node.imageUrl || '';
  return node.selectedOutput || node.outputImages?.[0] || '';
}

function CanvasNodeCard({ data }: NodeProps<CanvasFlowNode>) {
  const { node, selected, connecting, connections, allNodes, layerCanvasSize, layerColors, onSelect, onStartConnect, onRemoveConnection } = data;
  const incomingImage = allNodes.find(item => connections.some(connection => connection.targetNodeId === node.id && connection.sourceNodeId === item.id && !!getNodeImageUrl(item)));
  const nodeConnections = connections.filter(connection => connection.sourceNodeId === node.id || connection.targetNodeId === node.id);

  return (
    <div
      data-canvas-node
      data-node-id={node.id}
      className={cn(
        'group h-full overflow-visible rounded-lg border transition-shadow',
        node.type === 'frame' ? 'bg-background/20 shadow-none' : 'bg-card shadow-sm',
        selected ? 'border-primary ring-2 ring-primary/20' : connecting ? 'border-emerald-500 ring-2 ring-emerald-500/20' : 'border-border',
      )}
      style={{ borderColor: node.type === 'frame' && !selected ? node.color || '#22c55e' : undefined }}
      onPointerDown={(event) => {
        const target = event.target as HTMLElement;
        if (target.closest('button,input,textarea,[role="combobox"],.nodrag')) return;
        onSelect(node.id, event.ctrlKey || event.metaKey || event.shiftKey);
      }}
    >
      <Handle type="target" position={Position.Left} id="input" className="!h-3 !w-3 !border-2 !border-background !bg-primary" />
      <Handle type="source" position={Position.Right} id="output" className="!h-3 !w-3 !border-2 !border-background !bg-primary" />

      <button
        type="button"
        className={cn(
          'nodrag absolute right-0 top-1/2 z-20 flex h-7 w-7 -translate-y-1/2 translate-x-1/2 items-center justify-center rounded-full border bg-background text-muted-foreground opacity-0 shadow-sm transition-all hover:border-primary hover:text-primary group-hover:opacity-100',
          connecting ? 'border-emerald-500 bg-emerald-500 text-white opacity-100' : '',
        )}
        title="从此模块开始连线"
        onPointerDown={(event) => event.stopPropagation()}
        onClick={(event) => {
          event.stopPropagation();
          onStartConnect(node.id);
        }}
      >
        <Link2 className="h-3.5 w-3.5" />
      </button>

      <div className="h-full overflow-hidden rounded-lg">
        <div className="flex h-10 items-center justify-between border-b bg-muted/40 px-3">
          <div className="flex min-w-0 items-center gap-2 text-sm font-medium">
            {node.type === 'text' ? <Type className="h-4 w-4" /> : null}
            {node.type === 'image' ? <ImageIcon className="h-4 w-4" /> : null}
            {node.type === 'text2img' ? <Brush className="h-4 w-4" /> : null}
            {node.type === 'img2img' ? <FileImage className="h-4 w-4" /> : null}
            {node.type === 'layeredImage' ? <Layers className="h-4 w-4" /> : null}
            {node.type === 'frame' ? <Maximize2 className="h-4 w-4" /> : null}
            <span className="truncate">{node.title}</span>
          </div>
          {node.status === 'generating' ? <Loader2 className="h-4 w-4 animate-spin text-primary" /> : <Move className="h-4 w-4 text-muted-foreground" />}
        </div>

        <div className="h-[calc(100%-2.5rem)] p-3">
          {node.type === 'text' ? (
            <div className="h-full whitespace-pre-wrap rounded-md bg-muted/30 p-3 text-sm leading-6">{node.text}</div>
          ) : null}

          {node.type === 'image' ? (
            <div className="h-full overflow-hidden rounded-md bg-muted">
              {node.imageUrl ? (
                // eslint-disable-next-line @next/next/no-img-element
                <img src={node.imageUrl} alt={node.title} className="h-full w-full object-contain" />
              ) : (
                <div className="flex h-full items-center justify-center text-sm text-muted-foreground">未选择图片</div>
              )}
            </div>
          ) : null}

          {node.type === 'text2img' || node.type === 'img2img' ? (
            <div className="flex h-full flex-col gap-3">
              <div className="line-clamp-3 min-h-14 rounded-md bg-muted/30 p-3 text-sm text-muted-foreground">
                {node.prompt || (connections.some(connection => connection.targetNodeId === node.id) ? '已连接上游内容，可继续补充描述' : '在右侧输入创作描述')}
              </div>
              {node.type === 'img2img' ? (
                <div className="h-20 overflow-hidden rounded-md border bg-muted">
                  {node.referenceImage || getNodeImageUrl(incomingImage) ? (
                    // eslint-disable-next-line @next/next/no-img-element
                    <img src={node.referenceImage || getNodeImageUrl(incomingImage)} alt="参考图" className="h-full w-full object-contain" />
                  ) : (
                    <div className="flex h-full items-center justify-center text-xs text-muted-foreground">从右侧选择参考图或连接图片节点</div>
                  )}
                </div>
              ) : null}
              <div className="min-h-0 flex-1 overflow-hidden rounded-md border bg-muted">
                {node.selectedOutput ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img src={node.selectedOutput} alt="生成结果" className="h-full w-full object-contain" />
                ) : (
                  <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
                    {node.status === 'failed' ? node.error || '生成失败' : node.status === 'generating' ? '生成中...' : '等待生成'}
                  </div>
                )}
              </div>
            </div>
          ) : null}

          {node.type === 'layeredImage' ? (
            <div className="grid h-full grid-cols-[1fr_120px] gap-3 rounded-md border bg-muted/20 p-3">
              <div className="relative overflow-hidden rounded-md border bg-background">
                <div className="absolute inset-0 bg-[linear-gradient(45deg,hsl(var(--muted))_25%,transparent_25%),linear-gradient(-45deg,hsl(var(--muted))_25%,transparent_25%),linear-gradient(45deg,transparent_75%,hsl(var(--muted))_75%),linear-gradient(-45deg,transparent_75%,hsl(var(--muted))_75%)] bg-[length:24px_24px] bg-[position:0_0,0_12px,12px_-12px,-12px_0px] opacity-35" />
                {(node.layers || []).filter(layer => layer.visible).map(layer => (
                  <div
                    key={layer.id}
                    className={cn(
                      'absolute border border-white/60 shadow-sm',
                      layer.type === 'text' ? 'flex items-center px-2 text-xs font-semibold' : 'rounded-md',
                      layer.locked ? 'outline outline-1 outline-dashed outline-muted-foreground/70' : '',
                    )}
                    style={{
                      left: `${(layer.x / layerCanvasSize) * 100}%`,
                      top: `${(layer.y / layerCanvasSize) * 100}%`,
                      width: `${(layer.width / layerCanvasSize) * 100}%`,
                      height: `${(layer.height / layerCanvasSize) * 100}%`,
                      backgroundColor: layer.type === 'text' ? 'transparent' : layer.color || layerColors[layer.type],
                      color: layer.color || layerColors.text,
                      opacity: layer.opacity ?? 1,
                    }}
                  >
                    {layer.type === 'text' ? layer.text || layer.name : null}
                  </div>
                ))}
              </div>
              <div className="min-w-0 space-y-1 overflow-y-auto">
                {(node.layers || []).slice().reverse().map(layer => (
                  <div key={layer.id} className="truncate rounded-md border bg-background px-2 py-1 text-[11px]">
                    <span className={layer.visible ? '' : 'text-muted-foreground line-through'}>{layer.name}</span>
                  </div>
                ))}
              </div>
            </div>
          ) : null}

          {node.type === 'frame' ? (
            <div className="flex h-full flex-col justify-end rounded-md border border-dashed bg-background/25 p-3 text-xs text-muted-foreground" style={{ borderColor: node.color || '#22c55e' }}>
              <div className="rounded-md bg-background/80 px-3 py-2 shadow-sm">{node.text || '流程分组'}</div>
            </div>
          ) : null}
        </div>
      </div>

      {selected && nodeConnections.length > 0 ? (
        <div className="nodrag absolute left-3 top-full z-30 mt-2 w-64 rounded-lg border bg-background/95 p-2 text-xs shadow-lg backdrop-blur">
          <div className="mb-1 font-medium text-muted-foreground">相关连线</div>
          <div className="space-y-1">
            {nodeConnections.slice(0, 4).map(connection => {
              const source = allNodes.find(item => item.id === connection.sourceNodeId);
              const target = allNodes.find(item => item.id === connection.targetNodeId);
              return (
                <div key={connection.id} className="flex items-center justify-between gap-2 rounded-md border px-2 py-1">
                  <span className="min-w-0 truncate">{source?.title || '未知模块'} → {target?.title || '未知模块'}</span>
                  <button className="text-destructive hover:underline" onClick={() => onRemoveConnection(connection.id)}>删</button>
                </div>
              );
            })}
          </div>
        </div>
      ) : null}
    </div>
  );
}

function sameFlowNodePositions(a: CanvasFlowNode[], b: CanvasFlowNode[]) {
  if (a.length !== b.length) return false;
  const bMap = new Map(b.map(node => [node.id, node]));
  return a.every(node => {
    const next = bMap.get(node.id);
    return !!next && node.position.x === next.position.x && node.position.y === next.position.y;
  });
}

function sameFlowNodes(a: CanvasFlowNode[], b: CanvasFlowNode[]) {
  if (a.length !== b.length) return false;
  const bMap = new Map(b.map(node => [node.id, node]));
  return a.every(node => {
    const next = bMap.get(node.id);
    return !!next
      && node.position.x === next.position.x
      && node.position.y === next.position.y
      && node.selected === next.selected
      && node.draggable === next.draggable
      && node.selectable === next.selectable
      && node.width === next.width
      && node.height === next.height
      && node.data.node === next.data.node
      && node.data.selected === next.data.selected
      && node.data.connecting === next.data.connecting
      && node.data.connections === next.data.connections
      && node.data.allNodes === next.data.allNodes;
  });
}

function clampViewport(viewport: FlowViewport, minZoom: number, maxZoom: number): CanvasViewport {
  return {
    x: viewport.x,
    y: viewport.y,
    zoom: Math.min(maxZoom, Math.max(minZoom, viewport.zoom)),
  };
}

function FlowCanvasInner({
  nodes,
  connections,
  viewport,
  selectedNodeIds,
  connectingFromId,
  editable,
  minZoom,
  maxZoom,
  layerCanvasSize,
  layerColors,
  onSelectNode,
  onSelectionChange,
  onStartConnect,
  onConnect,
  onRemoveConnection,
  onRemoveConnections,
  onNodesCommit,
  onViewportCommit,
  onReady,
  onPaneClick,
  onPaneDoubleClick,
}: ReactFlowCanvasProps) {
  const reactFlow = useReactFlow<CanvasFlowNode, Edge>();
  const draggingRef = useRef(false);
  const movingRef = useRef(false);
  const paneClickTimerRef = useRef<number | null>(null);

  const incomingNodes = useMemo<CanvasFlowNode[]>(() => nodes.map(node => ({
    id: node.id,
    type: 'canvasNode',
    position: { x: node.x, y: node.y },
    width: node.width,
    height: node.height,
    selected: selectedNodeIds.includes(node.id),
    draggable: editable,
    selectable: editable,
    zIndex: node.type === 'frame' ? Math.min(node.zIndex, 0) : node.zIndex,
    data: {
      node,
      selected: selectedNodeIds.includes(node.id),
      connecting: connectingFromId === node.id,
      connections,
      allNodes: nodes,
      layerCanvasSize,
      layerColors,
      onSelect: onSelectNode,
      onStartConnect,
      onRemoveConnection,
    },
    style: {
      width: node.width,
      height: node.height,
    },
  })), [connectingFromId, connections, editable, layerCanvasSize, layerColors, nodes, onRemoveConnection, onSelectNode, onStartConnect, selectedNodeIds]);

  const [flowNodes, setFlowNodes] = useState<CanvasFlowNode[]>(incomingNodes);

  const flowEdges = useMemo<Edge[]>(() => connections.map((connection, index) => ({
    id: connection.id,
    source: connection.sourceNodeId,
    target: connection.targetNodeId,
    sourceHandle: 'output',
    targetHandle: 'input',
    label: connection.label,
    type: 'smoothstep',
    markerEnd: { type: MarkerType.ArrowClosed },
    style: {
      stroke: CONNECTION_COLORS[index % CONNECTION_COLORS.length],
      strokeWidth: 2.5,
    },
    labelBgStyle: { fill: 'hsl(var(--background))', fillOpacity: 0.9 },
    labelStyle: { fill: 'hsl(var(--muted-foreground))', fontSize: 12 },
  })), [connections]);

  useEffect(() => {
    setFlowNodes(current => {
      if (draggingRef.current && sameFlowNodePositions(current, incomingNodes)) return current;
      if (sameFlowNodes(current, incomingNodes)) return current;
      return incomingNodes;
    });
  }, [incomingNodes]);

  useEffect(() => () => {
    if (paneClickTimerRef.current !== null) {
      window.clearTimeout(paneClickTimerRef.current);
    }
  }, []);

  useEffect(() => () => {
    onReady?.(null);
  }, [onReady]);

  const commitCurrentNodePositions = useCallback((options?: { history?: boolean; dirty?: boolean }) => {
    const positions = reactFlow.getNodes().map(node => ({
      id: node.id,
      x: node.position.x,
      y: node.position.y,
    }));
    onNodesCommit(positions, options);
  }, [onNodesCommit, reactFlow]);

  const handleNodesChange = useCallback((changes: NodeChange<CanvasFlowNode>[]) => {
    const relevantChanges = changes.filter(change => change.type === 'position' || change.type === 'select' || change.type === 'remove');
    if (relevantChanges.length === 0) return;

    setFlowNodes(current => {
      const removedIds = new Set(
        relevantChanges
          .filter((change): change is Extract<NodeChange<CanvasFlowNode>, { type: 'remove' }> => change.type === 'remove')
          .map(change => change.id),
      );
      let changed = removedIds.size > 0;
      const nextNodes = current.map(node => {
        if (removedIds.has(node.id)) return node;
        const positionChange = relevantChanges.find(
          (change): change is Extract<NodeChange<CanvasFlowNode>, { type: 'position' }> => change.type === 'position' && change.id === node.id && !!change.position,
        );
        const selectChange = relevantChanges.find(
          (change): change is Extract<NodeChange<CanvasFlowNode>, { type: 'select' }> => change.type === 'select' && change.id === node.id,
        );
        const nextPosition = positionChange?.position || node.position;
        const nextSelected = typeof selectChange?.selected === 'boolean' ? selectChange.selected : node.selected;
        if (nextPosition === node.position && nextSelected === node.selected) return node;
        changed = true;
        return {
          ...node,
          position: nextPosition,
          selected: nextSelected,
        };
      }).filter(node => !removedIds.has(node.id));
      return changed ? nextNodes : current;
    });

    const selected = relevantChanges
      .filter((change): change is Extract<NodeChange<CanvasFlowNode>, { type: 'select' }> => change.type === 'select' && change.selected)
      .map(change => change.id);
    if (selected.length > 0) onSelectionChange(selected);
  }, [onSelectionChange]);

  const handleEdgesChange = useCallback((changes: EdgeChange<Edge>[]) => {
    const removedIds = changes.filter(change => change.type === 'remove').map(change => change.id);
    if (removedIds.length > 0) onRemoveConnections(removedIds);
  }, [onRemoveConnections]);

  const handleConnect = useCallback((connection: Connection) => {
    if (!connection.source || !connection.target) return;
    onConnect(connection.source, connection.target);
  }, [onConnect]);

  const handleMoveEnd = useCallback((_event: MouseEvent | TouchEvent | null, nextViewport: FlowViewport) => {
    if (!movingRef.current) return;
    movingRef.current = false;
    const committedViewport = clampViewport(nextViewport, minZoom, maxZoom);
    onViewportCommit(committedViewport);
  }, [maxZoom, minZoom, onViewportCommit]);

  const handleNodeDragStart = useCallback(() => {
    draggingRef.current = true;
  }, []);

  const handleNodeDragStop = useCallback(() => {
    draggingRef.current = false;
    commitCurrentNodePositions({ history: true, dirty: true });
  }, [commitCurrentNodePositions]);

  const handleMoveStart = useCallback(() => {
    movingRef.current = true;
  }, []);

  const handlePaneReactFlowClick = useCallback((event: React.MouseEvent) => {
    const point = reactFlow.screenToFlowPosition({ x: event.clientX, y: event.clientY });
    if (event.detail >= 2) {
      if (paneClickTimerRef.current !== null) {
        window.clearTimeout(paneClickTimerRef.current);
        paneClickTimerRef.current = null;
      }
      onPaneDoubleClick(reactFlow.screenToFlowPosition({ x: event.clientX, y: event.clientY }, { snapToGrid: true, snapGrid: SNAP_GRID }));
      return;
    }
    if (paneClickTimerRef.current !== null) {
      window.clearTimeout(paneClickTimerRef.current);
    }
    paneClickTimerRef.current = window.setTimeout(() => {
      paneClickTimerRef.current = null;
      onPaneClick(point, event);
    }, 180);
  }, [onPaneClick, onPaneDoubleClick, reactFlow]);

  const handleSelectionChange = useCallback(({ nodes: selectedNodes }: { nodes: CanvasFlowNode[] }) => {
    onSelectionChange(selectedNodes.map(node => node.id));
  }, [onSelectionChange]);

  const getMiniMapNodeColor = useCallback((node: FlowNode) => (
    selectedNodeIds.includes(node.id) ? 'hsl(var(--primary))' : 'hsl(var(--muted-foreground))'
  ), [selectedNodeIds]);

  const handleInit = useCallback((instance: ReactFlowInstance<CanvasFlowNode, Edge>) => {
    onReady?.({
      setViewport: (nextViewport) => instance.setViewport(nextViewport, { duration: 120 }),
      zoomTo: (zoom) => instance.zoomTo(Math.min(maxZoom, Math.max(minZoom, zoom)), { duration: 120 }),
      fitBounds: (bounds, options) => instance.fitBounds(bounds, options),
      getViewport: () => clampViewport(instance.getViewport(), minZoom, maxZoom),
    });
  }, [maxZoom, minZoom, onReady]);

  return (
    <ReactFlow
      className="canvas-flow"
      nodes={flowNodes}
      edges={flowEdges}
      nodeTypes={CANVAS_NODE_TYPES}
      minZoom={minZoom}
      maxZoom={maxZoom}
      snapToGrid
      snapGrid={SNAP_GRID}
      defaultViewport={viewport}
      zoomOnScroll
      zoomOnPinch
      panOnScroll={false}
      panOnDrag
      selectionOnDrag={false}
      multiSelectionKeyCode={MULTI_SELECTION_KEYS}
      deleteKeyCode={null}
      zoomOnDoubleClick={false}
      nodesDraggable={editable}
      nodesConnectable={editable}
      nodesFocusable={false}
      edgesFocusable={false}
      connectionLineType={ConnectionLineType.SmoothStep}
      fitViewOptions={FLOW_FIT_VIEW_OPTIONS}
      onNodesChange={handleNodesChange}
      onEdgesChange={handleEdgesChange}
      onConnect={handleConnect}
      onNodeDragStart={handleNodeDragStart}
      onNodeDragStop={handleNodeDragStop}
      onMoveStart={handleMoveStart}
      onMoveEnd={handleMoveEnd}
      onPaneClick={handlePaneReactFlowClick}
      onSelectionChange={handleSelectionChange}
      onInit={handleInit}
    >
      <Background gap={20} size={1} color="hsl(var(--border))" />
      <Background gap={100} size={1.2} color="hsl(var(--muted-foreground))" />
      <Controls showInteractive={false} position="bottom-left" />
      <MiniMap
        position="bottom-right"
        pannable
        zoomable
        nodeColor={getMiniMapNodeColor}
        maskColor="hsl(var(--background) / 0.58)"
      />
    </ReactFlow>
  );
}

export function ReactFlowCanvas(props: ReactFlowCanvasProps) {
  return (
    <ReactFlowProvider>
      <FlowCanvasInner {...props} />
    </ReactFlowProvider>
  );
}
