package editor

import (
	"testing"

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

func TestDeleteChar(t *testing.T) {
	t.Run("test 'x' deletes character under cursor", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "x")

		m := getFinalModel(t, tm)
		if m.Line(0) != "ello" {
			t.Errorf("lines[0] = %q, want 'ello'", m.Line(0))
		}
	})

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

		m := getFinalModel(t, tm)
		if m.Line(0) != "helo" {
			t.Errorf("lines[0] = %q, want 'helo'", m.Line(0))
		}
	})

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

		m := getFinalModel(t, tm)
		if m.Line(0) != "hell" {
			t.Errorf("lines[0] = %q, want 'hell'", m.Line(0))
		}
	})

	t.Run("test 'xx' deletes two characters", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "x", "x")

		m := getFinalModel(t, tm)
		if m.Line(0) != "llo" {
			t.Errorf("lines[0] = %q, want 'llo'", m.Line(0))
		}
	})
}

func TestDeleteCharWithCount(t *testing.T) {
	t.Run("test '3x' deletes three characters", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "3", "x")

		m := getFinalModel(t, tm)
		if m.Line(0) != "lo" {
			t.Errorf("lines[0] = %q, want 'lo'", m.Line(0))
		}
	})

	t.Run("test '10x' with overflow", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "1", "0", "x")

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

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

		m := getFinalModel(t, tm)
		if m.Line(0) != "hlo" {
			t.Errorf("lines[0] = %q, want 'hlo'", m.Line(0))
		}
	})
}

func TestDeleteCharEdgeCases(t *testing.T) {
	t.Run("test 'x' on empty line does nothing", func(t *testing.T) {
		lines := []string{""}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "x")

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

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

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

	t.Run("test 'x' at last char deletes it", func(t *testing.T) {
		lines := []string{"ab"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
		sendKeys(tm, "x")

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

	t.Run("test 'x' with whitespace", func(t *testing.T) {
		lines := []string{"a b c"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
		sendKeys(tm, "x")

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

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

		m := getFinalModel(t, tm)
		if m.LineCount() != 2 {
			t.Errorf("LineCount() = %d, want 2", m.LineCount())
		}
		if m.Line(1) != "world" {
			t.Errorf("Line(1) = %q, want 'world'", m.Line(1))
		}
	})

	t.Run("test 'x' multiple times deletes multiple chars", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "x", "x", "x")

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

	t.Run("test 'x' on line with tabs", func(t *testing.T) {
		lines := []string{"a\tb"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
		sendKeys(tm, "x")

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

	t.Run("test '5x' with only 3 chars available", func(t *testing.T) {
		lines := []string{"abc"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "5", "x")

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

	t.Run("test 'x' in middle preserves surrounding chars", func(t *testing.T) {
		lines := []string{"abcde"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "x")

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

func TestDeleteToEndOfLine(t *testing.T) {
	t.Run("test 'D' deletes to end of line", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' from start of line deletes entire content", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "D")

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

	t.Run("test 'D' at last character", func(t *testing.T) {
		lines := []string{"hello"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' cursor position after delete", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "D")

		m := getFinalModel(t, tm)
		// Cursor should move to last character of remaining text
		if m.CursorX() != 4 {
			t.Errorf("CursorX() = %d, want 4", m.CursorX())
		}
	})

	t.Run("test 'D' deletes nothing on blank line", func(t *testing.T) {
		lines := []string{""}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "D")

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

	t.Run("test 'D' with count deletes following lines", func(t *testing.T) {
		lines := []string{"hello", "world", "hi", "mom"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "2", "D")

		m := getFinalModel(t, tm)
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %q, want '3'", m.LineCount())
		}
		if m.Line(0) != "he" {
			t.Errorf("Line(0) = %q, want 'he'", m.Line(0))
		}
		if m.Line(1) != "hi" {
			t.Errorf("Line(1) = %q, want 'hi'", m.Line(1))
		}
	})

	t.Run("test 'D' with count deletes following lines with overflow", func(t *testing.T) {
		lines := []string{"hello", "world", "hi", "mom"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "8", "D")

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

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

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

	t.Run("test 'D' at end of file", func(t *testing.T) {
		lines := []string{"line 1", "line 2"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
		sendKeys(tm, "D")

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

	t.Run("test 'D' preserves lines above", func(t *testing.T) {
		lines := []string{"line 1", "line 2", "line 3"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
		sendKeys(tm, "D")

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

	t.Run("test 'D' cursor clamps to valid position", func(t *testing.T) {
		lines := []string{"hello world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "D")

		m := getFinalModel(t, tm)
		if m.Line(0) != "he" {
			t.Errorf("Line(0) = %q, want 'he'", m.Line(0))
		}
		// Cursor should clamp to last char
		if m.CursorX() != 1 {
			t.Errorf("CursorX() = %d, want 1", m.CursorX())
		}
	})

	t.Run("test 'D' on whitespace-only line", func(t *testing.T) {
		lines := []string{"    "}
		tm := newTestModelWithLines(t, lines)
		sendKeys(tm, "D")

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

	t.Run("test 'D' from middle of whitespace-only line", func(t *testing.T) {
		lines := []string{"    "}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' with tabs", func(t *testing.T) {
		lines := []string{"hello\tworld"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' on line with only one char remaining after cursor", func(t *testing.T) {
		lines := []string{"ab"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' does not affect line below", func(t *testing.T) {
		lines := []string{"hello", "world"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
		sendKeys(tm, "D")

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

	t.Run("test 'D' with multiple lines", func(t *testing.T) {
		lines := []string{"first line", "second line", "third line"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
		sendKeys(tm, "D")

		m := getFinalModel(t, tm)
		if m.Line(0) != "first" {
			t.Errorf("Line(0) = %q, want 'first'", m.Line(0))
		}
		if m.LineCount() != 3 {
			t.Errorf("LineCount() = %d, want 3", m.LineCount())
		}
	})

	t.Run("test 'D' preserves cursor Y position", func(t *testing.T) {
		lines := []string{"line 1", "line 2", "line 3"}
		tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 1})
		sendKeys(tm, "D")

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