package gocui

import (
	"reflect"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestTextArea(t *testing.T) {
	tests := []struct {
		actions           func(*TextArea)
		wrapWidth         int
		expectedContent   string
		expectedCursor    int
		expectedClipboard string
	}{
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("c")
			},
			expectedContent:   "abc",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
			},
			expectedContent:   "a\nc",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcd")
			},
			expectedContent:   "abcd",
			expectedCursor:    4,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("a字cd")
			},
			expectedContent:   "a字cd",
			expectedCursor:    6,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.BackSpaceChar()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.BackSpaceChar()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.BackSpaceChar()
			},
			expectedContent:   "a",
			expectedCursor:    1,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.DeleteChar()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.DeleteChar()
			},
			expectedContent:   "a",
			expectedCursor:    1,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.MoveCursorLeft()
				textarea.DeleteChar()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("c")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.DeleteChar()
			},
			expectedContent:   "ac",
			expectedCursor:    1,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.MoveCursorLeft()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.MoveCursorLeft()
			},
			expectedContent:   "a",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.MoveCursorLeft()
			},
			expectedContent:   "ab",
			expectedCursor:    1,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.MoveCursorRight()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.MoveCursorRight()
			},
			expectedContent:   "a",
			expectedCursor:    1,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.MoveCursorLeft()
				textarea.MoveCursorRight()
			},
			expectedContent:   "ab",
			expectedCursor:    2,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("漢")
				textarea.TypeCharacter("字")
				textarea.MoveCursorLeft()
			},
			expectedContent:   "漢字",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.ToggleOverwrite()
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
			},
			expectedContent:   "ab",
			expectedCursor:    2,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("c")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.ToggleOverwrite()
				textarea.TypeCharacter("d")
			},
			expectedContent:   "adc",
			expectedCursor:    2,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.MoveLeftWord()
			},
			expectedContent: "aaa bbb",
			expectedCursor:  4,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa\nbbb")
				textarea.MoveLeftWord()
			},
			expectedContent: "aaa\nbbb",
			expectedCursor:  4,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.GoToStartOfLine()
				textarea.MoveLeftWord()
			},
			wrapWidth:       4,
			expectedContent: "aaa bbb",
			expectedCursor:  0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb\n")
				textarea.MoveLeftWord()
			},
			expectedContent: "aaa bbb\n",
			expectedCursor:  7,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.MoveLeftWord()
				textarea.MoveLeftWord()
			},
			expectedContent: "aaa bbb",
			expectedCursor:  0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa")
				textarea.GoToStartOfLine()
				textarea.MoveLeftWord()
			},
			expectedContent: "aaa",
			expectedCursor:  0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.MoveRightWord()
			},
			expectedContent: "aaa bbb",
			expectedCursor:  7,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa\nbbb")
				textarea.GoToStartOfLine()
				textarea.MoveCursorLeft()
				textarea.MoveRightWord()
			},
			expectedContent: "aaa\nbbb",
			expectedCursor:  4,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.GoToStartOfLine()
				textarea.MoveCursorLeft()
				textarea.MoveRightWord()
			},
			wrapWidth:       4,
			expectedContent: "aaa bbb",
			expectedCursor:  7,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb")
				textarea.GoToStartOfLine()
				textarea.MoveRightWord()
			},
			expectedContent: "aaa bbb",
			expectedCursor:  3,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("aaa bbb\n")
				textarea.MoveCursorLeft()
				textarea.GoToStartOfLine()
				textarea.MoveRightWord()
				textarea.MoveRightWord()
			},
			expectedContent: "aaa bbb\n",
			expectedCursor:  7,
		},
		{
			actions: func(textarea *TextArea) {
				// overwrite mode acts same as normal mode when cursor is at the end
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("c")
				textarea.ToggleOverwrite()
				textarea.TypeCharacter("d")
			},
			expectedContent:   "abcd",
			expectedCursor:    4,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.DeleteToStartOfLine()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.DeleteToStartOfLine()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "ab",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.DeleteToStartOfLine()
			},
			expectedContent:   "ab",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.DeleteToStartOfLine()
			},
			expectedContent:   "ab",
			expectedCursor:    2,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
				textarea.TypeCharacter("d")
				textarea.DeleteToStartOfLine()
			},
			expectedContent:   "ab\n",
			expectedCursor:    3,
			expectedClipboard: "cd",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.GoToStartOfLine()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.MoveCursorLeft()
				textarea.GoToStartOfLine()
			},
			expectedContent:   "a",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
				textarea.TypeCharacter("d")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.GoToStartOfLine()
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
				textarea.TypeCharacter("d")
				textarea.GoToStartOfLine()
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
				textarea.TypeCharacter("d")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.GoToStartOfLine()
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.GoToEndOfLine()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("a")
				textarea.TypeCharacter("b")
				textarea.TypeCharacter("\n")
				textarea.TypeCharacter("c")
				textarea.TypeCharacter("d")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.GoToEndOfLine()
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    5,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.SetCursor2D(10, 10)
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.SetCursor2D(-1, -1)
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\ncd")
				textarea.SetCursor2D(0, 0)
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\ncd")
				textarea.SetCursor2D(2, 0)
			},
			expectedContent:   "ab\ncd",
			expectedCursor:    2,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\ncd\nef")
				textarea.SetCursor2D(2, 1)
			},
			expectedContent:   "ab\ncd\nef",
			expectedCursor:    5,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcd\n\nijkl")
				textarea.MoveCursorUp()
			},
			expectedContent:   "abcd\n\nijkl",
			expectedCursor:    5,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcdef\n老老老")
				textarea.MoveCursorLeft()
				textarea.MoveCursorUp()
			},
			expectedContent:   "abcdef\n老老老",
			expectedCursor:    4,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcdef\n老老老")
				textarea.MoveCursorUp()
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorDown()
			},
			expectedContent:   "abcdef\n老老老",
			expectedCursor:    13,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcd\nef")
				textarea.MoveCursorUp()
				textarea.GoToEndOfLine()
				textarea.MoveCursorDown()
			},
			expectedContent:   "abcd\nef",
			expectedCursor:    7,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abcd")
				textarea.MoveCursorUp()
			},
			expectedContent:   "abcd",
			expectedCursor:    4,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abcdefg`)
				textarea.Clear()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abcdefg`)
				textarea.Clear()
			},
			expectedContent:   "",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc def`)
				textarea.MoveCursorLeft()
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc f",
			expectedCursor:    4,
			expectedClipboard: "de",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc  def   `)
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc  ",
			expectedCursor:    5,
			expectedClipboard: "def   ",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc def\nghi")
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc def\n",
			expectedCursor:    8,
			expectedClipboard: "ghi",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc def\nghi")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc defghi",
			expectedCursor:    7,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc(def)`)
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc(def",
			expectedCursor:    7,
			expectedClipboard: ")",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc(def`)
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc(",
			expectedCursor:    4,
			expectedClipboard: "def",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc`)
				textarea.GoToStartOfLine()
				textarea.BackSpaceWord()
			},
			expectedContent:   "abc",
			expectedCursor:    0,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc`)
				textarea.Yank()
			},
			expectedContent:   "abc",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc def`)
				textarea.DeleteToStartOfLine()
				textarea.Yank()
				textarea.Yank()
			},
			expectedContent:   "abc defabc def",
			expectedCursor:    14,
			expectedClipboard: "abc def",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc\ndef")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorUp()
				textarea.DeleteToEndOfLine()
			},
			expectedContent:   "a\ndef",
			expectedCursor:    1,
			expectedClipboard: "bc",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc\ndef")
				textarea.MoveCursorUp()
				textarea.DeleteToEndOfLine()
			},
			expectedContent:   "abcdef",
			expectedCursor:    3,
			expectedClipboard: "",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc def`)
				textarea.BackSpaceWord()
				textarea.Yank()
				textarea.Yank()
			},
			expectedContent:   "abc defdef",
			expectedCursor:    10,
			expectedClipboard: "def",
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString(`abc def`)
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.DeleteToEndOfLine()
				textarea.Yank()
				textarea.Yank()
			},
			expectedContent:   "abc defef",
			expectedCursor:    9,
			expectedClipboard: "ef",
		},
	}

	for i, test := range tests {
		t.Run(strconv.Itoa(i+1), func(t *testing.T) {
			textarea := &TextArea{}
			if test.wrapWidth > 0 {
				textarea.AutoWrap = true
				textarea.AutoWrapWidth = test.wrapWidth
			}
			test.actions(textarea)
			assert.EqualValues(t, test.expectedContent, textarea.GetUnwrappedContent())
			assert.EqualValues(t, test.expectedCursor, textarea.cursor)
			assert.EqualValues(t, test.expectedClipboard, textarea.clipboard)
		})
	}
}

func TestGetCursorXY(t *testing.T) {
	tests := []struct {
		actions   func(*TextArea)
		wrapWidth int
		expectedX int
		expectedY int
	}{
		{
			actions: func(textarea *TextArea) {
				// do nothing
			},
			expectedX: 0,
			expectedY: 0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("\n")
			},
			expectedX: 0,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("\na")
			},
			expectedX: 1,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("\n")
				textarea.MoveCursorUp()
			},
			expectedX: 0,
			expectedY: 0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("\n\n")
				textarea.MoveCursorUp()
			},
			expectedX: 0,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\ncd")
			},
			expectedX: 2,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\n")
			},
			expectedX: 0,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\n")
				textarea.MoveCursorLeft()
			},
			expectedX: 2,
			expectedY: 0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\n\n")
			},
			expectedX: 0,
			expectedY: 2,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("ab\n\n")
				textarea.MoveCursorLeft()
			},
			expectedX: 0,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeCharacter("漢")
				textarea.TypeCharacter("字")
			},
			expectedX: 4,
			expectedY: 0,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc de")
				textarea.MoveCursorLeft()
			},
			wrapWidth: 4,
			expectedX: 1,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc de")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
			},
			wrapWidth: 4,
			expectedX: 0,
			expectedY: 1,
		},
		{
			actions: func(textarea *TextArea) {
				textarea.TypeString("abc de")
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
				textarea.MoveCursorLeft()
			},
			wrapWidth: 4,
			expectedX: 3,
			expectedY: 0,
		},
	}

	for i, test := range tests {
		t.Run(strconv.Itoa(i+1), func(t *testing.T) {
			textarea := &TextArea{}
			if test.wrapWidth > 0 {
				textarea.AutoWrap = true
				textarea.AutoWrapWidth = test.wrapWidth
			}
			test.actions(textarea)
			x, y := textarea.GetCursorXY()
			assert.EqualValues(t, test.expectedX, x)
			assert.EqualValues(t, test.expectedY, y)

			// As a sanity check, test that setting the cursor back to (x, y) results in the same cursor position:
			cursor := textarea.cursor
			textarea.SetCursor2D(x, y)
			assert.EqualValues(t, cursor, textarea.cursor)
		})
	}
}

func Test_AutoWrapContent(t *testing.T) {
	tests := []struct {
		name                   string
		content                string
		autoWrapWidth          int
		expectedWrappedContent string
		expectedSoftLineBreaks []int
	}{
		{
			name:                   "empty content",
			content:                "",
			autoWrapWidth:          7,
			expectedWrappedContent: "",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "no wrapping necessary",
			content:                "abcde",
			autoWrapWidth:          7,
			expectedWrappedContent: "abcde",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "wrap at whitespace",
			content:                "abcde xyz",
			autoWrapWidth:          7,
			expectedWrappedContent: "abcde \nxyz",
			expectedSoftLineBreaks: []int{6},
		},
		{
			name:                   "take wide characters into account",
			content:                "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 x y", // the flag has a width of 2
			autoWrapWidth:          7,
			expectedWrappedContent: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 x \ny",
			expectedSoftLineBreaks: []int{60},
		},
		{
			name:                   "take wide characters into account at line end",
			content:                "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿", // the flag has a width of 2
			autoWrapWidth:          7,
			expectedWrappedContent: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 \n🏴󠁧󠁢󠁥󠁮󠁧󠁿",
			expectedSoftLineBreaks: []int{58},
		},
		{
			name:                   "lots of whitespace is preserved at end of line",
			content:                "abcde      xyz",
			autoWrapWidth:          7,
			expectedWrappedContent: "abcde      \nxyz",
			expectedSoftLineBreaks: []int{11},
		},
		{
			name:                   "don't wrap inside long word when there's no whitespace",
			content:                "abc defghijklmn opq",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc \ndefghijklmn \nopq",
			expectedSoftLineBreaks: []int{4, 16},
		},
		{
			name:                   "don't break at space after footnote symbol",
			content:                "abc\n[1]: https://long/link\ndef",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc\n[1]: https://long/link\ndef",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "don't break at space after footnote symbol at soft line start",
			content:                "abc def [1]: https://long/link\nghi",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc def \n[1]: https://long/link\nghi",
			expectedSoftLineBreaks: []int{8},
		},
		{
			name:                   "do break at subsequent space after footnote symbol",
			content:                "abc\n[1]: normal text follows\ndef",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc\n[1]: normal \ntext \nfollows\ndef",
			expectedSoftLineBreaks: []int{16, 21},
		},
		{
			name:                   "don't break at space after trailer at beginning of message",
			content:                "Signed-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "Signed-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "don't break at space after trailer in a trailer block at the end of a message",
			content:                "abc\n\nSigned-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nSigned-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "don't break at space after trailer with URL value",
			content:                "abc\n\nBug: https://example.com/a/very/long/path\nIssue: http://example.com/a/very/long/path\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nBug: https://example.com/a/very/long/path\nIssue: http://example.com/a/very/long/path\n",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "do break at space if trailer is not in a trailer block at the end",
			content:                "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\nMore text here\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nSigned-off-by: \nJohn Doe \n<john@doe.com>\n\nMore text \nhere\n",
			expectedSoftLineBreaks: []int{20, 29, 55},
		},
		{
			// Each line in the trailer block is judged on its own, so a line
			// that isn't recognized as a trailer wraps without affecting the
			// real trailers around it.
			name:                   "keep a trailer next to a non-trailer line in the same block",
			content:                "abc\n\nFixes: a long description that wraps\nSigned-off-by: John Doe <john@doe.com>\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nFixes: a \nlong \ndescription \nthat wraps\nSigned-off-by: John Doe <john@doe.com>\n",
			expectedSoftLineBreaks: []int{14, 19, 31},
		},
		{
			name:                   "don't break at space after trailer when the block ends in a blank line",
			content:                "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\n",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "do break normal text after non-hyphenated key",
			content:                "However: in this commit blah blah blah\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "However: \nin this \ncommit \nblah blah \nblah\n",
			expectedSoftLineBreaks: []int{9, 17, 24, 34},
		},
		{
			name:                   "do break at space after trailer if there is no space after the colon",
			content:                "abc\n\nSigned-off-by:John Doe <john@doe.com>\n",
			autoWrapWidth:          10,
			expectedWrappedContent: "abc\n\nSigned-off-by:John \nDoe \n<john@doe.com>\n",
			expectedSoftLineBreaks: []int{24, 28},
		},
		{
			name:                   "hard line breaks",
			content:                "abc\ndef\n",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc\ndef\n",
			expectedSoftLineBreaks: []int{},
		},
		{
			name:                   "mixture of hard and soft line breaks",
			content:                "abc def ghi jkl mno\npqr stu vwx yz\n",
			autoWrapWidth:          7,
			expectedWrappedContent: "abc def \nghi jkl \nmno\npqr stu \nvwx yz\n",
			expectedSoftLineBreaks: []int{8, 16, 28},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			textArea := &TextArea{content: tt.content, AutoWrapWidth: tt.autoWrapWidth, AutoWrap: true}
			cells, softLineBreakIndices := contentToCells(tt.content, tt.autoWrapWidth)
			textArea.cells = cells
			if !reflect.DeepEqual(textArea.GetContent(), tt.expectedWrappedContent) {
				t.Errorf("autoWrapContentImpl() wrappedContent = %v, expected %v", textArea.GetContent(), tt.expectedWrappedContent)
			}
			if !reflect.DeepEqual(softLineBreakIndices, tt.expectedSoftLineBreaks) {
				t.Errorf("autoWrapContentImpl() softLineBreakIndices = %v, expected %v", softLineBreakIndices, tt.expectedSoftLineBreaks)
			}

			// As a sanity check, run through all characters of the original content,
			// convert the cursor to the wrapped cursor, and check that the character
			// in the wrapped content at that position is the same:
			origCursor := 0
			for _, chr := range stringToGraphemes(tt.content) {
				wrappedIndex := textArea.contentCursorToCellCursor(origCursor)
				if chr != textArea.cells[wrappedIndex].char {
					t.Errorf("Runes in orig content and wrapped content don't match at %d: expected %v, got %v", origCursor, chr, textArea.cells[wrappedIndex].char)
				}

				// Also, check that converting the wrapped position back to the
				// orig position yields the original value again:
				origIndexAgain := textArea.cellCursorToContentCursor(wrappedIndex)
				if origCursor != origIndexAgain {
					t.Errorf("wrappedCursorToOrigCursor doesn't yield original position: expected %d, got %d", origCursor, origIndexAgain)
				}

				origCursor += len(chr)
			}
		})
	}
}

var testContent = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque vehicula mi at elit pellentesque, eu pulvinar ligula molestie.
In vitae orci vitae elit fermentum lobortis sed in nisi.
Nam non odio nisi.
Donec vitae elit enim.
Pellentesque faucibus dolor at metus elementum sollicitudin.
Mauris eu orci vel odio ornare feugiat eget ac nisl.
Nam at dolor erat.
Integer sit amet rutrum lectus, mollis pretium sapien.
Maecenas ligula ipsum, congue vitae rhoncus eget, volutpat at quam.
Donec ac ultricies tortor, sit amet sollicitudin urna.
Integer porta ornare diam a imperdiet.
Praesent vulputate mi turpis, in porttitor diam commodo a.
Donec ut enim ligula.

[Thïs-is-not-à-fôôtnöte]: https://example.com/footnote

Sïgned-öff-by: This is not a trailer

[1]: This is a footnote

Signed-off-by: John Doe <john@doe.com>
`

func BenchmarkTypeCharacter(b *testing.B) {
	textArea := &TextArea{content: testContent, AutoWrapWidth: 72, AutoWrap: true}
	textArea.SetCursor2D(0, 0)

	b.ResetTimer()
	for b.Loop() {
		textArea.TypeCharacter("a")
	}
}
