import { memo } from 'react';
import { scaleBand, 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 { BarElement } from '../../common/BarElement';

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

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

import { call } from '../../utils';
import { TextLabelSettings, BackgroundSettings } from '../../types';
import { BarSpacing } from '../bar/types';
import { TooltipBinder } from '../../hooks/useTooltip';

export type BarStackChartSVGProps = AxesSettings &
  BackgroundSettings &
  TextLabelSettings &
  BarSpacing & {
    data: ChartNestedData[];
    width: number;
    height: number;
    horizontal?: boolean;
    maxValue?: number;
    nice?: boolean;
    valueFormat?: string;
    colors: string[];

    onBarClick?: (
      data: ChartData,
      ev: React.MouseEvent<SVGGElement, MouseEvent>
    ) => void;
    bindTooltip?: TooltipBinder<ChartData>;
  };

export const defaultProps = {
  colors: COLORS,
  spacing: 0.2,
  showGrid: true,
  showBottomAxis: true,
  nice: true,
  textLabel: false,
  textLabelSize: 12,
};

function BarStackChartSVG({
  data,
  width,
  height,
  valueFormat,
  colors,
  spacing,
  spacingInner,
  spacingOuter,
  background,
  showGrid,
  gridColor,
  textColor,
  axisColor,
  showLeftAxis,
  axisLeftAngle,
  labelLeft,
  showRightAxis,
  axisRightAngle,
  labelRight,
  showBottomAxis,
  axisBottomAngle,
  labelBottom,
  axisLeftProps,
  axisRightProps,
  axisBottomProps,
  onBarClick,
  bindTooltip,
  horizontal,
  nice,
  maxValue,
  textLabel,
  textLabelSize,
  textLabelColor,
}: BarStackChartSVGProps & typeof defaultProps) {
  //TODO temp fix for axes bounds recalculation
  const refreshKey = useRefreshKey([data]);

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

  const [chartWidth, chartHeight, margin] = bounds;

  const keys = itemsSelector(data[0]).map(labelSelector);
  const domainMax =
    maxValue !== undefined
      ? maxValue
      : Math.max(
          ...data
            .map(itemsSelector)
            .map((arr) => arr.map(valueSelector).reduce((a, b) => a + b), 0)
        );
  const stacks = data.map(groupSelector);

  // scales
  const colorScale = scaleOrdinal({
    domain: keys,
    range: colors,
  });

  // horizontal
  const xScaleLinear = scaleLinear({
    range: [0, chartWidth],
    domain: [0, domainMax],
    nice,
  });
  const yScaleBand = scaleBand({
    range: [0, chartHeight],
    domain: stacks,
    padding: spacing,
    paddingInner: spacingInner,
    paddingOuter: spacingOuter,
  });

  // vertical
  const xScaleBand = scaleBand({
    range: [0, chartWidth],
    domain: stacks,
    padding: spacing,
    paddingInner: spacingInner,
    paddingOuter: spacingOuter,
  });
  const yScaleLinear = scaleLinear({
    range: [chartHeight, 0],
    domain: [0, domainMax],
    nice,
  });

  const axisProps = horizontal
    ? { xScale: xScaleLinear, yScale: yScaleBand }
    : { xScale: xScaleBand, yScale: yScaleLinear };
  const gridProps = horizontal
    ? { xScale: xScaleLinear }
    : { yScale: yScaleLinear };

  return (
    <svg width={width} height={height}>
      {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}
          color={gridColor}
          {...gridProps}
        />
      )}
      <Group top={margin.top} left={margin.left}>
        {data.map((barStack, barStackIndex) => {
          const transform = horizontal
            ? `translate(0, ${yScaleBand(barStack.key)})`
            : `translate(${xScaleBand(barStack.key)}, 0)`;
          let offset = 0;
          return (
            <g transform={transform} key={barStack.key}>
              {barStack.data.map((datum, barIndex) => {
                const value = valueSelector(datum);
                const label = labelSelector(datum);
                let width: number, height: number, x: number, y: number;
                if (horizontal) {
                  width = xScaleLinear(value);
                  height = yScaleBand.bandwidth();
                  x = offset;
                  y = 0;
                  offset += width;
                } else {
                  width = xScaleBand.bandwidth();
                  height = chartHeight - yScaleLinear(value);
                  x = 0;
                  y = chartHeight - offset - height;
                  offset += height;
                }

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

                const fill = colorScale(label);

                const handleClick: React.MouseEventHandler<SVGGElement> = (
                  ev
                ) => onBarClick!(datum, ev);
                const tooltipProps = call(bindTooltip, datum, formattedValue);

                return (
                  <BarElement
                    x={x}
                    y={y}
                    width={width}
                    height={height}
                    horizontal={horizontal}
                    fill={fill}
                    textLabelColor={textLabelColor}
                    textLabelSize={textLabelSize}
                    value={textLabel ? formattedValue : undefined}
                    onClick={onBarClick && handleClick}
                    {...tooltipProps}
                    key={`${barStackIndex}-${barIndex}-${datum.label}`}
                  />
                );
              })}
            </g>
          );
        })}
      </Group>
      <Axes
        {...axisProps}
        width={chartWidth}
        height={chartHeight}
        margin={margin}
        showLeftAxis={showLeftAxis}
        axisLeftAngle={axisLeftAngle}
        labelLeft={labelLeft}
        showRightAxis={showRightAxis}
        axisRightAngle={axisRightAngle}
        labelRight={labelRight}
        showBottomAxis={showBottomAxis}
        axisBottomAngle={axisBottomAngle}
        labelBottom={labelBottom}
        textColor={textColor}
        axisColor={axisColor}
        horizontal={horizontal}
        onMount={updateBounds}
        axisLeftProps={axisLeftProps}
        axisRightProps={axisRightProps}
        axisBottomProps={axisBottomProps}
        key={refreshKey}
      />
    </svg>
  );
}

BarStackChartSVG.defaultProps = defaultProps;

export default memo(BarStackChartSVG);
