import { normalize, toRad } from '../../geometry'
import type {
  NodeAnchorDefinition,
  NodeAnchorResolvedDefinition,
} from './index'
import { type ResolveOptions, resolve } from './util'

export interface OrthEndpointOptions extends ResolveOptions {
  padding: number
}

const orthogonal: NodeAnchorResolvedDefinition<OrthEndpointOptions> = (
  view,
  magnet,
  refPoint,
  options,
) => {
  const angle = normalize(view.cell.getAngle())
  const bbox = view.cell.visible
    ? view.getBBoxOfElement(magnet)
    : view.cell.getBBox()
  const result = bbox.getCenter()
  const topLeft = bbox.getTopLeft()
  const bottomRight = bbox.getBottomRight()

  let padding = options.padding
  if (!Number.isFinite(padding)) {
    padding = 0
  }

  if (
    topLeft.y + padding <= refPoint.y &&
    refPoint.y <= bottomRight.y - padding
  ) {
    const dy = refPoint.y - result.y
    result.x +=
      angle === 0 || angle === 180 ? 0 : (dy * 1) / Math.tan(toRad(angle))
    result.y += dy
  } else if (
    topLeft.x + padding <= refPoint.x &&
    refPoint.x <= bottomRight.x - padding
  ) {
    const dx = refPoint.x - result.x
    result.y += angle === 90 || angle === 270 ? 0 : dx * Math.tan(toRad(angle))
    result.x += dx
  }

  return result
}

/**
 * Tries to place the anchor of the edge inside the view bbox so that the
 * edge is made orthogonal. The anchor is placed along two line segments
 * inside the view bbox (between the centers of the top and bottom side and
 * between the centers of the left and right sides). If it is not possible
 * to place the anchor so that the edge would be orthogonal, the anchor is
 * placed at the center of the view bbox instead.
 */
export const orth = resolve<
  NodeAnchorResolvedDefinition<OrthEndpointOptions>,
  NodeAnchorDefinition<OrthEndpointOptions>
>(orthogonal)
