package motion

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

// MoveToTop implements Motion (gg) - linewise
type MoveToTop struct{}

func (a MoveToTop) Execute(m action.Model) tea.Cmd {
	m.SetCursorY(0)
	m.ClampCursorX()
	return nil
}

func (a MoveToTop) Type() action.MotionType { return action.Linewise }

// MoveToBottom implements Motion (G) - linewise
type MoveToBottom struct{}

func (a MoveToBottom) Execute(m action.Model) tea.Cmd {
	m.SetCursorY(m.LineCount() - 1)
	m.ClampCursorX()
	return nil
}

func (a MoveToBottom) Type() action.MotionType { return action.Linewise }

// MoveToLineStart implements Motion (0) - charwise
type MoveToLineStart struct{}

func (a MoveToLineStart) Execute(m action.Model) tea.Cmd {
	m.SetCursorX(0)
	m.ClampCursorX()
	return nil
}

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

// MoveToLineEnd implements Motion ($) - charwise
type MoveToLineEnd struct{}

func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
	m.SetCursorX(len(m.Line(m.CursorY())))
	m.ClampCursorX()
	return nil
}

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

// MoveToLineContentStart implements Motion (_) - charwise
type MoveToLineContentStart struct{}

func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
	line := m.Line(m.CursorY())
	x := 0
	for x < len(line) {
		ch := line[x]
		if ch != ' ' && ch != '\t' {
			break
		}
		x++
	}

	// If we are on the last char, we overflew, back once
	if x == len(line) && x > 0 {
		x--
	}

	m.SetCursorX(x)
	return nil
}

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

// MoveToColumn implements Motion (|) - charwise
type MoveToColumn struct {
	Count int
}

func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
	line := m.Line(m.CursorY())
	col := min(a.Count-1, len(line)-1)

	m.SetCursorX(col)
	m.ClampCursorX()
	return nil
}

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

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

// TODO: Count for these, maybe?

// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
type ScrollDownHalfPage struct{}

func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
	viewportHeight := m.ViewPortH()
	if viewportHeight <= 0 {
		return nil
	}

	scroll := viewportHeight / 2
	scrollOff := m.Settings().ScrollOff

	// Current relative position in viewport
	relY := m.CursorY() - m.ScrollY()

	// Scroll down, clamped to valid range
	newScrollY := m.ScrollY() + scroll
	maxScroll := max(0, m.LineCount()-viewportHeight)
	newScrollY = min(newScrollY, maxScroll)
	m.SetScrollY(newScrollY)

	// Maintain relative position, respecting scrollOff
	if relY < scrollOff {
		relY = scrollOff
	}
	if relY > viewportHeight-1-scrollOff {
		relY = viewportHeight - 1 - scrollOff
	}

	newCursorY := newScrollY + relY
	newCursorY = max(0, min(newCursorY, m.LineCount()-1))
	m.SetCursorY(newCursorY)
	m.ClampCursorX()

	return nil
}

func (a ScrollDownHalfPage) Type() action.MotionType { return action.Linewise }

// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
type ScrollUpHalfPage struct{}

func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
	viewportHeight := m.ViewPortH()
	if viewportHeight <= 0 {
		return nil
	}
	scroll := viewportHeight / 2
	scrollOff := m.Settings().ScrollOff

	// Current relative position in viewport
	relY := m.CursorY() - m.ScrollY()

	// Scroll up, clamped to valid range
	newScrollY := m.ScrollY() - scroll
	newScrollY = max(0, newScrollY)
	m.SetScrollY(newScrollY)

	// Maintain relative position, respecting scrollOff
	if relY < scrollOff {
		relY = scrollOff
	}
	if relY > viewportHeight-1-scrollOff {
		relY = viewportHeight - 1 - scrollOff
	}

	newCursorY := newScrollY + relY
	newCursorY = max(0, min(newCursorY, m.LineCount()-1))
	m.SetCursorY(newCursorY)
	m.ClampCursorX()

	return nil
}

func (a ScrollUpHalfPage) Type() action.MotionType { return action.Linewise }
