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

import { buildOpenPencilClipboardHTML } from '@open-pencil/core/clipboard'
import { createEditor } from '@open-pencil/core/editor'
import type { Editor } from '@open-pencil/core/editor'

function copiedRectangleHtml(name = 'Pasted') {
  const source = createEditor()
  const sourcePageId = source.state.currentPageId
  const pasted = source.graph.createNode('RECTANGLE', sourcePageId, {
    name,
    x: 0,
    y: 0,
    width: 20,
    height: 20
  })
  return buildOpenPencilClipboardHTML([pasted], source.graph)
}

function copiedFrameHtml() {
  const source = createEditor()
  const sourcePageId = source.state.currentPageId
  const frame = source.graph.createNode('FRAME', sourcePageId, {
    name: 'Pasted frame',
    x: 0,
    y: 0,
    width: 40,
    height: 40
  })
  source.graph.createNode('RECTANGLE', frame.id, {
    name: 'Pasted child',
    x: 5,
    y: 6,
    width: 10,
    height: 12
  })
  return buildOpenPencilClipboardHTML([frame], source.graph)
}

function createTarget(editor: Editor, parentId = editor.state.currentPageId) {
  return editor.graph.createNode('ELLIPSE', parentId, {
    name: 'Target',
    x: 100,
    y: 100,
    width: 40,
    height: 40
  })
}

describe('paste to replace', () => {
  test('replaces selected nodes with pasted OpenPencil nodes', async () => {
    const html = copiedRectangleHtml()
    const editor = createEditor()
    const pageId = editor.state.currentPageId
    const target = createTarget(editor)

    editor.select([target.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })

    expect(editor.graph.getNode(target.id)).toBeUndefined()
    const [createdId] = [...editor.state.selectedIds]
    const created = editor.graph.getNode(createdId)
    expect(created?.name).toBe('Pasted')
    expect(created?.parentId).toBe(pageId)
    expect(created?.x).toBe(110)
    expect(created?.y).toBe(110)
  })

  test('undo and redo preserve replaced and pasted subtrees', async () => {
    const html = copiedRectangleHtml()
    const editor = createEditor()
    const pageId = editor.state.currentPageId
    const target = createTarget(editor)

    editor.select([target.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })
    const [createdId] = [...editor.state.selectedIds]

    editor.undo.undo()

    expect(editor.graph.getNode(createdId)).toBeUndefined()
    expect(editor.graph.getNode(target.id)?.parentId).toBe(pageId)
    expect(editor.state.selectedIds).toEqual(new Set([target.id]))

    editor.undo.redo()

    expect(editor.graph.getNode(target.id)).toBeUndefined()
    expect(editor.graph.getNode(createdId)?.parentId).toBe(pageId)
    expect(editor.state.selectedIds).toEqual(new Set([createdId]))
  })

  test('falls back to regular paste when all selected targets are locked', async () => {
    const html = copiedRectangleHtml()
    const editor = createEditor()
    const pageId = editor.state.currentPageId
    const target = editor.graph.createNode('ELLIPSE', pageId, {
      name: 'Locked target',
      x: 100,
      y: 100,
      width: 40,
      height: 40,
      locked: true
    })

    editor.select([target.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })

    expect(editor.graph.getNode(target.id)?.parentId).toBe(pageId)
    const [createdId] = [...editor.state.selectedIds]
    expect(createdId).not.toBe(target.id)
    expect(editor.graph.getNode(createdId)?.parentId).toBe(pageId)
  })

  test('replaces only top-level selected targets', async () => {
    const html = copiedRectangleHtml()
    const editor = createEditor()
    const pageId = editor.state.currentPageId
    const parent = editor.graph.createNode('FRAME', pageId, {
      name: 'Parent target',
      x: 100,
      y: 100,
      width: 80,
      height: 80
    })
    const child = editor.graph.createNode('RECTANGLE', parent.id, {
      name: 'Child target',
      x: 10,
      y: 10,
      width: 20,
      height: 20
    })

    editor.select([parent.id, child.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })

    expect(editor.graph.getNode(parent.id)).toBeUndefined()
    expect(editor.graph.getNode(child.id)).toBeUndefined()
    const [createdId] = [...editor.state.selectedIds]
    expect(editor.graph.getNode(createdId)?.parentId).toBe(pageId)
  })

  test('replaces targets inside a frame and preserves parent order through undo and redo', async () => {
    const html = copiedRectangleHtml()
    const editor = createEditor()
    const pageId = editor.state.currentPageId
    const frame = editor.graph.createNode('FRAME', pageId, {
      name: 'Container',
      x: 50,
      y: 50,
      width: 300,
      height: 300
    })
    const before = editor.graph.createNode('RECTANGLE', frame.id, { name: 'Before', x: 0, y: 0 })
    const target = createTarget(editor, frame.id)
    const after = editor.graph.createNode('RECTANGLE', frame.id, { name: 'After', x: 200, y: 200 })

    editor.select([target.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })
    const [createdId] = [...editor.state.selectedIds]

    expect(editor.graph.getNode(createdId)?.parentId).toBe(frame.id)
    expect(editor.graph.getNode(frame.id)?.childIds).toEqual([before.id, createdId, after.id])

    editor.undo.undo()
    expect(editor.graph.getNode(frame.id)?.childIds).toEqual([before.id, target.id, after.id])

    editor.undo.redo()
    expect(editor.graph.getNode(frame.id)?.childIds).toEqual([before.id, createdId, after.id])
  })

  test('preserves pasted child subtrees', async () => {
    const html = copiedFrameHtml()
    const editor = createEditor()
    const target = createTarget(editor)

    editor.select([target.id])
    await editor.pasteFromHTML(html, undefined, { replaceSelection: true })

    const [createdId] = [...editor.state.selectedIds]
    const created = editor.graph.getNode(createdId)
    expect(created?.type).toBe('FRAME')
    expect(created?.childIds).toHaveLength(1)
    const childId = created?.childIds[0]
    expect(childId ? editor.graph.getNode(childId)?.name : undefined).toBe('Pasted child')
  })
})
