/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import type { Markup, Node } from '@antv/x6'
import { ref, onMounted, Ref } from 'vue'
import { Graph } from '@antv/x6'
import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
import { debounce } from 'lodash'
import { useResizeObserver } from '@vueuse/core'
import ContextMenuTool from './dag-context-menu'

interface Options {
  readonly: Ref<boolean>
  graph: Ref<Graph | undefined>
}

/**
 * Canvas Init
 * 1. Bind the graph to the dom
 * 2. Redraw when the page is resized
 * 3. Register custom graphics
 */
export function useCanvasInit(options: Options) {
  // Whether the graph can be operated
  const { readonly, graph } = options

  const paper = ref<HTMLElement>() // The graph mount HTMLElement
  const minimap = ref<HTMLElement>() // The minimap mount HTMLElement
  const container = ref<HTMLElement>() // The container of paper and minimap

  /**
   * Graph Init, bind graph to the dom
   */
  function graphInit() {
    Graph.registerNodeTool('contextmenu', ContextMenuTool, true)

    return new Graph({
      container: paper.value,
      selecting: {
        enabled: true,
        multiple: true,
        rubberband: true,
        rubberEdge: true,
        movable: true,
        showNodeSelectionBox: false
      },
      scaling: {
        min: 0.2,
        max: 2
      },
      mousewheel: {
        enabled: true,
        modifiers: ['ctrl', 'meta']
      },
      scroller: true,
      grid: {
        size: 10,
        visible: true
      },
      snapline: true,
      minimap: {
        enabled: true,
        container: minimap.value,
        scalable: true,
        width: 250,
        height: 150
      },
      interacting: {
        edgeLabelMovable: false,
        nodeMovable: !readonly.value,
        magnetConnectable: !readonly.value
      },
      connecting: {
        // Whether multiple edges can be created between the same start node and end
        allowMulti: false,
        // Whether a point is allowed to connect to a blank position on the canvas
        allowBlank: false,
        // The start node and the end node are the same node
        allowLoop: false,
        // Whether an edge is allowed to link to another edge
        allowEdge: false,
        // Whether edges are allowed to link to nodes
        allowNode: true,
        // Whether to allow edge links to ports
        allowPort: false,
        // Whether all available ports or nodes are highlighted when you drag the edge
        highlight: true,
        createEdge() {
          return graph.value?.createEdge({
            shape: X6_EDGE_NAME
          })
        },
        validateConnection(data) {
          const { sourceCell, targetCell } = data

          if (
              sourceCell &&
              targetCell &&
              sourceCell.isNode() &&
              targetCell.isNode()
          ) {
            const sourceData = sourceCell.getData()
            if (!sourceData) return true
            if (sourceData.taskType !== 'CONDITIONS') return true
            const edges = graph.value?.getConnectedEdges(sourceCell)
            if (!edges || edges.length < 2) return true
            let len = 0
            return !edges.some((edge) => {
              if (edge.getSourceCellId() === sourceCell.id) {
                len++
              }
              return len > 2
            })
          }

          return true
        },
        validateEdge({ edge }) {
          const sourceData = edge.getSourceNode()?.getData()
          const targetData = edge.getTargetNode()?.getData()
          edge?.setAttrs({
            line: {
              strokeDasharray:
                  sourceData.taskExecuteType === 'STREAM' ||
                  targetData.taskExecuteType === 'STREAM'
                      ? '5 5'
                      : 'none'
            }
          })
          return true
        }
      },
      highlighting: {
        nodeAvailable: {
          name: 'className',
          args: {
            className: 'available'
          }
        },
        magnetAvailable: {
          name: 'className',
          args: {
            className: 'available'
          }
        },
        magnetAdsorbed: {
          name: 'className',
          args: {
            className: 'adsorbed'
          }
        }
      }
    })
  }

  onMounted(() => {
    graph.value = graphInit()
    // Make sure the edge starts with node, not port
    graph.value.on('edge:connected', ({ isNew, edge }) => {
      if (isNew) {
        const sourceNode = edge.getSourceNode() as Node
        edge.setSource(sourceNode)
      }
    })

    // Add a node tool when the mouse entering
    graph.value.on('node:mouseenter', ({ node }) => {
      const nodeName = node.getData().taskName
      const markup = node.getMarkup() as Markup.JSONMarkup[]
      const fo = markup.filter((m) => m.tagName === 'foreignObject')[0]

      node.addTools({
        name: 'button',
        args: {
          markup: [
            {
              tagName: 'text',
              textContent: nodeName,
              attrs: {
                fill: '#868686',
                'font-size': 16,
                'text-anchor': 'center'
              }
            }
          ],
          x: 0,
          y: 0,
          offset: { x: 0, y: fo ? -28 : -10 }
        }
      })
    })

    // Remove all tools when the mouse leaving
    graph.value.on('node:mouseleave', ({ node }) => {
      node.removeTool('button')
    })
  })

  /**
   * Redraw when the page is resized
   */
  const resize = debounce(() => {
    if (container.value && true) {
      const w = container.value.offsetWidth
      const h = container.value.offsetHeight
      graph.value?.resize(w, h)
    }
  }, 200)
  useResizeObserver(container, resize)

  /**
   * Register custom cells
   */
  function registerCustomCells() {
    Graph.unregisterNode(X6_NODE_NAME)
    Graph.unregisterEdge(X6_EDGE_NAME)
    Graph.registerNode(X6_NODE_NAME, { ...NODE })
    Graph.registerEdge(X6_EDGE_NAME, { ...EDGE })
  }
  onMounted(() => {
    registerCustomCells()
  })

  return {
    graph,
    paper,
    minimap,
    container
  }
}
