import { memo, useRef } from 'react';
import { scaleLinear, scaleOrdinal } from '@visx/scale';
import { Group } from '@visx/group';

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

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

import { format } from '../../utils/format';
import { call } from '../../utils';

import { COLORS } from '../../config';

import { BackgroundSettings } from '../../types';
import { TooltipBinder } from '../../hooks/useTooltip';

export type ScatterDatum = {
  scaleX: number;
  scaleY: number;
  label: string;
  value: number;
};

export type ScatterChartSVGProps = AxesSettings &
  BackgroundSettings & {
    data: ScatterDatum[];
    width: number;
    height: number;
    minBubbleSize?: number;
    maxBubbleSize?: number;
    useMask?: boolean;
    valueFormat?: string;

    strokeColor?: string;
    fillOpacity?: number;
    onItemClick?: (
      data: ScatterDatum,
      i: number,
      ev: React.MouseEvent<SVGGElement, MouseEvent>
    ) => void;
    bindTooltip?: TooltipBinder<ScatterDatum>;
  };

const defaultProps = {
  useMask: true,
  showGrid: true,
  showLeftAxis: true,
  showRightAxis: false,
  showBottomAxis: true,
  strokeColor: 'white',
  fillOpacity: 1,
};
function ScatterChartSVG({
  data,
  width,
  height,
  minBubbleSize,
  maxBubbleSize,
  useMask,
  background,
  showGrid,
  gridColor,
  textColor,
  axisColor,
  showLeftAxis,
  axisLeftAngle,
  labelLeft,
  showRightAxis,
  axisRightAngle,
  labelRight,
  showBottomAxis,
  axisBottomAngle,
  labelBottom,
  axisLeftProps,
  axisRightProps,
  axisBottomProps,
  strokeColor,
  fillOpacity,
  valueFormat,
  onItemClick,
  bindTooltip,
}: ScatterChartSVGProps & typeof defaultProps) {
  let bubbleRadius = 24;

  //TODO temp fix for axes bounds recalculation
  const refreshKey = useRefreshKey([data]);

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

  const [chartWidth, chartHeight, margin] = bounds;

  const keys = Array.from(new Set(data.map((d) => d.label))); // TODO refactor selector

  const xValues = data.map((d) => d.scaleX);
  const yValues = data.map((d) => d.scaleY);
  const values = data.map((d) => d.value);

  const colorScale = scaleOrdinal({
    domain: keys,
    range: COLORS,
  });

  const xScale = scaleLinear({
    range: [0, chartWidth],
    domain: [0, Math.max(...xValues)],
    nice: true,
  });

  const yScale = scaleLinear({
    range: [chartHeight, 0],
    domain: [0, Math.max(...yValues)],
    nice: true,
  });

  // const getMaxRadius = () => {
  //   const minPadding = chartHeight / yScale.ticks().length;
  //   return Math.max(minPadding, 5);
  // };
  // const maxRadius = maxBubbleSize || getMaxRadius();
  // const minRadius = minBubbleSize || maxRadius * 0.1;

  const minFraction = chartHeight / yScale.ticks().length;
  const maxRadius = maxBubbleSize || minFraction;
  const minRadius = minBubbleSize || minFraction / 2;

  const zScale = scaleLinear({
    range: [minRadius, maxRadius],
    domain: [Math.min(0, ...values), Math.max(1, ...values)],
    nice: true,
  });

  const maskId = useRef(+new Date() + '').current;

  return (
    <svg width={width} height={height}>
      <mask
        id={maskId}
        style={{ maskType: 'alpha' }}
        maskUnits="userSpaceOnUse"
      >
        <rect width={chartWidth} height={chartHeight} fill="#fff" />
      </mask>
      {background && (
        <rect
          x={margin.left}
          y={margin.top}
          width={chartWidth}
          height={chartHeight}
          fill={background}
        />
      )}
      {showGrid && (
        <Grid
          top={margin.top}
          left={margin.left}
          width={chartWidth}
          height={chartHeight}
          xScale={xScale}
          yScale={yScale}
          color={gridColor}
        />
      )}
      <Group
        top={margin.top}
        left={margin.left}
        mask={useMask ? `url(#${maskId})` : undefined}
      >
        {data.map((datum, i) => {
          const color = colorScale(datum.label);

          let tooltip = {
            ...datum,
            color,
          };

          const formattedValue = valueFormat
            ? format(valueFormat, datum)
            : String(datum.value);

          const handleClick: React.MouseEventHandler<SVGGElement> = (ev) => {
            onItemClick!(datum, i, ev);
          };
          const tooltipProps = call(bindTooltip, tooltip, formattedValue);

          return (
            <circle
              cx={xScale(datum.scaleX)}
              cy={yScale(datum.scaleY)}
              r={zScale(datum.value || 0)}
              stroke={strokeColor || color}
              strokeWidth={1}
              strokeOpacity={1}
              fill={color}
              fillOpacity={fillOpacity}
              onClick={onItemClick && handleClick}
              {...tooltipProps}
              key={i}
            />
          );
        })}
      </Group>
      <Axes
        width={chartWidth}
        height={chartHeight}
        margin={margin}
        xScale={xScale}
        yScale={yScale}
        showLeftAxis={showLeftAxis}
        axisLeftAngle={axisLeftAngle}
        labelLeft={labelLeft}
        showRightAxis={showRightAxis}
        axisRightAngle={axisRightAngle}
        labelRight={labelRight}
        showBottomAxis={showBottomAxis}
        axisBottomAngle={axisBottomAngle}
        labelBottom={labelBottom}
        textColor={textColor}
        axisColor={axisColor}
        horizontal={true}
        onMount={updateBounds}
        axisLeftProps={axisLeftProps}
        axisRightProps={axisRightProps}
        axisBottomProps={axisBottomProps}
        key={refreshKey}
      />
    </svg>
  );
}

ScatterChartSVG.defaultProps = defaultProps;

export default memo(ScatterChartSVG);
