package operator

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

// Implements Operator (d)
type DeleteOperator struct{}

func (o DeleteOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
	switch m.Mode() {
	case action.VisualMode:
		deleteCharSelection(m, start, end)
	case action.VisualLineMode:
		deleteLineSelection(m, start, end)
	case action.VisualBlockMode:
		deleteBlockSelection(m, start, end)
	case action.NormalMode:
		deleteNormalMode(m, start, end, mtype)
	}
	return nil
}

// Verify DeleteOperator implements DoublePresser
var _ action.DoublePresser = DeleteOperator{}

// Double press handles dd - delete the entire line
func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
	// If we have a higher value than lines remaining, we can only run so many times
	opCount := min(count, m.LineCount()-m.CursorY())

	var lines []string

	for range opCount {
		y := m.CursorY()
		lines = append(lines, m.Line(y))

		m.DeleteLine(y)

		if m.LineCount() == 0 {
			m.InsertLine(0, "")
		}

		if y >= m.LineCount() {
			y = m.LineCount() - 1
		}

		m.SetCursorY(y)
		m.ClampCursorX()
	}

	// Put her in the register!
	m.UpdateDefaultRegister(action.LinewiseRegister, lines)

	return nil
}

func deleteNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
	// Normalize so start is always before or equal to end
	if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
		start, end = end, start
	}

	// Linewise motions (j, k, G, gg) always operate on whole lines
	if mtype == action.Linewise {
		deleteLineSelection(m, start, end)
		return
	}

	// Charwise motions on same line
	if start.Line == end.Line {
		// No movement = nothing to delete
		if start.Col == end.Col && mtype == action.CharwiseExclusive {
			return
		}
		// Exclusive motion: end position not included, so back up one
		if mtype == action.CharwiseExclusive {
			end.Col--
		}
		if end.Col >= start.Col {
			deleteCharSelection(m, start, end)
		}
		return
	}

	// Charwise motion spanning multiple lines (e.g., d/search)
	deleteCharSelection(m, start, end)
}

func deleteCharSelection(m action.Model, start, end action.Position) {
	if start.Line == end.Line {
		line := m.Line(start.Line)
		endCol := min(end.Col+1, len(line))
		m.SetLine(start.Line, line[:start.Col]+line[endCol:])
	} else {
		startLine := m.Line(start.Line)
		endLine := m.Line(end.Line)

		prefix := startLine[:start.Col]
		suffix := ""
		if end.Col+1 < len(endLine) {
			suffix = endLine[end.Col+1:]
		}

		// Delete from end back to start to preserve indices
		for i := end.Line; i >= start.Line; i-- {
			m.DeleteLine(i)
		}
		m.InsertLine(start.Line, prefix+suffix)
	}

	m.SetCursorY(start.Line)
	m.SetCursorX(start.Col)
	m.ClampCursorX()
}

func deleteLineSelection(m action.Model, start, end action.Position) {
	var lines []string

	for i := end.Line; i >= start.Line; i-- {
		lines = append(lines, m.Line(i))
		m.DeleteLine(i)
	}

	if m.LineCount() == 0 {
		m.InsertLine(0, "")
	}

	y := start.Line
	if y >= m.LineCount() {
		y = m.LineCount() - 1
	}

	m.SetCursorY(y)
	m.ClampCursorX()

	// Update registers
	m.UpdateDefaultRegister(action.LinewiseRegister, lines)
}

func deleteBlockSelection(m action.Model, start, end action.Position) {
	startCol := min(start.Col, end.Col)
	endCol := max(start.Col, end.Col)

	for y := start.Line; y <= end.Line; y++ {
		line := m.Line(y)
		if startCol >= len(line) {
			continue
		}
		ec := min(endCol+1, len(line))
		m.SetLine(y, line[:startCol]+line[ec:])
	}

	m.SetCursorY(start.Line)
	m.SetCursorX(startCol)
	m.ClampCursorX()
}
