import * as d3 from 'd3';
import { LINK_SELECTOR, NODE_SELECTOR, IS_HIGHLIGHTED } from './constants';

export const highlightIn = 1;
export const highlightOut = 0.2;

const getCurrent = (idx, collection) => d3.select(collection[idx]);
const getLiveLinks = () => d3.selectAll(`.${LINK_SELECTOR}.${IS_HIGHLIGHTED}`);
const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`);

export const getLiveLinksAsDict = () => {
  return Object.fromEntries(
    getLiveLinks()
      .data()
      .map((d) => [d.uid, d]),
  );
};
export const currentIsLive = (idx, collection) =>
  getCurrent(idx, collection).classed(IS_HIGHLIGHTED);

const backgroundLinks = (selection) => selection.style('stroke-opacity', highlightOut);
const backgroundNodes = (selection) => selection.attr('stroke', '#f2f2f2');
const foregroundLinks = (selection) => selection.style('stroke-opacity', highlightIn);
const foregroundNodes = (selection) => selection.attr('stroke', (d) => d.color);
const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity);
const renewNodes = (selection) => selection.attr('stroke', (d) => d.color);

export const getAllLinkAncestors = (node) => {
  if (node.targetLinks) {
    return node.targetLinks.flatMap((n) => {
      return [n, ...getAllLinkAncestors(n.source)];
    });
  }

  return [];
};

const getAllNodeAncestors = (node) => {
  let allNodes = [];

  if (node.targetLinks) {
    allNodes = node.targetLinks.flatMap((n) => {
      return getAllNodeAncestors(n.source);
    });
  }

  return [...allNodes, node.uid];
};

export const highlightLinks = (d, idx, collection) => {
  const currentLink = getCurrent(idx, collection);
  const currentSourceNode = d3.select(`#${d.source.uid}`);
  const currentTargetNode = d3.select(`#${d.target.uid}`);

  /* Higlight selected link, de-emphasize others */
  backgroundLinks(getOtherLinks());
  foregroundLinks(currentLink);

  /* Do the same to related nodes */
  backgroundNodes(getNodesNotLive());
  foregroundNodes(currentSourceNode);
  foregroundNodes(currentTargetNode);
};

const highlightPath = (parentLinks, parentNodes) => {
  /* de-emphasize everything else */
  backgroundLinks(getOtherLinks());
  backgroundNodes(getNodesNotLive());

  /* highlight correct links */
  parentLinks.forEach(({ uid }) => {
    foregroundLinks(d3.select(`#${uid}`)).classed(IS_HIGHLIGHTED, true);
  });

  /* highlight correct nodes */
  parentNodes.forEach((id) => {
    foregroundNodes(d3.select(`#${id}`)).classed(IS_HIGHLIGHTED, true);
  });
};

const restoreNodes = () => {
  /*
    When paths are unclicked, they can take down nodes that
    are still in use for other paths. This checks the live paths and
    rehighlights their nodes.
  */

  getLiveLinks().each((d) => {
    foregroundNodes(d3.select(`#${d.source.uid}`)).classed(IS_HIGHLIGHTED, true);
    foregroundNodes(d3.select(`#${d.target.uid}`)).classed(IS_HIGHLIGHTED, true);
  });
};

const restorePath = (parentLinks, parentNodes, baseOpacity) => {
  parentLinks.forEach(({ uid }) => {
    renewLinks(d3.select(`#${uid}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
  });

  parentNodes.forEach((id) => {
    d3.select(`#${id}`).classed(IS_HIGHLIGHTED, false);
  });

  if (d3.selectAll(`.${IS_HIGHLIGHTED}`).empty()) {
    renewLinks(getOtherLinks(), baseOpacity);
    renewNodes(getNodesNotLive());
    return;
  }

  backgroundLinks(getOtherLinks());
  backgroundNodes(getNodesNotLive());
  restoreNodes();
};

export const restoreLinks = (baseOpacity) => {
  /*
    if there exist live links, reset to highlight out / pale
    otherwise, reset to base
  */

  if (d3.selectAll(`.${IS_HIGHLIGHTED}`).empty()) {
    renewLinks(d3.selectAll(`.${LINK_SELECTOR}`), baseOpacity);
    renewNodes(d3.selectAll(`.${NODE_SELECTOR}`));
    return;
  }

  backgroundLinks(getOtherLinks());
  backgroundNodes(getNodesNotLive());
};

export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => {
  if (currentIsLive(idx, collection)) {
    restorePath([d], [d.source.uid, d.target.uid], baseOpacity);
    restoreNodes();
    return;
  }

  highlightPath([d], [d.source.uid, d.target.uid]);
};

export const togglePathHighlights = (baseOpacity, d, idx, collection) => {
  const parentLinks = getAllLinkAncestors(d);
  const parentNodes = getAllNodeAncestors(d);
  const currentNode = getCurrent(idx, collection);

  /* if this node is already live, make it unlive and reset its path */
  if (currentIsLive(idx, collection)) {
    currentNode.classed(IS_HIGHLIGHTED, false);
    restorePath(parentLinks, parentNodes, baseOpacity);
    return;
  }

  highlightPath(parentLinks, parentNodes);
};
