package command

import (
	"fmt"
	"strconv"
	"strings"

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

// QuitMsg signals the application should quit
type QuitMsg struct{}

// ErrorMsg signals an error to display
type ErrorMsg struct {
	Err error
}

// cmdQuit handles :quit / :q
func cmdQuit(m action.Model, args []string) tea.Cmd {
	return func() tea.Msg {
		return tea.Quit()
	}
}

// cmdQuitAll handles :qall / :qa
func cmdQuitAll(m action.Model, args []string) tea.Cmd {
	return func() tea.Msg {
		return tea.Quit()
	}
}

// cmdWrite handles :write / :w
func cmdWrite(m action.Model, args []string) tea.Cmd {
	// TODO: Implement file saving
	// If args provided, save to that filename
	// Otherwise save to current file
	return nil
}

// cmdWriteAll handles :wall / :wa
func cmdWriteAll(m action.Model, args []string) tea.Cmd {
	// TODO: Implement saving all buffers
	return nil
}

// cmdWriteQuit handles :wq
func cmdWriteQuit(m action.Model, args []string) tea.Cmd {
	// TODO: Save then quit
	return func() tea.Msg {
		return tea.Quit()
	}
}

// cmdRegisters handles :register
func cmdRegisters(m action.Model, args []string) tea.Cmd {
	// TODO: This is temporary, for debugging
	if len(args) < 1 {
		m.SetCommandError(fmt.Errorf("Please provide a name. Register dump not yet implemented."))
		return nil
	}

	if len(args[0]) != 1 {
		m.SetCommandError(fmt.Errorf("Name should be a single character."))
		return nil
	}

	name := rune(args[0][0])
	reg, found := m.GetRegister(name)
	if !found {
		m.SetCommandError(fmt.Errorf("Could not find register '%c'.", name))
		return nil
	}

	content := strings.Join(reg.Content, "\\n")
	t := reg.Type
	m.SetCommandOutput(fmt.Sprintf("Type: %d Name: \"%c Content: %s", t, name, content))
	return nil
}

// cmdSet handles :set option[=value]
// Examples:
//
//	:set number        - enable number
//	:set nonumber      - disable number
//	:set number!       - toggle number
//	:set tabstop=4     - set tabstop to 4
//	:set ts=4          - set tabstop to 4 (abbreviation)
func cmdSet(m action.Model, args []string) tea.Cmd {
	if len(args) == 0 {
		out := fmt.Sprintf("%+v", m.Settings())
		m.SetCommandOutput(out)
		return nil
	}

	for _, arg := range args {
		if err := parseSetOption(m, arg); err != nil {
			m.SetCommandError(err)
			return nil
		}
	}

	return nil
}

// Setting represents a configurable option
type Setting struct {
	Name      string
	ShortForm string
	Type      SettingType
	Get       func(s action.Settings) any
	Set       func(m action.Model, val any)
}

type SettingType int

const (
	BoolSetting SettingType = iota
	IntSetting
	StringSetting
)

// settingsMap defines all available settings
var settingsMap = []Setting{
	{
		Name:      "number",
		ShortForm: "nu",
		Type:      BoolSetting,
		Get:       func(s action.Settings) any { return s.Number },
		Set: func(m action.Model, val any) {
			s := m.Settings()
			s.Number = val.(bool)
			m.SetSettings(s)
		},
	},
	{
		Name:      "relativenumber",
		ShortForm: "rnu",
		Type:      BoolSetting,
		Get:       func(s action.Settings) any { return s.RelativeNumber },
		Set: func(m action.Model, val any) {
			s := m.Settings()
			s.RelativeNumber = val.(bool)
			m.SetSettings(s)
		},
	},
	{
		Name:      "tabstop",
		ShortForm: "ts",
		Type:      IntSetting,
		Get:       func(s action.Settings) any { return s.TabSize },
		Set: func(m action.Model, val any) {
			s := m.Settings()
			s.TabSize = val.(int)
			m.SetSettings(s)
		},
	},
	{
		Name:      "scrolloff",
		ShortForm: "so",
		Type:      IntSetting,
		Get:       func(s action.Settings) any { return s.ScrollOff },
		Set: func(m action.Model, val any) {
			s := m.Settings()
			s.ScrollOff = val.(int)
			m.SetSettings(s)
		},
	},
}

func lookupSetting(name string) *Setting {
	for i := range settingsMap {
		s := &settingsMap[i]
		if name == s.Name || name == s.ShortForm {
			return s
		}
		// Prefix matching
		if len(name) >= len(s.ShortForm) && strings.HasPrefix(s.Name, name) {
			return s
		}
	}
	return nil
}

func parseSetOption(m action.Model, opt string) error {
	// Handle toggle: option!
	if name, ok := strings.CutSuffix(opt, "!"); ok {
		setting := lookupSetting(name)
		if setting == nil {
			return nil // Unknown setting
		}
		if setting.Type == BoolSetting {
			// Toggle the boolean
			currentVal := setting.Get(m.Settings()).(bool)
			setting.Set(m, !currentVal)
		}
		return nil
	}

	// Handle disable: nooption
	if name, ok := strings.CutPrefix(opt, "no"); ok {
		setting := lookupSetting(name)
		if setting == nil {
			return nil
		}
		if setting.Type == BoolSetting {
			setting.Set(m, false)
		}
		return nil
	}

	// Handle assignment: option=value
	if strings.Contains(opt, "=") {
		parts := strings.SplitN(opt, "=", 2)
		name, value := parts[0], parts[1]
		setting := lookupSetting(name)
		if setting == nil {
			return nil
		}
		switch setting.Type {
		case IntSetting:
			intVal, err := strconv.Atoi(value)
			if err != nil {
				return err
			}
			setting.Set(m, intVal)
		case StringSetting:
			setting.Set(m, value)
		case BoolSetting:
			// Handle :set option=true / :set option=false
			boolVal := value == "true" || value == "1" || value == "yes"
			setting.Set(m, boolVal)
		}
		return nil
	}

	// Handle enable: option (boolean only)
	setting := lookupSetting(opt)
	if setting == nil {
		return nil
	}
	if setting.Type == BoolSetting {
		setting.Set(m, true)
	}

	return nil
}
