package operator

import (
	"fmt"

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

// Implements Operator (y)
type YankOperator struct{}

func (o YankOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
	switch m.Mode() {
	case action.VisualMode:
		yankVisualMode(m, start, end)
	case action.VisualLineMode:
		yankVisualLineMode(m, start, end)
	case action.VisualBlockMode:
		yankVisualBlockMode(m, start, end)
	case action.NormalMode:
		yankNormalMode(m, start, end, mtype)
	default:
		m.SetCommandError(fmt.Errorf("'y' operator not yet implemented."))
	}

	m.SetCursorX(start.Col)
	m.SetCursorY(start.Line)
	return nil
}

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

func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
	y := m.CursorY()

	// If we have a higher value than lines remaining, we can only run so many times
	opCount := min(count, m.LineCount()-y)

	var lines []string

	for i := range opCount {
		lines = append(lines, m.Line(y+i))
	}

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

	return nil
}

func yankNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
	switch {
	case mtype.IsCharwise():
		// This shouldn't happen
		if start.Line != end.Line {
			m.SetCommandError(fmt.Errorf("Start line and end line must match for charwise yank operations."))
			return
		}

		line := m.Line(start.Line)

		startX := min(start.Col, end.Col)
		endX := max(start.Col, end.Col)

		// Inclusive motions include the end character
		if mtype == action.CharwiseInclusive {
			endX++
		}

		endX = min(endX, len(line)) // Catch overflow

		cnt := line[startX:endX]
		m.UpdateDefaultRegister(action.CharwiseRegister, []string{cnt})

	case mtype == action.Linewise:
		// This shouldn't happen
		if start.Col != end.Col {
			m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
			return
		}

		// These don't need to be validated, they are validated before being passed into the function
		startY := min(start.Line, end.Line)
		endY := max(start.Line, end.Line)

		cnt := m.Lines()[startY : endY+1]
		m.UpdateDefaultRegister(action.LinewiseRegister, cnt)
	}
}

func yankVisualMode(m action.Model, start, end action.Position) {
	// Normalize so start is before end
	if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
		start, end = end, start
	}

	// Single line selection
	if start.Line == end.Line {
		line := m.Line(start.Line)
		endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
		startCol := min(start.Col, len(line))
		cnt := line[startCol:endCol]
		m.UpdateDefaultRegister(action.CharwiseRegister, []string{cnt})
		return
	}

	// Multi-line selection
	var content []string

	// First line: from start.Col to end of line
	firstLine := m.Line(start.Line)
	startCol := min(start.Col, len(firstLine))
	content = append(content, firstLine[startCol:])

	// Middle lines: entire lines
	for y := start.Line + 1; y < end.Line; y++ {
		content = append(content, m.Line(y))
	}

	// Last line: from beginning to end.Col (inclusive)
	lastLine := m.Line(end.Line)
	endCol := min(end.Col+1, len(lastLine))
	content = append(content, lastLine[:endCol])

	m.UpdateDefaultRegister(action.CharwiseRegister, content)
}

func yankVisualLineMode(m action.Model, start, end action.Position) {
	// This shouldn't happen
	if start.Col != end.Col {
		m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
		return
	}

	// These don't need to be validated, they are validated before being passed into the function
	startY := min(start.Line, end.Line)
	endY := max(start.Line, end.Line)

	cnt := m.Lines()[startY : endY+1]
	m.UpdateDefaultRegister(action.LinewiseRegister, cnt)

}

func yankVisualBlockMode(m action.Model, start, end action.Position) {
	// Normalize so startY <= endY and startX <= endX
	startY := min(start.Line, end.Line)
	endY := max(start.Line, end.Line)
	startX := min(start.Col, end.Col)
	endX := max(start.Col, end.Col) + 1 // +1 for inclusive

	var content []string

	for y := startY; y <= endY; y++ {
		line := m.Line(y)

		// Handle lines shorter than the block selection
		if startX >= len(line) {
			content = append(content, "")
			continue
		}

		lineEndX := min(endX, len(line))
		content = append(content, line[startX:lineEndX])
	}

	m.UpdateDefaultRegister(action.BlockwiseRegister, content)
}
