import { select, line, curveLinear } from 'd3';
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils.js';
import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js';
import { getConfig } from '../../mermaid/src/config.js';
import { log } from '../../mermaid/src/logger.js';
import utils from '../../mermaid/src/utils.js';
import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js';
import common from '../../mermaid/src/diagrams/common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js';
import ELK from 'elkjs/lib/elk.bundled.js';
import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js';
import { addEdgeMarkers } from '../../mermaid/src/dagre-wrapper/edgeMarker.js';

const elk = new ELK();

let portPos = {};

const conf = {};
export const setConf = function (cnf) {
  const keys = Object.keys(cnf);
  for (const key of keys) {
    conf[key] = cnf[key];
  }
};

let nodeDb = {};

// /**
//  * Function that adds the vertices found during parsing to the graph to be rendered.
//  *
//  * @param vert Object containing the vertices.
//  * @param g The graph that is to be drawn.
//  * @param svgId
//  * @param root
//  * @param doc
//  * @param diagObj
//  */
export const addVertices = async function (vert, svgId, root, doc, diagObj, parentLookupDb, graph) {
  const svg = root.select(`[id="${svgId}"]`);
  const nodes = svg.insert('g').attr('class', 'nodes');
  const keys = [...vert.keys()];

  // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
  await Promise.all(
    keys.map(async function (id) {
      const vertex = vert.get(id);

      /**
       * Variable for storing the classes for the vertex
       *
       * @type {string}
       */
      let classStr = 'default';
      if (vertex.classes.length > 0) {
        classStr = vertex.classes.join(' ');
      }
      classStr = classStr + ' flowchart-label';
      const styles = getStylesFromArray(vertex.styles);

      // Use vertex id as text in the box if no text is provided by the graph definition
      let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;

      // We create a SVG label, either by delegating to addHtmlLabel or manually
      const labelData = { width: 0, height: 0 };

      const ports = [
        {
          id: vertex.id + '-west',
          layoutOptions: {
            'port.side': 'WEST',
          },
        },
        {
          id: vertex.id + '-east',
          layoutOptions: {
            'port.side': 'EAST',
          },
        },
        {
          id: vertex.id + '-south',
          layoutOptions: {
            'port.side': 'SOUTH',
          },
        },
        {
          id: vertex.id + '-north',
          layoutOptions: {
            'port.side': 'NORTH',
          },
        },
      ];

      let radius = 0;
      let _shape = '';
      let layoutOptions = {};
      // Set the shape based parameters
      switch (vertex.type) {
        case 'round':
          radius = 5;
          _shape = 'rect';
          break;
        case 'square':
          _shape = 'rect';
          break;
        case 'diamond':
          _shape = 'question';
          layoutOptions = {
            portConstraints: 'FIXED_SIDE',
          };
          break;
        case 'hexagon':
          _shape = 'hexagon';
          break;
        case 'odd':
          _shape = 'rect_left_inv_arrow';
          break;
        case 'lean_right':
          _shape = 'lean_right';
          break;
        case 'lean_left':
          _shape = 'lean_left';
          break;
        case 'trapezoid':
          _shape = 'trapezoid';
          break;
        case 'inv_trapezoid':
          _shape = 'inv_trapezoid';
          break;
        case 'odd_right':
          _shape = 'rect_left_inv_arrow';
          break;
        case 'circle':
          _shape = 'circle';
          break;
        case 'ellipse':
          _shape = 'ellipse';
          break;
        case 'stadium':
          _shape = 'stadium';
          break;
        case 'subroutine':
          _shape = 'subroutine';
          break;
        case 'cylinder':
          _shape = 'cylinder';
          break;
        case 'group':
          _shape = 'rect';
          break;
        case 'doublecircle':
          _shape = 'doublecircle';
          break;
        default:
          _shape = 'rect';
      }

      // Add the node
      const node = {
        labelStyle: styles.labelStyle,
        shape: _shape,
        labelText: vertexText,
        labelType: vertex.labelType,
        rx: radius,
        ry: radius,
        class: classStr,
        style: styles.style,
        id: vertex.id,
        link: vertex.link,
        linkTarget: vertex.linkTarget,
        tooltip: diagObj.db.getTooltip(vertex.id) || '',
        domId: diagObj.db.lookUpDomId(vertex.id),
        haveCallback: vertex.haveCallback,
        width: vertex.type === 'group' ? 500 : undefined,
        dir: vertex.dir,
        type: vertex.type,
        props: vertex.props,
        padding: getConfig().flowchart.padding,
      };
      let boundingBox;
      let nodeEl;

      // Add the element to the DOM
      if (node.type !== 'group') {
        nodeEl = await insertNode(nodes, node, vertex.dir);
        boundingBox = nodeEl.node().getBBox();
      } else {
        const { shapeSvg, bbox } = await labelHelper(nodes, node, undefined, true);
        labelData.width = bbox.width;
        labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
        labelData.height = bbox.height;
        labelData.labelNode = shapeSvg.node();
        node.labelData = labelData;
      }
      // const { shapeSvg, bbox } = await labelHelper(svg, node, undefined, true);

      const data = {
        id: vertex.id,
        ports: vertex.type === 'diamond' ? ports : [],
        // labelStyle: styles.labelStyle,
        // shape: _shape,
        layoutOptions,
        labelText: vertexText,
        labelData,
        // labels: [{ text: vertexText }],
        // rx: radius,
        // ry: radius,
        // class: classStr,
        // style: styles.style,
        // link: vertex.link,
        // linkTarget: vertex.linkTarget,
        // tooltip: diagObj.db.getTooltip(vertex.id) || '',
        domId: diagObj.db.lookUpDomId(vertex.id),
        // haveCallback: vertex.haveCallback,
        width: boundingBox?.width,
        height: boundingBox?.height,
        // dir: vertex.dir,
        type: vertex.type,
        // props: vertex.props,
        // padding: getConfig().flowchart.padding,
        // boundingBox,
        el: nodeEl,
        parent: parentLookupDb.parentById[vertex.id],
      };
      // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
      // graph.children.push({
      //   ...data,
      // });
      // }
      nodeDb[node.id] = data;
      // log.trace('setNode', {
      //   labelStyle: styles.labelStyle,
      //   shape: _shape,
      //   labelText: vertexText,
      //   rx: radius,
      //   ry: radius,
      //   class: classStr,
      //   style: styles.style,
      //   id: vertex.id,
      //   domId: diagObj.db.lookUpDomId(vertex.id),
      //   width: vertex.type === 'group' ? 500 : undefined,
      //   type: vertex.type,
      //   dir: vertex.dir,
      //   props: vertex.props,
      //   padding: getConfig().flowchart.padding,
      //   parent: parentLookupDb.parentById[vertex.id],
      // });
    })
  );
  return graph;
};

const getNextPosition = (position, edgeDirection, graphDirection) => {
  const portPos = {
    TB: {
      in: {
        north: 'north',
      },
      out: {
        south: 'west',
        west: 'east',
        east: 'south',
      },
    },
    LR: {
      in: {
        west: 'west',
      },
      out: {
        east: 'south',
        south: 'north',
        north: 'east',
      },
    },
    RL: {
      in: {
        east: 'east',
      },
      out: {
        west: 'north',
        north: 'south',
        south: 'west',
      },
    },
    BT: {
      in: {
        south: 'south',
      },
      out: {
        north: 'east',
        east: 'west',
        west: 'north',
      },
    },
  };
  portPos.TD = portPos.TB;
  return portPos[graphDirection][edgeDirection][position];
  // return 'south';
};

const getNextPort = (node, edgeDirection, graphDirection) => {
  log.info('getNextPort', { node, edgeDirection, graphDirection });
  if (!portPos[node]) {
    switch (graphDirection) {
      case 'TB':
      case 'TD':
        portPos[node] = {
          inPosition: 'north',
          outPosition: 'south',
        };
        break;
      case 'BT':
        portPos[node] = {
          inPosition: 'south',
          outPosition: 'north',
        };
        break;
      case 'RL':
        portPos[node] = {
          inPosition: 'east',
          outPosition: 'west',
        };
        break;
      case 'LR':
        portPos[node] = {
          inPosition: 'west',
          outPosition: 'east',
        };
        break;
    }
  }
  const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition;

  if (edgeDirection === 'in') {
    portPos[node].inPosition = getNextPosition(
      portPos[node].inPosition,
      edgeDirection,
      graphDirection
    );
  } else {
    portPos[node].outPosition = getNextPosition(
      portPos[node].outPosition,
      edgeDirection,
      graphDirection
    );
  }
  return result;
};

const getEdgeStartEndPoint = (edge, dir) => {
  let source = edge.start;
  let target = edge.end;

  // Save the original source and target
  const sourceId = source;
  const targetId = target;

  const startNode = nodeDb[source];
  const endNode = nodeDb[target];

  if (!startNode || !endNode) {
    return { source, target };
  }

  if (startNode.type === 'diamond') {
    source = `${source}-${getNextPort(source, 'out', dir)}`;
  }

  if (endNode.type === 'diamond') {
    target = `${target}-${getNextPort(target, 'in', dir)}`;
  }

  // Add the edge to the graph
  return { source, target, sourceId, targetId };
};

/**
 * Add edges to graph based on parsed graph definition
 *
 * @param {object} edges The edges to add to the graph
 * @param {object} g The graph object
 * @param cy
 * @param diagObj
 * @param graph
 * @param svg
 */
export const addEdges = function (edges, diagObj, graph, svg) {
  log.info('abc78 edges = ', edges);
  const labelsEl = svg.insert('g').attr('class', 'edgeLabels');
  let linkIdCnt = {};
  let dir = diagObj.db.getDirection();
  let defaultStyle;
  let defaultLabelStyle;

  if (edges.defaultStyle !== undefined) {
    const defaultStyles = getStylesFromArray(edges.defaultStyle);
    defaultStyle = defaultStyles.style;
    defaultLabelStyle = defaultStyles.labelStyle;
  }

  edges.forEach(function (edge) {
    // Identify Link
    const linkIdBase = 'L-' + edge.start + '-' + edge.end;
    // count the links from+to the same node to give unique id
    if (linkIdCnt[linkIdBase] === undefined) {
      linkIdCnt[linkIdBase] = 0;
      log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
    } else {
      linkIdCnt[linkIdBase]++;
      log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
    }
    let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
    log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
    const linkNameStart = 'LS-' + edge.start;
    const linkNameEnd = 'LE-' + edge.end;

    const edgeData = { style: '', labelStyle: '' };
    edgeData.minlen = edge.length || 1;
    //edgeData.id = 'id' + cnt;

    // Set link type for rendering
    if (edge.type === 'arrow_open') {
      edgeData.arrowhead = 'none';
    } else {
      edgeData.arrowhead = 'normal';
    }

    // Check of arrow types, placed here in order not to break old rendering
    edgeData.arrowTypeStart = 'arrow_open';
    edgeData.arrowTypeEnd = 'arrow_open';

    /* eslint-disable no-fallthrough */
    switch (edge.type) {
      case 'double_arrow_cross':
        edgeData.arrowTypeStart = 'arrow_cross';
      case 'arrow_cross':
        edgeData.arrowTypeEnd = 'arrow_cross';
        break;
      case 'double_arrow_point':
        edgeData.arrowTypeStart = 'arrow_point';
      case 'arrow_point':
        edgeData.arrowTypeEnd = 'arrow_point';
        break;
      case 'double_arrow_circle':
        edgeData.arrowTypeStart = 'arrow_circle';
      case 'arrow_circle':
        edgeData.arrowTypeEnd = 'arrow_circle';
        break;
    }

    let style = '';
    let labelStyle = '';

    switch (edge.stroke) {
      case 'normal':
        style = 'fill:none;';
        if (defaultStyle !== undefined) {
          style = defaultStyle;
        }
        if (defaultLabelStyle !== undefined) {
          labelStyle = defaultLabelStyle;
        }
        edgeData.thickness = 'normal';
        edgeData.pattern = 'solid';
        break;
      case 'dotted':
        edgeData.thickness = 'normal';
        edgeData.pattern = 'dotted';
        edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
        break;
      case 'thick':
        edgeData.thickness = 'thick';
        edgeData.pattern = 'solid';
        edgeData.style = 'stroke-width: 3.5px;fill:none;';
        break;
    }
    if (edge.style !== undefined) {
      const styles = getStylesFromArray(edge.style);
      style = styles.style;
      labelStyle = styles.labelStyle;
    }

    edgeData.style = edgeData.style += style;
    edgeData.labelStyle = edgeData.labelStyle += labelStyle;

    if (edge.interpolate !== undefined) {
      edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
    } else if (edges.defaultInterpolate !== undefined) {
      edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
    } else {
      edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
    }

    if (edge.text === undefined) {
      if (edge.style !== undefined) {
        edgeData.arrowheadStyle = 'fill: #333';
      }
    } else {
      edgeData.arrowheadStyle = 'fill: #333';
      edgeData.labelpos = 'c';
    }

    edgeData.labelType = edge.labelType;
    edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');

    if (edge.style === undefined) {
      edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
    }

    edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');

    edgeData.id = linkId;
    edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;

    const labelEl = insertEdgeLabel(labelsEl, edgeData);

    // calculate start and end points of the edge, note that the source and target
    // can be modified for shapes that have ports
    const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
    log.debug('abc78 source and target', source, target);
    // Add the edge to the graph
    graph.edges.push({
      id: 'e' + edge.start + edge.end,
      sources: [source],
      targets: [target],
      sourceId,
      targetId,
      labelEl: labelEl,
      labels: [
        {
          width: edgeData.width,
          height: edgeData.height,
          orgWidth: edgeData.width,
          orgHeight: edgeData.height,
          text: edgeData.label,
          layoutOptions: {
            'edgeLabels.inline': 'true',
            'edgeLabels.placement': 'CENTER',
          },
        },
      ],
      edgeData,
    });
  });
  return graph;
};

// TODO: break out and share with dagre wrapper. The current code in dagre wrapper also adds
// adds the line to the graph, but we don't need that here. This is why we can't use the dagre
// wrapper directly for this
/**
 * Add the markers to the edge depending on the type of arrow is
 * @param svgPath
 * @param edgeData
 * @param diagramType
 * @param arrowMarkerAbsolute
 * @param id
 */
const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute, id) {
  let url = '';
  // Check configuration for absolute path
  if (arrowMarkerAbsolute) {
    url =
      window.location.protocol +
      '//' +
      window.location.host +
      window.location.pathname +
      window.location.search;
    url = url.replace(/\(/g, '\\(');
    url = url.replace(/\)/g, '\\)');
  }

  // look in edge data and decide which marker to use
  addEdgeMarkers(svgPath, edgeData, url, id, diagramType);
};

/**
 * Returns the all the styles from classDef statements in the graph definition.
 *
 * @param text
 * @param diagObj
 * @returns {Map<string, import('../../mermaid/src/diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
 */
export const getClasses = function (text, diagObj) {
  log.info('Extracting classes');
  return diagObj.db.getClasses();
};

const addSubGraphs = function (db) {
  const parentLookupDb = { parentById: {}, childrenById: {} };
  const subgraphs = db.getSubGraphs();
  log.info('Subgraphs - ', subgraphs);
  subgraphs.forEach(function (subgraph) {
    subgraph.nodes.forEach(function (node) {
      parentLookupDb.parentById[node] = subgraph.id;
      if (parentLookupDb.childrenById[subgraph.id] === undefined) {
        parentLookupDb.childrenById[subgraph.id] = [];
      }
      parentLookupDb.childrenById[subgraph.id].push(node);
    });
  });

  subgraphs.forEach(function (subgraph) {
    const data = { id: subgraph.id };
    if (parentLookupDb.parentById[subgraph.id] !== undefined) {
      data.parent = parentLookupDb.parentById[subgraph.id];
    }
  });
  return parentLookupDb;
};

const calcOffset = function (src, dest, parentLookupDb) {
  const ancestor = findCommonAncestor(src, dest, parentLookupDb);
  if (ancestor === undefined || ancestor === 'root') {
    return { x: 0, y: 0 };
  }

  const ancestorOffset = nodeDb[ancestor].offset;
  return { x: ancestorOffset.posX, y: ancestorOffset.posY };
};

const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb, id) {
  const offset = calcOffset(edge.sourceId, edge.targetId, parentLookupDb);

  const src = edge.sections[0].startPoint;
  const dest = edge.sections[0].endPoint;
  const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];

  const segPoints = segments.map((segment) => [segment.x + offset.x, segment.y + offset.y]);
  const points = [
    [src.x + offset.x, src.y + offset.y],
    ...segPoints,
    [dest.x + offset.x, dest.y + offset.y],
  ];

  const { x, y } = getLineFunctionsWithOffset(edge.edgeData);
  const curve = line().x(x).y(y).curve(curveLinear);
  const edgePath = edgesEl
    .insert('path')
    .attr('d', curve(points))
    .attr('class', 'path ' + edgeData.classes)
    .attr('fill', 'none');
  Object.entries(edgeData).forEach(([key, value]) => {
    if (key !== 'classes') {
      edgePath.attr(key, value);
    }
  });
  const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel');
  const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl));
  const box = edgeWithLabel.node().firstChild.getBoundingClientRect();
  edgeWithLabel.attr('width', box.width);
  edgeWithLabel.attr('height', box.height);

  edgeG.attr(
    'transform',
    `translate(${edge.labels[0].x + offset.x}, ${edge.labels[0].y + offset.y})`
  );
  addMarkersToEdge(edgePath, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute, id);
};

/**
 * Recursive function that iterates over an array of nodes and inserts the children of each node.
 * It also recursively populates the inserts the children of the children and so on.
 * @param nodeArray
 * @param parentLookupDb
 */
const insertChildren = (nodeArray, parentLookupDb) => {
  nodeArray.forEach((node) => {
    // Check if we have reached the end of the tree
    if (!node.children) {
      node.children = [];
    }
    // Check if the node has children
    const childIds = parentLookupDb.childrenById[node.id];
    // If the node has children, add them to the node
    if (childIds) {
      childIds.forEach((childId) => {
        node.children.push(nodeDb[childId]);
      });
    }
    // Recursive call
    insertChildren(node.children, parentLookupDb);
  });
};

/**
 * Draws a flowchart in the tag with id: id based on the graph definition in text.
 *
 * @param text
 * @param id
 */

export const draw = async function (text, id, _version, diagObj) {
  const { securityLevel, flowchart: conf } = getConfig();
  nodeDb = {};
  portPos = {};
  const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy');
  let graph = {
    id: 'root',
    layoutOptions: {
      'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
      'elk.layered.spacing.edgeNodeBetweenLayers': conf?.nodeSpacing ? `${conf.nodeSpacing}` : '30',
      // 'elk.layered.mergeEdges': 'true',
      'elk.direction': 'DOWN',
      // 'elk.ports.sameLayerEdges': true,
      // 'nodePlacement.strategy': 'SIMPLE',
    },
    children: [],
    edges: [],
  };
  log.info('Drawing flowchart using v3 renderer', elk);

  // Set the direction,
  // Fetch the default direction, use TD if none was found
  let dir = diagObj.db.getDirection();
  switch (dir) {
    case 'BT':
      graph.layoutOptions['elk.direction'] = 'UP';
      break;
    case 'TB':
      graph.layoutOptions['elk.direction'] = 'DOWN';
      break;
    case 'LR':
      graph.layoutOptions['elk.direction'] = 'RIGHT';
      break;
    case 'RL':
      graph.layoutOptions['elk.direction'] = 'LEFT';
      break;
  }

  // Find the root dom node to ne used in rendering
  // Handle root and document for when rendering in sandbox mode
  let sandboxElement;
  if (securityLevel === 'sandbox') {
    sandboxElement = select('#i' + id);
  }
  const root =
    securityLevel === 'sandbox'
      ? select(sandboxElement.nodes()[0].contentDocument.body)
      : select('body');
  const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;

  const svg = root.select(`[id="${id}"]`);
  // Define the supported markers for the diagram
  const markers = ['point', 'circle', 'cross'];

  // Add the marker definitions to the svg as marker tags
  insertMarkers(svg, markers, diagObj.type, id);

  // Fetch the vertices/nodes and edges/links from the parsed graph definition
  const vert = diagObj.db.getVertices();

  // Setup nodes from the subgraphs with type group, these will be used
  // as nodes with children in the subgraph
  let subG;
  const subGraphs = diagObj.db.getSubGraphs();
  log.info('Subgraphs - ', subGraphs);
  for (let i = subGraphs.length - 1; i >= 0; i--) {
    subG = subGraphs[i];
    diagObj.db.addVertex(
      subG.id,
      { text: subG.title, type: subG.labelType },
      'group',
      undefined,
      subG.classes,
      subG.dir
    );
  }

  // debugger;
  // Add an element in the svg to be used to hold the subgraphs container
  // elements
  const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');

  // Create the lookup db for the subgraphs and their children to used when creating
  // the tree structured graph
  const parentLookupDb = addSubGraphs(diagObj.db);

  // Add the nodes to the graph, this will entail creating the actual nodes
  // in order to get the size of the node. You can't get the size of a node
  // that is not in the dom so we need to add it to the dom, get the size
  // we will position the nodes when we get the layout from elkjs
  graph = await addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);

  // Time for the edges, we start with adding an element in the node to hold the edges
  const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
  // Fetch the edges form the parsed graph definition
  const edges = diagObj.db.getEdges();

  // Add the edges to the graph, this will entail creating the actual edges
  graph = addEdges(edges, diagObj, graph, svg);

  // Iterate through all nodes and add the top level nodes to the graph
  const nodes = Object.keys(nodeDb);
  nodes.forEach((nodeId) => {
    const node = nodeDb[nodeId];
    if (!node.parent) {
      graph.children.push(node);
    }
    // Subgraph
    if (parentLookupDb.childrenById[nodeId] !== undefined) {
      node.labels = [
        {
          text: node.labelText,
          layoutOptions: {
            'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
          },
          width: node.labelData.width,
          height: node.labelData.height,
          // width: 100,
          // height: 100,
        },
      ];
      delete node.x;
      delete node.y;
      delete node.width;
      delete node.height;
    }
  });

  insertChildren(graph.children, parentLookupDb);
  log.info('after layout', JSON.stringify(graph, null, 2));
  const g = await elk.layout(graph);
  drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
  utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
  log.info('after layout', g);
  g.edges?.map((edge) => {
    insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb, id);
  });
  setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
  // Remove element after layout
  renderEl.remove();
};

const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {
  nodeArray.forEach(function (node) {
    if (node) {
      nodeDb[node.id].offset = {
        posX: node.x + relX,
        posY: node.y + relY,
        x: relX,
        y: relY,
        depth,
        width: node.width,
        height: node.height,
      };
      if (node.type === 'group') {
        const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');
        subgraphEl
          .insert('rect')
          .attr('class', 'subgraph subgraph-lvl-' + (depth % 5) + ' node')
          .attr('x', node.x + relX)
          .attr('y', node.y + relY)
          .attr('width', node.width)
          .attr('height', node.height);
        const label = subgraphEl.insert('g').attr('class', 'label');
        const labelCentering = getConfig().flowchart.htmlLabels ? node.labelData.width / 2 : 0;
        label.attr(
          'transform',
          `translate(${node.labels[0].x + relX + node.x + labelCentering}, ${
            node.labels[0].y + relY + node.y + 3
          })`
        );
        label.node().appendChild(node.labelData.labelNode);

        log.info('Id (UGH)= ', node.type, node.labels);
      } else {
        log.info('Id (UGH)= ', node.id);
        node.el.attr(
          'transform',
          `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
        );
      }
    }
  });
  nodeArray.forEach(function (node) {
    if (node && node.type === 'group') {
      drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj, depth + 1);
    }
  });
};

export default {
  getClasses,
  draw,
};
