/* eslint-disable */
import * as d3 from "d3";
import PropTypes from "prop-types";
import React, { useCallback, useEffect, useRef } from "react";

const ScatterPlotChart = ({
  data = [],
  chartClass = "scatterPlot1",
  yVar = "%sma(50)",
  xVar = "rsi(14)",
  dotRadius = 4,
}) => {
  //I put the various things you might want to customise at the top
  const dotStrokeWidth = 0.5;
  const dotFillOpacity = 0.4;
  const dotTooltipOpacity = 0.2;
  const xVarFormat = ".2%";
  const yVarFormat = ".2f";
  const margins = {
    left: 5,
    top: 5,
    quadrant: 5,
    quadrantBorderRadius: 30,
    scatter: 15,
    dot: 2 + dotRadius,
    tooltip: 3,
  };
  const gradientStartColor = {
    topLeft: "#41E7B0",
    topRight: "#5D5FEF",
    bottomLeft: "#943ebf",
    bottomRight: "#F40467",
  };
  const colors = {
    topLeft: "#5D5FEF",
    topRight: "#41E7B0",
    bottomLeft: "#F40467",
    bottomRight: "#943ebf",
  };
  const tooltipFill = "#D0D0D0";
  const tooltipStroke = "#A0A0A0";
  const tooltipFontSize = 10;

  const ref = useRef();
  const myClass = chartClass; // this can be passed in so multiple instances on same page
  let svg = null;
  const chartData = data;

  const initiateChart = useCallback(() => {
    //declare svg + get parentElement dimensions
    svg = d3.select(ref.current);
    const parentElement = svg.node().parentElement;
    let chartWidth = Math.min(
      parentElement.clientWidth,
      parentElement.clientHeight
    );

    const quadrantWidth =
      (chartWidth - margins.left * 2) / 2 - margins.quadrant;
    //size svg + add mouseover interaction
    svg.attr("width", chartWidth).attr("height", chartWidth);

    //tooltip rect + max width variable
    const tooltipMaxWidth = chartWidth / 2.5;

    svg
      .select(".tooltipRect")
      .attr("visibility", "hidden")
      .attr("pointer-events", "none")
      .attr("fill", tooltipFill)
      .attr("stroke", tooltipStroke)
      .attr("rx", 5)
      .attr("ry", 5)
      .attr("transform", "translate(" + margins.left + "," + margins.top + ")");

    //yExtent
    const yPositiveMax = d3.max(chartData, (d) => (d[yVar] >= 0 ? d[yVar] : 0));
    const yNegativeMax = d3.min(chartData, (d) => (d[yVar] < 0 ? d[yVar] : 0));

    //quadrants
    const quadrants = [
      {
        name: "topLeft",
        xRange: [0, 0.5],
        yRange: [1, 0.5],
        fill: colors["topLeft"],
      },
      {
        name: "topRight",
        xRange: [0.5, 1],
        yRange: [1, 0.5],
        fill: colors["topRight"],
      },
      {
        name: "bottomLeft",
        xRange: [0, 0.5],
        yRange: [0.5, 0],
        fill: colors["bottomLeft"],
      },
      {
        name: "bottomRight",
        xRange: [0.5, 1],
        yRange: [0.5, 0],
        fill: colors["bottomRight"],
      },
    ];
    //add scales to quadrants
    quadrants.map(
      (m) =>
        (m.xScale = d3
          .scaleLinear()
          .domain(m.xRange)
          .range([margins.dot, quadrantWidth - margins.dot]))
    );
    quadrants.map(
      (m) =>
        (m.yExtent = m.name.includes("top")
          ? [0, yPositiveMax]
          : [yNegativeMax, 0])
    );
    quadrants.map(
      (m) =>
        (m.yScale = d3
          .scaleLinear()
          .domain(m.yExtent)
          .range([margins.dot, quadrantWidth - margins.dot]))
    );
    //add quadrants and tooltip lines to data
    chartData.map((m) => (m[xVar] = m[xVar] / 100));
    chartData.map((m) => (m.xQuadrant = m[xVar] > 0.5 ? "Right" : "Left"));
    chartData.map((m) => (m.yQuadrant = m[yVar] >= 0 ? "top" : "bottom"));
    chartData.map(
      (m) =>
        (m.quadrant = quadrants.find(
          (f) => f.name === m.yQuadrant + m.xQuadrant
        ))
    );
    chartData.map((m) => (m.tooltipLines = getLines(m)));
    //quadrant group join
    const quadrantGroup = svg
      .select(".backgroundGroup")
      .selectAll(".quadrantGroup" + myClass)
      .data(quadrants)
      .join(function (group) {
        const enter = group
          .append("g")
          .attr("class", "quadrantGroup" + myClass);
        enter.append("path").attr("class", "quadrantRect");
        const gradient = enter
          .append("defs")
          .append("radialGradient")
          .attr("class", "radialGradient");
        gradient.append("stop").attr("class", "stop1");
        gradient.append("stop").attr("class", "stop2");
        return enter;
      });

    //I can change this so the rectangle is in fact a path with rounded corners
    //but this was quicker for set up as not sure re: final design

    quadrantGroup
      .select(".radialGradient")
      .attr("id", (d, i) => "radialGradient" + i)
      .attr("cy", getGradientY)
      .attr("cx", getGradientX)
      .attr("r", 1.5);

    quadrantGroup
      .select(".stop1")
      .attr("offset", "0%")
      .attr("stop-color", (d) => gradientStartColor[d.name]);

    quadrantGroup
      .select(".stop2")
      .attr("offset", "60%")
      .attr("stop-color", (d) => colors[d.name]);

    quadrantGroup
      .select(".quadrantRect")
      .style("filter", getDropShadow)
      .attr(
        "transform",
        (d) =>
          "translate(" +
          (chartWidth * d.xRange[0] + margins.left) +
          "," +
          (chartWidth * (1 - d.yRange[0]) + margins.top) +
          ")"
      )
      .attr("d", (d) => roundedRectData(quadrantWidth, quadrantWidth, d.name))
      .attr("fill", (d, i) => "url(#radialGradient" + i + ")");

    //dot group join
    const dotGroup = svg
      .select(".chartGroup")
      .selectAll(".dotGroup" + myClass)
      .data(chartData)
      .join(function (group) {
        const enter = group.append("g").attr("class", "dotGroup" + myClass);
        enter.append("circle").attr("class", "dataDot");
        return enter;
      });

    dotGroup
      .select(".dataDot")
      .attr("cx", (d) => d.quadrant.xScale(d[xVar]))
      .attr("cy", (d) => d.quadrant.yScale(d[yVar]))
      .attr("r", dotRadius)
      .attr("fill", "white")
      .attr("fill-opacity", dotFillOpacity)
      .attr("stroke", "white")
      .attr("stroke-width", dotStrokeWidth)
      .attr(
        "transform",
        (d) =>
          "translate(" +
          (d.quadrant.name.includes("Right")
            ? chartWidth / 2 + margins.left
            : margins.top) +
          "," +
          (d.quadrant.name.includes("bottom") ? chartWidth / 2 : 0) +
          ")"
      )
      .on("mouseover", function (event, d) {
        const tooltipLineLength = d.tooltipLines.length;
        //dot opacity
        svg.selectAll(".dataDot").attr("opacity", dotTooltipOpacity);
        d3.select(this).attr("opacity", 1);
        //dynamic tooltip width/height depending on content
        const myWidth =
          d3.max(d.tooltipLines, (m, i) =>
            measureWidth(
              m,
              i >= tooltipLineLength - 2 ? tooltipFontSize - 1 : tooltipFontSize
            )
          ) +
          margins.tooltip * 2;
        const myHeight =
          d.tooltipLines.length * (tooltipFontSize + 2) + margins.tooltip * 2;
        //dynamic tooltip positioning depending on quadrant
        let myX =
          d.quadrant.xScale(d[xVar]) +
          (d.quadrant.name.includes("Right") ? chartWidth / 2 : 0) +
          (d.quadrant.name.includes("Right")
            ? -(myWidth + dotRadius + 2)
            : dotRadius + 2);
        let myY =
          d.quadrant.yScale(d[yVar]) +
          (d.quadrant.name.includes("bottom") ? chartWidth / 2 : 0) +
          (d.quadrant.name.includes("top")
            ? dotRadius + 2
            : -(myHeight + dotRadius * 2 + 2));
        //tooltip def
        d3.select(".tooltipRect")
          .attr("visibility", "visible")
          .attr("x", myX + margins.left)
          .attr("y", myY + margins.top)
          .attr("width", myWidth)
          .attr("height", myHeight);
        //lines group
        const linesGroup = svg
          .select(".tooltipLinesGroup")
          .attr("visibility", "visible")
          .selectAll(".linesGroup" + myClass)
          .data(d.tooltipLines)
          .join(function (group) {
            const enter = group
              .append("g")
              .attr("class", "linesGroup" + myClass);
            enter.append("text").attr("class", "tooltipLine");
            return enter;
          });

        linesGroup.attr(
          "transform",
          "translate(" +
            (myX + margins.left + myWidth / 2) +
            "," +
            (myY + margins.top + dotRadius) +
            ")"
        );

        linesGroup
          .select(".tooltipLine")
          .attr("font-size", (d, i) =>
            i >= tooltipLineLength - 2 ? tooltipFontSize - 1 : tooltipFontSize
          )
          .attr("font-weight", (d, i) => (i === 0 ? "bold" : "normal"))
          .attr("text-anchor", "middle")
          .attr("x", margins.tooltip)
          .attr(
            "y",
            (d, i) =>
              margins.tooltip + tooltipFontSize + i * (tooltipFontSize + 2)
          )
          .text((d) => d);
      })
      .on("mouseout", function () {
        svg.select(".tooltipRect").attr("visibility", "hidden");
        svg.select(".tooltipLinesGroup").attr("visibility", "hidden");
        svg.selectAll(".dataDot").attr("opacity", 1);
      });

    function getLines(m) {
      const myText = m.name;
      //splits names into number of lines based on tooltipMaxWidth
      let words = myText.split(" ");
      const lines = [m.ticker];
      let currentLine = "";
      words.forEach((d, i) => {
        currentLine += (i === 0 ? "" : " ") + d;
        if (measureWidth(currentLine, tooltipFontSize) > tooltipMaxWidth) {
          lines.push(
            currentLine
              .split(" ")
              .filter((f) => f !== d)
              .join(" ")
          );
          currentLine = d;
        }
      });
      lines.push(currentLine);
      lines.push(xVar.toUpperCase() + ": " + d3.format(xVarFormat)(m[xVar]));
      lines.push(yVar.toUpperCase() + ": " + d3.format(yVarFormat)(m[yVar]));
      return lines;
    }

    function getGradientX(d) {
      if (d.name === "topLeft") {
        return 1;
      } else if (d.name === "topRight") {
        return 0;
      } else if (d.name === "bottomLeft") {
        return 1;
      } else if (d.name === "bottomRight") {
        return 0;
      }
    }

    function getGradientY(d) {
      if (d.name === "topLeft") {
        return 1;
      } else if (d.name === "topRight") {
        return 1;
      } else if (d.name === "bottomLeft") {
        return 0;
      } else if (d.name === "bottomRight") {
        return 0;
      }
    }

    function getDropShadow(d) {
      const startShadow = "drop-shadow(";
      const endShadow = " 2px rgb(0 0 0 / 0.4))";
      let midShadow = "";
      if (d.name === "topLeft") {
        midShadow = "-2px -2px";
      } else if (d.name === "topRight") {
        midShadow = "2px -2px";
      } else if (d.name === "bottomLeft") {
        midShadow = "-2px 2px";
      } else if (d.name === "bottomRight") {
        midShadow = "2px 2px";
      }
      return startShadow + midShadow + endShadow;
    }
    function roundedRectData(w, h, quadrantName) {
      let tlr = 0,
        trr = 0,
        brr = 0,
        blr = 0;
      if (quadrantName === "topLeft") {
        tlr = margins.quadrantBorderRadius;
      } else if (quadrantName === "topRight") {
        trr = margins.quadrantBorderRadius;
      } else if (quadrantName === "bottomLeft") {
        blr = margins.quadrantBorderRadius;
      } else if (quadrantName === "bottomRight") {
        brr = margins.quadrantBorderRadius;
      }

      return (
        "M 0 " +
        tlr +
        " A " +
        tlr +
        " " +
        tlr +
        " 0 0 1 " +
        tlr +
        " 0" +
        " L " +
        (w - trr) +
        " 0" +
        " A " +
        trr +
        " " +
        trr +
        " 0 0 1 " +
        w +
        " " +
        trr +
        " L " +
        w +
        " " +
        (h - brr) +
        " A " +
        brr +
        " " +
        brr +
        " 0 0 1 " +
        (w - brr) +
        " " +
        h +
        " L " +
        blr +
        " " +
        h +
        " A " +
        blr +
        " " +
        blr +
        " 0 0 1 0 " +
        (h - blr) +
        " Z"
      );
    }
    function measureWidth(myText, myFontSize) {
      //this was not 100% accurate - I think because of the font family
      //I upped it to 1.2em so that the text always fits.  Seems to work well..
      const context = document.createElement("canvas").getContext("2d");
      context.font = myFontSize * 1.2 + "px sans-serif";
      return context.measureText(myText).width;
    }
  });

  useEffect(() => {
    //copied this from another project
    initiateChart();
  }, [initiateChart, data, ref]);

  return (
    <svg ref={ref}>
      <defs>
        <filter id={"drop-shadow"}>
          <feGaussianBlur></feGaussianBlur>
        </filter>
      </defs>
      <g className={"backgroundGroup"}></g>
      <g className={"chartGroup"}></g>
      <rect className={"tooltipRect"}></rect>
      <g className={"tooltipLinesGroup"}></g>
    </svg>
  );
};

ScatterPlotChart.propTypes = {
  data: PropTypes.array,
  chartClass: PropTypes.string,
  yVar: PropTypes.string,
  xVar: PropTypes.string,
};

export default ScatterPlotChart;
