import {type Connection, type Edge, getOutgoers, type Node} from '@xyflow/react'
import {find, has, isEmpty, isEqual, lpad, toStr} from 'licia'
import NodeRegistry from './NodeRegistry.tsx'

export class CheckError extends Error {
  readonly id: string

  constructor(
    id: number,
    message: string,
  ) {
    super(message)
    this.id = `E${lpad(toStr(id), 6, '0')}`
  }

  public toString(): string {
    return `${this.id}: ${this.message}`
  }
}

const getNodeById = (id: string, nodes: Node[]) => find(nodes, (n: Node) => isEqual(n.id, id))

// @ts-ignore
export const checkAddNode: (type: string, nodes: Node[], edges: Edge[]) => void = (type, nodes, edges) => {
}

export const sourceNodeNotFoundError = () => new CheckError(200, '连线起始节点未找到')
export const targetNodeNotFoundError = () => new CheckError(201, '连线目标节点未找到')
export const nodeToSelfError = () => new CheckError(203, '节点不能直连自身')
export const hasCycleError = () => new CheckError(204, '禁止流程循环')

const hasCycle = (sourceNode: Node, targetNode: Node, nodes: Node[], edges: Edge[], visited = new Set<string>()) => {
  if (visited.has(targetNode.id)) return false
  visited.add(targetNode.id)
  for (const outgoer of getOutgoers(targetNode, nodes, edges)) {
    if (isEqual(outgoer.id, sourceNode.id)) return true
    if (hasCycle(sourceNode, outgoer, nodes, edges, visited)) return true
  }
}

export const checkAddConnection: (connection: Connection, nodes: Node[], edges: Edge[]) => void = (connection, nodes, edges) => {
  let sourceNode = getNodeById(connection.source, nodes)
  if (!sourceNode) {
    throw sourceNodeNotFoundError()
  }
  let targetNode = getNodeById(connection.target, nodes)
  if (!targetNode) {
    throw targetNodeNotFoundError()
  }

  // 禁止流程出现环，必须是有向无环图
  if (isEqual(sourceNode.id, targetNode.id)) {
    throw nodeToSelfError()
  } else if (hasCycle(sourceNode, targetNode, nodes, edges)) {
    throw hasCycleError()
  }

  // let newEdges = [...clone(edges), {...connection, id: uuid()}]
  // let {hasAbnormalEdges} = getParallelInfo(nodes, newEdges)
  // if (hasAbnormalEdges) {
  //   throw hasRedundantEdgeError()
  // }
}

export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点')
export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`)
export const nodeTypeNotFound = () => new CheckError(302, '节点类型不存在')
export const nodeError = (nodeId: string, reason?: string) => new CheckError(303, reason ?? `节点配置存在错误：${nodeId}`)

// @ts-ignore
export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => void = (inputSchema, nodes, edges, data) => {
  if (isEmpty(nodes)) {
    throw atLeastOneNode()
  }

  for (let node of nodes) {
    let nodeId = node.id
    if (!has(data, nodeId) || !data[nodeId]?.finished) {
      throw hasUnfinishedNode(nodeId)
    }

    if (!has(node, 'type')) {
      throw nodeTypeNotFound()
    }
    let nodeType = node.type!
    let nodeDefine = NodeRegistry[nodeType]
    for (let checker of nodeDefine.checkers) {
      let checkResult = checker(nodeId, inputSchema, nodes, edges, data)
      if (checkResult.error) {
        throw nodeError(nodeId, checkResult.message)
      }
    }
  }
}