import { memo } from 'react';
import { AnyD3Scale, scaleBand, scaleLinear } from '@visx/scale';
import { Group } from '@visx/group';
import { Area, LinePath } from '@visx/shape';

import { useBounds } from '../../hooks/useBounds';
import { useRefreshKey } from '../../hooks/useRefreshKey';

import { Axes, AxesSettings } from '../../common/Axes';
import { Grid } from '../../common/Grid';
import { MouseMoveHandler } from '../../common/MouseMoveHandler';

import { max } from '../../utils';
import { COLORS, curveFactory } from '../../config';
import { BackgroundSettings } from '../../types';

import {
  ChartData,
  ChartNestedData,
  groupSelector,
  itemsSelector,
  labelSelector,
  valueSelector,
} from '../../utils/data';

export type LineChartSVGProps = AxesSettings &
  BackgroundSettings & {
    data: ChartNestedData[];
    width: number;
    height: number;
    maxValue?: number;
    curve?: string;
    colors?: string[];
    fillOpacity?: number;
    strokeWidth?: number;
    onHover?: any;
  };

export const defaultProps = {
  curve: 'linear',
  colors: COLORS,
  fillArea: true,
  fillOpacity: 0,
  strokeWidth: 2,
  showGrid: true,
  showLeftAxis: true,
  showRightAxis: false,
  showBottomAxis: true,
};

type GraphProps = {
  id: string | number;
  data: ChartData[];
  xScale: AnyD3Scale;
  yScale: AnyD3Scale;
  valueSelector: any;
  color: string;
  curve: string;
  fillOpacity?: number;
  strokeWidth?: number;
};

const Graph = ({
  id,
  data,
  xScale,
  yScale,
  valueSelector,
  color,
  curve,
  fillOpacity,
  strokeWidth,
}: GraphProps) => {
  return (
    <g>
      {fillOpacity ? (
        <g>
          <defs>
            <linearGradient
              id={`gradient_${id}`}
              x1="0%"
              y1="0%"
              x2="0%"
              y2="100%"
            >
              <stop offset="0%" stopColor={color} stopOpacity={1} />
              <stop offset="100%" stopColor={color} stopOpacity={0} />
            </linearGradient>
          </defs>
          <Area
            // className
            data={data}
            // defined
            x={(d, i) => xScale(i)}
            y0={(d) => yScale.range()[0]}
            y1={(d) => yScale(valueSelector(d))}
            fill={`url(#gradient_${id})`}
            opacity={fillOpacity}
            curve={curveFactory(curve)}
          />
        </g>
      ) : null}
      <LinePath
        // className
        data={data}
        x={(d, i) => xScale(i)}
        y={(d) => yScale(valueSelector(d))}
        stroke={color || '#000'}
        strokeWidth={strokeWidth}
        curve={curveFactory(curve)}
      />
    </g>
  );
};

function LineChartSVG({
  data,
  width,
  height,
  maxValue,
  curve,
  colors,
  fillOpacity,
  strokeWidth,
  background,
  showGrid,
  gridColor,
  textColor,
  axisColor,
  showLeftAxis,
  axisLeftAngle,
  labelLeft,
  showRightAxis,
  axisRightAngle,
  labelRight,
  showBottomAxis,
  axisBottomAngle,
  labelBottom,
  axisLeftProps,
  axisRightProps,
  axisBottomProps,
  onHover,
}: LineChartSVGProps & typeof defaultProps) {
  //TODO temp fix for axes bounds recalculation
  const refreshKey = useRefreshKey([data]);

  const [bounds, updateBounds] = useBounds(width, height, {
    top: 16,
    left: 0,
    right: 0,
    bottom: 0,
  });

  const [chartWidth, chartHeight, margin] = bounds;

  const chartData = data.map(itemsSelector);
  const domainMax =
    maxValue !== undefined
      ? maxValue
      : max(chartData, (d) => max(d, valueSelector));

  const basis = chartData[0];

  const dataPoints = new Array(basis.length).fill([]).map((arr, index) => {
    return data
      .map((line, lineIndex) => {
        const datum = itemsSelector(line)[index];
        return {
          ...datum,
          id: line.id,
          label: groupSelector(line),
          color: colors[lineIndex],
        };
      })
      .flat();
  });

  // scales
  const xScale = scaleLinear({
    range: [0, chartWidth],
    domain: [0, basis.length - 1],
  });

  const xScaleBand = scaleBand({
    range: [0, chartWidth],
    domain: basis.map(labelSelector),
    paddingInner: 1,
    paddingOuter: 0,
  });

  const yScale = scaleLinear({
    range: [chartHeight, 0],
    domain: [0, domainMax],
    nice: true,
  });

  const colorScale = (index: number) => colors[index % colors.length];

  return (
    <svg width={width} height={height}>
      {background && (
        <rect
          x={margin.left}
          y={margin.top}
          width={chartWidth}
          height={chartHeight}
          fill={background}
        />
      )}
      {showGrid && (
        <Grid
          width={chartWidth}
          height={height}
          top={margin.top}
          left={margin.left}
          yScale={yScale}
          color={gridColor}
        />
      )}
      <Group top={margin.top} left={margin.left}>
        {data.map((datum, i) => {
          const graph = itemsSelector(datum);
          return (
            <Graph
              id={datum.id}
              data={graph}
              xScale={xScale}
              yScale={yScale}
              // labelSelector={labelSelector}
              valueSelector={valueSelector}
              color={colorScale(i)}
              fillOpacity={fillOpacity}
              strokeWidth={strokeWidth}
              curve={curve}
              key={datum.id}
            />
          );
        })}
      </Group>
      {onHover && (
        <MouseMoveHandler
          width={chartWidth}
          height={chartHeight}
          top={margin.top}
          left={margin.left}
          data={dataPoints}
          xScale={xScale}
          yScale={(d) => yScale(valueSelector(d))}
          onHover={onHover}
        />
      )}
      <Axes
        width={chartWidth}
        height={chartHeight}
        margin={margin}
        xScale={xScaleBand}
        yScale={yScale}
        showLeftAxis={showLeftAxis}
        axisLeftAngle={axisLeftAngle}
        labelLeft={labelLeft}
        showRightAxis={showRightAxis}
        axisRightAngle={axisRightAngle}
        labelRight={labelRight}
        showBottomAxis={showBottomAxis}
        axisBottomAngle={axisBottomAngle}
        labelBottom={labelBottom}
        textColor={textColor}
        axisColor={axisColor}
        onMount={updateBounds}
        axisLeftProps={axisLeftProps}
        axisRightProps={axisRightProps}
        axisBottomProps={axisBottomProps}
        key={refreshKey}
      />
    </svg>
  );
}

LineChartSVG.defaultProps = defaultProps;

export default memo(LineChartSVG);
