-- Test suite for TextEditor module
package.path = package.path .. ";./?.lua;./modules/?.lua"

require("testing.loveStub")
local luaunit = require("testing.luaunit")
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")

TestTextEditor = {}

-- Mock dependencies
local MockContext = {
  _immediateMode = false,
  _focusedElement = nil,
}

local MockStateManager = {
  getState = function(id)
    return nil
  end,
  saveState = function(id, state) end,
}

-- Helper to create TextEditor with dependencies
local function createTextEditor(config)
  config = config or {}
  return TextEditor.new(config, {
    Context = MockContext,
    StateManager = MockStateManager,
    Color = Color,
    utils = utils,
  })
end

-- Helper to create mock element
local function createMockElement()
  return {
    _stateId = "test-element-1",
    width = 200,
    height = 30,
  }
end

-- Test: new() creates instance with defaults
function TestTextEditor:test_new_creates_with_defaults()
  local editor = createTextEditor()

  luaunit.assertNotNil(editor)
  luaunit.assertFalse(editor.editable)
  luaunit.assertFalse(editor.multiline)
  luaunit.assertFalse(editor.passwordMode)
  luaunit.assertEquals(editor.inputType, "text")
  luaunit.assertEquals(editor._textBuffer, "")
  luaunit.assertEquals(editor._cursorPosition, 0)
  luaunit.assertFalse(editor._focused)
end

-- Test: new() accepts configuration
function TestTextEditor:test_new_accepts_config()
  local editor = createTextEditor({
    editable = true,
    multiline = true,
    passwordMode = true,
    text = "Hello",
    placeholder = "Enter text",
    maxLength = 100,
    inputType = "email",
  })

  luaunit.assertTrue(editor.editable)
  luaunit.assertTrue(editor.multiline)
  luaunit.assertTrue(editor.passwordMode)
  luaunit.assertEquals(editor._textBuffer, "Hello")
  luaunit.assertEquals(editor.placeholder, "Enter text")
  luaunit.assertEquals(editor.maxLength, 100)
  luaunit.assertEquals(editor.inputType, "email")
end

-- Test: new() sanitizes initial text
function TestTextEditor:test_new_sanitizes_initial_text()
  local editor = createTextEditor({
    text = "Hello\n\nWorld",
    multiline = false,
    allowNewlines = false,
  })

  -- Newlines should be removed for single-line
  luaunit.assertNotEquals(editor._textBuffer, "Hello\n\nWorld")
end

-- Test: initialize() sets element reference
function TestTextEditor:test_initialize_sets_element()
  local editor = createTextEditor()
  local element = createMockElement()

  editor:initialize(element)

  luaunit.assertEquals(editor._element, element)
end

-- Test: getText() returns current text
function TestTextEditor:test_getText_returns_text()
  local editor = createTextEditor({ text = "Hello World" })

  luaunit.assertEquals(editor:getText(), "Hello World")
end

-- Test: getText() returns empty string for nil buffer
function TestTextEditor:test_getText_returns_empty_for_nil()
  local editor = createTextEditor()
  editor._textBuffer = nil

  luaunit.assertEquals(editor:getText(), "")
end

-- Test: setText() updates text buffer
function TestTextEditor:test_setText_updates_buffer()
  local editor = createTextEditor()

  editor:setText("New text")

  luaunit.assertEquals(editor:getText(), "New text")
end

-- Test: setText() sanitizes text by default
function TestTextEditor:test_setText_sanitizes()
  local editor = createTextEditor({
    multiline = false,
    allowNewlines = false,
  })

  editor:setText("Line1\nLine2")

  -- Should remove newlines for single-line
  local text = editor:getText()
  luaunit.assertFalse(text:find("\n") ~= nil)
end

-- Test: setText() skips sanitization when requested
function TestTextEditor:test_setText_skips_sanitization()
  local editor = createTextEditor({
    multiline = false,
    allowNewlines = false,
  })

  editor:setText("Line1\nLine2", true) -- skipSanitization = true

  luaunit.assertEquals(editor:getText(), "Line1\nLine2")
end

-- Test: insertText() adds text at position
function TestTextEditor:test_insertText_at_position()
  local editor = createTextEditor({ text = "Hello" })

  editor:insertText(" World", 5)

  luaunit.assertEquals(editor:getText(), "Hello World")
end

-- Test: insertText() adds text at start
function TestTextEditor:test_insertText_at_start()
  local editor = createTextEditor({ text = "World" })

  editor:insertText("Hello ", 0)

  luaunit.assertEquals(editor:getText(), "Hello World")
end

-- Test: deleteText() removes text range
function TestTextEditor:test_deleteText_removes_range()
  local editor = createTextEditor({ text = "Hello World" })

  editor:deleteText(5, 11) -- Remove " World"

  luaunit.assertEquals(editor:getText(), "Hello")
end

-- Test: deleteText() handles reversed positions
function TestTextEditor:test_deleteText_handles_reversed()
  local editor = createTextEditor({ text = "Hello World" })

  editor:deleteText(11, 5) -- Reversed: should swap

  luaunit.assertEquals(editor:getText(), "Hello")
end

-- Test: replaceText() replaces range with new text
function TestTextEditor:test_replaceText_replaces_range()
  local editor = createTextEditor({ text = "Hello World" })

  editor:replaceText(6, 11, "Lua")

  luaunit.assertEquals(editor:getText(), "Hello Lua")
end

-- Test: setCursorPosition() sets cursor
function TestTextEditor:test_setCursorPosition()
  local editor = createTextEditor({ text = "Hello" })

  editor:setCursorPosition(3)

  luaunit.assertEquals(editor:getCursorPosition(), 3)
end

-- Test: setCursorPosition() clamps to valid range
function TestTextEditor:test_setCursorPosition_clamps()
  local editor = createTextEditor({ text = "Hello" })

  editor:setCursorPosition(100) -- Beyond text length

  luaunit.assertEquals(editor:getCursorPosition(), 5)
end

-- Test: moveCursorBy() moves cursor relative
function TestTextEditor:test_moveCursorBy()
  local editor = createTextEditor({ text = "Hello" })
  editor:setCursorPosition(2)

  editor:moveCursorBy(2)

  luaunit.assertEquals(editor:getCursorPosition(), 4)
end

-- Test: moveCursorToStart() moves to beginning
function TestTextEditor:test_moveCursorToStart()
  local editor = createTextEditor({ text = "Hello" })
  editor:setCursorPosition(3)

  editor:moveCursorToStart()

  luaunit.assertEquals(editor:getCursorPosition(), 0)
end

-- Test: moveCursorToEnd() moves to end
function TestTextEditor:test_moveCursorToEnd()
  local editor = createTextEditor({ text = "Hello" })

  editor:moveCursorToEnd()

  luaunit.assertEquals(editor:getCursorPosition(), 5)
end

-- Test: setSelection() sets selection range
function TestTextEditor:test_setSelection()
  local editor = createTextEditor({ text = "Hello World" })

  editor:setSelection(0, 5)

  local start, endPos = editor:getSelection()
  luaunit.assertEquals(start, 0)
  luaunit.assertEquals(endPos, 5)
end

-- Test: hasSelection() returns true when selected
function TestTextEditor:test_hasSelection_true()
  local editor = createTextEditor({ text = "Hello" })
  editor:setSelection(0, 5)

  luaunit.assertTrue(editor:hasSelection())
end

-- Test: hasSelection() returns false when no selection
function TestTextEditor:test_hasSelection_false()
  local editor = createTextEditor({ text = "Hello" })

  luaunit.assertFalse(editor:hasSelection())
end

-- Test: clearSelection() removes selection
function TestTextEditor:test_clearSelection()
  local editor = createTextEditor({ text = "Hello" })
  editor:setSelection(0, 5)

  editor:clearSelection()

  luaunit.assertFalse(editor:hasSelection())
end

-- Test: getSelectedText() returns selected text
function TestTextEditor:test_getSelectedText()
  local editor = createTextEditor({ text = "Hello World" })
  editor:setSelection(0, 5)

  luaunit.assertEquals(editor:getSelectedText(), "Hello")
end

-- Test: deleteSelection() removes selected text
function TestTextEditor:test_deleteSelection()
  local editor = createTextEditor({ text = "Hello World" })
  editor:setSelection(0, 6)

  editor:deleteSelection()

  luaunit.assertEquals(editor:getText(), "World")
  luaunit.assertFalse(editor:hasSelection())
end

-- Test: selectAll() selects entire text
function TestTextEditor:test_selectAll()
  local editor = createTextEditor({ text = "Hello World" })

  editor:selectAll()

  local start, endPos = editor:getSelection()
  luaunit.assertEquals(start, 0)
  luaunit.assertEquals(endPos, 11)
end

-- Test: sanitization with maxLength
function TestTextEditor:test_sanitize_max_length()
  local editor = createTextEditor({
    maxLength = 5,
  })

  editor:setText("HelloWorld")

  luaunit.assertEquals(editor:getText(), "Hello")
end

-- Test: sanitization disabled
function TestTextEditor:test_sanitization_disabled()
  local editor = createTextEditor({
    sanitize = false,
    multiline = false,
    allowNewlines = false,
  })

  editor:setText("Line1\nLine2")

  -- Should NOT sanitize newlines when disabled
  luaunit.assertEquals(editor:getText(), "Line1\nLine2")
end

-- Test: customSanitizer callback
function TestTextEditor:test_custom_sanitizer()
  local editor = createTextEditor({
    customSanitizer = function(text)
      return text:upper()
    end,
  })

  editor:setText("hello")

  luaunit.assertEquals(editor:getText(), "HELLO")
end

-- Test: allowNewlines follows multiline setting
function TestTextEditor:test_allowNewlines_follows_multiline()
  local editor = createTextEditor({
    multiline = true,
  })

  luaunit.assertTrue(editor.allowNewlines)

  editor = createTextEditor({
    multiline = false,
  })

  luaunit.assertFalse(editor.allowNewlines)
end

-- Test: allowNewlines can be overridden
function TestTextEditor:test_allowNewlines_override()
  local editor = createTextEditor({
    multiline = true,
    allowNewlines = false,
  })

  luaunit.assertFalse(editor.allowNewlines)
end

-- Test: allowTabs defaults to true
function TestTextEditor:test_allowTabs_default()
  local editor = createTextEditor()

  luaunit.assertTrue(editor.allowTabs)
end

-- Test: cursorBlinkRate default
function TestTextEditor:test_cursorBlinkRate_default()
  local editor = createTextEditor()

  luaunit.assertEquals(editor.cursorBlinkRate, 0.5)
end

-- Test: selectOnFocus default
function TestTextEditor:test_selectOnFocus_default()
  local editor = createTextEditor()

  luaunit.assertFalse(editor.selectOnFocus)
end

-- Test: onSanitize callback triggered when text is sanitized
function TestTextEditor:test_onSanitize_callback()
  local callbackCalled = false
  local originalText = nil
  local sanitizedText = nil

  local editor = createTextEditor({
    maxLength = 5,
    onSanitize = function(element, original, sanitized)
      callbackCalled = true
      originalText = original
      sanitizedText = sanitized
    end,
  })

  local mockElement = createMockElement()
  editor:initialize(mockElement)

  -- Insert text that exceeds maxLength
  editor:_sanitizeText("This is a long text that exceeds max length")

  luaunit.assertTrue(callbackCalled)
  luaunit.assertEquals(originalText, "This is a long text that exceeds max length")
  luaunit.assertEquals(sanitizedText, "This ")
end

-- Test: initialize with immediate mode and existing state
function TestTextEditor:test_initialize_immediate_mode_with_state()
  local mockStateManager = {
    getState = function(id)
      return {
        _focused = true,
        _textBuffer = "restored text",
        _cursorPosition = 10,
        _selectionStart = 2,
        _selectionEnd = 5,
        _cursorBlinkTimer = 0.3,
        _cursorVisible = false,
        _cursorBlinkPaused = true,
        _cursorBlinkPauseTimer = 1.0,
      }
    end,
    saveState = function(id, state) end,
  }

  local mockContext = {
    _immediateMode = true,
    _focusedElement = nil,
  }

  local editor = TextEditor.new({}, {
    Context = mockContext,
    StateManager = mockStateManager,
    Color = Color,
    utils = utils,
  })

  local mockElement = createMockElement()
  editor:initialize(mockElement)

  -- State should be fully restored
  luaunit.assertEquals(editor._textBuffer, "restored text")
  luaunit.assertEquals(editor._cursorPosition, 10)
  luaunit.assertEquals(editor._selectionStart, 2)
  luaunit.assertEquals(editor._selectionEnd, 5)
  luaunit.assertEquals(editor._cursorBlinkTimer, 0.3)
  luaunit.assertEquals(editor._cursorVisible, false)
  luaunit.assertEquals(editor._cursorBlinkPaused, true)
  luaunit.assertEquals(editor._cursorBlinkPauseTimer, 1.0)
  luaunit.assertTrue(editor._focused)
  luaunit.assertEquals(mockContext._focusedElement, mockElement)
end

-- Test: customSanitizer function
function TestTextEditor:test_customSanitizer()
  local editor = createTextEditor({
    customSanitizer = function(text)
      return text:upper()
    end,
  })

  local result = editor:_sanitizeText("hello world")
  luaunit.assertEquals(result, "HELLO WORLD")
end

-- Test: sanitize disabled
function TestTextEditor:test_sanitize_disabled()
  local editor = createTextEditor({
    sanitize = false,
    maxLength = 5,
  })

  local result = editor:_sanitizeText("This is a very long text")
  -- Should not be truncated since sanitize is false
  luaunit.assertEquals(result, "This is a very long text")
end

if not _G.RUNNING_ALL_TESTS then
  os.exit(luaunit.LuaUnit.run())
end
