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

import type { CanvasKit } from 'canvaskit-wasm'

import { TextEditor, type SceneNode } from '@open-pencil/core'

import { expectDefined } from '#tests/helpers/assert'

const mockCk = {} as CanvasKit

function createEditor(text = 'Hello World') {
  const editor = new TextEditor(mockCk)
  const node = { id: 'test-node', text } as SceneNode
  editor.start(node)
  return { editor, node }
}

function editorState(editor: TextEditor) {
  return expectDefined(editor.state, 'text editor state')
}

describe('TextEditor', () => {
  test('start initializes state', () => {
    const { editor } = createEditor()
    expect(editor.isActive).toBe(true)
    expect(editor.nodeId).toBe('test-node')
    expect(editor.state?.text).toBe('Hello World')
    expect(editor.state?.cursor).toBe(11)
  })

  test('stop returns result and clears state', () => {
    const { editor } = createEditor()
    const result = editor.stop()
    expect(result?.nodeId).toBe('test-node')
    expect(result?.text).toBe('Hello World')
    expect(editor.isActive).toBe(false)
    expect(editor.nodeId).toBeNull()
  })

  test('stop on inactive returns null', () => {
    const editor = new TextEditor(mockCk)
    expect(editor.stop()).toBeNull()
  })

  test('insert at end', () => {
    const { editor, node } = createEditor()
    editor.insert('!', node)
    expect(editor.state?.text).toBe('Hello World!')
    expect(editor.state?.cursor).toBe(12)
  })

  test('insert at cursor position', () => {
    const { editor, node } = createEditor()
    editorState(editor).cursor = 5
    editor.insert(' Beautiful', node)
    expect(editor.state?.text).toBe('Hello Beautiful World')
  })

  test('insert replaces selection', () => {
    const { editor, node } = createEditor()
    editorState(editor).selectionAnchor = 0
    editorState(editor).cursor = 5
    editor.insert('Goodbye', node)
    expect(editor.state?.text).toBe('Goodbye World')
    expect(editor.state?.cursor).toBe(7)
    expect(editor.state?.selectionAnchor).toBeNull()
  })

  test('backspace deletes char before cursor', () => {
    const { editor, node } = createEditor()
    editor.backspace(node)
    expect(editor.state?.text).toBe('Hello Worl')
    expect(editor.state?.cursor).toBe(10)
  })

  test('backspace at start does nothing', () => {
    const { editor, node } = createEditor()
    editorState(editor).cursor = 0
    editor.backspace(node)
    expect(editor.state?.text).toBe('Hello World')
  })

  test('backspace deletes selection', () => {
    const { editor, node } = createEditor()
    editorState(editor).selectionAnchor = 6
    editorState(editor).cursor = 11
    editor.backspace(node)
    expect(editor.state?.text).toBe('Hello ')
    expect(editor.state?.cursor).toBe(6)
  })

  test('delete removes char after cursor', () => {
    const { editor, node } = createEditor()
    editorState(editor).cursor = 0
    editor.delete(node)
    expect(editor.state?.text).toBe('ello World')
    expect(editor.state?.cursor).toBe(0)
  })

  test('delete at end does nothing', () => {
    const { editor, node } = createEditor()
    editor.delete(node)
    expect(editor.state?.text).toBe('Hello World')
  })

  test('delete removes selection', () => {
    const { editor, node } = createEditor()
    editorState(editor).selectionAnchor = 0
    editorState(editor).cursor = 5
    editor.delete(node)
    expect(editor.state?.text).toBe(' World')
  })

  test('moveLeft', () => {
    const { editor } = createEditor()
    editor.moveLeft()
    expect(editor.state?.cursor).toBe(10)
  })

  test('moveRight at end stays', () => {
    const { editor } = createEditor()
    editor.moveRight()
    expect(editor.state?.cursor).toBe(11)
  })

  test('moveLeft collapses selection to start', () => {
    const { editor } = createEditor()
    editorState(editor).selectionAnchor = 3
    editorState(editor).cursor = 8
    editor.moveLeft()
    expect(editor.state?.cursor).toBe(3)
    expect(editor.state?.selectionAnchor).toBeNull()
  })

  test('moveRight collapses selection to end', () => {
    const { editor } = createEditor()
    editorState(editor).selectionAnchor = 3
    editorState(editor).cursor = 8
    editor.moveRight()
    expect(editor.state?.cursor).toBe(8)
    expect(editor.state?.selectionAnchor).toBeNull()
  })

  test('selectAll', () => {
    const { editor } = createEditor()
    editor.selectAll()
    expect(editor.state?.selectionAnchor).toBe(0)
    expect(editor.state?.cursor).toBe(11)
  })

  test('hasSelection', () => {
    const { editor } = createEditor()
    expect(editor.hasSelection()).toBe(false)
    editorState(editor).selectionAnchor = 0
    expect(editor.hasSelection()).toBe(true)
  })

  test('hasSelection false when anchor equals cursor', () => {
    const { editor } = createEditor()
    editorState(editor).selectionAnchor = editorState(editor).cursor
    expect(editor.hasSelection()).toBe(false)
  })

  test('getSelectionRange', () => {
    const { editor } = createEditor()
    expect(editor.getSelectionRange()).toBeNull()
    editorState(editor).selectionAnchor = 8
    editorState(editor).cursor = 3
    expect(editor.getSelectionRange()).toEqual([3, 8])
  })

  test('getSelectedText', () => {
    const { editor } = createEditor()
    editorState(editor).selectionAnchor = 0
    editorState(editor).cursor = 5
    expect(editor.getSelectedText()).toBe('Hello')
  })

  test('selectWord', () => {
    const { editor } = createEditor()
    editor.selectWord(2)
    expect(editor.state?.selectionAnchor).toBe(0)
    expect(editor.state?.cursor).toBe(5)
  })

  test('selectWord on second word', () => {
    const { editor } = createEditor()
    editor.selectWord(8)
    expect(editor.state?.selectionAnchor).toBe(6)
    expect(editor.state?.cursor).toBe(11)
  })

  test('moveWordLeft', () => {
    const { editor } = createEditor()
    editor.moveWordLeft()
    expect(editor.state?.cursor).toBe(6)
  })

  test('moveWordRight from start', () => {
    const { editor } = createEditor()
    editorState(editor).cursor = 0
    editor.moveWordRight()
    expect(editor.state?.cursor).toBe(6)
  })

  test('moveLeft with extend creates selection', () => {
    const { editor } = createEditor()
    editorState(editor).cursor = 5
    editor.moveLeft(true)
    expect(editor.state?.selectionAnchor).toBe(5)
    expect(editor.state?.cursor).toBe(4)
  })
})
