package editor

import (
	"testing"

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

// =============================================================================
// cc (Change Line) Tests
// =============================================================================

func TestChangeLine(t *testing.T) {
	t.Run("cc changes first line and enters insert mode", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello", "world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
		// First line should be empty (ready for insert)
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "world" {
			t.Errorf("Line(1) = %q, want 'world'", m.Line(1))
		}
	})

	t.Run("cc changes middle line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "line one" {
			t.Errorf("Line(0) = %q, want 'line one'", m.Line(0))
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
		if m.Line(2) != "line three" {
			t.Errorf("Line(2) = %q, want 'line three'", m.Line(2))
		}
	})

	t.Run("cc changes last line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello", "world"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hello" {
			t.Errorf("Line(0) = %q, want 'hello'", m.Line(0))
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
	})

	t.Run("cc puts deleted line in register", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world", "second line"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "c")

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

	t.Run("cc on single line file", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"only line"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.LineCount() != 1 {
			t.Errorf("LineCount() = %d, want 1", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("cc cursor at column 0", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello", "world"}),
			WithCursorPos(action.Position{Line: 0, Col: 3}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		// Cursor should be at column 0 on the empty line
		if m.CursorX() != 0 {
			t.Errorf("CursorX() = %d, want 0", m.CursorX())
		}
		if m.CursorY() != 0 {
			t.Errorf("CursorY() = %d, want 0", m.CursorY())
		}
	})
}

func TestChangeLineWithCount(t *testing.T) {
	t.Run("2cc changes two lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three", "line four"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "2", "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have 3 lines: empty + line three + line four
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "line three" {
			t.Errorf("Line(1) = %q, want 'line three'", m.Line(1))
		}
	})

	t.Run("3cc changes three lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"one", "two", "three", "four", "five"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "3", "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have 3 lines: one + empty + five
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(0) != "one" {
			t.Errorf("Line(0) = %q, want 'one'", m.Line(0))
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
		if m.Line(2) != "five" {
			t.Errorf("Line(2) = %q, want 'five'", m.Line(2))
		}
	})

	t.Run("5cc with only 3 lines remaining changes all remaining", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"one", "two", "three"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "5", "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.LineCount() != 1 {
			t.Errorf("LineCount() = %d, want 1", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("2cc puts both lines in register", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"first", "second", "third"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "2", "c", "c")

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		if len(reg.Content) != 2 {
			t.Errorf("register content length = %d, want 2", len(reg.Content))
		}
	})
}

// =============================================================================
// c with Horizontal Motions Tests
// =============================================================================

func TestChangeWithHorizontalMotion(t *testing.T) {
	t.Run("cl changes single character", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "l")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "ello world" {
			t.Errorf("Line(0) = %q, want 'ello world'", m.Line(0))
		}
	})

	t.Run("c2l changes two characters", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "2", "l")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "llo world" {
			t.Errorf("Line(0) = %q, want 'llo world'", m.Line(0))
		}
	})

	t.Run("ch changes character to the left", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 5}),
		)
		sendKeys(tm, "c", "h")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hell world" {
			t.Errorf("Line(0) = %q, want 'hell world'", m.Line(0))
		}
	})

	t.Run("c$ changes to end of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "c", "$")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hello " {
			t.Errorf("Line(0) = %q, want 'hello '", m.Line(0))
		}
	})

	t.Run("c0 changes to start of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "c", "0")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "world" {
			t.Errorf("Line(0) = %q, want 'world'", m.Line(0))
		}
	})

	t.Run("c^ changes to first non-blank", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"   hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 8}),
		)
		sendKeys(tm, "c", "^")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// ^ is exclusive motion, so position 8 (space) is not included
		// Delete positions 3-7 ("hello"), leaving "   " + " world" = "    world"
		if m.Line(0) != "    world" {
			t.Errorf("Line(0) = %q, want '    world'", m.Line(0))
		}
	})
}

// =============================================================================
// c with Word Motions Tests
// =============================================================================

func TestChangeWithWordMotion(t *testing.T) {
	t.Run("cw changes word", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "w")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "world" {
			t.Errorf("Line(0) = %q, want 'world'", m.Line(0))
		}
	})

	t.Run("cw from middle of word changes to next word", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 2}),
		)
		sendKeys(tm, "c", "w")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "heworld" {
			t.Errorf("Line(0) = %q, want 'heworld'", m.Line(0))
		}
	})

	t.Run("ce changes to end of word", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "e")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != " world" {
			t.Errorf("Line(0) = %q, want ' world'", m.Line(0))
		}
	})

	t.Run("cb changes backward word", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "c", "b")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "world" {
			t.Errorf("Line(0) = %q, want 'world'", m.Line(0))
		}
	})

	t.Run("c2w changes two words", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"one two three four"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "2", "w")

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

	t.Run("cW changes WORD", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello.world next"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "W")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "next" {
			t.Errorf("Line(0) = %q, want 'next'", m.Line(0))
		}
	})

	t.Run("cE changes to end of WORD", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello.world next"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "E")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != " next" {
			t.Errorf("Line(0) = %q, want ' next'", m.Line(0))
		}
	})
}

// =============================================================================
// c with Vertical Motions Tests
// =============================================================================

func TestChangeWithVerticalMotion(t *testing.T) {
	t.Run("cj changes current and next line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "j")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have empty line + line three
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "line three" {
			t.Errorf("Line(1) = %q, want 'line three'", m.Line(1))
		}
	})

	t.Run("ck changes current and previous line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "c", "k")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have empty line + line three
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "line three" {
			t.Errorf("Line(1) = %q, want 'line three'", m.Line(1))
		}
	})

	t.Run("c2j changes three lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"one", "two", "three", "four", "five"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "2", "j")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have empty + four + five
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "four" {
			t.Errorf("Line(1) = %q, want 'four'", m.Line(1))
		}
	})
}

// =============================================================================
// c with Jump Motions Tests
// =============================================================================

func TestChangeWithJumpMotion(t *testing.T) {
	t.Run("cG changes from cursor to end of file", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "G")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// All lines should be replaced with one empty line
		if m.LineCount() != 1 {
			t.Errorf("LineCount() = %d, want 1", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("cgg changes from cursor to start of file", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 2, Col: 0}),
		)
		sendKeys(tm, "c", "g", "g")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// All lines should be replaced with one empty line
		if m.LineCount() != 1 {
			t.Errorf("LineCount() = %d, want 1", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("cG from middle changes to end", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three", "line four"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "c", "G")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have line one + empty
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
		if m.Line(0) != "line one" {
			t.Errorf("Line(0) = %q, want 'line one'", m.Line(0))
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
	})
}

// =============================================================================
// C (Change to end of line) Tests
// =============================================================================

func TestChangeToEndOfLine(t *testing.T) {
	t.Run("C changes from cursor to end of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "C")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hello " {
			t.Errorf("Line(0) = %q, want 'hello '", m.Line(0))
		}
	})

	t.Run("C at start of line clears entire line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "C")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("C at end of line enters insert mode", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello"}),
			WithCursorPos(action.Position{Line: 0, Col: 4}),
		)
		sendKeys(tm, "C")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should delete last char
		if m.Line(0) != "hell" {
			t.Errorf("Line(0) = %q, want 'hell'", m.Line(0))
		}
	})

	t.Run("C puts deleted text in register", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "C")

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

// =============================================================================
// s (Substitute character) Tests
// =============================================================================

func TestSubstituteCharacter(t *testing.T) {
	t.Run("s deletes character and enters insert mode", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "s")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "ello" {
			t.Errorf("Line(0) = %q, want 'ello'", m.Line(0))
		}
	})

	t.Run("2s deletes two characters", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "2", "s")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "llo" {
			t.Errorf("Line(0) = %q, want 'llo'", m.Line(0))
		}
	})

	t.Run("s at end of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello"}),
			WithCursorPos(action.Position{Line: 0, Col: 4}),
		)
		sendKeys(tm, "s")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hell" {
			t.Errorf("Line(0) = %q, want 'hell'", m.Line(0))
		}
	})
}

// =============================================================================
// S (Substitute line) Tests
// =============================================================================

func TestSubstituteLine(t *testing.T) {
	t.Run("S clears line and enters insert mode", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 5}),
		)
		sendKeys(tm, "S")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("S preserves other lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "S")

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

	t.Run("2S substitutes two lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"one", "two", "three", "four"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "2", "S")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have empty + three + four
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
		if m.Line(1) != "three" {
			t.Errorf("Line(1) = %q, want 'three'", m.Line(1))
		}
	})
}

// =============================================================================
// Visual Mode Change Tests
// =============================================================================

func TestVisualModeChange(t *testing.T) {
	t.Run("vc changes single character", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "v", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "ello world" {
			t.Errorf("Line(0) = %q, want 'ello world'", m.Line(0))
		}
	})

	t.Run("v with motion then c changes selection", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "v", "e", "c") // select "hello"

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != " world" {
			t.Errorf("Line(0) = %q, want ' world'", m.Line(0))
		}
	})

	t.Run("v$c changes to end of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "v", "$", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "hello " {
			t.Errorf("Line(0) = %q, want 'hello '", m.Line(0))
		}
	})

	t.Run("visual selection spanning lines then c", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 0, Col: 5}),
		)
		sendKeys(tm, "v", "j", "l", "l", "l", "l", "c") // select across lines

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should merge lines with selection removed
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
	})

	t.Run("visual change puts deleted text in register", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "v", "l", "l", "l", "l", "c") // select "hello"

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

func TestVisualLineModeChange(t *testing.T) {
	t.Run("Vc changes line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "V", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
	})

	t.Run("Vjc changes multiple lines", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three", "line four"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "V", "j", "c") // select lines two and three

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// Should have: line one, empty, line four
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
		if m.Line(0) != "line one" {
			t.Errorf("Line(0) = %q, want 'line one'", m.Line(0))
		}
		if m.Line(1) != "" {
			t.Errorf("Line(1) = %q, want ''", m.Line(1))
		}
		if m.Line(2) != "line four" {
			t.Errorf("Line(2) = %q, want 'line four'", m.Line(2))
		}
	})

	t.Run("visual line change puts lines in register", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"line one", "line two", "line three"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "V", "j", "c") // select first two lines

		m := getFinalModel(t, tm)
		reg, ok := m.GetRegister('"')
		if !ok {
			t.Fatal("unnamed register not found")
		}
		if reg.Type != action.LinewiseRegister {
			t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
		}
		if len(reg.Content) != 2 {
			t.Errorf("register content length = %d, want 2", len(reg.Content))
		}
	})
}

// =============================================================================
// Edge Cases
// =============================================================================

func TestChangeEdgeCases(t *testing.T) {
	t.Run("cc on empty line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"", "world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "c")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("cw on last word of line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello world"}),
			WithCursorPos(action.Position{Line: 0, Col: 6}),
		)
		sendKeys(tm, "c", "w")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		// cw on last word should change to end of line
		if m.Line(0) != "hello " {
			t.Errorf("Line(0) = %q, want 'hello '", m.Line(0))
		}
	})

	t.Run("c$ on empty line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{""}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "$")

		m := getFinalModel(t, tm)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
		if m.Line(0) != "" {
			t.Errorf("Line(0) = %q, want ''", m.Line(0))
		}
	})

	t.Run("cj at last line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello", "world"}),
			WithCursorPos(action.Position{Line: 1, Col: 0}),
		)
		sendKeys(tm, "c", "j")

		m := getFinalModel(t, tm)
		// cj at last line should just change current line (can't go down)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
	})

	t.Run("ck at first line", func(t *testing.T) {
		tm := newTestModel(t,
			WithLines([]string{"hello", "world"}),
			WithCursorPos(action.Position{Line: 0, Col: 0}),
		)
		sendKeys(tm, "c", "k")

		m := getFinalModel(t, tm)
		// ck at first line should just change current line (can't go up)
		if m.Mode() != action.InsertMode {
			t.Errorf("Mode() = %v, want InsertMode", m.Mode())
		}
	})
}
