package action

import (
	"strings"

	tea "github.com/charmbracelet/bubbletea"
)

// EnterInsert implements Action (i)
type EnterInsert struct {
	Count int
}

func (a EnterInsert) Execute(m Model) tea.Cmd {
	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a EnterInsert) WithCount(n int) Action {
	return EnterInsert{Count: n}
}

// EnterInsertAfter implements Action (a)
type EnterInsertAfter struct {
	Count int
}

func (a EnterInsertAfter) Execute(m Model) tea.Cmd {
	m.SetCursorX(m.CursorX() + 1)
	m.ClampCursorX()

	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a EnterInsertAfter) WithCount(n int) Action {
	return EnterInsertAfter{Count: n}
}

// EnterInsertLineStart implements Action (I)
type EnterInsertLineStart struct {
	Count int
}

func (a EnterInsertLineStart) Execute(m Model) tea.Cmd {
	m.SetCursorX(0)
	m.ClampCursorX()

	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a EnterInsertLineStart) WithCount(n int) Action {
	return EnterInsertLineStart{Count: n}
}

// EnterInsertLineEnd implements Action (A)
type EnterInsertLineEnd struct {
	Count int
}

func (a EnterInsertLineEnd) Execute(m Model) tea.Cmd {
	m.SetCursorX(len(m.Line(m.CursorY())))
	m.ClampCursorX()

	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a EnterInsertLineEnd) WithCount(n int) Action {
	return EnterInsertLineEnd{Count: n}
}

// OpenLineBelow implements Action (o)
type OpenLineBelow struct {
	Count int
}

func (a OpenLineBelow) Execute(m Model) tea.Cmd {
	pos := m.CursorY()

	if pos >= m.LineCount() {
		m.InsertLine(m.LineCount(), "")
	} else {
		m.InsertLine(pos+1, "")
	}

	m.SetCursorY(m.CursorY() + 1)
	m.SetCursorX(0)

	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a OpenLineBelow) WithCount(n int) Action {
	return OpenLineBelow{Count: n}
}

// OpenLineAbove implements Action (O)
type OpenLineAbove struct {
	Count int
}

func (a OpenLineAbove) Execute(m Model) tea.Cmd {
	pos := m.CursorY()
	m.InsertLine(pos, "")
	m.SetCursorX(0)

	// Start recording
	m.SetInsertRecording(a.Count, a)
	m.SetMode(InsertMode)
	return nil
}

func (a OpenLineAbove) WithCount(n int) Action {
	return OpenLineAbove{Count: n}
}

// --- Insert mode edit actions ---

// InsertChar inserts a single character (or rune sequence) at the cursor
type InsertChar struct {
	Char string
}

func (a InsertChar) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	l := m.Line(y)
	if x < len(l) {
		m.SetLine(y, l[:x]+a.Char+l[x:])
	} else {
		m.SetLine(y, l+a.Char)
	}
	m.SetCursorX(x + len(a.Char))
	return nil
}

// InsertNewline splits the current line at the cursor (enter key)
type InsertNewline struct{}

func (a InsertNewline) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	l := m.Line(y)
	if x == len(l) {
		m.InsertLine(y+1, "")
	} else {
		m.SetLine(y, l[:x])
		m.InsertLine(y+1, l[x:])
	}
	m.SetCursorY(y + 1)
	m.SetCursorX(0)
	return nil
}

// InsertBackspace deletes the character before the cursor
type InsertBackspace struct{}

func (a InsertBackspace) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	l := m.Line(y)
	if x > 0 {
		m.SetLine(y, l[:x-1]+l[x:])
		m.SetCursorX(x - 1)
	} else if y > 0 {
		prevLine := m.Line(y - 1)
		newX := len(prevLine)
		m.SetLine(y-1, prevLine+l)
		m.DeleteLine(y)
		m.SetCursorY(y - 1)
		m.SetCursorX(newX)
	}
	return nil
}

// InsertDelete deletes the character under/after the cursor (delete key)
type InsertDelete struct{}

func (a InsertDelete) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	l := m.Line(y)
	if x == len(l) && y < m.LineCount()-1 {
		nextLine := m.Line(y + 1)
		m.SetLine(y, l+nextLine)
		m.DeleteLine(y + 1)
	} else if x < len(l) {
		m.SetLine(y, l[:x]+l[x+1:])
	}
	return nil
}

// InsertTab inserts spaces equal to the tab size
type InsertTab struct{}

func (a InsertTab) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	l := m.Line(y)
	tabs := strings.Repeat(" ", m.Settings().TabSize)
	if x < len(l) {
		m.SetLine(y, l[:x]+tabs+l[x:])
	} else {
		m.SetLine(y, l+tabs)
	}
	m.SetCursorX(x + len(tabs))
	return nil
}

// InsertDeletePreviousWord deletes the word before the cursor (ctrl+w)
type InsertDeletePreviousWord struct{}

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

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

func (a InsertDeletePreviousWord) Execute(m Model) tea.Cmd {
	x, y := m.CursorX(), m.CursorY()
	line := m.Line(y)

	// At start of line: merge with previous line (same as backspace)
	if x == 0 {
		if y > 0 {
			prevLine := m.Line(y - 1)
			newX := len(prevLine)
			m.SetLine(y-1, prevLine+line)
			m.DeleteLine(y)
			m.SetCursorY(y - 1)
			m.SetCursorX(newX)
		}
		return nil
	}

	// Scan backwards to find the new cursor position (don't mutate yet)
	newX := x

	// If we are on puncuation, we should just skip them all and quit
	if isPunctuation(line[newX-1]) {
		for newX > 0 && isPunctuation(line[newX-1]) {
			newX--
		}

		m.SetLine(y, line[:newX]+line[x:])
		m.SetCursorX(newX)

		return nil
	}

	// Skip whitespace immediately before the cursor
	for newX > 0 && (line[newX-1] == ' ' || line[newX-1] == '\t') {
		newX--
	}

	// Skip the word characters before the cursor
	for newX > 0 && isWordChar(line[newX-1]) {
		newX--
	}

	// Delete everything from newX up to x in one operation
	m.SetLine(y, line[:newX]+line[x:])
	m.SetCursorX(newX)

	return nil
}
