import * as React from 'react'
import * as Recharts from 'recharts'

import { cn } from '@lib/utils'

import { type ChartConfig, ChartContainer } from './base'
import { type ValidDataKeysOf } from './util'

export type LineChartProps<
  T extends object,
  XKey extends keyof T & string,
  YKey extends keyof T & string,
  // These parameters are never provided, we're using the defaults as way of
  // storing the generic types for use in the rest of the signature.
  K extends string & keyof T = ValidDataKeysOf<T, number>,
> = {
  data: T[]
  plots: K[]
  x?: XKey | AxisConfig<T, XKey>
  y?: YKey | AxisConfig<T, YKey>
  config: ChartConfig<K, { type: 'linear' | 'natural' | 'step'; dot?: boolean }>
  /** When `true`, the chart will not be announced by screen readers or given
   * accessibility enhancements for keyboard navigation. This is intended for
   * decorative charts that do not convey information to users or for charts that
   * are only intended for sighted users.
   */
  decorative?: boolean
  /** You can pass additional children to the chart. This can be used to render
   * things like a tooltip or grid lines.
   */
  children?: React.ReactNode
} & LineChartEvents<T> &
  React.ComponentProps<'div'>

export type LineChartEvents<T extends object> = {
  onDatapointClick?: (data: [T, ...T[]]) => void
}

export type AxisConfig<T extends object, K extends string & keyof T> = {
  key: K
  formatter?: (value: T[K]) => string
  ticks?: boolean
  divider?: boolean
}

export function LineChart<T extends object, XKey extends keyof T & string, YKey extends keyof T & string>({
  data,
  plots,
  x,
  y,
  config,
  //
  onDatapointClick,
  //
  className,
  children,
  ...delegated
}: LineChartProps<T, XKey, YKey>) {
  return (
    <ChartContainer config={config} className={cn('max-w-full', className)} {...delegated}>
      <Recharts.LineChart
        onClick={
          onDatapointClick &&
          ((data) => {
            if (data.activePayload && data.activePayload.length > 0) {
              // A this point, we know more about the payload than TypeScript does.
              // We've already confirmed the array has at least one element, and
              // we already know the payloads will be `T` so we can safely cast
              // this map to `[T, ...T[]]`.
              const datapoints = data.activePayload.map(({ payload }) => payload) as [T, ...T[]]
              onDatapointClick(datapoints)
            }
          })
        }
        accessibilityLayer
        data={data}
        margin={{
          left: 12,
          right: 12,
        }}
      >
        <Recharts.CartesianGrid vertical={false} />
        {x && (
          <Recharts.XAxis
            {...(typeof x === 'string'
              ? { dataKey: x }
              : {
                  dataKey: x.key,
                  tickLine: x.ticks,
                  axisLine: x.divider,
                  tickFormatter: x.formatter,
                })}
          />
        )}
        {y && (
          <Recharts.YAxis
            {...(typeof y === 'string'
              ? { dataKey: y }
              : {
                  dataKey: y.key,
                  tickLine: y.ticks,
                  axisLine: y.divider,
                  tickFormatter: y.formatter,
                })}
          />
        )}
        {children}
        {plots.map((key) => (
          <Recharts.Line
            key={key}
            dataKey={key}
            type={config[key].type}
            stroke={`var(--color-${key})`}
            strokeWidth={2}
            dot={config[key].dot ?? false}
          />
        ))}
      </Recharts.LineChart>
    </ChartContainer>
  )
}
