import { memo } from 'react';
import { AnyD3Scale, scaleBand, scaleLinear } from '@visx/scale';
import { Group } from '@visx/group';
import { AnyScaleBand } from '@visx/shape/lib/types';

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, renderProp } from '../../utils';
import { valueSelector, labelSelector, ChartData } from '../../utils/data';
import { format } from '../../utils/format';

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

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

function horizontalBar(
  xScale: AnyD3Scale,
  yScale: AnyScaleBand,
  value: number,
  label: string
) {
  const width = xScale(value);
  const height = yScale.bandwidth();
  const x = 0;
  const y = yScale(label)!; // can't be undefined because label is coming from 'domainSelector';
  return { x, y, width, height };
}

export function verticalBar(
  xScale: AnyScaleBand,
  yScale: AnyD3Scale,
  value: number,
  label: string,
  chartHeight: number
) {
  const width = xScale.bandwidth();
  const height = Math.max(0, chartHeight - yScale(value));
  const x = xScale(label)!; // can't be undefined because label is coming from 'domainSelector'
  const y = chartHeight - height;
  return { x, y, width, height };
}

const TEXT_LABEL_OFFSET = 24;

export type BarChartSVGProps<T = ChartData> = AxesSettings &
  BackgroundSettings &
  TextLabelSettings &
  BarSpacing & {
    data: Readonly<Readonly<T>[]>;
    width: number;
    height: number | string;
    maxValue?: number;
    horizontal?: boolean;
    valueFormat?: string;

    barColor?: string;
    barSize?: number;

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

export const defaultProps = {
  barColor: DEFAULT_COLOR,
  spacing: 0.2,

  showGrid: true,
  showBottomAxis: true,

  textLabel: true,
  textLabelSize: 12,
};

function BarChartSVG({
  data,
  width,
  height,
  maxValue,
  barColor,
  barSize,
  spacing,
  spacingInner,
  spacingOuter,
  background,
  showGrid,
  gridColor,
  textColor,
  axisColor,
  showLeftAxis,
  axisLeftAngle,
  labelLeft,
  showRightAxis,
  axisRightAngle,
  labelRight,
  showBottomAxis,
  axisBottomAngle,
  labelBottom,
  axisLeftProps,
  axisRightProps,
  axisBottomProps,
  onBarClick,
  bindTooltip,
  horizontal,
  valueFormat,
  textLabel,
  textLabelSize,
  textLabelColor,
}: BarChartSVGProps & typeof defaultProps) {
  // const _textLabel = selector<ChartData>((textLabel as keyof ChartData)!);

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

  const computedHeight =
    height === 'auto' && barSize ? data.length * barSize : (height as number);

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

  const [chartWidth, chartHeight, margin] = bounds;

  const keys = data.map(labelSelector);
  const domainMax =
    maxValue !== undefined ? maxValue : Math.max(...data.map(valueSelector));

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

  const yScaleBand = scaleBand({
    range: [0, chartHeight],
    domain: keys,
    padding: spacing,
    paddingInner: spacingInner,
    paddingOuter: spacingOuter,
  });

  // vertical
  const xScaleBand = scaleBand({
    range: [0, chartWidth],
    domain: keys,
    padding: spacing,
    paddingInner: spacingInner,
    paddingOuter: spacingOuter,
  });

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

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

  const gridProps = horizontal
    ? { xScale: xScaleLinear }
    : { yScale: yScaleLinear };

  return (
    <svg width={width} height={computedHeight}>
      {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((datum, i) => {
          const domain = labelSelector(datum);
          const value = valueSelector(datum);
          // const label = _textLabel(datum);

          const bar = horizontal
            ? horizontalBar(xScaleLinear, yScaleBand, value, domain)
            : verticalBar(xScaleBand, yScaleLinear, value, domain, chartHeight);

          const fill = renderProp(barColor, datum, i); //TODO refactor

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

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

          return (
            <BarElement
              x={bar.x!}
              y={bar.y!}
              width={bar.width}
              height={bar.height}
              horizontal={horizontal}
              fill={fill}
              textLabelColor={textLabelColor}
              textLabelSize={textLabelSize}
              value={textLabel ? formattedValue : undefined}
              labelPosition="offset"
              onClick={onBarClick && handleClick}
              {...tooltipProps}
              key={datum.id || i}
            />
          );
        })}
      </Group>
      <Axes
        width={chartWidth}
        height={chartHeight}
        margin={margin}
        {...axisProps}
        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>
  );
}

BarChartSVG.defaultProps = defaultProps;

export default memo(BarChartSVG);
