import type { ElementDefinition } from "cytoscape";
import type { Vertex } from "./elements";

const sortElementsByLevel = (vertices: Vertex[]): Record<number, Vertex[]> => {
  const sortedVertices: Record<number, Vertex[]> = {};

  vertices.forEach((vertex) => {
    if (!sortedVertices[vertex.level]) {
      sortedVertices[vertex.level] = [];
    }

    sortedVertices[vertex.level].push(vertex);
  });

  return sortedVertices;
};

const getCurrentIndent = (
  id: string,
  edges: string[] | undefined,
  vertices: Vertex[]
): [number, number] => {
  let currentIndent = 0;
  let bottomIndent = 0;

  if (!edges?.length) {
    return [100, 0];
  }

  const calculatePointIndent = (pointId: string, subIndent = false) => {
    const point = vertices.find(({ id }) => pointId === id);

    if (point?.edges) {
      if (!subIndent) {
        currentIndent += point.edges.length * 100;
      } else {
        bottomIndent += (point.edges.length - 1) * 200;
      }

      point.edges.forEach((edge) => {
        calculatePointIndent(edge, true);
      });
    }
  };

  calculatePointIndent(id);

  return [currentIndent, bottomIndent];
};

const sortVertices = (
  elements: ElementDefinition[],
  vertices: Vertex[]
): ElementDefinition[] => {
  const sortedVertices: ElementDefinition[] = [];

  elements.forEach((element) => {
    const currentVertex = vertices.find(({ id }) => element.data.id === id);

    if (!currentVertex || (currentVertex?.edges?.length || 0) < 2) {
      sortedVertices.push(element);

      return;
    }

    const firstChildId = currentVertex.edges?.at(0);
    const lastChildId = currentVertex.edges?.at(-1);
    const firstChild = elements.find(({ data }) => data.id === firstChildId);
    const lastChild = elements.find(({ data }) => data.id === lastChildId);

    if (!firstChild?.position || !lastChild?.position) {
      sortedVertices.push(element);

      return;
    }

    const parentVertexTopIndent =
      firstChild.position.y +
      (lastChild.position.y - firstChild.position.y) / 2;

    sortedVertices.push({
      ...element,
      position: { x: element.position?.x || 0, y: parentVertexTopIndent },
    });
  });

  return sortedVertices;
};

export const createGraphElements = (
  vertices: Vertex[]
): ElementDefinition[] => {
  const elements: ElementDefinition[] = [];
  const bridges: ElementDefinition[] = [];
  const sortedVertices = sortElementsByLevel(vertices);

  Object.values(sortedVertices).forEach((vertexItems) => {
    let indent = 0;

    vertexItems.forEach(({ id, label, level, edges }) => {
      const [currentIndent, bottomIndent] = getCurrentIndent(
        id,
        edges,
        vertices
      );
      const parentVertex = vertices.find(({ edges }) => edges?.includes(id));
      const parentElement = elements.find(
        ({ data }) => parentVertex?.id === data.id
      );

      const minTopIndent =
        (parentElement?.position?.y || 0) -
        (parentVertex?.edges?.length || 0) * 100;

      if (minTopIndent > indent) {
        indent = minTopIndent;
      }

      elements.push({
        data: { id, label },
        position: {
          x: level * 250,
          y: indent + currentIndent,
        },
      });

      edges?.forEach((edge) => {
        bridges.push({
          data: { source: id, target: edge },
        });
      });

      indent += currentIndent * 2 + bottomIndent;
    });
  });

  const sortedElements = sortVertices(elements, vertices);

  return [...sortedElements, ...bridges];
};
