import { AxisBottom, AxisLeft } from "@visx/axis";
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom";
import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft";
import { localPoint } from "@visx/event";
import { GridColumns, GridRows } from "@visx/grid";
import { Group } from "@visx/group";
import { scaleBand, scaleLinear } from "@visx/scale";
import { LinePath } from "@visx/shape";
import { useTooltip, useTooltipInPortal } from "@visx/tooltip";
import React from "react";
import { Color } from "utils/Color";

import styles from "./LineGraph.module.css";

interface LineData {
  label: string;
  yValues: number[];
}

interface PointData {
  x: string;
  y: number;
}

interface LineGraphData {
  xValues: string[];
  lines: LineData[];
}

interface LineGraphProps {
  data: LineGraphData;
  /** Width of the entire graph, in pixels. */
  width: number;
  /** Height of the entire graph, in pixels. */
  height: number;
  /** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */
  margin: {
    top: number;
    bottom: number;
    left: number;
    right: number;
  };
  className?: string;
  /** Font size of the category tick labels, in pixels. Default is 16px. */
  xTickLabelSize?: number;
  /** Font size of the value tick labels, in pixels. Default is 16px. */
  yTickLabelSize?: number;
  /** Font size of the value that appears when hovering over a bar, in pixels. */
  hoverLabelSize?: number;
  /** Label text for the category axis. */
  xAxisLabel?: string;
  /** Font size of the label for the cateogry axis, in pixels. */
  xAxisLabelSize?: number;
  /** Controls the distance between the category axis label and the category axis. */
  xAxisLabelOffset?: number;
  /** Label text for the value axis. */
  yAxisLabel?: string;
  /** Font size of the label for the value axis, in pixels. */
  yAxisLabelSize?: number;
  /** Controls the distance between the value axis label and the value axis. */
  yAxisLabelOffset?: number;
}

const DEFAULT_LABEL_SIZE = 16;

export function LineGraph(props: LineGraphProps) {
  const {
    width,
    height,
    margin,
    data,
    className,
    xTickLabelSize = DEFAULT_LABEL_SIZE,
    yTickLabelSize = DEFAULT_LABEL_SIZE,
    hoverLabelSize,
    xAxisLabel,
    xAxisLabelSize = DEFAULT_LABEL_SIZE,
    xAxisLabelOffset = 0,
    yAxisLabel,
    yAxisLabelSize = DEFAULT_LABEL_SIZE,
    yAxisLabelOffset = 0,
  } = props;

  const {
    tooltipData,
    tooltipLeft,
    tooltipTop,
    tooltipOpen,
    showTooltip,
    hideTooltip,
  } = useTooltip();

  // If you don't want to use a Portal, simply replace `TooltipInPortal` below with
  // `Tooltip` or `TooltipWithBounds` and remove `containerRef`
  const { containerRef, TooltipInPortal } = useTooltipInPortal({
    // use TooltipWithBounds
    detectBounds: true,
    // when tooltip containers are scrolled, this will correctly update the Tooltip position
    scroll: true,
  });

  const yMax = height - margin.top - margin.bottom;
  const xMax = width - margin.left - margin.right;

  const actualData = data.lines.map((line) => {
    return line.yValues.map((val, idx) => {
      return { x: data.xValues[idx], y: val };
    });
  });

  // data accessors
  const getX = (d: PointData) => d.x;
  const getY = (d: PointData) => d.y;

  // scales
  const xScale = scaleBand({
    range: [0, xMax],
    domain: data.xValues,
  });

  const yScale = scaleLinear<number>({
    range: [0, yMax],
    nice: true,
    domain: [100, 0],
  });

  return (
    <>
      <svg ref={containerRef} width={width} height={height}>
        <Group top={margin.top} left={margin.left}>
          <GridColumns
            scale={xScale}
            height={yMax}
            left={margin.left}
            numTicks={5}
            stroke={Color.tertiaryBackground}
            strokeWidth={4}
            strokeDasharray="10"
            strokeLinecap="round"
          />
          <GridRows
            scale={yScale}
            width={xMax}
            left={margin.left * 2}
            numTicks={data.xValues.length}
            stroke={Color.tertiaryBackground}
            strokeWidth={4}
            strokeDasharray="10"
            strokeLinecap="round"
          />
          <AxisBottom
            scale={xScale}
            top={margin.top + yMax}
            left={margin.left}
            hideAxisLine
            hideTicks
            tickLabelProps={() => {
              return {
                ...bottomTickLabelProps(),
                className: styles.tickLabel,
                dy: "-0.25rem",
                fontSize: `${xTickLabelSize / 16}rem`,
                width: xScale.bandwidth(),
              };
            }}
          />
          <AxisLeft
            scale={yScale}
            left={margin.left}
            hideAxisLine
            hideTicks
            numTicks={5}
            tickLabelProps={() => {
              return {
                ...leftTickLabelProps(),
                className: styles.tickLabel,
                dx: "0.75rem",
                dy: "0.25rem",
                fontSize: `${yTickLabelSize / 16}rem`,
              };
            }}
          />
          <Group left={margin.left + xMax / (data.xValues.length * 2)}>
            {actualData.map((lineData, i) => {
              const even = i % 2 === 0;
              return (
                <Group key={`line-${i}`}>
                  <LinePath
                    onMouseMove={(e) => {
                      const eventSvgCoords = localPoint(
                        // ownerSVGElement is given by visx docs but not recognized by typescript
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-ignore
                        e.target.ownerSVGElement as Element,
                        e
                      );
                      showTooltip({
                        tooltipData: data.lines[i].label,
                        tooltipTop: eventSvgCoords.y,
                        tooltipLeft: eventSvgCoords.x,
                      });
                    }}
                    onMouseOut={hideTooltip}
                    data={lineData}
                    className={styles.line}
                    x={(d) => xScale(getX(d)) ?? 0}
                    y={(d) => yScale(getY(d)) ?? 0}
                    stroke={even ? Color.primaryAccent : Color.secondaryAccent}
                    strokeWidth={3}
                    strokeOpacity={2}
                  />
                </Group>
              );
            })}
          </Group>
        </Group>
      </svg>

      {tooltipOpen && (
        <TooltipInPortal
          // set this to random so it correctly updates with parent bounds
          key={Math.random()}
          top={tooltipTop}
          left={tooltipLeft}
          className={styles.tooltip}
          unstyled
          applyPositionStyle
        >
          {tooltipData}
        </TooltipInPortal>
      )}
    </>
  );
}
