import React, { useMemo } from 'react'
import { useAtom } from 'jotai'
import { max, sum } from 'd3-array'
import {
  useGG,
  tooltipState,
  TooltipContent,
  XTooltip,
  YTooltip,
  Aes,
  themeState,
  TooltipProps,
} from '@graphique/graphique'
import { DefaultTooltip } from './DefaultTooltip'
import { FocusType, Position } from '../types'

interface StackMidpoint {
  groupVal: string
  yVal: string | number
  xVal: number
}

interface Props<Datum> {
  x: (d: Datum) => number | undefined
  y: (d: Datum) => number | undefined
  yAdj?: number
  aes?: Aes<Datum>
  focusType: FocusType
  stackMidpoints?: StackMidpoint[]
  position: Position
}

export const Tooltip = <Datum,>({
  x,
  y,
  yAdj = 0,
  aes,
  focusType,
  stackMidpoints,
  position,
}: Props<Datum>) => {
  const { ggState } = useGG<Datum>() || {}
  const { id, scales, copiedScales, height } = ggState || {
    height: 0,
  }

  const [{ datum, xFormat, yFormat, content }] =
    useAtom<TooltipProps<Datum>>(tooltipState)

  const [{ geoms }] = useAtom(themeState)

  const yVal = useMemo(
    () => datum && datum[0] && aes?.y && aes.y(datum[0]),
    [datum, aes],
  )

  const yCoord = useMemo(
    () =>
      (datum?.[0] &&
        (y(datum?.[0]) ?? 0) + (focusType === FocusType.GROUP ? yAdj : 0)) ||
      0,
    [yAdj, datum, y, focusType],
  )

  const group = useMemo(
    () => scales?.groupAccessor || (() => '__group'),
    [scales],
  )

  const hasGroup = useMemo(() => {
    const datumGroup = datum?.length && group(datum?.[0])?.toString()
    return datumGroup && scales?.groups?.includes(datumGroup)
  }, [scales?.groups, group, datum])

  const xVal = useMemo(() => {
    if (
      focusType === FocusType.INDIVIDUAL &&
      stackMidpoints &&
      datum &&
      datum?.length
    ) {
      const datumGroup = group(datum[0])
      const focusedStack = stackMidpoints.find(
        ({ yVal: stackY, groupVal }) =>
          stackY === yVal && groupVal === datumGroup,
      )

      return focusedStack ? scales?.xScale(focusedStack.xVal) : undefined
    }

    if (position === Position.FILL) return scales?.xScale(1)
    if (
      position === Position.STACK &&
      datum &&
      scales?.groups &&
      aes?.x &&
      scales.xScale
    ) {
      return scales.xScale(sum(datum.map((d) => aes.x(d) as number)))
    }
    return datum ? max(datum, (d) => x(d)) : undefined
  }, [datum, scales, geoms, aes, focusType, stackMidpoints, yVal, group])

  const fillDomain = copiedScales?.fillScale?.domain()

  const groupVals = useMemo(() => {
    const vals =
      datum &&
      datum
        .filter((d) => aes?.x(d))
        .sort(
          (a, b) =>
            fillDomain!.indexOf(String(group(a))) -
            fillDomain!.indexOf(String(group(b))),
        )
        .map((md) => {
          const thisGroup = group(md)

          const mark = (
            <svg width={12} height={12}>
              <rect
                transform="translate(1, 1)"
                width={12}
                height={12}
                fill={
                  geoms?.bar?.fill ||
                  (copiedScales?.fillScale
                    ? copiedScales.fillScale(thisGroup)
                    : 'none')
                }
                stroke={
                  geoms?.bar?.stroke ||
                  (copiedScales?.strokeScale
                    ? copiedScales.strokeScale(thisGroup)
                    : 'none')
                }
                strokeDasharray={
                  geoms?.bar?.strokeDasharray ||
                  (copiedScales?.strokeDasharrayScale
                    ? copiedScales.strokeDasharrayScale(thisGroup)
                    : undefined)
                }
                strokeWidth={geoms?.bar?.strokeWidth}
                fillOpacity={geoms?.bar?.fillOpacity}
                strokeOpacity={geoms?.bar?.strokeOpacity}
              />
            </svg>
          )
          return {
            datum,
            group: thisGroup,
            mark,
            x: xVal,
            y: yVal,
            formattedX: xFormat ? xFormat(aes?.x(md)) : aes?.x(md),
            formattedY: yFormat ? yFormat(yVal) : String(yVal),
          }
        })
    return vals as TooltipContent<Datum>[]
  }, [
    datum,
    yVal,
    group,
    x,
    aes,
    yFormat,
    xFormat,
    copiedScales,
    geoms,
    scales,
  ])

  const tooltipValue = content
    ? datum && <div>{content(groupVals)}</div>
    : datum && <DefaultTooltip data={groupVals} />

  if (datum && groupVals[0] && focusType === FocusType.INDIVIDUAL && hasGroup)
    return (
      <div>
        <XTooltip
          id={id as string}
          left={groupVals[0].x ?? 0}
          top={-(height - yCoord)}
          value={tooltipValue}
          yPosition="above"
          align={position === Position.DODGE ? 'left' : 'center'}
        />
      </div>
    )

  return datum && groupVals[0] ? (
    <div>
      <YTooltip
        id={id as string}
        left={groupVals[0].x ?? 0}
        top={-(height - yCoord)}
        value={tooltipValue}
        wait
      />
    </div>
  ) : null
}
