package tui

import (
	"albedo/cli/internal/daemon"
	"slices"
	"strings"

	tea "charm.land/bubbletea/v2"
)

type ChatCommand struct {
	Name        string
	Description string
}

var CommonCommands = []ChatCommand{
	{Name: "/a", Description: "browse sessions"},
	{Name: "/t", Description: "show or hide thinking"},
	{Name: "/v", Description: "expand or collapse code, output and diffs"},
	{Name: "/status", Description: "show state, workspace and usage"},
	{Name: "/q", Description: "quit"},
}

var AppCommands = []ChatCommand{
	{Name: "/login", Description: "add or select a model provider"},
	{Name: "/new", Description: "start a new coding session"},
	{Name: "/sessions", Description: "switch to another session"},
	{Name: "/agents", Description: "watch and message this session's agents (ctrl+o)"},
	{Name: "/extensions", Description: "manage this session's extension plugins"},
	{Name: "/skills", Description: "manage loaded skills and per-skill defaults"},
	{Name: "/instructions", Description: "manage AGENTS.md and other instruction files"},
	{Name: "/mcp", Description: "manage MCP servers and authentication"},
	{Name: "/tree", Description: "branch this session from a history checkpoint"},
	{Name: "/cd", Description: "move this session to another folder"},
}

type CommandMenuModel struct {
	Styles    Styles
	Dismissed string
	Catalog   []daemon.SessionCommand
	Selected  int
}

func NewCommandMenuModel() CommandMenuModel {
	return CommandMenuModel{
		Styles: DefaultStyles,
	}
}

func (m CommandMenuModel) Matches(input string) []ChatCommand {
	if !strings.HasPrefix(input, "/") || strings.Contains(input, "\n") || input == m.Dismissed {
		return nil
	}

	all := slices.Clone(AppCommands)
	for _, cmd := range m.Catalog {
		all = append(all, ChatCommand{
			Name:        cmd.Name,
			Description: cmd.Description,
		})
	}
	all = append(all, CommonCommands...)

	// De-duplicate by name (last occurrence wins), placing exact match first.
	seen := make(map[string]bool, len(all))
	var direct, others []ChatCommand
	for _, cmd := range slices.Backward(all) {
		if seen[cmd.Name] {
			continue
		}
		seen[cmd.Name] = true
		if cmd.Name == input {
			direct = append(direct, cmd)
		} else if strings.HasPrefix(cmd.Name, input) {
			others = append(others, cmd)
		}
	}
	slices.Reverse(others)
	return append(direct, others...)
}

// OnKey handles navigation within the command popup. Returns true if consumed.
func (m *CommandMenuModel) OnKey(msg tea.KeyPressMsg, input string, replace func(string), submit func(string)) bool {
	matches := m.Matches(input)
	if len(matches) == 0 {
		return false
	}

	idx := max(0, min(m.Selected, len(matches)-1))

	switch msg.String() {
	case "esc":
		m.Dismissed = input
	case "up":
		if idx > 0 {
			m.Selected = idx - 1
		}
	case "down":
		if idx < len(matches)-1 {
			m.Selected = idx + 1
		}
	case "tab":
		replace(matches[idx].Name)
	case "enter":
		submit(matches[idx].Name)
	default:
		m.Selected = 0
		return false
	}
	return true
}

func (m CommandMenuModel) View(input string) string {
	matches := m.Matches(input)
	if len(matches) == 0 {
		return ""
	}

	idx := max(0, min(m.Selected, len(matches)-1))
	start := max(0, idx-3)
	end := min(len(matches), max(4, idx+1))

	var b strings.Builder
	for i := start; i < end; i++ {
		cmd := matches[i]
		desc := cmd.Name + "  " + m.Styles.Faint.Render(cmd.Description)
		if i == idx {
			b.WriteString(selectedLine(selectBar()+" "+desc, 0))
		} else {
			b.WriteString("  ")
			b.WriteString(desc)
		}
		b.WriteByte('\n')
	}
	return b.String()
}
