package editor

import (
	"fmt"
	"strings"

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

type Model struct {
	// lines   []string
	// cursor  cursor
	// anchor  cursor // starting point for visual modes
	// scrollY int
	mode  action.Mode
	win_h int
	win_w int

	activeWindow int
	windows      []*action.Window

	input *input.Handler

	// Insert repetition
	insertCount  int
	insertKeys   []string
	insertAction action.Action

	// Command mode
	command       string
	commandCursor int
	commandError  error
	commandOutput string

	// Settings
	settings action.Settings

	// Registers
	registers map[rune]action.Register // name -> register
}

func NewModel(lines []string, pos action.Position) *Model {
	m := Model{
		// lines: lines,
		// cursor: cursor{
		// 	x: pos.Col,
		// 	y: pos.Line,
		// },
		// scrollY:   0,
		mode:      action.NormalMode,
		command:   "",
		input:     input.NewHandler(),
		settings:  action.NewDefaultSettings(),
		registers: action.DefaultRegisters(),

		windows: []*action.Window{},
	}

	// Temporary
	win := action.NewEmptyWindow(lines, 0, 0)
	win.Cursor = pos // Set initial cursor position
	m.windows = append(m.windows, win)
	m.activeWindow = win.Id

	return &m
}

func (m Model) Init() tea.Cmd {
	return nil
}

// Implement action.Model interface

func (m *Model) Lines() []string {
	win := m.ActiveWindow()
	return win.Buffer.Lines
}

func (m *Model) Line(idx int) string {
	win := m.ActiveWindow()
	return win.Buffer.Line(idx)
}

func (m *Model) SetLine(idx int, content string) {
	win := m.ActiveWindow()
	win.Buffer.SetLine(idx, content)
}

func (m *Model) InsertLine(idx int, content string) {
	win := m.ActiveWindow()
	win.Buffer.InsertLine(idx, content)
}

func (m *Model) DeleteLine(idx int) {
	win := m.ActiveWindow()
	win.Buffer.DeleteLine(idx)
}

func (m *Model) LineCount() int {
	win := m.ActiveWindow()
	return win.Buffer.LineCount()
}

func (m *Model) CursorX() int {
	win := m.ActiveWindow()
	return win.Cursor.Col
}

func (m *Model) CursorY() int {
	win := m.ActiveWindow()
	return win.Cursor.Line
}

func (m *Model) SetCursorX(x int) {
	win := m.ActiveWindow()
	win.Cursor.Col = x
}

func (m *Model) SetCursorY(y int) {
	win := m.ActiveWindow()
	win.Cursor.Line = y
}

// Anchor methods
func (m *Model) AnchorX() int {
	win := m.ActiveWindow()
	return win.Anchor.Col
}

func (m *Model) AnchorY() int {
	win := m.ActiveWindow()
	return win.Anchor.Line
}

func (m *Model) SetAnchorX(x int) {
	win := m.ActiveWindow()
	win.Anchor.Col = x
}

func (m *Model) SetAnchorY(y int) {
	win := m.ActiveWindow()
	win.Anchor.Line = y
}

// Insert methods
func (m *Model) InsertKeys() []string {
	return m.insertKeys
}

func (m *Model) SetInsertKeys(keys []string) {
	m.insertKeys = keys
}

// Command mode
func (m *Model) Command() string {
	return m.command
}

func (m *Model) SetCommand(cmd string) {
	m.command = cmd
}

func (m *Model) CommandCursor() int {
	return m.commandCursor
}

func (m *Model) SetCommandCursor(cur int) {
	if cur < 0 {
		m.commandCursor = 0
	} else if cur >= len(m.command) {
		m.commandCursor = len(m.command)
	} else {
		m.commandCursor = cur
	}
}

func (m *Model) CommandError() error {
	return m.commandError
}

func (m *Model) SetCommandError(err error) {
	m.commandError = err
}

func (m *Model) CommandOutput() string {
	return m.commandOutput
}

func (m *Model) SetCommandOutput(out string) {
	m.commandOutput = out
}

// Settings
func (m *Model) Settings() action.Settings {
	return m.settings
}

func (m *Model) SetSettings(s action.Settings) {
	m.settings = s
}

// Registers
func (m *Model) Registers() map[rune]action.Register {
	return m.registers
}

func (m *Model) GetRegister(name rune) (action.Register, bool) {
	reg, found := m.registers[name]
	return reg, found
}

func (m *Model) SetRegister(name rune, t action.RegisterType, cnt []string) error {
	if _, found := m.GetRegister(name); !found {
		return fmt.Errorf("Register '%c' does not exist.", name)
	}

	// TODO: This might be slow, pointers maybe?
	reg := action.Register{Type: t, Content: cnt}
	m.registers[name] = reg

	return nil
}

func (m *Model) UpdateDefaultRegister(t action.RegisterType, cnt []string) {
	// Shift numbered registers: 0 -> 1 -> 2 -> ... -> 9 -> _ (discarded)
	for i := rune('9'); i > '0'; i-- {
		m.registers[i] = m.registers[i-1]
	}

	// 0 and " both hold the new content independently
	m.SetRegister('0', t, cnt)
	m.SetRegister('"', t, cnt)
}

// Window
func (m *Model) ScrollY() int {
	win := m.ActiveWindow()
	return win.ScrollY
}

func (m *Model) SetScrollY(y int) {
	win := m.ActiveWindow()
	win.ScrollY = y
}

func (m *Model) WinH() int {
	win := m.ActiveWindow()
	return win.Height
}

func (m *Model) WinW() int {
	win := m.ActiveWindow()
	return win.Width
}

func (m *Model) ViewPortH() int {
	win := m.ActiveWindow()
	return win.Height - 2
}

func (m *Model) ClampCursorX() {
	win := m.ActiveWindow()
	win.ClampCursorX()
}

// AdjustScroll ensures the cursor stays within the viewport with scrollOff margins.
// Call this after any cursor movement.
func (m *Model) AdjustScroll() {
	viewportHeight := m.ViewPortH()
	if viewportHeight <= 0 {
		return
	}

	// Effective scrollOff (can't be more than half the viewport)
	off := min(m.Settings().ScrollOff, viewportHeight/2)

	// Cursor too close to top — scroll up
	if m.CursorY() < m.ScrollY()+off {
		m.SetScrollY(m.CursorY() - off)
	}

	// Cursor too close to bottom — scroll down
	if m.CursorY() > m.ScrollY()+viewportHeight-1-off {
		m.SetScrollY(m.CursorY() - viewportHeight + 1 + off)
	}

	// Clamp scrollY to valid range
	maxScroll := max(0, m.LineCount()-viewportHeight)
	m.SetScrollY(max(0, min(m.ScrollY(), maxScroll)))
}

// Windows
func (m *Model) Windows() []*action.Window {
	return m.windows
}

func (m *Model) ActiveWindowId() int {
	return m.activeWindow
}

func (m *Model) ActiveWindow() *action.Window {
	winId := m.ActiveWindowId()
	for i := range m.Windows() {
		if m.windows[i].Id == winId {
			return m.windows[i]
		}
	}
	panic("Could not find window")
}

func (m *Model) Mode() action.Mode {
	return m.mode
}

func (m *Model) SetMode(mode action.Mode) {
	m.mode = mode
}

func (m *Model) SetInsertRecording(count int, act action.Action) {
	m.insertCount = count
	m.insertKeys = []string{}
	m.insertAction = act
}

func (m *Model) GetCursorPosition() *action.Position {
	// Return a copy of the position
	win := m.ActiveWindow()
	pos := win.Cursor
	return &pos
}

func (m *Model) replayInsert() {
	win := m.ActiveWindow()

	// Replay (count - 1) more times
	for i := 1; i < m.insertCount; i++ {
		// For 'o' and 'O', we need to create a new line first
		switch m.insertAction.(type) {
		case action.OpenLineBelow:
			pos := win.Cursor.Line
			win.Buffer.Lines = append(win.Buffer.Lines[:pos+1], append([]string{""}, win.Buffer.Lines[pos+1:]...)...)
			win.Cursor.Line++
			win.Cursor.Col = 0
		case action.OpenLineAbove:
			pos := win.Cursor.Line
			win.Buffer.Lines = append(win.Buffer.Lines[:pos], append([]string{""}, win.Buffer.Lines[pos:]...)...)
			win.Cursor.Col = 0
			// 'i' and 'a' don't need setup - just replay keys
		}

		// Replay each recorded keystroke
		for _, key := range m.insertKeys {
			m.processInsertKey(key)
		}
	}
}

func (m *Model) ExitInsertMode() {
	win := m.ActiveWindow()
	if m.insertCount > 1 {
		m.replayInsert()
	}
	if win.Cursor.Col > 0 {
		win.Cursor.Col--
	}
	m.mode = action.NormalMode
	m.insertCount = 0
	m.insertKeys = nil
}

func (m *Model) processInsertKey(key string) {
	x := m.CursorX()
	y := m.CursorY()
	l := m.Line(y)

	switch key {
	case "enter":
		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)

	case "backspace":
		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)
		}

	case "delete":
		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:])
		}

	case "tab":
		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))

	case "up":
		if y > 0 {
			m.SetCursorY(y - 1)
			m.ClampCursorX()
		}

	case "down":
		if y+1 < m.LineCount() {
			m.SetCursorY(y + 1)
			m.ClampCursorX()
		}

	case "left":
		if x > 0 {
			m.SetCursorX(x - 1)
		} else if y > 0 {
			prevLine := m.Line(y - 1)
			m.SetCursorX(len(prevLine))
			m.SetCursorY(y - 1)
		}

	case "right":
		if x < len(l) {
			m.SetCursorX(x + 1)
		} else if y+1 < m.LineCount() {
			m.SetCursorX(0)
			m.SetCursorY(y + 1)
		}

	default:
		if x < len(l) {
			m.SetLine(y, l[:x]+key+l[x:])
		} else {
			m.SetLine(y, l+key)
		}
		m.SetCursorX(x + len(key))
	}
}
