import { Dom, ObjectExt, Util, Vector } from '../../common'
import { Config } from '../../config'
import type { EdgeView } from '../../view'
import type { SimpleAttrs } from '../attr'
import type { HighlighterDefinition } from './index'

export interface StrokeHighlighterOptions {
  padding?: number
  rx?: number
  ry?: number
  attrs?: SimpleAttrs
}

const defaultOptions: StrokeHighlighterOptions = {
  padding: 3,
  rx: 0,
  ry: 0,
  attrs: {
    'stroke-width': 3,
    stroke: '#FEB663',
  },
}

export const stroke: HighlighterDefinition<StrokeHighlighterOptions> = {
  highlight(cellView, magnet, options) {
    const id = getHighlighterId(magnet, options)
    if (hasCache(id)) {
      return
    }

    // eslint-disable-next-line
    options = ObjectExt.defaultsDeep({}, options, defaultOptions)

    const magnetVel = Vector.create(magnet as SVGElement)
    let pathData
    let magnetBBox

    try {
      pathData = magnetVel.toPathData()
    } catch (error) {
      // Failed to get path data from magnet element.
      // Draw a rectangle around the entire cell view instead.
      magnetBBox = Util.bbox(magnetVel.node, true)
      pathData = Dom.rectToPathData({ ...options, ...magnetBBox })
    }

    const path = Dom.createSvgElement('path')
    Dom.attr(path, {
      d: pathData,
      'pointer-events': 'none',
      'vector-effect': 'non-scaling-stroke',
      fill: 'none',
      ...(options.attrs ? Dom.kebablizeAttrs(options.attrs) : null),
    })

    // const highlightVel = v.create('path').attr()

    if (cellView.isEdgeElement(magnet)) {
      Dom.attr(path, 'd', (cellView as EdgeView).getConnectionPathData())
    } else {
      let highlightMatrix = magnetVel.getTransformToElement(
        cellView.container as SVGElement,
      )

      // Add padding to the highlight element.
      const padding = options.padding
      if (padding) {
        if (magnetBBox == null) {
          magnetBBox = Util.bbox(magnetVel.node, true)
        }

        const cx = magnetBBox.x + magnetBBox.width / 2
        const cy = magnetBBox.y + magnetBBox.height / 2

        magnetBBox = Util.transformRectangle(magnetBBox, highlightMatrix)

        const width = Math.max(magnetBBox.width, 1)
        const height = Math.max(magnetBBox.height, 1)
        const sx = (width + padding) / width
        const sy = (height + padding) / height

        const paddingMatrix = Dom.createSVGMatrix({
          a: sx,
          b: 0,
          c: 0,
          d: sy,
          e: cx - sx * cx,
          f: cy - sy * cy,
        })

        highlightMatrix = highlightMatrix.multiply(paddingMatrix)
      }

      Dom.transform(path, highlightMatrix)
    }

    Dom.addClass(path, Config.prefix('highlight-stroke'))

    const cell = cellView.cell
    const removeHandler = () => removeHighlighter(id)

    cell.on('removed', removeHandler)
    if (cell.model) {
      cell.model.on('reseted', removeHandler)
    }

    cellView.container.appendChild(path)
    setCache(id, path)
  },

  unhighlight(cellView, magnet, opt) {
    removeHighlighter(getHighlighterId(magnet, opt))
  },
}

function getHighlighterId(magnet: Element, options: StrokeHighlighterOptions) {
  Dom.ensureId(magnet)
  return magnet.id + JSON.stringify(options)
}

const cache: { [id: string]: Element } = {}

function setCache(id: string, elem: Element) {
  cache[id] = elem
}

function hasCache(id: string) {
  return cache[id] != null
}

function removeHighlighter(id: string) {
  const elem = cache[id]
  if (elem) {
    Dom.remove(elem)
    delete cache[id]
  }
}
