import { type JSONObject, type KeyValue, ObjectExt } from '../../common'
import type { CellView } from '../../view'
import { markerRegistry } from '../marker'
import type { AttrDefinition, ComplexAttrs, SimpleAttrs } from './index'

function qualify(value: any) {
  return typeof value === 'string' || ObjectExt.isPlainObject(value)
}

export const sourceMarker: AttrDefinition = {
  qualify,
  set(marker: string | JSONObject, { view, attrs }) {
    return createMarker('marker-start', marker, view, attrs)
  },
}

export const targetMarker: AttrDefinition = {
  qualify,
  set(marker: string | JSONObject, { view, attrs }) {
    return createMarker('marker-end', marker, view, attrs, {
      transform: 'rotate(180)',
    })
  },
}

export const vertexMarker: AttrDefinition = {
  qualify,
  set(marker: string | JSONObject, { view, attrs }) {
    return createMarker('marker-mid', marker, view, attrs)
  },
}

function createMarker(
  type: 'marker-start' | 'marker-end' | 'marker-mid',
  marker: string | JSONObject,
  view: CellView,
  attrs: ComplexAttrs,
  manual: SimpleAttrs = {},
) {
  const def = typeof marker === 'string' ? { name: marker } : marker
  const { name, args, ...others } = def
  let preset = others

  if (name && typeof name === 'string') {
    const fn = markerRegistry.get(name)
    if (fn) {
      preset = fn({ ...others, ...(args as KeyValue) })
    } else {
      return markerRegistry.onNotFound(name)
    }
  }

  const options: any = {
    ...normalizeAttr(attrs, type),
    ...manual,
    ...preset,
  }

  return {
    [type]: `url(#${view.graph.defineMarker(options)})`,
  }
}

function normalizeAttr(
  attr: ComplexAttrs,
  type: 'marker-start' | 'marker-end' | 'marker-mid',
) {
  const result: SimpleAttrs = {}

  // The context 'fill' is disregared here. The usual case is to use the
  // marker with a connection(for which 'fill' attribute is set to 'none').
  const stroke = attr.stroke
  if (typeof stroke === 'string') {
    result.stroke = stroke
    result.fill = stroke
  }

  if (type !== 'marker-mid') {
    const strokeWidth = parseFloat(
      (attr.strokeWidth || attr['stroke-width']) as string,
    )
    if (Number.isFinite(strokeWidth) && strokeWidth > 1) {
      const offset = Math.ceil(strokeWidth / 2)
      result.refX = type === 'marker-start' ? offset : -offset
    }
  }

  return result
}
