import { ReactNode, useEffect, useRef, useState } from "react";
import { apiAuth } from "./api";
import {
  GraphCanvas,
  GraphCanvasRef,
  GraphEdge,
  GraphNode,
  SelectionProps,
  SelectionResult,
  useSelection,
} from "reagraph";
import { customTheme } from "./NetworkTheme";
import { useSession } from "./Session";
import { useNavigate } from "react-router";

interface NetworkData {
  nodes: GraphNode[];
  edges: GraphEdge[];
}
interface CustomSelectionProps extends SelectionProps {
  ignore: (GraphEdge | undefined)[];
}

const useCustomSelection = (props: CustomSelectionProps): SelectionResult => {
  var result = useSelection(props);
  result.actives = result.actives.filter(
    (s) => !props.ignore.map((edge) => edge?.id).includes(s)
  );
  const ignored_nodes = props.ignore.map((edge) =>
    edge &&
    result.selections?.includes(edge.source) &&
    !result.selections?.includes(edge.target)
      ? edge.target
      : ""
  );
  result.actives = result.actives.filter((s) => !ignored_nodes.includes(s));
  return result;
};

export const GraphComponent = () => {
  let initialData = { nodes: [], edges: [] } as NetworkData;
  const [data, setData] = useState(initialData);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [threed, setThreed] = useState(false);
  const [likes, setLikes] = useState(2);
  const [popularity, setPopularity] = useState(false);
  const [mutuality, setMutuality] = useState(false);
  const { user, teams } = useSession();
  const navigate = useNavigate();
  useEffect(() => {
    user?.scopes.includes(`team:${teams?.activeTeam}`) ||
      teams?.activeTeam === 42 ||
      navigate("/", { replace: true });
  }, [user]);

  async function loadData() {
    setLoading(true);
    if (teams) {
      await apiAuth(`analysis/graph_json/${teams?.activeTeam}`, null)
        .then((data) => {
          if (data.detail) {
            setError(data.detail);
            return initialData;
          } else {
            setError("");
            return data as Promise<NetworkData>;
          }
        })
        .then((data) => setData(data))
        .catch(() => setError("no access"));
      setLoading(false);
    } else setError("team unknown");
  }

  useEffect(() => {
    loadData();
  }, [teams]);

  const graphRef = useRef<GraphCanvasRef | null>(null);

  function handleThreed() {
    setThreed(!threed);
    graphRef.current?.fitNodesInView();
    graphRef.current?.centerGraph();
    graphRef.current?.resetControls();
  }

  function handlePopularity() {
    popularityLabel(!popularity);
    setPopularity(!popularity);
  }

  function handleMutuality() {
    colorMatches(!mutuality);
    setMutuality(!mutuality);
  }

  function showLabel() {
    switch (likes) {
      case 0:
        return "dislike";
      case 1:
        return "both";
      case 2:
        return "like";
    }
  }

  function findMatches(edges: GraphEdge[]) {
    const adjacencyList = edges.map(
      (edge) => edge.source + edge.target + edge.data.relation
    );
    return edges.filter((edge) =>
      adjacencyList.includes(edge.target + edge.source + edge.data.relation)
    );
  }
  //const matches = useMemo(() => findMatches(data.edges), [])

  function colorMatches(mutuality: boolean) {
    const matches = findMatches(data.edges);
    const newEdges = data.edges;
    if (mutuality) {
      newEdges.forEach((edge) => {
        if (
          (likes === 1 || edge.data.relation === likes) &&
          matches.map((edge) => edge.id).includes(edge.id)
        ) {
          edge.fill = "#9c3";
          if (edge.size) edge.size = edge.size * 1.5;
        }
      });
    } else {
      newEdges.forEach((edge) => {
        if (
          (likes === 1 || edge.data.relation === likes) &&
          matches.map((edge) => edge.id).includes(edge.id)
        ) {
          edge.fill = edge.data.origFill;
          edge.size = edge.data.origSize;
        }
      });
    }
    setData({ nodes: data.nodes, edges: newEdges });
  }

  function popularityLabel(popularity: boolean) {
    const newNodes = data.nodes;
    console.log(data.nodes);
    if (popularity) {
      newNodes.forEach(
        (node) => (node.subLabel = `pop.: ${node.data.inDegree.toFixed(1)}`)
      );
    } else {
      newNodes.forEach((node) => (node.subLabel = undefined));
    }
    setData({ nodes: newNodes, edges: data.edges });
  }

  useEffect(() => {
    if (mutuality) colorMatches(false);
    colorMatches(mutuality);
  }, [likes]);

  const {
    selections,
    actives,
    onNodeClick,
    onCanvasClick,
    onNodePointerOver,
    onNodePointerOut,
  } = useCustomSelection({
    ref: graphRef,
    nodes: data.nodes,
    edges: data.edges.filter((edge) => edge.data.relation === likes),
    ignore: data.edges.map((edge) => {
      if (likes === 1 && edge.data.relation !== 2) return edge;
    }),
    pathSelectionType: "out",
    pathHoverType: "in",
    type: "multiModifier",
  });

  let content: ReactNode;
  if (loading) {
    content = <span className="loader" />;
  } else if (error) {
    content = <span>{error}</span>;
  } else {
    content = (
      <>
        <GraphCanvas
          draggable
          cameraMode={threed ? "rotate" : "pan"}
          layoutType={threed ? "forceDirected3d" : "forceDirected2d"}
          layoutOverrides={{
            nodeStrength: -200,
            linkDistance: 100,
          }}
          labelType="nodes"
          sizingType="attribute"
          sizingAttribute={popularity ? "inDegree" : undefined}
          ref={graphRef}
          theme={customTheme}
          nodes={data.nodes}
          edges={data.edges.filter(
            (edge) => edge.data.relation === likes || likes === 1
          )}
          selections={selections}
          actives={actives}
          onCanvasClick={onCanvasClick}
          onNodeClick={onNodeClick}
          onNodePointerOut={onNodePointerOut}
          onNodePointerOver={onNodePointerOver}
        />
        <button
          className="infobutton"
          onClick={() => {
            const dialog = document.querySelector("dialog[id='InfoDialog']");
            (dialog as HTMLDialogElement).showModal();
          }}
        >
          info
        </button>

        <dialog
          id="InfoDialog"
          style={{ textAlign: "left" }}
          onClick={(event) => {
            event.currentTarget.close();
          }}
        >
          scroll to zoom
          <br />
          <br />
          <b>hover</b>: show inbound links
          <br />
          <b>click</b>: show outward links
          <br />
          <br />
          multi-selection possible
          <br />
          with <i>Ctrl</i> or <i>Shift</i>
          <br />
          <br />
          drag to pan/rotate
        </dialog>
      </>
    );
  }

  return (
    <div style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0 }}>
      <div className="controls">
        <div className="control" onClick={handleMutuality}>
          <div className="switch">
            <input type="checkbox" checked={mutuality} onChange={() => {}} />
            <span className="slider round"></span>
          </div>
          <span>mutuality</span>
        </div>

        <div className="control" onClick={handleThreed}>
          <span>2D</span>
          <div className="switch">
            <input type="checkbox" checked={threed} onChange={() => {}} />
            <span className="slider round"></span>
          </div>
          <span>3D</span>
        </div>

        <div className="control">
          <div className="stack column">
            <datalist id="markers">
              <option value="0"></option>
              <option value="1"></option>
              <option value="2"></option>
            </datalist>
            <div id="three-slider">
              <label>😬</label>
              <input
                type="range"
                list="markers"
                min="0"
                max="2"
                step="1"
                width="16px"
                onChange={(evt) => setLikes(Number(evt.target.value))}
              />
              <label>😍</label>
            </div>
            {showLabel()}
          </div>
        </div>

        <div className="control" onClick={handlePopularity}>
          <div className="switch">
            <input type="checkbox" checked={popularity} onChange={() => {}} />
            <span className="slider round"></span>
          </div>
          <span>
            popularity<sup>*</sup>
          </span>
        </div>
      </div>

      {popularity && (
        <div
          style={{ position: "absolute", bottom: 0, right: "10px", zIndex: 10 }}
        >
          <span className="grey" style={{ fontSize: "70%" }}>
            <sup>*</sup>popularity meassured by rank-weighted in-degree
          </span>
        </div>
      )}
      {content}
    </div>
  );
};
