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 { call, max } from '../../utils';
import { COLORS } from '../../config';
import { TextLabelSettings, BackgroundSettings } from '../../types';

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

export const defaultProps = {
  colors: COLORS,
  barSpacing: 0,
  groupSpacing: 0.2,

  showGrid: true,

  showLeftAxis: true,
  showBottomAxis: true,
  textLabel: false,
  textLabelSize: 12,
};

export type InnerDatum = {
  index: number;
  label: string;
  value: number;
};

export type BarGroupChartSVGProps = BackgroundSettings &
  AxesSettings &
  TextLabelSettings & {
    data: ChartNestedData[];
    maxValue?: number;
    width: number;
    height: number;
    colors?: string[];
    groupSpacing?: number;
    groupSpacingInner?: number;
    groupSpacingOuter?: number;
    barSpacing?: number;
    horizontal?: boolean;
    valueFormat?: string;

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

function BarGroupChartSVG({
  data,
  maxValue,
  width,
  height,
  colors,
  groupSpacing,
  groupSpacingInner,
  groupSpacingOuter,
  barSpacing,
  background,
  showGrid,
  gridColor,
  textColor,
  axisColor,
  showLeftAxis,
  axisLeftAngle,
  labelLeft,
  showRightAxis,
  axisRightAngle,
  labelRight,
  showBottomAxis,
  axisBottomAngle,
  labelBottom,
  axisLeftProps,
  axisRightProps,
  axisBottomProps,
  onBarClick,
  bindTooltip,
  horizontal,
  valueFormat,
  textLabel,
  textLabelSize,
  textLabelColor,
}: BarGroupChartSVGProps) {
  //TODO temp fix for axes bounds recalculation
  const refreshKey = useRefreshKey([data]);

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

  const [chartWidth, chartHeight, margin] = bounds;

  const keys = itemsSelector(data[0]).map(labelSelector);
  const domainMax =
    maxValue !== undefined
      ? maxValue
      : max(data, (d) => max(itemsSelector(d), valueSelector));
  const groups = data.map(groupSelector);

  // horizontal
  const xScale = scaleLinear({
    range: [0, chartWidth],
    domain: [0, domainMax],
    nice: true,
  });

  const yScaleGroup = scaleBand({
    range: [0, chartHeight],
    domain: groups,
    padding: groupSpacing,
    paddingInner: groupSpacingInner,
    paddingOuter: groupSpacingOuter,
  });

  const yScaleBar = scaleBand({
    range: [0, yScaleGroup.bandwidth()],
    domain: keys,
    paddingInner: barSpacing,
  });

  // vertical
  const xScaleGroup = scaleBand({
    range: [0, chartWidth],
    domain: groups,
    padding: groupSpacing,
    paddingInner: groupSpacingInner,
    paddingOuter: groupSpacingOuter,
  });

  const xScaleBar = scaleBand({
    range: [0, xScaleGroup.bandwidth()],
    domain: keys,
    paddingInner: barSpacing,
  });

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

  const color = scaleOrdinal({
    domain: keys,
    range: colors,
  });

  const gridProps = horizontal ? { xScale } : { yScale };
  const axisProps = horizontal
    ? { xScale: xScale, yScale: yScaleGroup }
    : { xScale: xScaleGroup, yScale };

  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((barGroup, barGroupIndex) => {
          const transform = horizontal
            ? `translate(0, ${yScaleGroup(barGroup.key)})`
            : `translate(${xScaleGroup(barGroup.key)}, 0)`;
          return (
            <g transform={transform} key={barGroup.key}>
              {barGroup.data.map((datum, barIndex) => {
                const value = valueSelector(datum);
                const label = labelSelector(datum);
                let width, height, x, y;
                if (horizontal) {
                  width = chartWidth - xScale(value);
                  height = yScaleBar.bandwidth();
                  x = 0;
                  y = yScaleBar(label)!; // can't be undefined because label is coming from 'domainSelector'
                } else {
                  width = xScaleBar.bandwidth();
                  height = chartHeight - yScale(value);
                  x = xScaleBar(label)!; // can't be undefined because label is coming from 'domainSelector'
                  y = chartHeight - height;
                }

                const fill = color(label);

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

                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}
                    labelPosition="inset"
                    onClick={onBarClick && handleClick}
                    {...tooltipProps}
                    key={`${barGroupIndex}-${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>
  );
}

BarGroupChartSVG.defaultProps = defaultProps;

export default memo(BarGroupChartSVG);
