import React from 'react';
import { Group } from '@visx/group';
import { AxisLeft, AxisRight, AxisBottom, TickRendererProps } from '@visx/axis';

import { numTicksForHeight } from '../utils';

const renderTickComponent = ({
  formattedValue,
  rotate,
  ...props
}: TickRendererProps) => {
  if (rotate) {
    props.transform = `rotate(${rotate}, ${props.x}, ${props.y})`;
  }
  return <text {...props}>{formattedValue}</text>;
};

const LABEL_OFFSET_X = 4;
const LABEL_OFFSET_Y = 4;

function getProp(parent: Element, selector: string, property: string) {
  const el = parent.querySelector(selector);
  if (el) {
    const box = (el as SVGSVGElement).getBBox() as any;
    const value = box[property];
    return Math.ceil(value);
  } else {
    return 0;
  }
}

type Bounds = {
  leftAxis?: number;
  leftAxisLabel?: number;
  rightAxis?: number;
  rightAxisLabel?: number;
  bottomAxis?: number;
  bottomAxisLabel?: number;
};

interface AxesProps {
  width: number;
  height: number;
  xScale: any;
  yScale: any;
  horizontal: boolean;
  margin: Record<string, number>;
  bounds?: Bounds;
  showLeftAxis?: boolean;
  axisLeftAngle?: number;
  labelLeft?: string;
  showRightAxis?: boolean;
  axisRightAngle?: number;
  labelRight?: string;
  showBottomAxis?: boolean;
  axisBottomAngle?: number;
  labelBottom?: string;
  textFontSize?: number;
  textColor?: string;
  axisColor?: string;
  axisLeftProps?: React.ComponentProps<typeof AxisLeft>;
  axisRightProps?: React.ComponentProps<typeof AxisRight>;
  axisBottomProps?: React.ComponentProps<typeof AxisBottom>;
  onMount?: (axes: any) => void;
}

export type AxesSettings = Pick<
  AxesProps,
  | 'textColor'
  | 'axisColor'
  | 'showLeftAxis'
  | 'axisLeftAngle'
  | 'showRightAxis'
  | 'axisRightAngle'
  | 'showBottomAxis'
  | 'axisBottomAngle'
  | 'labelLeft'
  | 'labelRight'
  | 'labelBottom'
  | 'textFontSize'
  | 'axisLeftProps'
  | 'axisRightProps'
  | 'axisBottomProps'
>;

const defaultProps: Partial<AxesProps> = {
  textFontSize: 12,
};

export const Axes = (props: AxesProps) => {
  const labelProps: {
    fill?: string;
    fontSize?: number;
    fontStyle?: string;
    textAnchor?: 'middle' | 'inherit' | 'end' | 'start';
  } = {
    fill: props.textColor,
    fontSize: props.textFontSize,
    fontStyle: 'italic',
    textAnchor: 'middle',
  };
  const tickLabelProps = {
    fill: props.textColor,
    fontSize: props.textFontSize,
  };

  const axesRef = React.useRef<SVGGElement | null>(null);
  const [bounds, setBounds] = React.useState<Bounds>({});

  const {
    labelLeft,
    showLeftAxis,
    axisLeftAngle,
    labelRight,
    showRightAxis,
    axisRightAngle,
    labelBottom,
    showBottomAxis,
    axisBottomAngle,
    onMount,
  } = props;

  React.useEffect(() => {
    if (typeof onMount === 'function') {
      requestAnimationFrame(() => {
        if (axesRef.current) {
          const axes = { ...props.bounds };

          const getSize = (selector: any, property: string) =>
            getProp(axesRef.current!, selector, property);

          axes.leftAxis = getSize('.visx-axis-left', 'width');
          axes.leftAxisLabel = getSize(
            '.visx-axis-left .visx-axis-label',
            'height'
          );

          axes.rightAxis = getSize('.visx-axis-right', 'width');
          axes.rightAxisLabel = getSize(
            '.visx-axis-right .visx-axis-label',
            'height'
          );

          axes.bottomAxis = getSize('.visx-axis-bottom', 'height');
          axes.bottomAxisLabel = getSize(
            '.visx-axis-bottom .visx-axis-label',
            'height'
          );

          setBounds(axes);
          onMount(axes);
        }
      });
    }
  }, [
    labelLeft,
    showLeftAxis,
    axisLeftAngle,
    labelRight,
    showRightAxis,
    axisRightAngle,
    labelBottom,
    showBottomAxis,
    axisBottomAngle,
    onMount,
  ]);

  return (
    <Group top={props.margin.top} innerRef={axesRef}>
      {props.showLeftAxis && (
        <AxisLeft
          top={0}
          left={props.margin.left}
          scale={props.yScale}
          hideAxisLine={true}
          hideTicks={false}
          hideZero={false}
          label={props.labelLeft}
          // labelClassName={string}
          labelOffset={bounds.leftAxis || 0}
          labelProps={labelProps}
          numTicks={
            props.horizontal ? undefined : numTicksForHeight(props.height)
          }
          // rangePadding={number}
          stroke={props.axisColor}
          // strokeDasharray={string}
          // strokeWidth={number}
          // tickClassName={string}
          tickComponent={renderTickComponent}
          // tickFormat={defaultTickFormat}
          tickLabelProps={(value) => {
            if (value === 0) {
              return {
                display: 'none',
              };
            }
            return {
              ...tickLabelProps,
              dx: -LABEL_OFFSET_X * 2,
              dy: LABEL_OFFSET_Y,
              textAnchor: 'end',
              rotate: props.axisLeftAngle,
            };
          }}
          tickLength={0}
          tickStroke={'transparent'}
          {...props.axisLeftProps}
        />
      )}
      {props.showRightAxis && (
        <AxisRight
          top={0}
          left={props.width + props.margin.left}
          scale={props.yScale}
          hideAxisLine={true}
          hideTicks={false}
          hideZero={false}
          label={props.labelRight}
          // labelClassName={string}
          labelOffset={bounds.rightAxis || 0}
          labelProps={labelProps}
          numTicks={
            props.horizontal ? undefined : numTicksForHeight(props.height)
          }
          // rangePadding={number}
          stroke={props.axisColor}
          // strokeDasharray={string}
          // strokeWidth={number}
          // tickClassName={string}
          tickComponent={renderTickComponent}
          // tickFormat={defaultTickFormat}
          tickLabelProps={(value) => {
            if (value === 0) {
              return {
                display: 'none',
              };
            }
            return {
              ...tickLabelProps,
              dx: LABEL_OFFSET_X * 2,
              dy: LABEL_OFFSET_Y,
              textAnchor: 'start',
              rotate: props.axisRightAngle,
            };
          }}
          tickLength={0}
          tickStroke={'transparent'}
          {...props.axisRightProps}
        />
      )}
      {props.showBottomAxis && (
        <AxisBottom
          top={props.height}
          left={props.margin.left}
          scale={props.xScale}
          label={props.labelBottom}
          labelOffset={bounds.bottomAxisLabel || 0}
          labelProps={labelProps}
          stroke={props.axisColor}
          tickComponent={(tickProps) => {
            if (!props.axisBottomAngle) {
              if (!props.showLeftAxis && tickProps.x === 0) {
                tickProps.textAnchor = 'start';
              }
              if (!props.showRightAxis && tickProps.x === props.width) {
                tickProps.textAnchor = 'end';
              }
            }
            return renderTickComponent(tickProps);
          }}
          tickLabelProps={(value, index) => ({
            ...tickLabelProps,
            dy: LABEL_OFFSET_Y,
            textAnchor: props.axisBottomAngle ? 'end' : 'middle',
            rotate: props.axisBottomAngle,
          })}
          tickStroke={props.axisColor}
          {...props.axisBottomProps}
        />
      )}
    </Group>
  );
};

Axes.defaultProps = defaultProps;
