package completer

import (
	"strings"

	"github.com/c-bata/go-prompt"
	"github.com/fatih/structs"
	"github.com/fishi0x01/vsh/cli"
	"github.com/fishi0x01/vsh/client"
	"github.com/fishi0x01/vsh/log"
)

// Completer struct for tab completion
type Completer struct {
	pathCompletionToggle bool
	client               *client.Client
}

// NewCompleter creates a new Completer with given client
func NewCompleter(client *client.Client, disableAutoCompletion bool) *Completer {
	return &Completer{
		pathCompletionToggle: !disableAutoCompletion,
		client:               client,
	}
}

// TogglePathCompletion enable/disable path auto-completion
func (c *Completer) TogglePathCompletion() {
	c.pathCompletionToggle = !c.pathCompletionToggle
	log.UserInfo("Use path auto-completion: %t", c.pathCompletionToggle)
}

func (c *Completer) getAbsoluteTopLevelSuggestions() []prompt.Suggest {
	var suggestions []prompt.Suggest
	for k := range c.client.KVBackends {
		suggestions = append(suggestions, prompt.Suggest{Text: "/" + k})
	}
	return suggestions
}

func (c *Completer) getRelativeTopLevelSuggestions() []prompt.Suggest {
	var suggestions []prompt.Suggest
	for k := range c.client.KVBackends {
		suggestions = append(suggestions, prompt.Suggest{Text: k})
	}
	return suggestions
}

func (c *Completer) absolutePathSuggestions(arg string) (result []prompt.Suggest) {
	if strings.Count(arg, "/") < 2 {
		result = c.getAbsoluteTopLevelSuggestions()
	} else {
		li := strings.LastIndex(arg, "/")
		queryPath := arg[0 : li+1]

		var options []string
		var err error
		options, err = c.client.List(queryPath)

		if err != nil {
			log.UserError("Error during auto-completion: %s", err)
			return result
		}

		options = append(options, "../")
		for _, node := range options {
			result = append(result, prompt.Suggest{Text: queryPath + node})
		}
	}

	filtered := prompt.FilterHasPrefix(result, arg, true)
	if len(filtered) > 0 {
		result = filtered
	}
	return result
}

func (c *Completer) relativePathSuggestions(arg string) (result []prompt.Suggest) {
	if c.client.Pwd == "/" && strings.Count(arg, "/") < 1 {
		result = c.getRelativeTopLevelSuggestions()
	} else {
		li := strings.LastIndex(arg, "/")
		queryPath := arg[0 : li+1]

		var options []string
		var err error
		options, err = c.client.List(c.client.Pwd + queryPath)

		if err != nil {
			return result
		}

		options = append(options, "../")
		for _, node := range options {
			result = append(result, prompt.Suggest{Text: queryPath + node})
		}
	}

	filtered := prompt.FilterHasPrefix(result, arg, true)
	if len(filtered) > 0 {
		result = filtered
	}
	return result
}

func isAbsolutePath(path string) bool {
	return strings.HasPrefix(path, "/")
}

func (c *Completer) isCommandArgument(p string) bool {
	words := strings.Split(p, " ")
	if len(words) < 2 {
		return false
	}

	commands := cli.NewCommands(c.client)
	for _, f := range structs.Fields(commands) {
		if words[0] == f.Value().(cli.Command).GetName() || words[0] == "toggle-auto-completion" {
			return true
		}
	}
	return false
}

func isCommand(p string) bool {
	return len(strings.Split(p, " ")) < 2
}

func (c *Completer) commandSuggestions(arg string) (result []prompt.Suggest) {
	result = make([]prompt.Suggest, 0)
	commands := cli.NewCommands(c.client)
	for _, f := range structs.Fields(commands) {
		val := f.Value().(cli.Command)
		result = append(result, prompt.Suggest{Text: val.GetName(), Description: cli.Usage(val)})
	}
	result = append(
		result,
		prompt.Suggest{
			Text:        "toggle-auto-completion",
			Description: "toggle path auto-completion on/off",
		},
	)

	filtered := prompt.FilterHasPrefix(result, arg, true)
	if len(filtered) > 0 {
		result = filtered
	}
	return result
}

// Complete suggestions for completion
func (c *Completer) Complete(in prompt.Document) (result []prompt.Suggest) {
	p := in.TextBeforeCursor()
	if isCommand(p) {
		result = c.commandSuggestions(in.GetWordBeforeCursor())
	} else if c.isCommandArgument(p) && c.pathCompletionToggle {
		cur := in.GetWordBeforeCursor()
		if isAbsolutePath(cur) {
			result = c.absolutePathSuggestions(cur)
		} else {
			result = c.relativePathSuggestions(cur)
		}
	}

	return result
}

// PromptPrefix returns the currently active prompt prefix
func (c *Completer) PromptPrefix() (string, bool) {
	return c.client.Name + " " + c.client.Pwd + "> ", true
}
