return function()
	describe("Input Component", function()
		local mock_time
		local mock_input
		local druid_system

		local druid
		local context

		before(function()
			mock_time = require("deftest.mock.time")
			mock_input = require("test.helper.mock_input")
			druid_system = require("druid.druid")

			mock_time.mock()
			mock_time.set(0)

			context = vmath.vector3()
			druid = druid_system.new(context)
		end)

		after(function()
			mock_time.unmock()
			druid:final()
			druid = nil
		end)

		it("Should create input component", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial Text")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			assert(input ~= nil)
			assert(input.button ~= nil)
			assert(input.text ~= nil)
			assert(input:get_text() == "Initial Text")

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should select and unselect input", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Text")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local on_input_select_calls = 0
			local on_input_unselect_calls = 0

			input.on_input_select:subscribe(function()
				on_input_select_calls = on_input_select_calls + 1
			end)

			input.on_input_unselect:subscribe(function()
				on_input_unselect_calls = on_input_unselect_calls + 1
			end)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			assert(input.is_selected == true)
			assert(on_input_select_calls == 1)

			-- Unselect input
			druid:on_input(mock_input.click_pressed(200, 200))
			druid:on_input(mock_input.click_released(200, 200))

			assert(input.is_selected == false)
			assert(on_input_unselect_calls == 1)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle text input", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local on_input_text_calls = 0
			input.on_input_text:subscribe(function()
				on_input_text_calls = on_input_text_calls + 1
			end)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Type "H"
			druid:on_input(mock_input.input_text("H"))
			assert(input:get_text() == "H")
			assert(on_input_text_calls == 1)

			-- Type "e"
			druid:on_input(mock_input.input_text("e"))
			assert(input:get_text() == "He")
			assert(on_input_text_calls == 2)

			-- Type "llo"
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("o"))

			assert(input:get_text() == "Hello")
			assert(on_input_text_calls == 5)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle backspace input", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Hello")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Delete one letter
			druid:on_input(hash("key_backspace"), {pressed = true})
			assert(input:get_text() == "Hell")

			-- Delete another letter
			druid:on_input(hash("key_backspace"), {pressed = true})
			assert(input:get_text() == "Hel")

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should set max length", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local on_input_full_calls = 0
			input.on_input_full:subscribe(function()
				on_input_full_calls = on_input_full_calls + 1
			end)

			-- Set max length to 5
			input:set_max_length(5)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Type "Hello"
			druid:on_input(mock_input.input_text("H"))
			druid:on_input(mock_input.input_text("e"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("o"))

			assert(input:get_text() == "Hello")
			assert(on_input_full_calls == 1)

			-- Try to type "World" - should truncate
			druid:on_input(mock_input.input_text("W"))
			druid:on_input(mock_input.input_text("o"))
			druid:on_input(mock_input.input_text("r"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("d"))

			assert(input:get_text() == "Hello")

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should validate allowed characters", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local on_input_wrong_calls = 0
			input.on_input_wrong:subscribe(function()
				on_input_wrong_calls = on_input_wrong_calls + 1
			end)

			-- Set allowed characters to only numbers
			input:set_allowed_characters("[0-9]")

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Type valid input "123"
			druid:on_input(mock_input.input_text("1"))
			druid:on_input(mock_input.input_text("2"))
			druid:on_input(mock_input.input_text("3"))

			assert(input:get_text() == "123")
			assert(on_input_wrong_calls == 0)

			-- Type invalid input "abc" - should be rejected
			druid:on_input(mock_input.input_text("a"))
			druid:on_input(mock_input.input_text("b"))
			druid:on_input(mock_input.input_text("c"))

			assert(input:get_text() == "123")
			assert(on_input_wrong_calls == 3)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle password input", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "")
			gui.set_font(text_node, "druid_text_bold")

			-- Create password input
			local input = druid:new_input(button_node, text_node, gui.KEYBOARD_TYPE_PASSWORD)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			druid:on_input(mock_input.input_text("H"))
			druid:on_input(mock_input.input_text("e"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("l"))
			druid:on_input(mock_input.input_text("o"))

			-- Raw text should be "Hello"
			assert(input:get_text() == "Hello")

			-- But displayed text should be "*****"
			assert(gui.get_text(text_node) == "*****")

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle enter key", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Verify input is selected
			assert(input.is_selected == true)

			-- Simulate enter key press to unselect
			assert(druid:on_input(hash("key_enter"), {released = true}) == true)

			-- Verify input is unselected
			assert(input.is_selected == false)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should reset changes", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Type some text
			druid:on_input(hash("text"), {text = "M"})
			druid:on_input(hash("text"), {text = "o"})
			druid:on_input(hash("text"), {text = "d"})

			assert(input:get_text() == "InitialMod")

			-- Reset changes
			input:reset_changes()

			-- Verify text is reset to initial value
			assert(input:get_text() == "Initial")
			assert(input.is_selected == false)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle selection and cursor manipulation", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Hello World")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local cursor_changes = 0
			input.on_select_cursor_change:subscribe(function()
				cursor_changes = cursor_changes + 1
			end)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))
			assert(cursor_changes == 1)

			-- Default cursor should be at the end
			assert(input.cursor_index == 11) -- "Hello World" length

			-- Move cursor to position 5
			input:select_cursor(5)
			assert(input.cursor_index == 5)
			assert(cursor_changes == 2)

			-- Move selection to the left by 1
			input:move_selection(-1)
			assert(input.cursor_index == 4)
			assert(cursor_changes == 3)

			-- Move selection to the right by 2
			input:move_selection(2)
			assert(input.cursor_index == 6)
			assert(cursor_changes == 4)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)

		it("Should handle empty input event", function()
			local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0))
			local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Text")
			gui.set_font(text_node, "druid_text_bold")

			local input = druid:new_input(button_node, text_node)

			local on_input_empty_calls = 0
			input.on_input_empty:subscribe(function()
				on_input_empty_calls = on_input_empty_calls + 1
			end)

			-- Select input
			druid:on_input(mock_input.click_pressed(50, 25))
			druid:on_input(mock_input.click_released(50, 25))

			-- Clear text
			input:set_text("")

			assert(on_input_empty_calls == 1)
			assert(input.is_empty == true)

			druid:remove(input)
			gui.delete_node(button_node)
			gui.delete_node(text_node)
		end)
	end)
end
