import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import { Color, colors } from '../../utils/colors.js'
import { measureTextWidth, Text, TEXT_VARIANT_STYLES } from '../Text/index.js'
import { getChartColorForString } from '../../utils/misc.js'
import { useFloating } from '@floating-ui/react-dom'
import { autoUpdate, offset, shift } from '@floating-ui/dom'
import { shadows } from '../../utils/shadows.js'
import { radius } from '../../utils/radius.js'
import { generatePath, linearScale } from '../../utils/charts.js'
import { Legend } from '../Legend/index.js'
import { FloatingPortal } from '@floating-ui/react'

type LineChartProps = {
  data: Record<string, number>[]
  xKey: string
  yKeys: string[]
  xFormat?: (x: number) => string
  yFormat?: (y: number) => string
  showLegend?: boolean
}

function LineChart({
  data,
  xKey,
  yKeys,
  xFormat,
  yFormat,
  showLegend = true,
}: LineChartProps) {
  const svgRef = useRef<SVGSVGElement>(null)
  const [svgRect, setSVGRect] = useState<DOMRect>(null)
  const [mouseX, setMouseX] = useState<null | number>(null)
  const [minX, maxX, minY, maxY] = useMemo(() => {
    let minX = Number.POSITIVE_INFINITY
    let maxX = Number.NEGATIVE_INFINITY
    let minY = Number.POSITIVE_INFINITY
    let maxY = Number.NEGATIVE_INFINITY

    for (const item of data) {
      if (item[xKey] < minX) minX = item[xKey]
      if (item[xKey] > maxX) maxX = item[xKey]

      for (const yKey of yKeys) {
        if (item[yKey] < minY) minY = item[yKey]
        if (item[yKey] > maxY) maxY = item[yKey]
      }
    }

    return [minX, maxX, minY, maxY]
  }, [xKey, yKeys, data])
  const [paddingTop, paddingRight, paddingBottom, paddingLeft] = useMemo(() => {
    const largestLabelCharacterCount = yFormat(maxY).length
    let paddingRight =
      12 +
      largestLabelCharacterCount * measureTextWidth('0', 'subtext-mono') +
      12

    return [4.5, paddingRight, 4.5 + 7.25 + 6, 12]
  }, [data, maxY, yFormat])
  const xScale = useMemo(() => {
    if (!svgRect) return null

    return linearScale(
      [minX, maxX],
      [paddingLeft, svgRect.width - paddingRight],
    )
  }, [minX, maxX, paddingLeft, svgRect, paddingRight])
  const yScale = useMemo(() => {
    if (!svgRect) return null

    return linearScale(
      [minY, maxY],
      [svgRect.height - paddingBottom, paddingTop],
    )
  }, [minY, maxY, paddingTop, svgRect, paddingBottom])
  const points = useMemo(() => {
    if (!xScale || !yScale) return {}
    const res: Record<string, { x: number; y: number }[]> = {}

    for (const yKey of yKeys) {
      res[yKey] = data.map((item) => ({
        x: xScale(item[xKey]),
        y: yScale(item[yKey]),
      }))
    }

    return res
  }, [data, yKeys, xScale, yScale])
  const hover = useMemo(() => {
    if (mouseX === null || !xScale) return null

    const xPoints = data.map((item) => xScale(item[xKey]))

    let closestIndex = 0
    let minDistance = Math.abs(mouseX - xPoints[0])

    for (let i = 1; i < xPoints.length; i++) {
      const distance = Math.abs(mouseX - xPoints[i])
      if (distance < minDistance) {
        minDistance = distance
        closestIndex = i
      }
    }

    return closestIndex
  }, [mouseX, data, xKey, xScale])
  const hoverMinYKey = useMemo(() => {
    if (hover === null) return ''
    let smallestValue = Number.POSITIVE_INFINITY
    let smallestValueYKey
    for (const yKey of yKeys) {
      if (data[hover][yKey] < smallestValue) {
        smallestValue = data[hover][yKey]
        smallestValueYKey = yKey
      }
    }

    return smallestValueYKey
  }, [data, hover])

  const { refs, floatingStyles } = useFloating({
    open: hover !== null,
    strategy: 'fixed',
    placement: 'bottom',
    middleware: [offset(8), shift({ padding: 8 })],
    whileElementsMounted: autoUpdate,
  })

  useLayoutEffect(() => {
    const ref = svgRef.current
    if (!ref) return () => {}

    function calculate() {
      setSVGRect(ref.getBoundingClientRect())
    }

    calculate()

    const resizeObserverInstance = new window.ResizeObserver(calculate)
    resizeObserverInstance.observe(ref)

    return () => {
      resizeObserverInstance.unobserve(ref)
    }
  }, [])

  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        display: 'flex',
        flexDirection: 'column',
        gap: 16,
      }}
    >
      <svg
        ref={svgRef}
        style={{
          width: '100%',
          height: '100%',
          background: colors.neutralInverted100,
        }}
        onMouseMove={(e) => {
          const mouseX = e.clientX - svgRect.left

          setMouseX(mouseX)
        }}
        onMouseLeave={() => {
          setMouseX(null)
        }}
      >
        {svgRect && (
          <>
            <g>
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={0}
                y1={4.5}
                x2={svgRect.width - paddingRight + 11}
                y2={4.5}
              />
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={svgRect.width - 9}
                y1={4.5}
                x2={svgRect.width}
                y2={4.5}
              />
              <text
                x={svgRect.width - 12}
                y={9}
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {yFormat(maxY)}
              </text>
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={0}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.25 -
                  1
                }
                x2={svgRect.width - paddingRight + 11}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.25 -
                  1
                }
              />
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={svgRect.width - 9}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.25 -
                  1
                }
                x2={svgRect.width}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.25 -
                  1
                }
              />
              <text
                x={svgRect.width - 12}
                y={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.25 +
                  3
                }
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {yFormat(minY + (maxY - minY) * 0.75)}
              </text>
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={0}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.5 -
                  1
                }
                x2={svgRect.width - paddingRight + 11}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.5 -
                  1
                }
              />
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={svgRect.width - 9}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.5 -
                  1
                }
                x2={svgRect.width}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.5 -
                  1
                }
              />
              <text
                x={svgRect.width - 12}
                y={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.5 +
                  3
                }
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {yFormat(minY + (maxY - minY) * 0.5)}
              </text>
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={0}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.75 -
                  1
                }
                x2={svgRect.width - paddingRight + 11}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.75 -
                  1
                }
              />
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={svgRect.width - 9}
                y1={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.75 -
                  1
                }
                x2={svgRect.width}
                y2={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.75 -
                  1
                }
              />
              <text
                x={svgRect.width - 12}
                y={
                  paddingTop +
                  (svgRect.height - paddingTop - paddingBottom) * 0.75 +
                  3
                }
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {yFormat(minY + (maxY - minY) * 0.25)}
              </text>
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={0}
                y1={svgRect.height - 4.5 - 7.25 - 6}
                x2={svgRect.width - paddingRight + 11}
                y2={svgRect.height - 4.5 - 7.25 - 6}
              />
              <line
                style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                x1={svgRect.width - 9}
                y1={svgRect.height - 4.5 - 7.25 - 6}
                x2={svgRect.width}
                y2={svgRect.height - 4.5 - 7.25 - 6}
              />
              <text
                x={svgRect.width - 12}
                y={svgRect.height - 0.5 - 7.25 - 6}
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {yFormat(minY)}
              </text>
            </g>

            <g>
              <text
                x={paddingLeft}
                y={svgRect.height - 0.5}
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                }}
              >
                {xFormat(minX)}
              </text>
              {svgRect.width > 500 && (
                <>
                  <text
                    x={(svgRect.width - paddingRight) * 0.25}
                    y={svgRect.height - 0.5}
                    style={{
                      fill: colors.neutral20,
                      pointerEvents: 'none',
                      userSelect: 'none',
                      textRendering: 'optimizeLegibility',
                      WebkitFontSmoothing: 'antialiased',
                      MozOsxFontSmoothing: 'grayscale',
                      WebkitTextSizeAdjust: '100%',
                      ...TEXT_VARIANT_STYLES['subtext-mono'],
                      textAnchor: 'middle',
                    }}
                  >
                    {xFormat(minX + (maxX - minX) * 0.25)}
                  </text>
                  <text
                    x={(svgRect.width - paddingRight) * 0.5}
                    y={svgRect.height - 0.5}
                    style={{
                      fill: colors.neutral20,
                      pointerEvents: 'none',
                      userSelect: 'none',
                      textRendering: 'optimizeLegibility',
                      WebkitFontSmoothing: 'antialiased',
                      MozOsxFontSmoothing: 'grayscale',
                      WebkitTextSizeAdjust: '100%',
                      ...TEXT_VARIANT_STYLES['subtext-mono'],
                      textAnchor: 'middle',
                    }}
                  >
                    {xFormat(minX + (maxX - minX) * 0.5)}
                  </text>
                  <text
                    x={(svgRect.width - paddingRight) * 0.75}
                    y={svgRect.height - 0.5}
                    style={{
                      fill: colors.neutral20,
                      pointerEvents: 'none',
                      userSelect: 'none',
                      textRendering: 'optimizeLegibility',
                      WebkitFontSmoothing: 'antialiased',
                      MozOsxFontSmoothing: 'grayscale',
                      WebkitTextSizeAdjust: '100%',
                      ...TEXT_VARIANT_STYLES['subtext-mono'],
                      textAnchor: 'middle',
                    }}
                  >
                    {xFormat(minX + (maxX - minX) * 0.75)}
                  </text>
                </>
              )}
              <text
                x={svgRect.width - paddingRight}
                y={svgRect.height - 0.5}
                style={{
                  fill: colors.neutral20,
                  pointerEvents: 'none',
                  userSelect: 'none',
                  textRendering: 'optimizeLegibility',
                  WebkitFontSmoothing: 'antialiased',
                  MozOsxFontSmoothing: 'grayscale',
                  WebkitTextSizeAdjust: '100%',
                  ...TEXT_VARIANT_STYLES['subtext-mono'],
                  textAnchor: 'end',
                }}
              >
                {xFormat(maxX)}
              </text>
            </g>

            <g>
              {yKeys.map((yKey) => (
                <>
                  <path
                    key={yKey + 'bg'}
                    d={generatePath(points[yKey])}
                    fill="none"
                    stroke={colors.neutralInverted100}
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                  <path
                    key={yKey}
                    d={generatePath(points[yKey])}
                    fill="none"
                    stroke={colors[getChartColorForString(yKey, false)]}
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </>
              ))}
            </g>

            {hover != null &&
              yKeys.map((yKey, index) => {
                return (
                  <g key={yKey + hover}>
                    {index === 0 && (
                      <line
                        style={{ stroke: colors.neutral10, strokeWidth: 1 }}
                        x1={points[yKey][hover].x}
                        y1={5}
                        x2={points[yKey][hover].x}
                        y2={svgRect.height - 5 - 7.25 - 6}
                      />
                    )}
                    <circle
                      r="4"
                      style={{
                        fill: colors.neutralInverted100,
                        transformOrigin: '0px 0px',
                        transform: `translate(${points[yKey][hover].x}px, ${points[yKey][hover].y}px)`,
                      }}
                    />
                    <circle
                      ref={
                        yKey === hoverMinYKey ? refs.setReference : undefined
                      }
                      r="4"
                      style={{
                        fill: colors[getChartColorForString(yKey, false)],
                        transformOrigin: '0px 0px',
                        transform: `translate(${points[yKey][hover].x}px, ${points[yKey][hover].y}px)`,
                      }}
                    />
                    <circle
                      r="2"
                      style={{
                        fill: colors.neutralInverted100,
                        transformOrigin: '0px 0px',
                        transform: `translate(${points[yKey][hover].x}px, ${points[yKey][hover].y}px)`,
                      }}
                    />
                  </g>
                )
              })}
            {hover == null &&
              yKeys.map((yKey) => (
                <>
                  <circle
                    r="4"
                    style={{
                      fill: colors.neutralInverted100,
                      transformOrigin: '0px 0px',
                      transform: `translate(${points[yKey][points[yKey].length - 1].x}px, ${points[yKey][points[yKey].length - 1].y}px)`,
                    }}
                  />
                  <circle
                    r="4"
                    style={{
                      fill: colors[getChartColorForString(yKey, false)],
                      transformOrigin: '0px 0px',
                      transform: `translate(${points[yKey][points[yKey].length - 1].x}px, ${points[yKey][points[yKey].length - 1].y}px)`,
                    }}
                  />
                  <circle
                    r="2"
                    style={{
                      fill: colors.neutralInverted100,
                      transformOrigin: '0px 0px',
                      transform: `translate(${points[yKey][points[yKey].length - 1].x}px, ${points[yKey][points[yKey].length - 1].y}px)`,
                    }}
                  />
                </>
              ))}
          </>
        )}
      </svg>
      {hover !== null && (
        <FloatingPortal>
          <div
            ref={refs.setFloating}
            style={{
              width: 160,
              pointerEvents: 'none',
              background: colors.neutralInverted100,
              border: `1px solid  ${colors.neutral20Adjusted}`,
              boxShadow: shadows.popoverSmall,
              display: 'flex',
              flexDirection: 'column',
              borderRadius: radius[8],
              padding: '6px 10px',
              gap: 4,
              ...floatingStyles,
            }}
          >
            <Text singleLine variant="subtext-medium" color="neutral60">
              {xFormat(data[hover][xKey])}
            </Text>
            {yKeys.map((yKey) => (
              <div style={{ display: 'flex', gap: 4, width: '100%' }}>
                <div
                  style={{
                    height: 12,
                    width: 4,
                    background: colors[getChartColorForString(yKey, false)],
                    borderRadius: radius[4],
                  }}
                />
                <div style={{ flexShrink: 0, marginRight: 'auto' }}>
                  <Text
                    singleLine
                    variant="subtext-medium"
                    color={getChartColorForString(yKey, false)}
                  >
                    {yKey}
                  </Text>
                </div>
                <Text singleLine variant="subtext-mono" color="neutral100">
                  {yFormat(data[hover][yKey])}
                </Text>
              </div>
            ))}
          </div>
        </FloatingPortal>
      )}
      {showLegend && (
        <div
          style={{
            display: 'flex',
            gap: 16,
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          {yKeys.map((yKey) => (
            <Legend
              key={yKey}
              label={yKey}
              color={getChartColorForString(yKey, false)}
            />
          ))}
        </div>
      )}
    </div>
  )
}

export type { LineChartProps }
export { LineChart }
