package motion

import (
	"git.gophernest.net/azpect/TextEditor/internal/action"
	tea "github.com/charmbracelet/bubbletea"
)

func isWordChar(c byte) bool {
	return (c >= 'a' && c <= 'z') ||
		(c >= 'A' && c <= 'Z') ||
		(c >= '0' && c <= '9') ||
		c == '_'
}

func isWordPunctuation(c byte) bool {
	return c != ' ' && c != '\t' && !isWordChar(c)
}

func nextWordStart(m action.Model, x, y int) (int, int) {
	line := m.Line(y)

	// Skip current class
	if x < len(line) {
		if isWordChar(line[x]) {
			for x < len(line) && isWordChar(line[x]) {
				x++
			}
		} else if line[x] != ' ' && line[x] != '\t' {
			// punctuation class
			for x < len(line) && isWordPunctuation(line[x]) {
				x++
			}
		}
	}

	// Skip whitespace and cross lines if needed
	for {
		// Walk over white space
		for x < len(line) && (line[x] == ' ' || line[x] == '\t') {
			x++
		}

		// Were on the new word, nothing else to do (no lines to cross
		if x < len(line) {
			break
		}

		// If next line is the end of the file, exit now
		if y+1 >= m.LineCount() {
			return x, y
		}

		// Move to first char of next line
		y++
		line = m.Line(y)
		x = 0

		// If the first char of the new line is no whitespace, stay here!
		if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
			break
		}
	}

	return x, y
}

func nextWORDStart(m action.Model, x, y int) (int, int) {
	line := m.Line(y)

	// Skip current WORD (all non-whitespace is one class for W)
	for x < len(line) && line[x] != ' ' && line[x] != '\t' {
		x++
	}

	// Skip whitespace and cross lines if needed
	for {
		// Walk over white space
		for x < len(line) && (line[x] == ' ' || line[x] == '\t') {
			x++
		}

		// Were on the new word, nothing else to do (no lines to cross
		if x < len(line) {
			break
		}

		// If next line is the end of the file, exit now
		if y+1 >= m.LineCount() {
			return x, y
		}

		// Move to first char of next line
		y++
		line = m.Line(y)
		x = 0

		// If the first char of the new line is no whitespace, stay here!
		if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
			break
		}
	}

	return x, y
}

func nextWordEnd(m action.Model, x, y int) (int, int) {
	line := m.Line(y)

	// Advance once to avoid being stuck on the current end
	x++
	if x >= len(line) {
		// At last line of file, pin cursor to end of file
		if y+1 >= m.LineCount() {
			return len(line) - 1, y
		}

		// Otherwise, move to next line
		y++
		x = 0
		line = m.Line(y)
	}

	// Skip whitespace and cross lines if needed
	for {
		// Walk over white space
		for x < len(line) && (line[x] == ' ' || line[x] == '\t') {
			x++
		}

		// Were on the new word, nothing else to do (no lines to cross
		if x < len(line) {
			break
		}

		// If next line is the end of the file, exit now
		if y+1 >= m.LineCount() {
			return x, y
		}

		// Move to first char of next line
		y++
		line = m.Line(y)
		x = 0
	}

	// Move to end of current char class, stop before it ends
	if isWordChar(line[x]) {
		for x+1 < len(line) && isWordChar(line[x+1]) {
			x++
		}
	} else {
		for x+1 < len(line) &&
			line[x+1] != ' ' &&
			line[x+1] != '\t' &&
			!isWordChar(line[x+1]) {
			x++
		}
	}

	return x, y
}

func nextWORDEnd(m action.Model, x, y int) (int, int) {
	line := m.Line(y)

	// Advance once to avoid being stuck on the current end
	x++
	if x >= len(line) {
		// At last line of file, pin cursor to end of file
		if y+1 >= m.LineCount() {
			return len(line) - 1, y
		}

		// Otherwise, move to next line
		y++
		x = 0
		line = m.Line(y)
	}

	// Skip whitespace and cross lines if needed
	for {
		// Walk over white space
		for x < len(line) && (line[x] == ' ' || line[x] == '\t') {
			x++
		}

		// Were on the new word, nothing else to do (no lines to cross
		if x < len(line) {
			break
		}

		// If next line is the end of the file, exit now
		if y+1 >= m.LineCount() {
			return x, y
		}

		// Move to first char of next line
		y++
		line = m.Line(y)
		x = 0
	}

	// Move to end of current WORD (all non-whitespace is one class)
	for x+1 < len(line) && line[x+1] != ' ' && line[x+1] != '\t' {
		x++
	}

	return x, y
}

func prevWordStart(m action.Model, x, y int) (int, int) {
	line := m.Line(y)

	// Back one to avoid being stuck on the current start
	x--
	if x < 0 {
		if y == 0 {
			return 0, 0 // beginning of file, stay put
		}
		y--
		line = m.Line(y)
		x = len(line) - 1
		if x < 0 {
			return 0, y // landed on an empty line
		}
	}

	// Skip whitespace backward, crossing lines if needed
	for {
		for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
			x--
		}
		if x >= 0 {
			break // landed on a non-whitespace char
		}
		if y == 0 {
			return 0, 0
		}
		y--
		line = m.Line(y)
		x = len(line) - 1
		if len(line) == 0 {
			return 0, y // empty line acts as a word boundary
		}
	}

	// Skip to the start of the current char class
	if isWordChar(line[x]) {
		for x-1 >= 0 && isWordChar(line[x-1]) {
			x--
		}
	} else {
		for x-1 >= 0 && isWordPunctuation(line[x-1]) {
			x--
		}
	}

	return x, y
}

// MoveForwardWord implements Motion (w) - charwise
type MoveForwardWord struct {
	Count int
}

func (a MoveForwardWord) Execute(m action.Model) tea.Cmd {
	x := m.CursorX()
	y := m.CursorY()
	for i := 0; i < a.Count; i++ {
		x, y = nextWordStart(m, x, y)
	}
	m.SetCursorX(x)
	m.SetCursorY(y)
	return nil
}

func (a MoveForwardWord) Type() action.MotionType { return action.CharwiseExclusive }

func (a MoveForwardWord) WithCount(n int) action.Action {
	return MoveForwardWord{Count: n}
}

// MoveForwardWORD implements Motion (W) - charwise
type MoveForwardWORD struct {
	Count int
}

func (a MoveForwardWORD) Execute(m action.Model) tea.Cmd {
	x := m.CursorX()
	y := m.CursorY()
	for i := 0; i < a.Count; i++ {
		x, y = nextWORDStart(m, x, y)
	}
	m.SetCursorX(x)
	m.SetCursorY(y)
	return nil
}

func (a MoveForwardWORD) Type() action.MotionType { return action.CharwiseExclusive }

func (a MoveForwardWORD) WithCount(n int) action.Action {
	return MoveForwardWORD{Count: n}
}

// MoveForwardWordEnd implements Motion (e) - charwise
type MoveForwardWordEnd struct {
	Count int
}

func (a MoveForwardWordEnd) Execute(m action.Model) tea.Cmd {
	x := m.CursorX()
	y := m.CursorY()
	for i := 0; i < a.Count; i++ {
		x, y = nextWordEnd(m, x, y)
	}
	m.SetCursorX(x)
	m.SetCursorY(y)
	return nil
}

func (a MoveForwardWordEnd) Type() action.MotionType { return action.CharwiseInclusive }

func (a MoveForwardWordEnd) WithCount(n int) action.Action {
	return MoveForwardWordEnd{Count: n}
}

// MoveForwardWORDEnd implements Motion (E) - charwise
type MoveForwardWORDEnd struct {
	Count int
}

func (a MoveForwardWORDEnd) Execute(m action.Model) tea.Cmd {
	x := m.CursorX()
	y := m.CursorY()
	for i := 0; i < a.Count; i++ {
		x, y = nextWORDEnd(m, x, y)
	}
	m.SetCursorX(x)
	m.SetCursorY(y)
	return nil
}

func (a MoveForwardWORDEnd) Type() action.MotionType { return action.CharwiseInclusive }

func (a MoveForwardWORDEnd) WithCount(n int) action.Action {
	return MoveForwardWORDEnd{Count: n}
}

// MoveBackwardWord implements Motion (b) - charwise
type MoveBackwardWord struct {
	Count int
}

func (a MoveBackwardWord) Execute(m action.Model) tea.Cmd {
	x := m.CursorX()
	y := m.CursorY()
	for i := 0; i < a.Count; i++ {
		x, y = prevWordStart(m, x, y)
	}
	m.SetCursorX(x)
	m.SetCursorY(y)
	return nil
}

func (a MoveBackwardWord) Type() action.MotionType { return action.CharwiseExclusive }

func (a MoveBackwardWord) WithCount(n int) action.Action {
	return MoveBackwardWord{Count: n}
}
