import {type Edge, getIncomers, type Node} from '@xyflow/react'
import type {Option} from 'amis/lib/Schema'
import {find, has, isEqual, max, min, unique} from 'licia'
import {type DependencyList, type MouseEvent as ReactMouseEvent, useCallback, useRef} from 'react'
import Queue from 'yocto-queue'
import {originTypeMap} from '../../pages/ai/task/InputSchema.tsx'
import {useFlowStore} from './store/FlowStore.ts'
import {type OutputVariable} from './types.ts'

export const getAllIncomerNodeById: (id: string, nodes: Node[], edges: Edge[]) => string[] = (id, nodes, edges) => {
  let queue = new Queue<Node>()
  queue.enqueue(find(nodes, node => isEqual(node.id, id))!)
  let result: string[] = []
  while (queue.size !== 0) {
    let currentNode = queue.dequeue()!
    for (const incomer of getIncomers(currentNode, nodes, edges)) {
      result.push(incomer.id)
      queue.enqueue(incomer)
    }
  }
  return unique(result, (a, b) => isEqual(a, b))
}

export const getAllIncomerNodeOutputVariables: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => OutputVariable[] = (id, inputSchema, nodes, edges, data) => {
  let inputSchemaVariables: OutputVariable[] = Object.keys(inputSchema).map(key => ({
    group: '流程入参',
    name: `${key}${inputSchema[key]?.label ? ` (${inputSchema[key].label})` : ''}`,
    type: originTypeMap[inputSchema[key]?.type ?? ''],
    variable: key,
  }))

  let currentNode = find(nodes, n => isEqual(id, n.id))
  if (!currentNode) {
    return inputSchemaVariables
  }

  let incomerIds = getAllIncomerNodeById(id, nodes, edges)
  if (currentNode.parentId) {
    incomerIds = [
      ...incomerIds,
      ...getAllIncomerNodeById(currentNode.parentId, nodes, edges),
    ]
  }

  let incomerVariables: OutputVariable[] = []
  for (const incomerId of incomerIds) {
    let nodeData = data[incomerId] ?? {}
    let group = incomerId
    if (has(nodeData, 'node') && has(nodeData.node, 'name')) {
      group = `${nodeData.node.name} ${incomerId}`
    }
    if (has(nodeData, 'outputs')) {
      let outputs = nodeData?.outputs ?? {}
      for (const key of Object.keys(outputs)) {
        incomerVariables.push({
          group: group,
          name: key,
          type: outputs[key].type,
          variable: `${incomerId}.${key}`,
        })
      }
    }
  }

  return [
    ...inputSchemaVariables,
    ...(currentNode.parentId ? [
      {
        group: '循环入参',
        name: 'loopIndex (当前迭代索引)',
        type: 'number',
        variable: 'loopIndex',
      } as OutputVariable,
      {
        group: '循环入参',
        name: 'loopItem (当前迭代对象)',
        type: 'object',
        variable: 'loopItem',
      } as OutputVariable,
    ] : []),
    ...incomerVariables,
  ]
}

export const generateAllIncomerOutputVariablesFormOptions: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => {
  let optionMap: Record<string, Option[]> = {}
  for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) {
    if (!optionMap[item.group]) {
      optionMap[item.group] = []
    }
    optionMap[item.group].push({
      label: item.name,
      value: item.variable,
    })
  }
  return Object.keys(optionMap)
    .map(key => ({
      label: key,
      children: optionMap[key],
    }))
}

type ConditionOperator = string | { label: string, value: string }
const textOperators: ConditionOperator[] = ['equal', 'not_equal', 'is_empty', 'is_not_empty', 'like', 'not_like', 'starts_with', 'ends_with']
const textDefaultOperator: string = 'equal'
const booleanOperators: ConditionOperator[] = [
  {label: '为真', value: 'is_true'},
  {label: '为假', value: 'is_false'},
]
const booleanDefaultOperator: string = 'is_true'
const numberOperators: ConditionOperator[] = [
  'equal',
  'not_equal',
  {label: '大于', value: 'greater'},
  {label: '大于或等于', value: 'greater_equal'},
  {label: '小于', value: 'less'},
  {label: '小于或等于', value: 'less_equal'},
]
const numberDefaultOperator: string = 'equal'

export const generateAllIncomerOutputVariablesConditions: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => {
  let optionMap: Record<string, Option[]> = {}
  for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) {
    if (!optionMap[item.group]) {
      optionMap[item.group] = []
    }
    optionMap[item.group].push({
      label: item.name,
      type: 'custom',
      name: item.variable,
      ...(item.type === 'text' ? {
        value: {
          type: 'input-text',
          required: true,
          clearable: true,
        },
        defaultOp: textDefaultOperator,
        operators: textOperators,
      } : {}),
      ...(item.type === 'boolean' ? {
        value: {
          type: 'wrapper',
          size: 'none',
        },
        defaultOp: booleanDefaultOperator,
        operators: booleanOperators,
      } : {}),
      ...(item.type === 'number' ? {
        value: {
          type: 'input-number',
          required: true,
          clearable: true,
        },
        defaultOp: numberDefaultOperator,
        operators: numberOperators,
      } : {}),
    })
  }
  return Object.keys(optionMap)
    .map(key => ({
      label: key,
      children: optionMap[key],
    }))
}

// 处理循环节点的边界问题
export const useNodeDrag = (deps: DependencyList) => {
  const currentPosition = useRef({x: 0, y: 0} as { x: number, y: number })
  const {setNode, getNodeById} = useFlowStore()

  const onNodeDragStart = useCallback(() => {
  }, deps)
  const onNodeDrag = useCallback((event: ReactMouseEvent, node: Node) => {
    event.stopPropagation()
    if (node.parentId) {
      let parentNode = getNodeById(node.parentId)
      if (parentNode) {
        let newPosition = {
          x: max(min(node.position.x, (parentNode.measured?.width ?? 0) - (node.measured?.width ?? 0) - 28), 28),
          y: max(min(node.position.y, (parentNode.measured?.height ?? 0) - (node.measured?.height ?? 0) - 28), 130),
        }
        setNode({
          ...node,
          position: newPosition,
        })
        currentPosition.current = newPosition
      }
    }
  }, deps)
  const onNodeDragEnd = useCallback((_event: ReactMouseEvent, node: Node) => {
    if (node.parentId) {
      setNode({
        ...node,
        position: currentPosition.current,
      })
    }
  }, deps)

  return {
    onNodeDragStart,
    onNodeDrag,
    onNodeDragEnd,
  }
}
