package editor

import (
	"testing"

	"git.gophernest.net/azpect/TextEditor/internal/action"
)

// --- w, e, and b Tests ---

func TestMoveForwardWord(t *testing.T) {
	t.Run("test 'w' moves forward one word", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 6 {
			t.Errorf("m.CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'www' moves forward three words", func(t *testing.T) {
		lines := []string{"hello world hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w", "w", "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 18 {
			t.Errorf("m.CursorX() = %d, want 18", m.CursorX())
		}
	})

	t.Run("test 'w' moves to next line", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 1 {
			t.Errorf("m.CursorY() = %d, want 1", m.CursorY())
		}
	})

	t.Run("test 'w' at end of file preserves position", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("m.CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'w' stops at punctuation", func(t *testing.T) {
		lines := []string{"hello.word"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'w' skips underscore", func(t *testing.T) {
		lines := []string{"hello_world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 11 {
			t.Errorf("m.CursorX() = %d, want 11", m.CursorX())
		}
	})

	t.Run("test 'w' moves once past punctuation", func(t *testing.T) {
		lines := []string{".hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 1 {
			t.Errorf("m.CursorX() = %d, want 1", m.CursorX())
		}
	})

	t.Run("test 'w' from middle of word skips only remaining chars", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "w")

		m := getFinalModel(t, tm)
		if m.CursorX() != 6 {
			t.Errorf("m.CursorX() = %d, want 6", m.CursorX())
		}
	})
}

func TestMoveToWordEnd(t *testing.T) {
	t.Run("test 'e' moves to end of current word", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 4 {
			t.Errorf("m.CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'eee' moves to end of three words", func(t *testing.T) {
		lines := []string{"hello world hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e", "e", "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 16 {
			t.Errorf("m.CursorX() = %d, want 16", m.CursorX())
		}
	})

	t.Run("test 'e' moves to next line when at end of word", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 4 {
			t.Errorf("m.CursorX() = %d, want 4", m.CursorX())
		}
		if m.CursorY() != 1 {
			t.Errorf("m.CursorY() = %d, want 1", m.CursorY())
		}
	})

	t.Run("test 'e' at end of file preserves position", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 4 {
			t.Errorf("m.CursorX() = %d, want 4", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("m.CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'e' stops at end of word before punctuation", func(t *testing.T) {
		lines := []string{"hello.world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 4 {
			t.Errorf("m.CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'e' stops at end of punctuation sequence", func(t *testing.T) {
		lines := []string{"..hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 1 {
			t.Errorf("m.CursorX() = %d, want 1", m.CursorX())
		}
	})

	t.Run("test 'e' skips underscore", func(t *testing.T) {
		lines := []string{"hello_world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 10 {
			t.Errorf("m.CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'e' moves past leading punctuation to end of word", func(t *testing.T) {
		lines := []string{".hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'e' from middle of word moves to end of current word", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 4 {
			t.Errorf("m.CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'ee' lands at end of each class in multi-char punctuation", func(t *testing.T) {
		lines := []string{"hello..world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "e", "e")

		m := getFinalModel(t, tm)
		if m.CursorX() != 6 {
			t.Errorf("m.CursorX() = %d, want 6", m.CursorX())
		}
	})
}

func TestMoveBackwardWord(t *testing.T) {
	t.Run("test 'b' moves to start of current word", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
	})

	t.Run("test 'b' at word start moves to end previous", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
	})

	t.Run("test 'bbb' moves to back three word", func(t *testing.T) {
		lines := []string{"hello world hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 23, Line: 0})
		sendKeys(tm, "b", "b", "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 6 {
			t.Errorf("m.CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'b' moves to prev line when at start of line", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("m.CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'b' at start of file preserves position", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
	})

	t.Run("test 'b' stops at punctuation", func(t *testing.T) {
		lines := []string{"hello.world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'b' stops at end of punctuation sequence", func(t *testing.T) {
		lines := []string{"..hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
	})

	t.Run("test 'b' stops at end of punctuation sequence before word", func(t *testing.T) {
		lines := []string{"hello..world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'b' stops at end of punctuation sequence on newline", func(t *testing.T) {
		lines := []string{"hello.", "world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 5 {
			t.Errorf("m.CursorX() = %d, want 5", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("m.CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'b' skips underscore", func(t *testing.T) {
		lines := []string{"hello_world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
		sendKeys(tm, "b")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("m.CursorX() = %d, want 0", m.CursorX())
		}
	})
}

// =============================================================================
// W (WORD motion) Tests
// =============================================================================
// In Vim, W moves forward to the start of the next WORD.
// A WORD is a sequence of non-blank characters separated by whitespace.
// Unlike 'w', punctuation does NOT create word boundaries for 'W'.
//
// Key difference from 'w':
//   "hello,world" with 'w': stops at "hello", ",", "world" (3 words)
//   "hello,world" with 'W': entire thing is 1 WORD
//
// =============================================================================

func TestMoveForwardWORD(t *testing.T) {
	// --- Basic Movement ---

	t.Run("test 'W' moves forward one WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Should move to 'w' in "world" at index 6
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'WW' moves forward two WORDs", func(t *testing.T) {
		lines := []string{"one two three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W", "W")

		m := getFinalModel(t, tm)
		// Should move to 't' in "three" at index 8
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test 'WWW' moves forward three WORDs", func(t *testing.T) {
		lines := []string{"one two three four"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W", "W", "W")

		m := getFinalModel(t, tm)
		// Should move to 'f' in "four" at index 14
		if m.CursorX() != 14 {
			t.Errorf("CursorX() = %d, want 14", m.CursorX())
		}
	})

	t.Run("test 'W' from middle of WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From middle of "hello", should move to start of "world"
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'W' from last char of WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From 'o' in "hello", should move to 'w' in "world"
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'W' from space before WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From space, should move to 'w' in "world"
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	// --- Key Difference: Punctuation Handling ---

	t.Run("test 'W' skips punctuation (does NOT stop at dot)", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Unlike 'w', 'W' treats "hello.world" as one WORD
		// Should skip to "next" at index 12
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test 'W' skips comma", func(t *testing.T) {
		lines := []string{"hello,world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "hello,world" is one WORD, should move to "next"
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test 'W' skips multiple punctuation", func(t *testing.T) {
		lines := []string{"hello...world!!! next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "hello...world!!!" is one WORD
		if m.CursorX() != 17 {
			t.Errorf("CursorX() = %d, want 17", m.CursorX())
		}
	})

	t.Run("test 'W' skips brackets and parens", func(t *testing.T) {
		lines := []string{"func(arg) next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "func(arg)" is one WORD
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'W' skips operators", func(t *testing.T) {
		lines := []string{"a+=b next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "a+=b" is one WORD
		if m.CursorX() != 5 {
			t.Errorf("CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'W' skips quotes", func(t *testing.T) {
		lines := []string{`"hello" next`}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// `"hello"` is one WORD
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test 'W' handles underscore (same as w)", func(t *testing.T) {
		lines := []string{"hello_world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "hello_world" is one WORD (same behavior as w for underscores)
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test 'W' on pure punctuation sequence", func(t *testing.T) {
		lines := []string{"... next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "..." is one WORD
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'W' from punctuation to next WORD", func(t *testing.T) {
		lines := []string{"hello. world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From '.' which is part of "hello.", should move to "world"
		if m.CursorX() != 7 {
			t.Errorf("CursorX() = %d, want 7", m.CursorX())
		}
	})

	// --- Line Crossing ---

	t.Run("test 'W' moves to next line", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 1 {
			t.Errorf("CursorY() = %d, want 1", m.CursorY())
		}
	})

	t.Run("test 'W' skips empty lines", func(t *testing.T) {
		lines := []string{"hello", "", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
	})

	t.Run("test 'W' skips multiple empty lines", func(t *testing.T) {
		lines := []string{"hello", "", "", "", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 4 {
			t.Errorf("CursorY() = %d, want 4", m.CursorY())
		}
	})

	t.Run("test 'W' skips whitespace-only lines", func(t *testing.T) {
		lines := []string{"hello", "   ", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
	})

	t.Run("test 'W' from middle line to next", func(t *testing.T) {
		lines := []string{"first", "second third", "fourth"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 1})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From "third" on line 1, should move to "fourth" on line 2
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
	})

	// --- End of File ---

	t.Run("test 'W' at end of file stays put", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// At last char, no more WORDs, should stay (or move to end)
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'W' on last WORD of file", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// On "world", no next WORD, should go to end or stay
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'W' past end of single line file", func(t *testing.T) {
		lines := []string{"word"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Only one WORD, should move to end or stay
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	// --- Whitespace Handling ---

	t.Run("test 'W' skips multiple spaces", func(t *testing.T) {
		lines := []string{"hello    world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 9 {
			t.Errorf("CursorX() = %d, want 9", m.CursorX())
		}
	})

	t.Run("test 'W' skips tabs", func(t *testing.T) {
		lines := []string{"hello\tworld"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Tab is whitespace, "world" starts after tab
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'W' skips mixed whitespace", func(t *testing.T) {
		lines := []string{"hello \t  world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "hello \t  world" - world starts at index 9
		// h=0, e=1, l=2, l=3, o=4, space=5, tab=6, space=7, space=8, w=9
		if m.CursorX() != 9 {
			t.Errorf("CursorX() = %d, want 9", m.CursorX())
		}
	})

	t.Run("test 'W' with leading whitespace", func(t *testing.T) {
		lines := []string{"   hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From start (in whitespace), should move to "hello" at index 3
		if m.CursorX() != 3 {
			t.Errorf("CursorX() = %d, want 3", m.CursorX())
		}
	})

	t.Run("test 'W' from leading whitespace", func(t *testing.T) {
		lines := []string{"   hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// From middle of leading whitespace, move to "hello"
		if m.CursorX() != 3 {
			t.Errorf("CursorX() = %d, want 3", m.CursorX())
		}
	})

	// --- Count Handling ---

	t.Run("test '2W' moves forward two WORDs", func(t *testing.T) {
		lines := []string{"one two three four"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "2", "W")

		m := getFinalModel(t, tm)
		// Skip "one" and "two", land on "three"
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test '3W' moves forward three WORDs", func(t *testing.T) {
		lines := []string{"one two three four five"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "3", "W")

		m := getFinalModel(t, tm)
		// Skip "one", "two", "three", land on "four"
		if m.CursorX() != 14 {
			t.Errorf("CursorX() = %d, want 14", m.CursorX())
		}
	})

	t.Run("test '5W' with fewer WORDs available", func(t *testing.T) {
		lines := []string{"one two three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "5", "W")

		m := getFinalModel(t, tm)
		// Only 3 WORDs, should stop at/after last WORD
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test '2W' across lines", func(t *testing.T) {
		lines := []string{"one", "two", "three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "2", "W")

		m := getFinalModel(t, tm)
		// Skip "one" and "two", land on "three"
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
	})

	t.Run("test '10W' moves as far as possible", func(t *testing.T) {
		lines := []string{"a b c"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "1", "0", "W")

		m := getFinalModel(t, tm)
		// Only 3 WORDs
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	// --- Comparison with 'w' ---

	t.Run("test 'W' vs 'w' on dotted text", func(t *testing.T) {
		// This test documents the difference between W and w
		lines := []string{"hello.world next"}

		// Test W behavior
		tm1 := newTestModelWithLines(t, lines)
		sendKeys(tm1, "W")
		m1 := getFinalModel(t, tm1)

		// Test w behavior
		tm2 := newTestModelWithLines(t, lines)
		sendKeys(tm2, "w")
		m2 := getFinalModel(t, tm2)

		// W should skip to "next", w should stop at "."
		if m1.CursorX() == m2.CursorX() {
			t.Errorf("W and w should behave differently on punctuation")
		}
		if m1.CursorX() != 12 {
			t.Errorf("W: CursorX() = %d, want 12", m1.CursorX())
		}
		if m2.CursorX() != 5 {
			t.Errorf("w: CursorX() = %d, want 5", m2.CursorX())
		}
	})

	// --- Edge Cases ---

	t.Run("test 'W' on empty file", func(t *testing.T) {
		lines := []string{""}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'W' on single character", func(t *testing.T) {
		lines := []string{"a"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Single char, no next WORD
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'W' on single punctuation", func(t *testing.T) {
		lines := []string{"."}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'W' on line with only whitespace after cursor", func(t *testing.T) {
		lines := []string{"hello   ", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Should skip trailing whitespace and go to next line
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 1 {
			t.Errorf("CursorY() = %d, want 1", m.CursorY())
		}
	})

	t.Run("test 'W' complex code-like text", func(t *testing.T) {
		lines := []string{"func(a,b) { return a+b; }"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// "func(a,b)" is one WORD, next is "{"
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'WW' on complex code-like text", func(t *testing.T) {
		lines := []string{"func(a,b) { return a+b; }"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "W", "W")

		m := getFinalModel(t, tm)
		// After "func(a,b)" and "{", should be on "return"
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test 'W' with URL-like text", func(t *testing.T) {
		lines := []string{"visit https://example.com/path?q=1 today"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// URL is one WORD, should move to "today"
		if m.CursorX() != 35 {
			t.Errorf("CursorX() = %d, want 35", m.CursorX())
		}
	})

	t.Run("test 'W' with email-like text", func(t *testing.T) {
		lines := []string{"contact user@example.com now"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 8, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Email is one WORD, should move to "now"
		if m.CursorX() != 25 {
			t.Errorf("CursorX() = %d, want 25", m.CursorX())
		}
	})

	t.Run("test 'W' with file path", func(t *testing.T) {
		lines := []string{"edit /home/user/file.txt now"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "W")

		m := getFinalModel(t, tm)
		// Path is one WORD, should move to "now"
		if m.CursorX() != 25 {
			t.Errorf("CursorX() = %d, want 25", m.CursorX())
		}
	})
}

func TestMoveForwardWORDWithOperator(t *testing.T) {
	t.Run("test 'dW' deletes WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "d", "W")

		m := getFinalModel(t, tm)
		// Should delete "hello.world " (including trailing space)
		if m.Line(0) != "next" {
			t.Errorf("Line(0) = %q, want 'next'", m.Line(0))
		}
	})

	t.Run("test 'dW' vs 'dw' on dotted text", func(t *testing.T) {
		// dW deletes entire "hello.world "
		lines1 := []string{"hello.world next"}
		tm1 := newTestModelWithLines(t, lines1)
		sendKeys(tm1, "d", "W")
		m1 := getFinalModel(t, tm1)

		// dw deletes only "hello"
		lines2 := []string{"hello.world next"}
		tm2 := newTestModelWithLines(t, lines2)
		sendKeys(tm2, "d", "w")
		m2 := getFinalModel(t, tm2)

		if m1.Line(0) != "next" {
			t.Errorf("dW: Line(0) = %q, want 'next'", m1.Line(0))
		}
		if m2.Line(0) != ".world next" {
			t.Errorf("dw: Line(0) = %q, want '.world next'", m2.Line(0))
		}
	})

	t.Run("test 'd2W' deletes two WORDs", func(t *testing.T) {
		lines := []string{"one two three four"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "d", "2", "W")

		m := getFinalModel(t, tm)
		if m.Line(0) != "three four" {
			t.Errorf("Line(0) = %q, want 'three four'", m.Line(0))
		}
	})

	t.Run("test 'yW' yanks WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "y", "W")

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		// Should yank "hello.world " (with trailing space)
		if len(reg.Content) != 1 || reg.Content[0] != "hello.world " {
			t.Errorf("register content = %q, want 'hello.world '", reg.Content)
		}
	})

	t.Run("test 'y2W' yanks two WORDs", func(t *testing.T) {
		lines := []string{"one two three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "y", "2", "W")

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		if len(reg.Content) != 1 || reg.Content[0] != "one two " {
			t.Errorf("register content = %q, want 'one two '", reg.Content)
		}
	})

	t.Run("test 'cW' changes WORD (placeholder - c not implemented)", func(t *testing.T) {
		// Skip if change operator not implemented
		t.Skip("Change operator not implemented yet")
	})
}

func TestMoveForwardWORDInVisualMode(t *testing.T) {
	t.Run("test 'vW' selects WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "W")

		m := getFinalModel(t, tm)
		if m.Mode() != action.VisualMode {
			t.Errorf("Mode() = %v, want VisualMode", m.Mode())
		}
		if m.AnchorX() != 0 {
			t.Errorf("AnchorX() = %d, want 0", m.AnchorX())
		}
		// Should extend to start of "next"
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test 'vWd' deletes selection", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "W", "d")

		m := getFinalModel(t, tm)
		// Visual selection from 0 to 12, delete "hello.world "
		if m.Line(0) != "ext" {
			t.Errorf("Line(0) = %q, want 'ext'", m.Line(0))
		}
	})

	t.Run("test 'v2W' selects two WORDs", func(t *testing.T) {
		lines := []string{"one two three four"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "2", "W")

		m := getFinalModel(t, tm)
		if m.AnchorX() != 0 {
			t.Errorf("AnchorX() = %d, want 0", m.AnchorX())
		}
		// Should extend to start of "three"
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test 'VW' in visual line mode", func(t *testing.T) {
		lines := []string{"hello world", "next line"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "V", "W")

		m := getFinalModel(t, tm)
		if m.Mode() != action.VisualLineMode {
			t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
		}
		// W should still move cursor, but line mode selects whole lines
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})
}

// =============================================================================
// E (WORD end) Motion Tests
// =============================================================================
// E moves forward to the end of the current WORD.
// Key difference from 'e': E treats ALL non-whitespace as one WORD.
// On "hello.world", 'e' stops at 'o' (end of "hello"), 'E' stops at 'd' (end of "hello.world").

func TestMoveForwardWORDEnd(t *testing.T) {
	// -------------------------------------------------------------------------
	// Basic Movement
	// -------------------------------------------------------------------------

	t.Run("test 'E' moves to end of WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "hello" ends at index 4
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'EE' moves to end of second WORD", func(t *testing.T) {
		lines := []string{"hello world test"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		// "world" ends at index 10
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'EEE' moves to end of third WORD", func(t *testing.T) {
		lines := []string{"hello world test"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E", "E")

		m := getFinalModel(t, tm)
		// "test" ends at index 15
		if m.CursorX() != 15 {
			t.Errorf("CursorX() = %d, want 15", m.CursorX())
		}
	})

	t.Run("test 'E' from middle of WORD", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// From index 2 in "hello", E goes to index 4 (end of "hello")
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'E' from last char of WORD moves to next WORD end", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// From end of "hello", E goes to end of "world" (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'E' from space moves to next WORD end", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// From space, E goes to end of "world" (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// Punctuation Handling (KEY DIFFERENCE from 'e')
	// -------------------------------------------------------------------------

	t.Run("test 'E' goes through punctuation (does NOT stop at dot)", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// E treats "hello.world" as one WORD, ends at 'd' (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'e' vs 'E' on dotted text", func(t *testing.T) {
		// First test 'e'
		lines := []string{"hello.world next"}
		tm1 := newTestModelWithLines(t, lines)
		sendKeys(tm1, "e")
		m1 := getFinalModel(t, tm1)

		// 'e' should stop at end of "hello" (index 4)
		if m1.CursorX() != 4 {
			t.Errorf("'e': CursorX() = %d, want 4", m1.CursorX())
		}

		// Now test 'E'
		tm2 := newTestModelWithLines(t, lines)
		sendKeys(tm2, "E")
		m2 := getFinalModel(t, tm2)

		// 'E' should go to end of "hello.world" (index 10)
		if m2.CursorX() != 10 {
			t.Errorf("'E': CursorX() = %d, want 10", m2.CursorX())
		}
	})

	t.Run("test 'E' goes through comma", func(t *testing.T) {
		lines := []string{"foo,bar baz"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "foo,bar" ends at index 6
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'E' goes through multiple punctuation", func(t *testing.T) {
		lines := []string{"a...b...c next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "a...b...c" ends at index 8
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test 'E' goes through brackets and parens", func(t *testing.T) {
		lines := []string{"func(arg) next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "func(arg)" ends at index 8
		if m.CursorX() != 8 {
			t.Errorf("CursorX() = %d, want 8", m.CursorX())
		}
	})

	t.Run("test 'E' goes through operators", func(t *testing.T) {
		lines := []string{"a+b*c next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "a+b*c" ends at index 4
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'E' goes through quotes", func(t *testing.T) {
		lines := []string{`"hello" next`}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// `"hello"` ends at index 6
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test 'E' on pure punctuation sequence", func(t *testing.T) {
		lines := []string{"... next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "..." ends at index 2
		if m.CursorX() != 2 {
			t.Errorf("CursorX() = %d, want 2", m.CursorX())
		}
	})

	t.Run("test 'E' from punctuation within WORD", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// From '.', E goes to 'd' (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// Line Crossing
	// -------------------------------------------------------------------------

	t.Run("test 'E' moves to next line when at end", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Should be at end of "world" on line 1, index 4
		if m.CursorY() != 1 {
			t.Errorf("CursorY() = %d, want 1", m.CursorY())
		}
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'E' skips empty lines", func(t *testing.T) {
		lines := []string{"hello", "", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		// First E: end of "hello" (0,4)
		// Second E: skips empty line, end of "world" (2,4)
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'E' skips whitespace-only lines", func(t *testing.T) {
		lines := []string{"hello", "   ", "world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		if m.CursorY() != 2 {
			t.Errorf("CursorY() = %d, want 2", m.CursorY())
		}
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// End of File Behavior
	// -------------------------------------------------------------------------

	t.Run("test 'E' at end of file stays put", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		// Go to end of file
		sendKeys(tm, "E", "E", "E")

		m := getFinalModel(t, tm)
		// Should stay at end of "hello" (index 4)
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})

	t.Run("test 'E' on last WORD of file", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E", "E")

		m := getFinalModel(t, tm)
		// Should be at end of "world" (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'E' on single character file", func(t *testing.T) {
		lines := []string{"a"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Already at end, should stay at 0
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// Whitespace Handling
	// -------------------------------------------------------------------------

	t.Run("test 'E' skips multiple spaces", func(t *testing.T) {
		lines := []string{"hello    world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		// "world" ends at index 13
		if m.CursorX() != 13 {
			t.Errorf("CursorX() = %d, want 13", m.CursorX())
		}
	})

	t.Run("test 'E' skips tabs", func(t *testing.T) {
		lines := []string{"hello\tworld"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		// "world" ends at index 10
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'E' with leading whitespace", func(t *testing.T) {
		lines := []string{"   hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// From start (in whitespace), E goes to end of "hello" (index 7)
		if m.CursorX() != 7 {
			t.Errorf("CursorX() = %d, want 7", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// Count Handling
	// -------------------------------------------------------------------------

	t.Run("test '2E' moves to end of second WORD", func(t *testing.T) {
		lines := []string{"one two three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "2", "E")

		m := getFinalModel(t, tm)
		// "two" ends at index 6
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test '3E' moves to end of third WORD", func(t *testing.T) {
		lines := []string{"one two three four"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "3", "E")

		m := getFinalModel(t, tm)
		// "three" ends at index 12
		if m.CursorX() != 12 {
			t.Errorf("CursorX() = %d, want 12", m.CursorX())
		}
	})

	t.Run("test '5E' with fewer WORDs available", func(t *testing.T) {
		lines := []string{"one two"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "5", "E")

		m := getFinalModel(t, tm)
		// Only 2 WORDs, should stop at end of "two" (index 6)
		if m.CursorX() != 6 {
			t.Errorf("CursorX() = %d, want 6", m.CursorX())
		}
	})

	t.Run("test '2E' across lines", func(t *testing.T) {
		lines := []string{"hello", "world test"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "2", "E")

		m := getFinalModel(t, tm)
		// First E: end of "hello" (0,4)
		// Second E: end of "world" (1,4)
		if m.CursorY() != 1 {
			t.Errorf("CursorY() = %d, want 1", m.CursorY())
		}
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	// -------------------------------------------------------------------------
	// Complex/Real-World Cases
	// -------------------------------------------------------------------------

	t.Run("test 'E' with URL-like text", func(t *testing.T) {
		lines := []string{"https://example.com/path next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Entire URL is one WORD, ends at index 23
		if m.CursorX() != 23 {
			t.Errorf("CursorX() = %d, want 23", m.CursorX())
		}
	})

	t.Run("test 'E' with email-like text", func(t *testing.T) {
		lines := []string{"user@example.com next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Entire email is one WORD, ends at index 15
		if m.CursorX() != 15 {
			t.Errorf("CursorX() = %d, want 15", m.CursorX())
		}
	})

	t.Run("test 'E' with file path", func(t *testing.T) {
		lines := []string{"/home/user/file.txt next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Entire path is one WORD, ends at index 18
		if m.CursorX() != 18 {
			t.Errorf("CursorX() = %d, want 18", m.CursorX())
		}
	})

	t.Run("test 'E' complex code-like text", func(t *testing.T) {
		lines := []string{"foo.bar(baz) next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "foo.bar(baz)" ends at index 11
		if m.CursorX() != 11 {
			t.Errorf("CursorX() = %d, want 11", m.CursorX())
		}
	})

	t.Run("test 'EE' complex code-like text", func(t *testing.T) {
		lines := []string{"foo.bar(baz) next.thing"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E", "E")

		m := getFinalModel(t, tm)
		// Second WORD "next.thing" ends at index 22
		if m.CursorX() != 22 {
			t.Errorf("CursorX() = %d, want 22", m.CursorX())
		}
	})

	t.Run("test 'E' on array access", func(t *testing.T) {
		lines := []string{"arr[0] next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// "arr[0]" ends at index 5
		if m.CursorX() != 5 {
			t.Errorf("CursorX() = %d, want 5", m.CursorX())
		}
	})

	t.Run("test 'E' on method chain", func(t *testing.T) {
		lines := []string{"obj.method().chain() next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "E")

		m := getFinalModel(t, tm)
		// Entire chain is one WORD, ends at index 19
		if m.CursorX() != 19 {
			t.Errorf("CursorX() = %d, want 19", m.CursorX())
		}
	})
}

func TestMoveForwardWORDEndWithOperator(t *testing.T) {
	t.Run("test 'dE' deletes to end of WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "d", "E")

		m := getFinalModel(t, tm)
		// Should delete "hello.world" leaving " next"
		if m.Line(0) != " next" {
			t.Errorf("Line(0) = %q, want ' next'", m.Line(0))
		}
	})

	t.Run("test 'dE' vs 'de' on dotted text", func(t *testing.T) {
		// First test 'de'
		lines := []string{"hello.world next"}
		tm1 := newTestModelWithLines(t, lines)
		sendKeys(tm1, "d", "e")
		m1 := getFinalModel(t, tm1)

		// 'de' should delete "hello" leaving ".world next"
		if m1.Line(0) != ".world next" {
			t.Errorf("'de': Line(0) = %q, want '.world next'", m1.Line(0))
		}

		// Now test 'dE'
		tm2 := newTestModelWithLines(t, lines)
		sendKeys(tm2, "d", "E")
		m2 := getFinalModel(t, tm2)

		// 'dE' should delete "hello.world" leaving " next"
		if m2.Line(0) != " next" {
			t.Errorf("'dE': Line(0) = %q, want ' next'", m2.Line(0))
		}
	})

	t.Run("test 'd2E' deletes two WORDs", func(t *testing.T) {
		lines := []string{"one.a two.b three"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "d", "2", "E")

		m := getFinalModel(t, tm)
		// Should delete "one.a two.b" leaving " three"
		if m.Line(0) != " three" {
			t.Errorf("Line(0) = %q, want ' three'", m.Line(0))
		}
	})

	t.Run("test 'yE' yanks WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "y", "E")

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		// E is inclusive, should yank "hello.world" (no trailing space)
		if len(reg.Content) != 1 || reg.Content[0] != "hello.world" {
			t.Errorf("register content = %q, want 'hello.world'", reg.Content)
		}
	})

	t.Run("test 'y2E' yanks two WORDs", func(t *testing.T) {
		lines := []string{"foo.bar baz.qux rest"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "y", "2", "E")

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		// E is inclusive, should yank "foo.bar baz.qux" (no trailing space)
		if len(reg.Content) != 1 || reg.Content[0] != "foo.bar baz.qux" {
			t.Errorf("register content = %q, want 'foo.bar baz.qux'", reg.Content)
		}
	})
}

func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
	t.Run("test 'vE' selects to end of WORD including punctuation", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "E")

		m := getFinalModel(t, tm)
		if m.Mode() != action.VisualMode {
			t.Errorf("Mode() = %v, want VisualMode", m.Mode())
		}
		// Cursor at end of "hello.world" (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})

	t.Run("test 'vEd' deletes selection", func(t *testing.T) {
		lines := []string{"hello.world next"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "E", "d")

		m := getFinalModel(t, tm)
		// Should delete "hello.world" leaving " next"
		if m.Line(0) != " next" {
			t.Errorf("Line(0) = %q, want ' next'", m.Line(0))
		}
	})

	t.Run("test 'v2E' selects to end of second WORD", func(t *testing.T) {
		lines := []string{"foo.bar baz.qux rest"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "v", "2", "E")

		m := getFinalModel(t, tm)
		// Cursor at end of "baz.qux" (index 14)
		if m.CursorX() != 14 {
			t.Errorf("CursorX() = %d, want 14", m.CursorX())
		}
	})

	t.Run("test 'VE' in visual line mode", func(t *testing.T) {
		lines := []string{"hello.world", "next line"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "V", "E")

		m := getFinalModel(t, tm)
		if m.Mode() != action.VisualLineMode {
			t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
		}
		// Cursor at end of "hello.world" (index 10)
		if m.CursorX() != 10 {
			t.Errorf("CursorX() = %d, want 10", m.CursorX())
		}
	})
}
