import { Group } from "@visx/group";
import Pie, { ProvidedProps } from "@visx/shape/lib/shapes/Pie";
import { Text } from "@visx/text";
import React from "react";

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

interface PieChartProps {
  data: PieChartData[];
  /** Width of the entire graph, including labels, in pixels. */
  width: number;
  /** Width of the outer ring of labels, in pixels. Label text may be cut off if specified value is too small. */
  labelWidth: number;
  /** Distance between pie slices, in pixels. */
  padRadius?: number;
  /** Distance of gap in center of pie graph, in pixels. */
  innerRadius?: number;
  /** Font size of text inside the pie, in pixels. */
  pieTextSize?: number;
  /** X-axis offset of the pie text, in pixels. */
  pieTextXOffset?: number;
  /** Y-axis offset of the pie text, in pixels. */
  pieTextYOffset?: number;
  /** Accessor function to get value to display as pie text from datum. */
  getPieDisplayValueFromDatum?: (datum: PieChartData) => string;
  /** Font size of labels outside the pie, in pixels. */
  labelTextSize?: number;
  /** X-axis offset of the label text, in pixels. */
  labelTextXOffset?: number;
  /** Y-axis offset of the label text, in pixels. */
  labelTextYOffset?: number;
  /** Accessor function to get value to display as label text from datum. */
  getLabelDisplayValueFromDatum?: (datum: PieChartData) => string;
  className?: string;
}

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

export function PieChart({
  data,
  width,
  labelWidth,
  padRadius = width * 0.35,
  innerRadius = width * 0.015,
  pieTextSize = 40,
  pieTextXOffset = 0,
  pieTextYOffset = 10,
  getPieDisplayValueFromDatum = (datum: PieChartData) => `${datum.value}%`,
  labelTextSize = 40,
  labelTextXOffset = 0,
  labelTextYOffset = 0,
  getLabelDisplayValueFromDatum = (datum: PieChartData) => `${datum.category}`,
  className,
}: PieChartProps) {
  const pieWidth = width * 0.5 - labelWidth;
  return (
    <svg className={className} width={width} height={width}>
      <Group top={width * 0.5} left={width * 0.5}>
        <Pie
          data={data}
          pieValue={(d: PieChartData) => d.value}
          cornerRadius={10}
          padAngle={0.075}
          padRadius={padRadius}
          innerRadius={innerRadius}
          outerRadius={pieWidth}
        >
          {(pie) => (
            <PieSlice
              {...pie}
              pieTextSize={pieTextSize}
              pieTextXOffset={pieTextXOffset}
              pieTextYOffset={pieTextYOffset}
              getPieDisplayValueFromDatum={getPieDisplayValueFromDatum}
            />
          )}
        </Pie>
        <Pie
          data={data}
          pieValue={(d: PieChartData) => d.value}
          innerRadius={pieWidth}
          outerRadius={width * 0.5}
        >
          {(pie) => (
            <PieSliceLabel
              {...pie}
              labelTextSize={labelTextSize}
              labelTextXOffset={labelTextXOffset}
              labelTextYOffset={labelTextYOffset}
              getLabelDisplayValueFromDatum={getLabelDisplayValueFromDatum}
            />
          )}
        </Pie>
      </Group>
    </svg>
  );
}

type PieSliceProps<PieChartData> = ProvidedProps<PieChartData> & {
  pieTextSize: number;
  pieTextXOffset: number;
  pieTextYOffset: number;
  getPieDisplayValueFromDatum: (datum: PieChartData) => string;
};

export function PieSlice({
  path,
  arcs,
  pieTextSize,
  pieTextXOffset,
  pieTextYOffset,
  getPieDisplayValueFromDatum,
}: PieSliceProps<PieChartData>) {
  return (
    <>
      {arcs.map((arc) => {
        const [centroidX, centroidY] = path.centroid(arc);
        const pathArc = path(arc) as string;

        return (
          <Group className={styles.group} key={`arc-${arc.data.category}`}>
            <path className={styles.piePath} d={pathArc} />
            <Text
              className={styles.pieText}
              x={centroidX + pieTextXOffset}
              y={centroidY + pieTextYOffset}
              textAnchor="middle"
              fontSize={pieTextSize}
            >
              {`${getPieDisplayValueFromDatum(arc.data)}`}
            </Text>
          </Group>
        );
      })}
    </>
  );
}

type PieSliceLabelProps<PieChartData> = ProvidedProps<PieChartData> & {
  labelTextSize: number;
  labelTextXOffset: number;
  labelTextYOffset: number;
  getLabelDisplayValueFromDatum: (datum: PieChartData) => string;
};

export function PieSliceLabel({
  path,
  arcs,
  labelTextSize,
  labelTextXOffset,
  labelTextYOffset,
  getLabelDisplayValueFromDatum,
}: PieSliceLabelProps<PieChartData>) {
  return (
    <>
      {arcs.map((arc) => {
        const [centroidX, centroidY] = path.centroid(arc);
        const pathArc = path(arc) as string;

        return (
          <Group className={styles.group} key={`arc-${arc.data.category}`}>
            <path className={styles.labelPath} d={pathArc} />
            <Text
              className={styles.labelText}
              x={centroidX + labelTextXOffset}
              y={centroidY + labelTextYOffset}
              textAnchor="middle"
              fontSize={labelTextSize}
            >
              {`${getLabelDisplayValueFromDatum(arc.data)}`}
            </Text>
          </Group>
        );
      })}
    </>
  );
}
