package input

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

type InputState int

const (
	StateReady InputState = iota
	StateCount
	StateOperatorPending
	StateMotionCount
)

// PositionGetter is used to get cursor position for operator ranges
type PositionGetter interface {
	GetCursorPosition() *action.Position
}

type Handler struct {
	state       InputState
	count1      int
	count2      int
	operator    action.Operator
	operatorKey string // track which key started operator (for dd, yy, cc)
	buffer      string // for display (what user has typed)
	pending     string // partial key sequence (e.g., "g" waiting for second key)

	// Keymaps
	normalKeymap  *Keymap
	visualKeymap  *Keymap
	insertKeymap  *Keymap
	commandKeymap *Keymap

	currentKeymap *Keymap
}

func NewHandler() *Handler {
	return &Handler{
		// keymap: NewNormalKeymap(),
		normalKeymap:  NewNormalKeymap(),
		visualKeymap:  NewVisualKeymap(),
		insertKeymap:  NewInsertKeymap(),
		commandKeymap: NewCommandKeymap(),
		currentKeymap: nil,
	}
}

func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
	// ESC always resets everything
	if key == "esc" {
		h.Reset()
		if m.Mode() == action.InsertMode {
			m.ExitInsertMode()
		} else {
			m.SetMode(action.NormalMode)
		}
		return nil
	}

	// Insert/command mode bypasses the normal state machine entirely
	switch m.Mode() {
	case action.InsertMode:
		return h.handleInsertKey(m, key)
	case action.CommandMode:
		return h.handleCommandKey(m, key)
	}

	// Try to accumulate count (only if no pending sequence)
	if h.pending == "" && h.tryAccumulateCount(key) {
		return nil
	}

	// Build the sequence (pending + new key)
	sequence := h.pending + key

	// Set working keymap
	switch m.Mode() {
	case action.NormalMode:
		h.currentKeymap = h.normalKeymap
	case action.VisualMode,
		action.VisualLineMode,
		action.VisualBlockMode:
		h.currentKeymap = h.visualKeymap
	}

	// Check for exact match with full sequence
	kind, binding := h.currentKeymap.Lookup(sequence)
	if kind != "" {
		h.pending = ""
		h.buffer += key
		return h.dispatch(m, kind, binding, sequence)
	}

	// No exact match - could this be a prefix of something?
	if h.currentKeymap.HasPrefix(sequence) {
		h.pending = sequence
		h.buffer += key
		return nil // wait for more keys
	}

	// Not a prefix either - if we had pending, try just the new key
	if h.pending != "" {
		h.pending = ""
		kind, binding = h.currentKeymap.Lookup(key)
		if kind != "" {
			h.buffer = key
			return h.dispatch(m, kind, binding, key)
		}
	}

	// Nothing matched
	h.Reset()
	return nil
}

// dispatch routes to the right handler based on current state
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
	switch h.state {
	case StateReady, StateCount:
		return h.handleInitial(m, kind, binding, key)
	case StateOperatorPending, StateMotionCount:
		return h.handleAfterOperator(m, kind, binding, key)
	}
	h.Reset()
	return nil
}

func (h *Handler) handleInitial(m action.Model, kind string, binding any, key string) tea.Cmd {
	count := h.effectiveCount()

	switch kind {
	case "motion":
		mot := binding.(action.Motion)
		if r, ok := mot.(action.Repeatable); ok {
			mot = r.WithCount(count).(action.Motion)
		}
		cmd := mot.Execute(m)
		h.Reset()
		return cmd

	case "operator":
		op := binding.(action.Operator)
		// In visual mode, the selection is already defined — operate immediately
		if m.Mode().IsVisualMode() {
			start, end := normalizeVisualSelection(m)
			// Visual line mode is linewise, others are charwise inclusive
			mtype := action.CharwiseInclusive
			if m.Mode() == action.VisualLineMode {
				mtype = action.Linewise
			}
			cmd := op.Operate(m, start, end, mtype)
			// Only reset to normal mode if operator didn't enter insert mode
			if m.Mode() != action.InsertMode {
				m.SetMode(action.NormalMode)
			}
			h.Reset()
			return cmd
		}
		// In normal mode, wait for a motion to define the range
		h.operator = op
		h.operatorKey = key
		h.state = StateOperatorPending
		return nil

	case "action":
		act := binding.(action.Action)
		if r, ok := act.(action.Repeatable); ok {
			act = r.WithCount(count)
		}
		cmd := act.Execute(m)
		h.Reset()
		return cmd
	}

	h.Reset()
	return nil
}

func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any, key string) tea.Cmd {
	count := h.effectiveCount()

	// dd, yy, cc - same operator key pressed twice
	if kind == "operator" && key == h.operatorKey {
		// Only call DoublePress if the operator supports it
		if dp, ok := h.operator.(action.DoublePresser); ok {
			cmd := dp.DoublePress(m, count)
			h.Reset()
			return cmd
		}
		h.Reset()
		return nil
	}

	// Motion after operator
	if kind == "motion" {
		mot := binding.(action.Motion)
		if r, ok := mot.(action.Repeatable); ok {
			mot = r.WithCount(count).(action.Motion)
		}
		// Get range and motion type
		pg := m.(PositionGetter)
		start := pg.GetCursorPosition()
		mot.Execute(m)
		end := pg.GetCursorPosition()
		cmd := h.operator.Operate(m, *start, *end, mot.Type())
		h.Reset()
		return cmd
	}

	h.Reset()
	return nil
}

func (h *Handler) tryAccumulateCount(key string) bool {
	if len(key) != 1 || key[0] < '0' || key[0] > '9' {
		return false
	}

	digit := int(key[0] - '0')

	// 0 at start is a motion, not a count
	if digit == 0 && h.currentCount() == 0 {
		return false
	}

	switch h.state {
	case StateReady, StateCount:
		h.count1 = h.count1*10 + digit
		h.state = StateCount
	case StateOperatorPending, StateMotionCount:
		h.count2 = h.count2*10 + digit
		h.state = StateMotionCount
	}

	h.buffer += key
	return true
}

func (h *Handler) currentCount() int {
	if h.state == StateOperatorPending || h.state == StateMotionCount {
		return h.count2
	}
	return h.count1
}

func (h *Handler) effectiveCount() int {
	c1, c2 := h.count1, h.count2
	if c1 == 0 {
		c1 = 1
	}
	if c2 == 0 {
		c2 = 1
	}
	return c1 * c2
}

func (h *Handler) Reset() {
	h.state = StateReady
	h.count1 = 0
	h.count2 = 0
	h.operator = nil
	h.operatorKey = ""
	h.buffer = ""
	h.pending = ""
}

func (h *Handler) Pending() string {
	return h.buffer
}

func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
	// Record the key for count replay (e.g. 5i...)
	m.SetInsertKeys(append(m.InsertKeys(), key))

	// Check the insert keymap first
	kind, binding := h.insertKeymap.Lookup(key)
	switch kind {
	case "action":
		return binding.(action.Action).Execute(m)
	case "motion":
		return binding.(action.Motion).Execute(m)
	}

	// Fallback: treat as a regular character to insert
	return action.InsertChar{Char: key}.Execute(m)
}

func (h *Handler) handleCommandKey(m action.Model, key string) tea.Cmd {
	kind, binding := h.commandKeymap.Lookup(key)
	switch kind {
	case "action":
		return binding.(action.Action).Execute(m)
	case "motion":
		return binding.(action.Motion).Execute(m)
	}

	// Fallback: treat as a regular character to insert
	return action.InsertCommandChar{Char: key}.Execute(m)
}

func normalizeVisualSelection(m action.Model) (action.Position, action.Position) {
	a := action.Position{Line: m.AnchorY(), Col: m.AnchorX()}
	c := action.Position{Line: m.CursorY(), Col: m.CursorX()}
	if a.Line < c.Line || (a.Line == c.Line && a.Col <= c.Col) {
		return a, c
	}
	return c, a
}
