import { describe, expect, test } from 'bun:test'

import { SceneGraph } from '@open-pencil/core'

import { getNodeOrThrow } from '#tests/helpers/assert'
import { createRect, firstPageId } from '#tests/helpers/scene'

describe('single-node alignment to parent', () => {
  function setup() {
    const graph = new SceneGraph()
    const frame = graph.createNode('FRAME', firstPageId(graph), {
      name: 'Container',
      x: 0,
      y: 0,
      width: 400,
      height: 300
    })
    const child = createRect(graph, frame.id, {
      name: 'Child',
      x: 50,
      y: 50,
      width: 100,
      height: 80
    })
    return { graph, frame, child }
  }

  test('align left within parent', () => {
    const { graph, child } = setup()
    graph.updateNode(child.id, { x: 0 })
    expect(getNodeOrThrow(graph, child.id).x).toBe(0)
  })

  test('align right within parent', () => {
    const { graph, frame, child } = setup()
    const targetX = frame.width - child.width
    graph.updateNode(child.id, { x: targetX })
    expect(getNodeOrThrow(graph, child.id).x).toBe(300)
  })

  test('align center horizontal within parent', () => {
    const { graph, frame, child } = setup()
    const targetX = (frame.width - child.width) / 2
    graph.updateNode(child.id, { x: targetX })
    expect(getNodeOrThrow(graph, child.id).x).toBe(150)
  })

  test('align top within parent', () => {
    const { graph, child } = setup()
    graph.updateNode(child.id, { y: 0 })
    expect(getNodeOrThrow(graph, child.id).y).toBe(0)
  })

  test('align bottom within parent', () => {
    const { graph, frame, child } = setup()
    const targetY = frame.height - child.height
    graph.updateNode(child.id, { y: targetY })
    expect(getNodeOrThrow(graph, child.id).y).toBe(220)
  })

  test('align center vertical within parent', () => {
    const { graph, frame, child } = setup()
    const targetY = (frame.height - child.height) / 2
    graph.updateNode(child.id, { y: targetY })
    expect(getNodeOrThrow(graph, child.id).y).toBe(110)
  })
})

describe('multi-node alignment', () => {
  function setup() {
    const graph = new SceneGraph()
    const page = firstPageId(graph)
    const a = createRect(graph, page, { name: 'A', x: 10, y: 20, width: 50, height: 30 })
    const b = createRect(graph, page, { name: 'B', x: 100, y: 80, width: 60, height: 40 })
    const c = createRect(graph, page, { name: 'C', x: 200, y: 50, width: 40, height: 50 })
    return { graph, a, b, c }
  }

  test('align left — all nodes move to min x', () => {
    const { graph, a, b, c } = setup()
    const nodes = [a, b, c].map((n) => getNodeOrThrow(graph, n.id))
    const abs = nodes.map((n) => graph.getAbsolutePosition(n.id))
    const minX = Math.min(...abs.map((p) => p.x))

    for (const n of nodes) {
      const nodeAbs = graph.getAbsolutePosition(n.id)
      graph.updateNode(n.id, { x: n.x + (minX - nodeAbs.x) })
    }

    expect(getNodeOrThrow(graph, a.id).x).toBe(10)
    expect(getNodeOrThrow(graph, b.id).x).toBe(10)
    expect(getNodeOrThrow(graph, c.id).x).toBe(10)
  })

  test('align right — all nodes align to max right edge', () => {
    const { graph, a, b, c } = setup()
    const nodes = [a, b, c].map((n) => getNodeOrThrow(graph, n.id))
    const abs = nodes.map((n) => graph.getAbsolutePosition(n.id))
    const maxX = Math.max(...abs.map((p, i) => p.x + nodes[i].width))

    for (const n of nodes) {
      const nodeAbs = graph.getAbsolutePosition(n.id)
      const targetX = maxX - n.width
      graph.updateNode(n.id, { x: n.x + (targetX - nodeAbs.x) })
    }

    expect(getNodeOrThrow(graph, a.id).x).toBe(190)
    expect(getNodeOrThrow(graph, b.id).x).toBe(180)
    expect(getNodeOrThrow(graph, c.id).x).toBe(200)
  })

  test('align top — all nodes move to min y', () => {
    const { graph, a, b, c } = setup()
    const nodes = [a, b, c].map((n) => getNodeOrThrow(graph, n.id))
    const abs = nodes.map((n) => graph.getAbsolutePosition(n.id))
    const minY = Math.min(...abs.map((p) => p.y))

    for (const n of nodes) {
      const nodeAbs = graph.getAbsolutePosition(n.id)
      graph.updateNode(n.id, { y: n.y + (minY - nodeAbs.y) })
    }

    expect(getNodeOrThrow(graph, a.id).y).toBe(20)
    expect(getNodeOrThrow(graph, b.id).y).toBe(20)
    expect(getNodeOrThrow(graph, c.id).y).toBe(20)
  })
})
