/**
 * Agent 画布操作：解析 Agent 返回的 nodes/edges/workflow/actions 并更新画布
 */
import { useRef, useCallback } from 'react';
import { useReactFlow, Edge } from '@xyflow/react';
import { NodeType, NodeStatus } from '../types/node';
import type { AppNodeType, AppNodeData } from '../types/node';
import {
  findNonOverlappingPosition,
  calculateLayoutPositions,
  findEmptySpace,
  LAYOUT_CONFIG,
  type LayoutMode,
} from '../utils/layoutUtils';

/** Agent 返回的节点描述（与后端/LLM 约定一致） */
export interface AgentNodeData {
  id?: string;
  type?: string;
  title?: string;
  name?: string;
  prompt?: string;
  content?: string;
  model?: string;
  aspectRatio?: string;
  template?: string;
  targetType?: string;
  image?: string;
  videoUri?: string;
  position?: { x: number; y: number };
  status?: string;
  [key: string]: unknown;
}

/** Agent 返回的边描述 */
export interface AgentEdgeData {
  id?: string;
  from?: string;
  to?: string;
  source?: string;
  target?: string;
}

/** 工作流块结构 */
export interface WorkflowData {
  nodes: AgentNodeData[];
  edges?: AgentEdgeData[];
  layout?: LayoutMode;
}

export function useAgentCanvasActions() {
  const idMappingRef = useRef<Map<string, string>>(new Map());
  const { getNodes, setNodes, getEdges, setEdges, addEdges, screenToFlowPosition } = useReactFlow();

  const mapNodeType = useCallback((typeStr: string): NodeType => {
    if (Object.values(NodeType).includes(typeStr as NodeType)) {
      return typeStr as NodeType;
    }
    const typeAliases: Record<string, NodeType> = {
      image: NodeType.IMAGE_GENERATOR,
      video: NodeType.VIDEO_GENERATOR,
      character: NodeType.INFO_DISPLAY,
      scene: NodeType.INFO_DISPLAY,
      prompt: NodeType.PROMPT_INPUT,
      lyrics: NodeType.LYRICS_GENERATOR,
      info: NodeType.INFO_DISPLAY,
      audio: NodeType.AUDIO_GENERATOR,
      editor: NodeType.IMAGE_EDITOR,
      analyzer: NodeType.VIDEO_ANALYZER,
    };
    return typeAliases[typeStr.toLowerCase()] ?? NodeType.INFO_DISPLAY;
  }, []);

  const addNodesFromAgent = useCallback(
    (nodesData: AgentNodeData[], layoutMode: LayoutMode = 'auto') => {
      const existingNodes = getNodes() as AppNodeType[];
      const viewportCenter = screenToFlowPosition({
        x: window.innerWidth / 2,
        y: window.innerHeight / 2,
      });
      const startPosition = findEmptySpace(
        existingNodes,
        LAYOUT_CONFIG.NODE_WIDTH * Math.min(nodesData.length, 3) + LAYOUT_CONFIG.SPACING_X * 2,
        LAYOUT_CONFIG.NODE_MIN_HEIGHT * Math.ceil(nodesData.length / 3) + LAYOUT_CONFIG.SPACING_Y * 2,
        viewportCenter
      );
      const positions = calculateLayoutPositions(
        nodesData.length,
        startPosition,
        layoutMode,
        existingNodes
      );
      idMappingRef.current.clear();
      const maxZIndex = existingNodes.reduce((max, node) => Math.max(max, node.zIndex ?? 0), 0);
      const createdNodes: AppNodeType[] = [];

      for (let i = 0; i < nodesData.length; i++) {
        const data = nodesData[i];
        const nodeType = mapNodeType(data.type ?? 'info');
        const tempId = data.id ?? `temp-${i}`;
        const realId = `agent-${Date.now()}-${i}`;
        idMappingRef.current.set(tempId, realId);
        const position = data.position
          ? { x: data.position.x, y: data.position.y }
          : positions[i];
        const finalPosition = findNonOverlappingPosition(
          [...existingNodes, ...createdNodes],
          position,
          LAYOUT_CONFIG.NODE_WIDTH,
          LAYOUT_CONFIG.NODE_MIN_HEIGHT
        );
        const newNode: AppNodeType = {
          id: realId,
          type: 'appNode',
          position: finalPosition,
          zIndex: maxZIndex + 1 + i,
          data: {
            title: data.title ?? data.name ?? 'Agent Node',
            type: nodeType,
            status: (data.status as NodeStatus) ?? NodeStatus.IDLE,
            prompt: data.prompt,
            content: data.content,
            model: data.model,
            aspectRatio: data.aspectRatio,
            template: data.template,
            targetType: data.targetType,
            image: data.image,
            videoUri: data.videoUri,
            ...data,
          } as AppNodeData,
        } as AppNodeType;
        createdNodes.push(newNode);
      }
      setNodes((nds) => [...nds, ...createdNodes]);
      return createdNodes;
    },
    [getNodes, screenToFlowPosition, mapNodeType, setNodes]
  );

  const addEdgesFromAgent = useCallback(
    (edgesData: AgentEdgeData[]) => {
      const newEdges: Edge[] = edgesData.map((edge, i) => {
        const sourceId =
          idMappingRef.current.get(edge.from ?? edge.source ?? '') ??
          edge.from ??
          edge.source;
        const targetId =
          idMappingRef.current.get(edge.to ?? edge.target ?? '') ?? edge.to ?? edge.target;
        return {
          id: edge.id ?? `edge-agent-${Date.now()}-${i}`,
          source: sourceId ?? '',
          target: targetId ?? '',
          type: 'gradient',
        };
      });
      addEdges(newEdges);
      return newEdges;
    },
    [addEdges]
  );

  const processDeleteNodes = useCallback(
    (nodeIds: string[]) => {
      const resolvedIds = nodeIds.map((id) => idMappingRef.current.get(id) ?? id);
      setNodes((nds) => nds.filter((n) => !resolvedIds.includes(n.id)));
      setEdges((eds) =>
        eds.filter(
          (e) => !resolvedIds.includes(e.source) && !resolvedIds.includes(e.target)
        )
      );
    },
    [setNodes, setEdges]
  );

  const processUpdateNode = useCallback(
    (nodeId: string, properties: Record<string, unknown>) => {
      const resolvedId = idMappingRef.current.get(nodeId) ?? nodeId;
      setNodes((nds) =>
        nds.map((n) => {
          if (n.id !== resolvedId) return n;
          if (n.type !== 'appNode') return n;
          return {
            ...n,
            data: { ...(n.data as AppNodeData), ...properties },
          } as AppNodeType;
        })
      );
    },
    [setNodes]
  );

  const processWorkflow = useCallback(
    (workflowData: WorkflowData) => {
      const { nodes: nodesData, edges: edgesData, layout = 'horizontal' } = workflowData;
      const newNodes = addNodesFromAgent(nodesData, layout);
      if (edgesData?.length) {
        setTimeout(() => addEdgesFromAgent(edgesData), 100);
      }
      return newNodes;
    },
    [addNodesFromAgent, addEdgesFromAgent]
  );

  const processAgentCommands = useCallback(
    (text: string): boolean => {
      let processed = false;

      const workflowMatch = text.match(/```json:workflow([\s\S]*?)```/);
      if (workflowMatch) {
        try {
          const workflowData = JSON.parse(workflowMatch[1]) as WorkflowData;
          if (workflowData.nodes && Array.isArray(workflowData.nodes)) {
            processWorkflow(workflowData);
            processed = true;
          }
        } catch {
          // ignore parse error
        }
      }

      if (!processed) {
        const nodesMatch = text.match(/```json:nodes([\s\S]*?)```/);
        if (nodesMatch) {
          try {
            const nodesData = JSON.parse(nodesMatch[1]) as AgentNodeData[];
            if (
              Array.isArray(nodesData) &&
              nodesData.length > 0 &&
              nodesData.every((n) => n.type ?? n.title ?? n.name ?? n.prompt)
            ) {
              addNodesFromAgent(nodesData);
              processed = true;
            }
          } catch {
            // ignore
          }
        }
      }

      const edgesMatch = text.match(/```json:edges([\s\S]*?)```/);
      if (edgesMatch) {
        try {
          const edgesData = JSON.parse(edgesMatch[1]) as AgentEdgeData[];
          if (Array.isArray(edgesData) && edgesData.length > 0) {
            addEdgesFromAgent(edgesData);
            processed = true;
          }
        } catch {
          // ignore
        }
      }

      const actionsMatch = text.match(/```json:actions([\s\S]*?)```/);
      if (actionsMatch) {
        try {
          const actionsData = JSON.parse(actionsMatch[1]) as
            | Array<{
                action?: string;
                type?: string;
                node_ids?: string[];
                nodeIds?: string[];
                node_id?: string;
                properties?: Record<string, unknown>;
                edges?: AgentEdgeData[];
                connections?: AgentEdgeData[];
              }>
            | { action?: string; node_ids?: string[]; nodeIds?: string[]; node_id?: string; properties?: Record<string, unknown>; edges?: AgentEdgeData[]; connections?: AgentEdgeData[] };
          if (Array.isArray(actionsData)) {
            actionsData.forEach((action) => {
              switch (action.action ?? action.type) {
                case 'delete_nodes':
                  if (action.node_ids ?? action.nodeIds) {
                    processDeleteNodes(action.node_ids ?? action.nodeIds ?? []);
                    processed = true;
                  }
                  break;
                case 'update_node':
                  if (action.node_id && action.properties) {
                    processUpdateNode(action.node_id, action.properties);
                    processed = true;
                  }
                  break;
                case 'create_connections':
                case 'connect_nodes':
                  if (action.edges ?? action.connections) {
                    addEdgesFromAgent(action.edges ?? action.connections ?? []);
                    processed = true;
                  }
                  break;
              }
            });
          } else if (actionsData && typeof actionsData === 'object' && 'action' in actionsData) {
            const a = actionsData as {
              action?: string;
              node_ids?: string[];
              nodeIds?: string[];
              node_id?: string;
              properties?: Record<string, unknown>;
              edges?: AgentEdgeData[];
              connections?: AgentEdgeData[];
            };
            switch (a.action) {
              case 'delete_nodes':
                if (a.node_ids ?? a.nodeIds) {
                  processDeleteNodes(a.node_ids ?? a.nodeIds ?? []);
                  processed = true;
                }
                break;
              case 'update_node':
                if (a.node_id && a.properties) {
                  processUpdateNode(a.node_id, a.properties);
                  processed = true;
                }
                break;
              case 'create_connections':
              case 'connect_nodes':
                if (a.edges ?? a.connections) {
                  addEdgesFromAgent(a.edges ?? a.connections ?? []);
                  processed = true;
                }
                break;
            }
          }
        } catch {
          // ignore
        }
      }

      // Fallback: generic ```json block (backward compatibility)
      if (!processed) {
        const genericMatch = text.match(/```json(?!:)(\s[\s\S]*?)```/);
        if (genericMatch) {
          try {
            const jsonContent = genericMatch[1].trim();
            if (jsonContent) {
              const data = JSON.parse(jsonContent) as WorkflowData | AgentNodeData[];
              if (data && typeof data === 'object' && !Array.isArray(data) && 'nodes' in data && Array.isArray((data as WorkflowData).nodes)) {
                processWorkflow(data as WorkflowData);
                processed = true;
              } else if (Array.isArray(data) && data.every((n: AgentNodeData) => n.type ?? n.title ?? n.name ?? n.prompt)) {
                addNodesFromAgent(data);
                processed = true;
              }
            }
          } catch {
            // ignore
          }
        }
      }

      return processed;
    },
    [
      processWorkflow,
      addNodesFromAgent,
      addEdgesFromAgent,
      processDeleteNodes,
      processUpdateNode,
    ]
  );

  return {
    addNodesFromAgent,
    addEdgesFromAgent,
    processDeleteNodes,
    processUpdateNode,
    processWorkflow,
    processAgentCommands,
  };
}
