import React, { useState, useCallback } from 'react'
import { ReactFlow, Controls, Background, useNodesState, useEdgesState, addEdge, BackgroundVariant, getNodesBounds } from '@xyflow/react'
import { toSvg } from 'html-to-image'
import '@xyflow/react/dist/style.css'
import './App.css'
import Toolbar from './components/Toolbar';
import ControlPanel from './components/ControlPanel';
import CustomBlock from './components/CustomBlock';
import ModalData from './components/ModalData'

const nodeTypes = {
  fbdBlock: CustomBlock
}

const EDGE_STYLES = {
  bool: { stroke: '#000', strokeWidth: 2 },
  int: { stroke: '#040089', strokeWidth: 3 },
  dint: { stroke: '#771061', strokeWidth: 3 },
  real: { stroke: '#EB3B13', strokeWidth: 3 },
  time: { stroke: '#AB7001', strokeWidth: 2.5 },
  string: { stroke: '#58E9D0', strokeWidth: 2.5 }
}


function App() {
  const [nodes, setNodes, onNodesChange] = useNodesState([])
  const [edges, setEdges, onEdgesChange] = useEdgesState([])
  const [flowInstance, setFlowInstance] = useState(null)
  const [edgeType, setEdgeType] = useState('bool')
  const [modalData, setModalData] = useState(null)

  function checkEdge(edge, currentNodes) {
    const sourceNode = currentNodes.find(node => node.id === edge.source)
    const targetNode = currentNodes.find(node => node.id === edge.target)
    if (!sourceNode || !targetNode) return null

    const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
    const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)

    const sourceType = sourceNode.data.outputs[sourceIndex].replace('$/', '') || 'INT'
    const targetType = targetNode.data.inputs[targetIndex].replace('$/', '') || 'INT'
    if (edge.source == 1000000006)
    if (sourceType === targetType && edge.data.type === sourceType.toLowerCase())
      return null

    let generalType = ''
    for (let t of sourceType.split('/'))
      if (targetType.includes(t))
        generalType += t + '/'
    if (generalType.endsWith('/'))
      generalType = generalType.slice(0, -1)
    if (!generalType) 
      generalType = sourceType.split('/')[0]
    return generalType
  }

  function updateTwoNode(edge, newType, currentNodes, currentEdges) {
    const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
    const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
    let change = false

    const nextNodes = currentNodes.map(node => {
      if (node.id === edge.source) {
        let newInputs = [...node.data.inputs]
        let newOutputs = [...node.data.outputs]

        if (node.data.outputs[sourceIndex]?.startsWith('$')) {
          for (let ind = 0; ind < newInputs.length; ind++)
            if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
              newInputs[ind] = '$/' + newType
              change = true
            }
          for (let ind = 0; ind < newOutputs.length; ind++)
            if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
              newOutputs[ind] = '$/' + newType
              change = true
            }
        }
        else
          newOutputs[sourceIndex] = newType
        return {
          ...node,
          data: {
            ...node.data,
            inputs: newInputs,
            outputs: newOutputs
          }
        }
      }

      else if (node.id === edge.target) {
        let newInputs = [...node.data.inputs]
        let newOutputs = [...node.data.outputs]

        if (node.data.inputs[targetIndex]?.startsWith('$')) {
          for (let ind = 0; ind < newInputs.length; ind++)
            if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
              newInputs[ind] = '$/' + newType
              change = true
            }
          for (let ind = 0; ind < newOutputs.length; ind++)
            if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
              newOutputs[ind] = '$/' + newType
              change = true
            }
        }
        else 
          newInputs[targetIndex] = newType

        return {
          ...node,
          data: {
            ...node.data,
            inputs: newInputs,
            outputs: newOutputs
          }
        }
      }
      return node
    })

    const nextEdges = currentEdges.map(ed => {
      if (edge.id === ed.id) {
        const currentStyle = EDGE_STYLES[newType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool']
        return {
          ...ed,
          style: currentStyle,
          data: {type: newType.toLowerCase().split('/')[0]}
        }
      }
      return ed
    })

    return {nextNodes, nextEdges, change}
  }

  function updateGraphTypes(currentEdges, currentNodes) {
    let globalChange = true
    while (globalChange) {
      globalChange = false

      for (const edge of currentEdges) {
        const newType = checkEdge(edge, currentNodes)

        if (newType) {
          const { nextNodes, nextEdges, change } = updateTwoNode(edge, newType, currentNodes, currentEdges)
          currentNodes = nextNodes
          currentEdges = nextEdges
          globalChange = globalChange || change
        }
      }
    }
    return {currentNodes, currentEdges}
  }

  const onConnect = useCallback(
    (params) => {
      const sourceNode = nodes.find(node => node.id === params.source)
      const sourceIndex = parseInt(params.sourceHandle.split('-')[1], 10)
      const sourceType = sourceNode?.data?.outputs[sourceIndex].replace('$/', '') || 'INT'

      const newEdge = { 
        ...params, 
        id: `e_${Date.now()}`,
        type: 'smoothstep',
        style: EDGE_STYLES[sourceType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool'],
        data: { type: sourceType.toLowerCase().split('/')[0] }
      }

      const allEdges = [...edges, newEdge]
      const { currentNodes, currentEdges } = updateGraphTypes(allEdges, [...nodes])
      setNodes(currentNodes)
      setEdges(currentEdges)
    }, [nodes, setNodes, setEdges, edgeType]
  )

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

  const onDrop = useCallback(
    (event) => {
      event.preventDefault()
      if (!flowInstance)
        return

      const dataStr = event.dataTransfer.getData('application/reactflow')
      if (!dataStr)
        return

      const {type, label, width, height, inputs = [], outputs = [], 
        inputsLabels = [], outputsLabels = [], offsetX = 0, offsetY = 0} = JSON.parse(dataStr)
      const position = flowInstance.screenToFlowPosition({
        x: event.clientX - offsetX,
        y: event.clientY - offsetY
      })

      if (['const', 'switch_type', 'input', 'output'].includes(type)) {
        setModalData({ type, label, width, height, position, inputs, outputs, inputsLabels, outputsLabels })
        return
      }

      const newNode = {
        id: `${Date.now()}`,
        type: 'fbdBlock',
        position,
        data: { type, label, width, height, inputs, outputs, inputsLabels, outputsLabels }
      }
      setNodes((currentNodes) => [...currentNodes, newNode])
    }, [flowInstance, setNodes]
  )

  function handleSaveModalData(formData) {
    if (!modalData)
      return

    let finalLabel = modalData.label
    let inputs = []
    let outputs = []

    if (modalData.type === 'const') {
      finalLabel = `${formData.value} (${formData.toType})`
      outputs = [formData.fromType]
    }
    else if (modalData.type === 'switch_type') {
      finalLabel = `${formData.fromType}_TO_${formData.toType}`
      inputs = [formData.fromType]
      outputs = [formData.toType]
    }
    else if (modalData.type === 'input') {
      finalLabel = `${formData.value} (${formData.fromType})`
      outputs = [formData.fromType]
    }
    else if (modalData.type === 'output') {
      finalLabel = `${formData.value} (${formData.fromType})`
      inputs = [formData.fromType]
    }

    const newNode = {
      id: `${Date.now()}`,
      type: 'fbdBlock',
      position: modalData.position,
      data: {
        type: modalData.type,
        label: finalLabel,
        width: modalData.width,
        height: modalData.height,
        value: formData.value,
        inputs,
        outputs,
        inputsLabels: modalData.inputsLabels,
        outputsLabels: modalData.outputsLabels,
        metaFromType: formData.fromType,
        metaToType: formData.toType
      }
    }

    setNodes((currentNodes) => [...currentNodes, newNode])
    setModalData(null)
  }

  const isValidConnection = useCallback((connection) => {
    if (connection.source === connection.target)
      return false

    function checkIfCycleExists(sourceId, targetId) {
      const visited = new Set()

      function dfs(currentId) {
        if (currentId === sourceId)
          return true
        if (visited.has(currentId))
          return false
        visited.add(currentId)

        const outgoingEdges = edges.filter(edge => edge.source === currentId)
        for (const edge of outgoingEdges)
          if (dfs(edge.target))
            return true
        return false
      }
      return dfs(targetId)
    }

    if (checkIfCycleExists(connection.source, connection.target))
      return false

    const sourceNode = nodes.find(node => node.id === connection.source)
    const targetNode = nodes.find(node => node.id === connection.target)
    if (!sourceNode || !targetNode)
      return false

    const sourceIndex = parseInt(connection.sourceHandle.split('-')[1], 10)
    const targetIndex = parseInt(connection.targetHandle.split('-')[1], 10)

    const sourceType = sourceNode.data.outputs[sourceIndex].replace('$/', '')
    const targetType = targetNode.data.inputs[targetIndex].replace('$/', '')

    for (let t of sourceType.split('/'))
      if (targetType.includes(t))
        return true

    return false
  }, [nodes, edges])

  function handleClear() {
    if ((nodes.length > 0 || edges.length > 0) && window.confirm("Очистить холст?")) {
      setNodes([])
      setEdges([])
    }
  }

  const handleExportSvg = useCallback(() => {
    const reactFlowViewport = document.querySelector('.react-flow__viewport')
    if (!reactFlowViewport || !nodes.length > 0) {
      alert('Не удалось найти холст для экспорта')
      return
    }

    const bounds = getNodesBounds(nodes)
    const padding = 30
    const widthExportWindow = bounds.width + padding * 2
    const heightExportWindow = bounds.height + padding * 2

    toSvg(reactFlowViewport, {
      backgroundColor: 'white',
      width: widthExportWindow,
      height: heightExportWindow,
      style: {
        width: `${widthExportWindow}px`,
        height: `${heightExportWindow}px`,
        transform: `translate(${-bounds.x + padding}px, ${-bounds.y + padding}px) scale(1)`,
        transformOrigin: 'top left'
      }
    }).then(async (dataUrl) => {
      if ('showSaveFilePicker' in window) {
        try {
          const handle = await window.showSaveFilePicker({
            suggestedName: 'fbd-scheme.svg',
            types: [{
              description: 'Векторная диаграмма SVG',
              accept: {'image/svg+xml': ['.svg']}
            }]
          })

          const writable = await handle.createWritable()
          const scheme = await fetch(dataUrl)
          const file = await scheme.blob()
          await writable.write(file)
          await writable.close()
        }
        catch (err) { console.log('Пользователь отменил сохранение или произошла ошибка: ', err) }
      }
      else {
        const link = document.createElement('a')
        link.download = 'fbd-scheme.svg'
        link.href = dataUrl
        link.click()
      }
    }).catch((error) => {
      console.error('Ошибка генерации SVG-файла: ', error)
      alert('Не удалось сгененировать SVG-файл')
    })
  }, [nodes])

  const handleExportXml = useCallback(async () => {
    if (nodes.length === 0) {
      alert('На холсте нет блоков для экспорта')
      return
    }

    let xml = `<?xml version="1.0" encoding="utf-8"?>\n`
    xml += `<project xmlns="http://www.plcopen.org/xml/tc6_0201" `
    xml += `xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" `
    xml += `xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" `
    xml += `xmlns:xhtml="http://www.w3.org/1999/xhtml\">\n`

    xml += `  <fileHeader companyName="University" productName="FBD Editor" productVersion="1" creationDateTime="${new Date().toISOString().split('.')[0] + 'Z'}"/>\n`
    xml += `  <contentHeader name="diagram" modificationDateTime="${new Date().toISOString().split('.')[0] + 'Z'}">\n`
    xml += `    <coordinateInfo>\n`
    xml += `      <fbd><scaling x="1" y="1"/></fbd>\n`
    xml += `      <ld><scaling x="1" y="1"/></ld>\n`
    xml += `      <sfc><scaling x="1" y="1"/></sfc>\n`
    xml += `    </coordinateInfo>\n`
    xml += `  </contentHeader>\n`
    xml += `  <types>\n`
    xml += `    <dataTypes/>\n`
    xml += `    <pous>\n`
    xml += `      <pou name="main" pouType="program">\n`

    function getPortType(type) {
      const newType = type.toUpperCase().replace('$/', '')

      if (!newType.includes('/'))
        return newType

      const types = newType.split('/')
      const hasAll = (...neededTypes) => neededTypes.every(t => types.includes(t))

      if (hasAll('BOOL', 'INT', 'DINT', 'REAL', 'TIME', 'STRING'))
        return 'ANY'
      if (hasAll('INT', 'DINT', 'REAL', 'TIME'))
        return 'ANY_MAGNITUDE'
      if (hasAll('INT', 'DINT', 'REAL'))
        return 'ANY_NUM'
      if (hasAll('INT', 'DINT'))
        return 'ANY_INT'
      return 'ANY'
    }

    let dopInd = 1
    const nodesIndexes = new Map()
    nodes.forEach(node => {
      let counts = 0
      if ((['input', 'const'].includes(node.data.type)) && node.data.value) {
        edges.forEach(edge => {
          if (edge.source === node.id)
            counts += 1
        })
      }
      else if (node.data.type === 'output' && node.data.value) {
        edges.forEach(edge => {
          if (edge.target === node.id)
            counts += 1
        })
      }
      else
        counts += 1

      if (counts !== 0) {
        nodesIndexes.set(node.id, [1000000000 + dopInd])
        dopInd += 1
      }

      for (let ind = 1; ind < counts; ind++, dopInd++) 
        nodesIndexes.get(node.id).push(1000000000 + dopInd)
    })

    function getNewBlockId(id) {
      const newInd = nodesIndexes.get(id)
      if (newInd.length > 1) {
        const t = nodesIndexes.get(id).splice(0, 1)[0]
        return t
      }
      else if (newInd)
        return newInd[0]

      dopInd += 1
      return 1000000000 + dopInd
    }

    xml += `        <interface>\n`

    const valuesIn = new Map()
    const valuesOut = new Map()
    nodes.forEach(node => {
      if (node.data.type === 'input' && node.data.value)
        valuesIn.set(node.data.value, node.data.metaFromType || 'ANY')
      else if (node.data.type === 'output' && node.data.value)
        valuesOut.set(node.data.value, node.data.metaFromType || 'ANY')
    })

    if (valuesIn) {
      xml += `          <inputVars>\n`
      valuesIn.forEach((type, value) => {
          xml += `            <variable name="${value}">\n`
          xml += `              <type><${getPortType(type)}/></type>\n`
          xml += `            </variable>\n`
      })
      xml += `          </inputVars>\n`
    }

    if (valuesOut) {
      xml += `          <outputVars>\n`
      valuesOut.forEach((type, value) => {
          xml += `            <variable name="${value}">\n`
          xml += `              <type><${getPortType(type)}/></type>\n`
          xml += `            </variable>\n`
      })
      xml += `          </outputVars>\n`
    }

    xml += `        </interface>\n`

    xml += `        <body>\n`
    xml += `          <FBD>\n\n`

    nodes.forEach(node => {
      const newInd = nodesIndexes.get(node.id)
      if (node && node.data.type === 'input' || node.data.type === 'const') {
        for (let i = 0; i < newInd.length; i++) {
          xml += `            <inVariable localId="${newInd[i]}" width="${node.data.width}" height="${node.data.height}">\n`
          xml += `              <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
          xml += `              <connectionPointOut/>\n`
          xml += `              <expression>${node.data.value}</expression>\n`
          xml += `            </inVariable>\n\n`
        }
      }
    })

    nodes.forEach(node => {
      const newInd = nodesIndexes.get(node.id)
      if (node.data.type === 'output') {
        for (let i = 0; i < newInd.length; i++) {
          xml += `            <outVariable localId="${newInd[i]}" width="${node.data.width}" height="${node.data.height}">\n`
          xml += `              <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
          xml += `              <connectionPointIn>\n`

          edges.forEach(edge => {
            if (edge.target === node.id) {
              const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
              const targetLabel = node.data.inputsLabels[targetIndex]
              
              if (targetLabel === 'IN') {
                const sourceNode = nodes.find(node => node.id === edge.source)
                if (['const', 'input'].includes(sourceNode.data.type))
                  xml += `                    <connection refLocalId="${getNewBlockId(edge.source)}"/>\n`
                else {
                  const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
                  const sourceLabel = sourceNode.data.outputsLabels[sourceIndex]
                  xml += `                    <connection refLocalId="${getNewBlockId(edge.source)}" formalParameter="${sourceLabel}"/>\n`
                }
              }
            }
          })
          xml += `              </connectionPointIn>\n`
          xml += `              <expression>${node.data.value}</expression>\n`
          xml += `            </outVariable>\n\n`
        }
      }
    })

    nodes.forEach(node => {
      if (!['output', 'input', 'const'].includes(node.data.type)) {
        xml += `            <block localId="${getNewBlockId(node.id)}" typeName="${node.data.type === 'switch_type' ? node.data.label.toUpperCase() : node.data.type.toUpperCase()}" width="${node.data.width}" height="${node.data.height}">\n`
        xml += `              <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
        xml += `              <inputVariables>\n`
        for (const portType of node.data.inputsLabels) {
          xml += `                <variable formalParameter="${portType}">\n`
          xml += `                  <connectionPointIn>\n`
          edges.forEach(edge => {
            if (edge.target === node.id) {
              const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
              const targetLabel = node.data.inputsLabels[targetIndex]

              if (targetLabel === portType) {
                const sourceNode = nodes.find(node => node.id === edge.source)
                if (['const', 'input'].includes(sourceNode.data.type))
                  xml += `                    <connection refLocalId="${getNewBlockId(edge.source)}"/>\n`
                else {
                  const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
                  const sourceLabel = sourceNode.data.outputsLabels[sourceIndex]
                  xml += `                    <connection refLocalId="${getNewBlockId(edge.source)}" formalParameter="${sourceLabel}"/>\n`
                }
              }
            }
          })
          xml += `                  </connectionPointIn>\n`
          xml += `                </variable>\n`
        }
        xml += `              </inputVariables>\n`
        xml += `              <inOutVariables/>\n`
        xml += `              <outputVariables>\n`
        for (const portType of node.data.outputsLabels) {
          xml += `                <variable formalParameter="${portType}">\n`
          xml += `                  <connectionPointOut/>\n`
          xml += `                </variable>\n`
        }
        xml += `              </outputVariables>\n`
        xml += `            </block>\n\n`
      }
    })

    xml += `          </FBD>\n`
    xml += `        </body>\n`
    xml += `      </pou>\n`
    xml += `    </pous>\n`
    xml += `  </types>\n`
    xml += `  <instances>\n`
    xml += `    <configurations/>\n`
    xml += `  </instances>\n`
    xml += `</project>`

    const file = new Blob([xml], {type: 'application/xml'})
    if ('showSaveFilePicker' in window) {
      try {
        const handle = await window.showSaveFilePicker({
          suggestedName: 'fbd-scheme.xml',
          types: [{
            description: 'FBD диаграмма в XML',
            accept: {'application/xml': ['.xml']}
          }]
        })

        const writable = await handle.createWritable()
        await writable.write(file)
        await writable.close()
      }
      catch (err) {
        console.log('Сохранение XML отменено или произошла ошибка:', err)
      }
    }
    else {
      const link = document.createElement('a')
      link.download = 'fbd-program.xml'
      link.href = URL.createObjectURL(file)
      link.click()
      URL.revokeObjectURL(link.href)
    }
  }, [nodes, edges])

  const handleImportXml = useCallback((event) => {
    const file = event.target.files[0]
    if (!file)
      return

    const reader = new FileReader()
    reader.onload = (e) => {
      const xmlText = e.target.result
      const parser = new DOMParser()
      const xml = parser.parseFromString(xmlText, 'application/xml')
      if (xml.querySelector('parsererror')) {
        alert('Ошибка импорта (невалидный XML-код)')
        return
      }

      const importedNodes = []
      const importedEdges = []
      const xmlIdToReactId = new Map()
      const interfaceVars = new Map() // name: { dataType, initialValue, varGroup }

      const xmlVariables = xml.querySelectorAll('interface * > variable')
      xmlVariables.forEach(v => {
        const name = v.getAttribute('name')
        const varType = v.parentElement.tagName.toLowerCase()
        const type = v.querySelector('type *')
        const value = v.querySelector('initialValue simpleValue')?.getAttribute('value') || ''
        if (name && type)
          interfaceVars.set(name, {
            dataType: type.tagName.toUpperCase(),
            value,
            varType
          })
      })

      function restoreType(recivedType) {
        const type = recivedType.toUpperCase()
        if (type === 'ANY') return 'BOOl/INT/DINT/REAL/TIME/STRING'
        if (type === 'ANY_MAGNITUDE') return 'INT/DINT/REAL/TIME'
        if (type === 'ANY_NUM') return 'INT/DINT/REAL'
        if (type === 'ANY_INT') return 'INT/DINT'
        return type
      }

      const xmlInVariables = xml.querySelectorAll('inVariable')
      xmlInVariables.forEach(inVar => {
        const xmlId = inVar.getAttribute('localId')
        const width = parseFloat(inVar.getAttribute('width') || '80')
        const height = parseFloat(inVar.getAttribute('height') || '40')
        const pos = inVar.querySelector('position')
        const x = parseFloat(pos?.getAttribute('x') || '0')
        const y = parseFloat(pos?.getAttribute('y') || '0')
        const value = inVar.querySelector('expression')?.textContent || ''

        function isItConst(recivedValue) {
          const value = recivedValue.toLowerCase().trim()
          const timeRegex = /^(t|time)#(-)?\d+(d|h|m|s|ms)(\d+(d|h|m|s|ms))*/
          const realRegex = /^-?\d+\.\d+$/
          const intRegex = /^-?\d+$/

          if (value === 'true' || value === 'false')
            return 'BOOL'
          if (timeRegex.test(value))
            return 'TIME'
          if (value.startsWith("'") && value.endsWith("'"))
            return 'STRING'
          if (realRegex.test(value))
            return 'REAL'
          if (intRegex.test(value)) {
            const num = Number(value)
            if (num >= -32768 && num <= 32767)
              return 'INT'
            if (num >= -2147483648 && num <= 2147483647)
              return 'DINT'
          }
          return null
        }

        let isConst = isItConst(value) ? true : false
        let varInfo = interfaceVars.get(value)
        const outputPortType = varInfo?.dataType || isItConst(value) || 'ANY'

        if (varInfo?.varType === 'tempvars')
          isConst = true

        const finalType = isConst ? 'const' : 'input'
        let finalValue = value
        if (varInfo?.varType === 'tempvars' && varInfo.value)
          finalValue = varInfo.value

        let existingNode = importedNodes.find(node => node.position.x === x && node.position.y === y && node.data.value === finalValue)
        if (!existingNode) {
          const newId = `${xmlId}`
          const label = finalType === 'const' ? `${finalValue} (${outputPortType})` : `${finalValue} (${outputPortType})`
          existingNode = {
            id: newId,
            type: 'fbdBlock',
            position: {x, y},
            data: {
              type: finalType, label,
              value: finalValue,
              metaFromType: outputPortType,
              width, height,
              inputs: [], 
              outputs: [restoreType(outputPortType)],
              inputsLabels: [],
              outputsLabels: ['OUT']
            }
          }
          importedNodes.push(existingNode)
        }
        xmlIdToReactId.set(xmlId, {reactId: existingNode.id, portIndex: 0, handleType: 'out'})
      })

      const xmlOutVariables = xml.querySelectorAll('outVariable')
      xmlOutVariables.forEach(outVar => {
        const xmlId = outVar.getAttribute('localId')
        const width = parseFloat(outVar.getAttribute('width') || '80')
        const height = parseFloat(outVar.getAttribute('height') || '40')
        const pos = outVar.querySelector('position')
        const x = parseFloat(pos?.getAttribute('x') || '0')
        const y = parseFloat(pos?.getAttribute('y') || '0')
        const value = outVar.querySelector('expression')?.textContent || ''
        let varInfo = interfaceVars.get(value)

        const dataType = varInfo?.dataType || 'ANY'
        let existingNode = importedNodes.find(node => node.position.x === x && node.position.y === y && node.data.value === value)
        if (!existingNode) {
          existingNode = {
            id: xmlId,
            type: 'fbdBlock',
            position: {x, y},
            data: {
              type: 'output',
              label: `${value} (${dataType})`,
              value,
              metaFromType: dataType,
              width, height,
              inputs: [restoreType(dataType)],
              outputs: [],
              inputsLabels: ['IN'],
              outputsLabels: []
            }
          }
          importedNodes.push(existingNode)
        }
        xmlIdToReactId.set(xmlId, {reactId: existingNode.id, portIndex: 0, handleType: 'in'})

        const connection = outVar.querySelector('connectionPointIn connection')
        if (connection) {
          const sourceNode = connection.getAttribute('refLocalId')
          const sourcePortLabel = connection.getAttribute('formalParameter')
          importedEdges.push({
            xmlSourceNode: sourceNode,
            xmlSourcePortLabel: sourcePortLabel,
            reactTargetId: existingNode.id,
            reactTargetPort: 'in-0'
          })
        }
      })

      const libraryBlocks = {
        and: { label: 'AND', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        or: { label: 'OR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        not: { label: 'NOT', inputs: ['BOOL'], outputs: ['BOOL'], inputsLabels: ['IN'], outputsLabels: ['OUT'] },
        xor: { label: 'XOR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        add: { label: 'ADD', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        sub: { label: 'SUB', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        mul: { label: 'MUL', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        div: { label: 'DIV', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        ton: { label: 'TON', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
        tof: { label: 'TOF', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
        tp: { label: 'TP', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
        ctu: { label: 'CTU', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'R', 'PV'], outputsLabels: ['Q', 'CV'] },
        ctd: { label: 'CTD', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CD', 'LD', 'PV'], outputsLabels: ['Q', 'CV'] },
        ctud: { label: 'CTUD', inputs: ['BOOL', 'BOOL', 'BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', 'BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'CD', 'R', 'LD', 'PV'], outputsLabels: ['QU', 'QD', 'CV'] },
        gt: { label: '>', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        lt: { label: '<', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        eq: { label: '=', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING', '$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
        sr: { label: 'SR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S1', 'R'], outputsLabels: ['Q1'] },
        rs: { label: 'RS', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S', 'R1'], outputsLabels: ['Q'] },
        move: { label: 'MOVE', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] },
        switch_type: { label: '*_TO_**', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] }
      }

      const xmlBlocks = xml.querySelectorAll('FBD > block')
      xmlBlocks.forEach(block => {
        const xmlId = block.getAttribute('localId')
        const xmlType = block.getAttribute('typeName')
        const pos = block.querySelector('position')
        const x = parseFloat(pos?.getAttribute('x') || '0')
        const y = parseFloat(pos?.getAttribute('y') || '0')
        const label = block.querySelector('content')?.textContent || libraryBlocks[xmlType.toLowerCase()]?.label || xmlType
        const value = block.querySelector('additionalData')?.getAttribute('value') || ''
        let inputsLabels = Array.from(block.querySelectorAll('inputVariables variable')).map(v => v.getAttribute('formalParameter'))
        let inputs = Array.from(block.querySelectorAll('inputVariables variable')).map(v => restoreType(v.getAttribute('dataType') || 'ANY'))
        let outputsLabels = Array.from(block.querySelectorAll('outputVariables variable')).map(v => v.getAttribute('formalParameter'))
        let outputs = Array.from(block.querySelectorAll('outputVariables variable')).map(v => restoreType(v.getAttribute('dataType') || 'ANY'))

        if (inputsLabels.length === libraryBlocks[xmlType.toLowerCase()]?.inputsLabels.length)
          inputsLabels = libraryBlocks[xmlType.toLowerCase()].inputsLabels
        if (outputsLabels.length === libraryBlocks[xmlType.toLowerCase()]?.outputsLabels.length)
          outputsLabels = libraryBlocks[xmlType.toLowerCase()].outputsLabels
        if (inputs.length === libraryBlocks[xmlType.toLowerCase()]?.inputs.length)
          inputs = libraryBlocks[xmlType.toLowerCase()].inputs
        if (outputs.length === libraryBlocks[xmlType.toLowerCase()]?.outputs.length)
          outputs = libraryBlocks[xmlType.toLowerCase()].outputs

        const isSwitchType = xmlType.includes('_TO_')
        const finalType = isSwitchType ? 'switch_type' : xmlType.toLowerCase()
        const width = parseFloat(block.getAttribute('width') || (isSwitchType ? '80' : 50 + (Math.max(inputs.length, outputs.length, 2) - 1) * 34))
        const height = parseFloat(block.getAttribute('height') || (isSwitchType ? '40' : '60'))
        const newNode = {
          id: xmlId,
          type: 'fbdBlock',
          position: {x, y},
          data: { type: finalType, label, value, width, height, inputs, outputs, inputsLabels, outputsLabels}
        }
        importedNodes.push(newNode)
        xmlIdToReactId.set(xmlId, { reactId: xmlId, inputsLabels, outputsLabels })

        const variable = block.querySelectorAll('inputVariables variable')
        variable.forEach((v, inputIndex) => {
          const connection = v.querySelector('connectionPointIn connection')
          if (connection) {
            const sourceNode = connection.getAttribute('refLocalId')
            const sourcePortLabel = connection.getAttribute('formalParameter')
            importedEdges.push({
              xmlSourceNode: sourceNode,
              xmlSourcePortLabel: sourcePortLabel,
              reactTargetId: xmlId,
              reactTargetPort: `in-${inputIndex}`
            })
          }
        })
      })

      const finalEdges = importedEdges.map((edgeData, index) => {
        const sourceMeta = xmlIdToReactId.get(edgeData.xmlSourceNode)
        if (!sourceMeta)
          return null
        let sourceHandle = 'out-0'
        if (sourceMeta.outputsLabels) {
          const outIndex = sourceMeta.outputsLabels.indexOf(edgeData.xmlSourcePortLabel)
          sourceHandle = `out-${outIndex >= 0 ? outIndex : 0}`
        }
        const sourceNode = importedNodes.find(node => node.id === sourceMeta.reactId)
        const sourceIndex = parseInt(sourceHandle.split('-')[1], 10)
        const sourceType = sourceNode?.data?.outputs[sourceIndex] || 'ANY'

        return {
          id: `e_${Date.now()}_${index}`,
          source: sourceMeta.reactId,
          target: edgeData.reactTargetId,
          sourceHandle: sourceHandle,
          targetHandle: edgeData.reactTargetPort,
          type: 'smoothstep',
          style: EDGE_STYLES[sourceType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool'],
          data: {type: sourceType.toLowerCase().split('/')[0]}
        }
      }).filter(Boolean)

      const finalNodes = importedNodes.map(node => {
        if (['add', 'sub', 'mul', 'div', 'move'].includes(node.data.type))
          return {
            ...node,
            data: {
              ...node.data,
              inputs: node.data.inputs.map(i => '$/' + i),
              outputs: node.data.outputs.map(i => '$/' + i)
            }
          }
        else if (['ctu', 'ctd', 'ctud'].includes(node.data.type))
           return {
            ...node,
            data: {
              ...node.data,
              inputs: node.data.inputs.map((i, ind) => {
                if (node.data.inputsLabels.toLowerCase() === 'pv')
                  return '$/' + i
                else return i
              }),
              outputs: node.data.outputs.map((i, ind) => {
                if (node.data.inputsLabels.toLowerCase() === 'cv')
                  return '$/' + i
                else return i
              })
            }
          }
        else if (['gt', 'lt', 'eq'].includes(node.data.type))
          return {
            ...node,
            data: {
              ...node.data,
              inputs: node.data.inputs.map(i => '$/' + i)
            }
          }
        else return node
      })

      try {
        const { currentNodes, currentEdges } = updateGraphTypes(finalEdges, finalNodes)
        setNodes(currentNodes)
        setEdges(currentEdges)
      }
      catch {
        alert('Произошла ошибка при обработке соединений')
        setEdges([])
        setNodes([finalNodes])
      }
    }
    reader.readAsText(file)
    event.target.value = ''
  }, [nodes, edges])

  return(
    <div className="App">
      <ControlPanel
        onClear = {handleClear}
        onExportSvg = {handleExportSvg}
        onExportXml = {handleExportXml}
        onImportXml = {handleImportXml}
      />
      <div className = "main-container">
        <Toolbar/>
        <div className = "main-canvas-container">
          <div className="canvas-container">
            <ReactFlow
              nodes = {nodes}
              edges = {edges}
              onNodesChange = {onNodesChange}
              onEdgesChange = {onEdgesChange}
              onConnect = {onConnect}
              nodeTypes = {nodeTypes}
              onInit = {setFlowInstance}
              onDrop = {onDrop}
              onDragOver = {onDragOver}
              isValidConnection = {isValidConnection}
              //fitView
            >
              <Controls/>
              <Background variant = {BackgroundVariant.Lines} color = "#eee" gap = {10}/>
            </ReactFlow>
          </div>
        </div>
      </div>
      <ModalData 
        modalData = {modalData} 
        onClose = {() => setModalData(null)} 
        onSave = {handleSaveModalData} 
      />
    </div>
  )
}

export default App