// 提取自 https://github.com/fnando/sparkline

function getY(max: number, height: number, diff: number, value: number) {
  return parseFloat((height - (value * height / max) + diff).toFixed(2))
}

function removeChildren(svg: any) {
  [...svg.querySelectorAll('*')].forEach(element => svg.removeChild(element))
}

function defaultFetch(entry: any) {
  return entry.value
}

function buildElement(tag: string, attrs: Record<string, any>) {
  const element = document.createElementNS('http://www.w3.org/2000/svg', tag)

  for (const name in attrs) {
    element.setAttribute(name, attrs[name])
  }

  return element
}

export function sparkline(svg: any, entries: any, options: any) {
  removeChildren(svg)

  if (entries.length <= 1) {
    return
  }

  options = options || {}

  if (typeof (entries[0]) === 'number') {
    entries = entries.map((entry: any) => {
      return { value: entry }
    })
  }

  // This function will be called whenever the mouse moves
  // over the SVG. You can use it to render something like a
  // tooltip.
  const onmousemove = options.onmousemove

  // This function will be called whenever the mouse leaves
  // the SVG area. You can use it to hide the tooltip.
  const onmouseout = options.onmouseout

  // Should we run in interactive mode? If yes, this will handle the
  // cursor and spot position when moving the mouse.
  const interactive = ('interactive' in options) ? options.interactive : !!onmousemove

  // Define how big should be the spot area.
  const spotRadius = options.spotRadius || 2
  const spotDiameter = spotRadius * 2

  // Define how wide should be the cursor area.
  const cursorWidth = options.cursorWidth || 2

  // Get the stroke width; this is used to compute the
  // rendering offset.
  const strokeWidth = parseFloat(svg.attributes['stroke-width'].value)

  // By default, data must be formatted as an array of numbers or
  // an array of objects with the value key (like `[{value: 1}]`).
  // You can set a custom function to return data for a different
  // data structure.
  const fetch = options.fetch || defaultFetch

  // Retrieve only values, easing the find for the maximum value.
  const values = entries.map((entry: any) => fetch(entry))

  // The rendering width will account for the spot size.
  const width = parseFloat(svg.attributes.width.value) - spotDiameter * 2

  // Get the SVG element's full height.
  // This is used
  const fullHeight = parseFloat(svg.attributes.height.value)

  // The rendering height accounts for stroke width and spot size.
  const height = fullHeight - (strokeWidth * 2) - spotDiameter

  // The maximum value. This is used to calculate the Y coord of
  // each sparkline datapoint.
  const max = Math.max(...values)

  // Some arbitrary value to remove the cursor and spot out of
  // the viewing canvas.
  const offscreen = -1000

  // Cache the last item index.
  const lastItemIndex = values.length - 1

  // Calculate the X coord base step.
  const offset = width / lastItemIndex

  // Hold all datapoints, which is whatever we got as the entry plus
  // x/y coords and the index.
  const datapoints: any[] = []

  // Hold the line coordinates.
  const pathY = getY(max, height, strokeWidth + spotRadius, values[0])
  let pathCoords = `M${spotDiameter} ${pathY}`

  values.forEach((value: number, index: number) => {
    const x = index * offset + spotDiameter
    const y = getY(max, height, strokeWidth + spotRadius, value)

    datapoints.push(Object.assign({}, entries[index], {
      index,
      x,
      y,
    }))

    pathCoords += ` L ${x} ${y}`
  })

  const path = buildElement('path', {
    class: 'sparkline--line',
    d: pathCoords,
    fill: 'none',
  })

  const fillCoords = `${pathCoords} V ${fullHeight} L ${spotDiameter} ${fullHeight} Z`

  const fill = buildElement('path', {
    class: 'sparkline--fill',
    d: fillCoords,
    stroke: 'none',
  })

  svg.appendChild(fill)
  svg.appendChild(path)

  if (!interactive) {
    return
  }

  const cursor = buildElement('line', {
    'class': 'sparkline--cursor',
    'x1': offscreen,
    'x2': offscreen,
    'y1': 0,
    'y2': fullHeight,
    'stroke-width': cursorWidth,
  })

  const spot = buildElement('circle', {
    class: 'sparkline--spot',
    cx: offscreen,
    cy: offscreen,
    r: spotRadius,
  })

  svg.appendChild(cursor)
  svg.appendChild(spot)

  const interactionLayer = buildElement('rect', {
    width: svg.attributes.width.value,
    height: svg.attributes.height.value,
    style: 'fill: transparent; stroke: transparent',
    class: 'sparkline--interaction-layer',
  })
  svg.appendChild(interactionLayer)

  interactionLayer.addEventListener('mouseout', (event: MouseEvent) => {
    cursor.setAttribute('x1', offscreen.toString())
    cursor.setAttribute('x2', offscreen.toString())

    spot.setAttribute('cx', offscreen.toString())

    if (onmouseout) {
      onmouseout(event)
    }
  })

  interactionLayer.addEventListener('mousemove', (event: MouseEvent) => {
    const mouseX = event.offsetX

    let nextDataPoint = datapoints.find((entry) => {
      return entry.x >= mouseX
    })

    if (!nextDataPoint) {
      nextDataPoint = datapoints[lastItemIndex]
    }

    const previousDataPoint = datapoints[datapoints.indexOf(nextDataPoint) - 1]
    let currentDataPoint
    let halfway

    if (previousDataPoint) {
      halfway = previousDataPoint.x + ((nextDataPoint.x - previousDataPoint.x) / 2)
      currentDataPoint = mouseX >= halfway ? nextDataPoint : previousDataPoint
    }
    else {
      currentDataPoint = nextDataPoint
    }

    const x = currentDataPoint.x
    const y = currentDataPoint.y

    spot.setAttribute('cx', x)
    spot.setAttribute('cy', y)

    cursor.setAttribute('x1', x)
    cursor.setAttribute('x2', x)

    if (onmousemove) {
      onmousemove(event, currentDataPoint)
    }
  })
}

export default sparkline
