import type { GraphNode, XYPosition } from '@vue-flow/core'
import { roundDown, roundUp } from './utils'

export interface NodeBoundingBox {
  id: string
  width: number
  height: number
  topLeft: XYPosition
  bottomLeft: XYPosition
  topRight: XYPosition
  bottomRight: XYPosition
}

export interface GraphBoundingBox {
  width: number
  height: number
  topLeft: XYPosition
  bottomLeft: XYPosition
  topRight: XYPosition
  bottomRight: XYPosition
  xMax: number
  yMax: number
  xMin: number
  yMin: number
}

/**
 * Get the bounding box of all nodes and the graph itself, as X/Y coordinates
 * of all corner points.
 *
 * @param storeNodes The node list
 * @param nodePadding Optional padding to add to the node's bounding boxes
 * @param graphPadding Optional padding to add to the graph's bounding box
 * @param roundTo If the coordinates should be rounded to this nearest integer
 * @returns Graph and nodes bounding boxes.
 */
export function getBoundingBoxes(storeNodes: GraphNode[], nodePadding = 0, graphPadding = 0, roundTo = 0) {
  // Guarantee that the given parameters are positive integers
  nodePadding = Math.max(Math.round(nodePadding), 0)
  graphPadding = Math.max(Math.round(graphPadding), 0)
  roundTo = Math.max(Math.round(roundTo), 0)

  nodePadding = Number.isInteger(nodePadding) ? nodePadding : 0
  graphPadding = Number.isInteger(graphPadding) ? graphPadding : 0
  roundTo = Number.isInteger(roundTo) ? roundTo : 0

  let xMax = Number.MIN_SAFE_INTEGER
  let yMax = Number.MIN_SAFE_INTEGER
  let xMin = Number.MAX_SAFE_INTEGER
  let yMin = Number.MAX_SAFE_INTEGER

  const nodes: NodeBoundingBox[] = storeNodes.map((node) => {
    const {
      computedPosition: { x, y },
      dimensions,
    } = node
    const width = Math.max(dimensions.width || 0, 1)
    const height = Math.max(dimensions.height || 0, 1)

    const position: XYPosition = {
      x: x || 0,
      y: y || 0,
    }
    const topLeft: XYPosition = {
      x: position.x - nodePadding,
      y: position.y - nodePadding,
    }
    const bottomLeft: XYPosition = {
      x: position.x - nodePadding,
      y: position.y + height + nodePadding,
    }
    const topRight: XYPosition = {
      x: position.x + width + nodePadding,
      y: position.y - nodePadding,
    }
    const bottomRight: XYPosition = {
      x: position.x + width + nodePadding,
      y: position.y + height + nodePadding,
    }

    if (roundTo > 0) {
      topLeft.x = roundDown(topLeft.x, roundTo)
      topLeft.y = roundDown(topLeft.y, roundTo)
      bottomLeft.x = roundDown(bottomLeft.x, roundTo)
      bottomLeft.y = roundUp(bottomLeft.y, roundTo)
      topRight.x = roundUp(topRight.x, roundTo)
      topRight.y = roundDown(topRight.y, roundTo)
      bottomRight.x = roundUp(bottomRight.x, roundTo)
      bottomRight.y = roundUp(bottomRight.y, roundTo)
    }

    if (topLeft.y < yMin) {
      yMin = topLeft.y
    }
    if (topLeft.x < xMin) {
      xMin = topLeft.x
    }
    if (bottomRight.y > yMax) {
      yMax = bottomRight.y
    }
    if (bottomRight.x > xMax) {
      xMax = bottomRight.x
    }

    return {
      id: node.id,
      width,
      height,
      topLeft,
      bottomLeft,
      topRight,
      bottomRight,
    }
  })

  xMax = xMax + graphPadding
  yMax = yMax + graphPadding
  xMin = xMin - graphPadding
  yMin = yMin - graphPadding

  const topLeft: XYPosition = {
    x: xMin,
    y: yMin,
  }

  const bottomLeft: XYPosition = {
    x: xMin,
    y: yMax,
  }

  const topRight: XYPosition = {
    x: xMax,
    y: yMin,
  }

  const bottomRight: XYPosition = {
    x: xMax,
    y: yMax,
  }

  const width = Math.abs(topLeft.x - topRight.x)
  const height = Math.abs(topLeft.y - bottomLeft.y)

  const graph: GraphBoundingBox = {
    topLeft,
    bottomLeft,
    topRight,
    bottomRight,
    width,
    height,
    xMax,
    yMax,
    xMin,
    yMin,
  }

  return { nodes, graph }
}
