import { localPoint } from "@visx/event";
import { Point } from "@visx/point";
import { scaleLog } from "@visx/scale";
import { Text } from "@visx/text";
import { TooltipWithBounds, useTooltip, withTooltip } from "@visx/tooltip";
import { Wordcloud as VisxWordcloud } from "@visx/wordcloud";
import React from "react";
import { Color } from "utils/Color";

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

interface WordCloudProps {
  data: Array<WordData>;
  /** Width of the graph, in px */
  width?: number;
  /** Height of the graph, in px */
  height?: number;
  /** Minimum padding between words, in px */
  wordPadding?: number;
  /** Weight of the font of the words */
  fontWeight?: number;
  /** The desired font size of the smallest word, in px.*/
  minFontSize?: number;
  /** The desired font size of the largest word, in px. */
  maxFontSize?: number;
  /** A random seed in the range [0, 1) used for placing the words, change this value to get an alternate placement of words */
  randomSeed?: number;
  /** Type of spiral used for rendering the words, either rectangular or archimedean */
  spiral?: "rectangular" | "archimedean";
  /** ClassName of the wrapper of the wordcloud */
  className?: string;
}

interface WordData {
  text: string;
  value: number;
}

const wordColors = [Color.primaryAccent, Color.primaryAccentLight];
const TOOLTIP_HORIZONTAL_SHIFT_SCALER = 12.0;

export const WordCloud = withTooltip(
  ({
    data,
    width,
    height,
    wordPadding,
    fontWeight,
    minFontSize,
    maxFontSize,
    randomSeed,
    spiral,
    className,
  }: WordCloudProps) => {
    const {
      tooltipData,
      tooltipLeft,
      tooltipTop,
      tooltipOpen,
      showTooltip,
      hideTooltip,
    } = useTooltip<WordData>();

    return (
      <div className={className}>
        <WordCloudWordsMemoized
          width={width}
          height={height}
          data={data}
          wordPadding={wordPadding}
          fontWeight={fontWeight}
          minFontSize={minFontSize}
          maxFontSize={maxFontSize}
          showTooltip={(data, left, top) => {
            showTooltip({
              tooltipData: data,
              tooltipLeft: left,
              tooltipTop: top,
            });
          }}
          hideTooltip={hideTooltip}
          tooltipLeft={tooltipLeft}
          tooltipTop={tooltipTop}
          randomSeed={randomSeed}
          spiral={spiral}
        />

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

/** The internal wordcloud component that actually lays out the word needs to be separate from the tooltip to prevent extra rerendering. */
type WordCloudWordsProps = Omit<WordCloudProps, "className"> & {
  showTooltip: (
    data: WordData,
    tooltipLeft: number,
    tooltipTop: number
  ) => void;
  hideTooltip: () => void;
  // tooltipLeft and tooltipTop are used for preventing unnessary renders
  tooltipLeft?: number;
  tooltipTop?: number;
};
const WordCloudWords: React.FC<WordCloudWordsProps> = ({
  data,
  width = 1000,
  height = 500,
  wordPadding = 30,
  fontWeight = 500,
  minFontSize = 20,
  maxFontSize = 150,
  randomSeed = 0.5,
  spiral = "rectangular",
  showTooltip,
  hideTooltip,
}) => {
  const fontScale = scaleLog({
    domain: [
      Math.min(...data.map((w) => w.value)),
      Math.max(...data.map((w) => w.value)),
    ],
    range: [minFontSize, maxFontSize],
  });
  const fontSizeSetter = (datum: WordData) => fontScale(datum.value);
  const fixedValueGenerator = () => randomSeed;
  return (
    <VisxWordcloud
      words={data}
      width={width}
      height={height}
      fontSize={fontSizeSetter}
      font="Inconsolata, monospace"
      padding={wordPadding}
      spiral={spiral}
      rotate={0}
      random={fixedValueGenerator}
    >
      {(cloudWords) =>
        cloudWords.map((word, index) => {
          return (
            <Text
              key={`wordcloud-word-${word.text ?? ""}-${index}`}
              fill={wordColors[index % wordColors.length]}
              transform={`translate(${word.x ?? 0}, ${word.y ?? 0})`}
              fontSize={word.size}
              fontFamily={word.font}
              fontWeight={fontWeight}
              className={styles.word}
              textAnchor="middle"
              onMouseMove={
                ((e: React.MouseEvent<SVGTextElement, MouseEvent>) => {
                  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
                  ) as Point;

                  if (word.text) {
                    showTooltip(
                      { text: word.text, value: data[index].value },
                      eventSvgCoords.x -
                        word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER,
                      eventSvgCoords.y
                    );
                  }
                }) as React.MouseEventHandler<SVGTextElement>
              }
              onMouseLeave={(_) => hideTooltip()}
            >
              {word.text}
            </Text>
          );
        })
      }
    </VisxWordcloud>
  );
};

const shouldNotRerender = (
  prevProps: WordCloudWordsProps,
  nextProps: WordCloudWordsProps
) => {
  if (
    // if width changes, rerender, else don't rerender for a tooltip change
    prevProps.width === nextProps.width &&
    (prevProps.tooltipLeft !== nextProps.tooltipLeft ||
      prevProps.tooltipTop !== nextProps.tooltipTop ||
      nextProps.tooltipLeft === undefined ||
      nextProps.tooltipTop === undefined)
  ) {
    return true; // do not re-render
  }
  return false; // will re-render
};

const WordCloudWordsMemoized = React.memo(WordCloudWords, shouldNotRerender);
