import { Graph } from '../graphlib/graph.mjs';
import forEach from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/forEach.mjs';
import min from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/min.mjs';
import map from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/map.mjs';
import isUndefined from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/isUndefined.mjs';
import has from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/has.mjs';
import uniqueId from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/uniqueId.mjs';
import range from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/range.mjs';
import max from '../../../../../lodash-es@4.17.23/node_modules/lodash-es/max.mjs';

/*
 * Adds a dummy node to the graph and return v.
 */
function addDummyNode(g, type, attrs, name) {
  var v;
  do {
    v = uniqueId(name);
  } while (g.hasNode(v));

  attrs.dummy = type;
  g.setNode(v, attrs);
  return v;
}

/*
 * Returns a new graph with only simple edges. Handles aggregation of data
 * associated with multi-edges.
 */
function simplify(g) {
  var simplified = new Graph().setGraph(g.graph());
  forEach(g.nodes(), function (v) {
    simplified.setNode(v, g.node(v));
  });
  forEach(g.edges(), function (e) {
    var simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 };
    var label = g.edge(e);
    simplified.setEdge(e.v, e.w, {
      weight: simpleLabel.weight + label.weight,
      minlen: Math.max(simpleLabel.minlen, label.minlen),
    });
  });
  return simplified;
}

function asNonCompoundGraph(g) {
  var simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph());
  forEach(g.nodes(), function (v) {
    if (!g.children(v).length) {
      simplified.setNode(v, g.node(v));
    }
  });
  forEach(g.edges(), function (e) {
    simplified.setEdge(e, g.edge(e));
  });
  return simplified;
}

/*
 * Finds where a line starting at point ({x, y}) would intersect a rectangle
 * ({x, y, width, height}) if it were pointing at the rectangle's center.
 */
function intersectRect(rect, point) {
  var x = rect.x;
  var y = rect.y;

  // Rectangle intersection algorithm from:
  // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
  var dx = point.x - x;
  var dy = point.y - y;
  var w = rect.width / 2;
  var h = rect.height / 2;

  if (!dx && !dy) {
    throw new Error('Not possible to find intersection inside of the rectangle');
  }

  var sx, sy;
  if (Math.abs(dy) * w > Math.abs(dx) * h) {
    // Intersection is top or bottom of rect.
    if (dy < 0) {
      h = -h;
    }
    sx = (h * dx) / dy;
    sy = h;
  } else {
    // Intersection is left or right of rect.
    if (dx < 0) {
      w = -w;
    }
    sx = w;
    sy = (w * dy) / dx;
  }

  return { x: x + sx, y: y + sy };
}

/*
 * Given a DAG with each node assigned "rank" and "order" properties, this
 * function will produce a matrix with the ids of each node.
 */
function buildLayerMatrix(g) {
  var layering = map(range(maxRank(g) + 1), function () {
    return [];
  });
  forEach(g.nodes(), function (v) {
    var node = g.node(v);
    var rank = node.rank;
    if (!isUndefined(rank)) {
      layering[rank][node.order] = v;
    }
  });
  return layering;
}

/*
 * Adjusts the ranks for all nodes in the graph such that all nodes v have
 * rank(v) >= 0 and at least one node w has rank(w) = 0.
 */
function normalizeRanks(g) {
  var min$1 = min(
    map(g.nodes(), function (v) {
      return g.node(v).rank;
    }),
  );
  forEach(g.nodes(), function (v) {
    var node = g.node(v);
    if (has(node, 'rank')) {
      node.rank -= min$1;
    }
  });
}

function removeEmptyRanks(g) {
  // Ranks may not start at 0, so we need to offset them
  var offset = min(
    map(g.nodes(), function (v) {
      return g.node(v).rank;
    }),
  );

  var layers = [];
  forEach(g.nodes(), function (v) {
    var rank = g.node(v).rank - offset;
    if (!layers[rank]) {
      layers[rank] = [];
    }
    layers[rank].push(v);
  });

  var delta = 0;
  var nodeRankFactor = g.graph().nodeRankFactor;
  forEach(layers, function (vs, i) {
    if (isUndefined(vs) && i % nodeRankFactor !== 0) {
      --delta;
    } else if (delta) {
      forEach(vs, function (v) {
        g.node(v).rank += delta;
      });
    }
  });
}

function addBorderNode(g, prefix, rank, order) {
  var node = {
    width: 0,
    height: 0,
  };
  if (arguments.length >= 4) {
    node.rank = rank;
    node.order = order;
  }
  return addDummyNode(g, 'border', node, prefix);
}

function maxRank(g) {
  return max(
    map(g.nodes(), function (v) {
      var rank = g.node(v).rank;
      if (!isUndefined(rank)) {
        return rank;
      }
    }),
  );
}

/*
 * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
 * function returns true for an entry it goes into `lhs`. Otherwise it goes
 * into `rhs.
 */
function partition(collection, fn) {
  var result = { lhs: [], rhs: [] };
  forEach(collection, function (value) {
    if (fn(value)) {
      result.lhs.push(value);
    } else {
      result.rhs.push(value);
    }
  });
  return result;
}

function notime(name, fn) {
  return fn();
}

export { addBorderNode, addDummyNode, asNonCompoundGraph, buildLayerMatrix, intersectRect, maxRank, normalizeRanks, notime, partition, removeEmptyRanks, simplify };
