import { AxisBottom, AxisLeft } from "@visx/axis";
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom";
import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft";
import { GridColumns, GridRows } from "@visx/grid";
import { Group } from "@visx/group";
import { scaleBand, scaleLinear } from "@visx/scale";
import { Bar } from "@visx/shape";
import { Text } from "@visx/text";
import React, { useState } from "react";

// TODO: refine props for styles
interface BarGraphProps {
  data: BarGraphData[];
  width: number;
  height: number;
  margin: {
    top: number;
    bottom: number;
    left: number;
    right: number;
  };
  className?: string;
}

interface BarGraphData {
  category: string;
  value: number;
}

const COLOURS = {
  salmon: "#ffcad0",
  navy: "#2c3651",
  white: "#ffffff",
  pink: "#ef83b1",
  darkpink: "#cc5773",
};

// TODO: styling, possibly refactor all into one component
export function BarGraphVertical(props: BarGraphProps) {
  const { width, height, margin, data, className } = props;

  const categoryMax = width - margin.left - margin.right;
  const valueMax = height - margin.top - margin.bottom;

  const getCategory = (d: BarGraphData) => d.category;
  const getValue = (d: BarGraphData) => d.value;

  const categoryScale = scaleBand({
    range: [0, categoryMax],
    domain: data.map(getCategory),
    padding: 0.1,
  });

  const valueScale = scaleLinear({
    range: [valueMax, 0],
    nice: true,
    domain: [0, Math.max(...data.map(getValue))],
  });

  const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
  const valuePoint = (d: BarGraphData) => valueScale(getValue(d));

  return (
    <svg className={className} width={width} height={height}>
      <GridRows
        scale={valueScale}
        width={categoryMax}
        top={margin.top}
        left={margin.left}
      />
      <Group top={margin.top} left={margin.left}>
        {data.map((d) => {
          const barHeight = valueMax - valuePoint(d);
          return (
            <Bar
              key={`bar-${getCategory(d)}`}
              x={categoryPoint(d)}
              y={valueMax - barHeight}
              width={categoryScale.bandwidth()}
              height={barHeight}
              fill="aqua"
            />
          );
        })}
      </Group>
      <AxisLeft scale={valueScale} top={margin.top} left={margin.left} />
      <AxisBottom
        scale={categoryScale}
        top={valueMax + margin.top}
        left={margin.left}
      />
    </svg>
  );
}

export function BarGraphHorizontal(props: BarGraphProps) {
  const { width, height, margin, data, className } = props;

  const barPadding = 0.4;

  const categoryMax = height - margin.top - margin.bottom;
  const valueMax = width - margin.left - margin.right;

  const getCategory = (d: BarGraphData) => d.category;
  const getValue = (d: BarGraphData) => d.value;

  const [isBarHovered, setIsBarHovered] = useState(
    Object.fromEntries(
      data.map((d, idx) => [`${getCategory(d)}-${idx}`, false])
    )
  );

  const categoryScale = scaleBand({
    range: [0, categoryMax],
    domain: data.map(getCategory),
    padding: barPadding,
  });

  const valueScale = scaleLinear({
    range: [0, valueMax],
    nice: true,
    domain: [0, Math.max(...data.map(getValue))],
  });

  const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
  const valuePoint = (d: BarGraphData) => valueScale(getValue(d));

  return (
    <svg className={className} width={width} height={height}>
      <filter id="glow">
        <feDropShadow
          dx="0"
          dy="0"
          stdDeviation={4}
          floodColor={COLOURS.darkpink}
        />
      </filter>
      <Group top={margin.top} left={margin.left}>
        <Group>
          {data.map((d, idx) => {
            const barName = `${getCategory(d)}-${idx}`;
            const barWidth = categoryScale.bandwidth();
            const backgroundBarWidth = barWidth / (1 - barPadding);
            return idx % 2 === 0 ? (
              <Bar
                key={`bar-${barName}-background`}
                x={0}
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                y={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2}
                width={valueMax}
                height={backgroundBarWidth}
                fill={COLOURS.navy}
              />
            ) : null;
          })}
        </Group>
        <GridColumns
          scale={valueScale}
          height={categoryMax}
          numTicks={5}
          stroke={COLOURS.white}
          strokeWidth={4}
          strokeDasharray="10"
          strokeLinecap="round"
        />
        <Group>
          {data.map((d, idx) => {
            const barName = `${getCategory(d)}-${idx}`;
            const barLength = valuePoint(d);
            const barWidth = categoryScale.bandwidth();
            return (
              <Group
                key={`bar-${barName}`}
                onMouseEnter={() => {
                  setIsBarHovered({ ...isBarHovered, [barName]: true });
                }}
                onMouseLeave={() => {
                  setIsBarHovered({ ...isBarHovered, [barName]: false });
                }}
              >
                <Bar
                  x={0}
                  y={categoryPoint(d)}
                  width={barLength}
                  height={barWidth}
                  fill={isBarHovered[barName] ? COLOURS.pink : COLOURS.salmon}
                  filter={isBarHovered[barName] ? "url(#glow)" : undefined}
                />
                {isBarHovered[barName] ? (
                  <Text
                    x={valuePoint(d) - 14}
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    y={categoryPoint(d)! + barWidth * 0.75}
                    fill={COLOURS.white}
                    fontFamily="Inconsolata"
                    fontWeight={800}
                    fontSize={barWidth * 0.75}
                    textAnchor="end"
                  >
                    {getValue(d)}
                  </Text>
                ) : null}
              </Group>
            );
          })}
        </Group>
      </Group>
      <AxisLeft
        scale={categoryScale}
        top={margin.top}
        left={margin.left}
        hideAxisLine
        hideTicks
        tickLabelProps={() => {
          return {
            ...leftTickLabelProps(),
            dx: "-0.5rem",
            dy: "0.25rem",
            fill: COLOURS.white,
            fontSize: "1rem",
            fontFamily: "Inconsolata",
            fontWeight: 800,
          };
        }}
      />
      <AxisBottom
        scale={valueScale}
        top={margin.top + categoryMax}
        left={margin.left}
        hideAxisLine
        hideTicks
        numTicks={5}
        tickLabelProps={() => {
          return {
            ...bottomTickLabelProps(),
            dy: "0.25rem",
            fill: COLOURS.white,
            fontSize: "1rem",
            fontFamily: "Inconsolata",
            fontWeight: 800,
          };
        }}
      />
    </svg>
  );
}
