import { Box, Button, useTheme } from "@mui/material";
import MaterialChartTooltip from "charts/MaterialChartTooltip";
import moment from "moment";
import { MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { useEffect, useState } from "react";
import {
  Legend,
  Line,
  LineChart,
  ReferenceArea,
  ResponsiveContainer,
  Tooltip,
  TooltipProps,
  XAxis,
  YAxis
} from "recharts";
import { roundTo } from "utils";

export interface LineData {
  timestamp: number;
  points: Point[];
}

export interface Point {
  value: number;
  node: number;
}

interface Props {
  lineData: LineData[];
  averagedData?: LineData[];
  numLines: number;
  describer: MeasurementDescriber;
  interactionLines?: JSX.Element[];
  overlays?: JSX.Element[];
  customHeight?: string | number;
  tooltip?: boolean;
  yMin?: number | string;
  yMax?: number | string;
  hideY?: boolean;
  newXDomain?: number[] | string[];
  multiGraphZoom?: (domain: number[] | string[]) => void;
  multiGraphZoomOut?: boolean;
}

export default function MultiLineGraph(props: Props) {
  const {
    lineData,
    numLines,
    describer,
    interactionLines,
    overlays,
    customHeight,
    tooltip,
    yMin,
    yMax,
    hideY,
    newXDomain,
    multiGraphZoom,
    multiGraphZoomOut,
    averagedData
  } = props;
  const [data, setData] = useState<any[]>([]);
  const theme = useTheme();
  const now = moment();
  const [refLeft, setRefLeft] = useState<number | undefined>();
  const [refRight, setRefRight] = useState<number | undefined>();
  const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);

  useEffect(() => {
    if (newXDomain) {
      setXDomain(newXDomain);
    }
  }, [newXDomain]);

  useEffect(() => {
    let ld: any[] = [];
    lineData.forEach(line => {
      let lines = {
        timestamp: line.timestamp
      } as any;
      line.points.forEach(point => {
        let nodeLabel = (numLines > 1 ? "Node " + (point.node + 1) + " " : "") + describer.label();
        let nodeDetails = describer.nodeDetails();
        if (nodeDetails) {
          nodeLabel = nodeDetails.labels[point.node];
        }
        lines[nodeLabel] = point.value;
      });
      ld.push(lines);
    });
    if (averagedData) {
      averagedData.forEach(line => {
        let lines = {
          timestamp: line.timestamp
        } as any;
        line.points.forEach(point => {
          let nodeLabel =
            (numLines > 1 ? "Node " + (point.node + 1) + " " : "") + describer.label() + "(avg)";
          let nodeDetails = describer.nodeDetails();
          if (nodeDetails) {
            nodeLabel = nodeDetails.labels[point.node] + "(avg)";
          }
          lines[nodeLabel] = point.value;
        });
        ld.push(lines);
      });
    }
    setData(ld);
  }, [lineData, describer, numLines, averagedData]);

  const buildLines = () => {
    let lines: JSX.Element[] = [];
    if (lineData.length > 0) {
      lineData[0].points.forEach(point => {
        let nodeLabel = (numLines > 1 ? "Node " + (point.node + 1) + " " : "") + describer.label();
        let nodeColour = describer.colour();
        let nodeDetails = describer.nodeDetails();
        if (nodeDetails) {
          nodeLabel = nodeDetails.labels[point.node];
          nodeColour = nodeDetails.colours[point.node];
        }
        lines.push(
          <Line
            strokeWidth={4}
            key={nodeLabel}
            dataKey={nodeLabel}
            stroke={nodeColour}
            type="monotone"
          />
        );
      });
    }
    if (averagedData && averagedData.length > 0) {
      averagedData[0].points.forEach(point => {
        let nodeLabel =
          (numLines > 1 ? "Node " + (point.node + 1) + " " : "") + describer.label() + "(avg)";
        let nodeDetails = describer.nodeDetails();
        if (nodeDetails) {
          nodeLabel = nodeDetails.labels[point.node] + "(avg)";
        }
        lines.push(
          <Line
            strokeWidth={5}
            key={nodeLabel}
            dataKey={nodeLabel}
            stroke={"white"}
            type="monotone"
          />
        );
      });
    }
    return lines;
  };

  const zoom = () => {
    let newDomain: number[] | string[] = ["dataMin", "dataMax"];
    if (refLeft && refRight && refLeft !== refRight) {
      refLeft < refRight ? (newDomain = [refLeft, refRight]) : (newDomain = [refRight, refLeft]);
      setRefLeft(undefined);
      setRefRight(undefined);
      if (multiGraphZoom) {
        multiGraphZoom(newDomain);
      } else {
        setXDomain(newDomain);
      }
    }
  };

  const zoomOut = () => {
    setXDomain(["dataMin", "dataMax"]);
  };

  return (
    <Box height={customHeight ?? 350}>
      {!multiGraphZoomOut && (
        <Button variant="outlined" onClick={zoomOut}>
          Zoom Out
        </Button>
      )}
      <ResponsiveContainer width={"100%"} height={"100%"}>
        <LineChart
          data={data}
          onMouseDown={(e: any) => {
            if (e) {
              setRefLeft(e.activeLabel);
            }
          }}
          onMouseMove={(e: any) => {
            if (e) {
              setRefRight(e.activeLabel);
            }
          }}
          onMouseUp={() => {
            setRefLeft(undefined);
            setRefRight(undefined);
            zoom();
          }}>
          {describer.nodeDetails() && <Legend verticalAlign="top" />}
          {tooltip && (
            <Tooltip
              animationEasing="ease-out"
              cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
              labelFormatter={timestamp => moment(timestamp).format("lll")}
              content={(props: TooltipProps<any, any>) => {
                return (
                  <MaterialChartTooltip
                    {...props}
                    valueFormatter={value => {
                      return describer.convertWithUnits(
                        roundTo(parseFloat(String(value)), 2), true
                      );
                    }}
                  />
                );
              }}
            />
          )}
          <YAxis
            axisLine={hideY}
            domain={[yMin || yMin === 0 ? yMin : "auto", yMax || yMax === 0 ? yMax : "auto"]}
            label={{
              value: describer.label() + " (" + describer.unit() + ")",
              position: "insideLeft",
              angle: -90,
              fill: theme.palette.text.primary
            }}
            tick={hideY ? false : { fill: theme.palette.text.primary }}
            allowDataOverflow
          />
          <XAxis
            allowDataOverflow
            dataKey="timestamp"
            domain={xDomain}
            name="Time"
            tickFormatter={timestamp => {
              let t = moment(timestamp);
              return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
            }}
            scale="time"
            type="number"
            tick={{ fill: theme.palette.text.primary }}
            stroke={theme.palette.divider}
            interval="preserveStartEnd"
          />
          {refLeft && refRight ? (
            <ReferenceArea x1={refLeft} x2={refRight} strokeOpacity={0.3} />
          ) : null}
          {buildLines()}
          {interactionLines}
          {overlays}
        </LineChart>
      </ResponsiveContainer>
    </Box>
  );
}
