import { useQuery } from '@tanstack/react-query'
import { useCallback, useMemo, useRef, useState } from 'react'
import type { NodeTypes, Node } from 'reactflow'
import ReactFlow, { Controls, Background, ReactFlowProvider } from 'reactflow'

import 'reactflow/dist/style.css'

import useApiUrlStore from '../../../app/store/apiStore'
import { WorkflowSelector } from '../../../app/store/selectors/workflowSelector'
import useWorkflowStore from '../../../app/store/workflowStore'
import type {
  FlowType,
  IFlow,
  SyntheticFlow,
  SyntheticFlowParameter,
  SyntheticWorkflow,
} from '../../../generated/ManagingApi'
import { WorkflowUsage, WorkflowClient } from '../../../generated/ManagingApi'
import type { IWorkflow, IFlowItem } from '../../../global/type'
import { Toast } from '../../mollecules'

import FeedTicker from './flows/feed/feedTicker'
import RsiDivergenceFlow from './flows/strategies/RsiDivergenceFlow'
import OpenPositionFlow from './flows/trading/OpenPositionFlow'
import WorkflowForm from './workflowForm'
import WorkflowSidebar from './workflowSidebar'

let id = 0
const getId = () => `dndnode_${id++}`

const mapToFlowRequest = (
  flow: IFlow,
  id: string,
  parentId: string
): SyntheticFlow => {
  return {
    id: id,
    parameters: flow.parameters as SyntheticFlowParameter[],
    parentId: parentId,
    type: flow.type,
  }
}

const WorkflowCanvas: React.FC = (props: any) => {
  const tabs = props
  const properties = tabs['data-props'] as IWorkflow
  const reactFlowWrapper = useRef(null)
  const [reactFlowInstance, setReactFlowInstance] = useState(null)
  const [isUpdated, setIsUpdated] = useState(false)
  const { apiUrl } = useApiUrlStore()
  const [usage, setUsage] = useState<WorkflowUsage>(WorkflowUsage.Trading)
  const [name, setName] = useState<string>(properties.name)
  const client = new WorkflowClient({}, apiUrl)

  const nodeTypes: NodeTypes = useMemo(
    () => ({
      FeedTicker: FeedTicker,
      OpenPosition: OpenPositionFlow,
      RsiDivergence: RsiDivergenceFlow,
    }),
    []
  )

  const {
    nodes,
    edges,
    onNodesChange,
    onEdgesChange,
    onConnect,
    initWorkFlow,
    setNodes,
    resetWorkflow,
  } = useWorkflowStore(WorkflowSelector)

  const { data, isLoading } = useQuery({
    onSuccess: (data) => {
      initWorkFlow([], properties.edges)
      if (data) {
        setNodes(properties.nodes.map((n, i) => mapToNodeItem(n, data, i)))
      }
    },
    queryFn: () => client.workflow_GetAvailableFlows(),
    queryKey: ['availableFlows'],
  })

  const mapToNodeItem = (
    flow: Node<IFlow>,
    availableFlows: IFlow[],
    index: number
  ): IFlowItem => {
    const nodeData = availableFlows.find((f) => f.type === flow.type)

    if (nodeData == null) {
      return {} as IFlowItem
    }

    nodeData.parameters = flow.data.parameters

    return {
      data: nodeData,
      id: flow.id,
      isConnectable: nodeData.acceptedInputs?.length > 0,
      position: { x: index * 400, y: index * 100 },
      type: flow.type as FlowType,
    }
  }

  const isValidConnection = (connection: any) => {
    if (reactFlowInstance == null) {
      return false
    }

    const sourceData: IFlow = reactFlowInstance.getNode(connection.source).data
    const targetData: IFlow = reactFlowInstance.getNode(connection.target).data

    return sourceData.outputTypes?.some(
      (output) => targetData.acceptedInputs?.indexOf(output) >= 0
    )
  }

  const handleOnConnect = useCallback((params: any) => {
    setIsUpdated(true)
    onConnect(params)
  }, [])

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

  const onDrop = useCallback(
    (event: any) => {
      event.preventDefault()

      if (reactFlowInstance == null || reactFlowWrapper.current == null) {
        return
      }

      const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect()
      const data = event.dataTransfer.getData('application/reactflow')
      if (typeof data === 'undefined' || !data) {
        return
      }

      const properties: string[] = data.split('-')
      const position = reactFlowInstance.project({
        x: event.clientX - reactFlowBounds.left,
        y: event.clientY - reactFlowBounds.top,
      })

      const flow = flows.find((flow) => flow.type === properties[0])
      const newNode: IFlowItem = {
        data: flow || ({ parameters: [] as SyntheticFlowParameter[] } as IFlow),
        id: getId(),
        isConnectable:
          (flow?.acceptedInputs && flow?.acceptedInputs?.length > 0) ?? false,
        position,
        type: properties[0] as FlowType,
      }

      newNode.data.parameters = []
      setNodes(nodes.concat(newNode))
    },
    [reactFlowInstance, data, nodes]
  )

  const handleOnSave = () => {
    const t = new Toast('Saving workflow')
    const nodesRequest: SyntheticFlow[] = []

    const client = new WorkflowClient({}, apiUrl)

    if (edges.length <= 0) {
      t.update('error', 'Workflow must have at least one edge')
      return
    }

    for (const node of nodes) {
      const data: IFlow = reactFlowInstance.getNode(node.id).data

      const parentId = edges.find((edge) => edge.target === node.id)?.source
      nodesRequest.push(mapToFlowRequest(data, node.id, parentId || ''))
    }

    const request: SyntheticWorkflow = {
      description: 'Test',
      flows: nodesRequest,
      name: properties.name,
      usage: usage,
    }

    client
      .workflow_PostWorkflow(request)
      .then((data) => {
        t.update('success', 'Workflow saved')
      })
      .catch((err) => {
        t.update('error', 'Error :' + err)
      })
  }

  const handleOnReset = () => {
    resetWorkflow()
  }

  const handleUsageChange = (event: any) => {
    setUsage(event.target.value)
  }

  return (
    <>
      <WorkflowForm
        name={name}
        usage={usage}
        handleUsageChange={handleUsageChange}
        handleOnSave={handleOnSave}
        handleOnReset={handleOnReset}
        isUpdated={isUpdated}
        setName={setName}
      />

      <div className="grid grid-cols-10 gap-4">
        <ReactFlowProvider>
          <div>
            <WorkflowSidebar flows={data} isLoading={isLoading} />
          </div>
          <div
            className="reactflow-wrapper col-span-8"
            ref={reactFlowWrapper}
            style={{ height: 800 }}
          >
            <ReactFlow
              nodes={nodes}
              edges={edges}
              onNodesChange={onNodesChange}
              onEdgesChange={onEdgesChange}
              onConnect={handleOnConnect}
              onDrop={onDrop}
              onInit={setReactFlowInstance}
              onDragOver={onDragOver}
              nodeTypes={nodeTypes}
              isValidConnection={isValidConnection}
              fitView
            >
              <Controls />
              <Background />
            </ReactFlow>
          </div>
        </ReactFlowProvider>
      </div>
    </>
  )
}

export default WorkflowCanvas
