// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package cmd

import (
	"strings"

	"github.com/derailed/k9s/internal/client"
)

// Interpreter tracks user prompt input.
type Interpreter struct {
	line string
	cmd  string
	args args
}

// NewInterpreter returns a new instance.
func NewInterpreter(s string) *Interpreter {
	c := Interpreter{
		line: s,
		args: make(args),
	}
	c.grok()

	return &c
}

// ClearNS clears the current namespace if any.
func (c *Interpreter) ClearNS() {
	c.SwitchNS(client.BlankNamespace)
}

// SwitchNS replaces the current namespace with the provided one.
func (c *Interpreter) SwitchNS(ns string) {
	if ons, ok := c.NSArg(); ok && ons != client.BlankNamespace {
		c.Reset(strings.TrimSpace(strings.Replace(c.line, " "+ons, " "+ns, 1)))
		return
	}
	if ns != client.BlankNamespace {
		c.Reset(strings.TrimSpace(c.line) + " " + ns)
	}
}

func (c *Interpreter) grok() {
	ff := strings.Fields(c.line)
	if len(ff) == 0 {
		return
	}
	c.cmd = strings.ToLower(ff[0])
	c.args = newArgs(c, ff[1:])
}

// HasNS returns true if ns is present in prompt.
func (c *Interpreter) HasNS() bool {
	ns, ok := c.args[nsKey]

	return ok && ns != ""
}

// Cmd returns the command.
func (c *Interpreter) Cmd() string {
	return c.cmd
}

func (c *Interpreter) Args() string {
	return strings.TrimSpace(strings.Replace(c.line, c.cmd, "", 1))
}

// IsBlank returns true if prompt is empty.
func (c *Interpreter) IsBlank() bool {
	return c.line == ""
}

// Amend merges prompts.
func (c *Interpreter) Amend(c1 *Interpreter) {
	c.cmd = c1.cmd
	if c.args == nil {
		c.args = make(args, len(c1.args))
	}
	for k, v := range c1.args {
		if v != "" {
			c.args[k] = v
		}
	}
}

// Reset resets with new command.
func (c *Interpreter) Reset(s string) *Interpreter {
	c.line = s
	c.grok()

	return c
}

// GetLine returns the prompt.
func (c *Interpreter) GetLine() string {
	return strings.TrimSpace(c.line)
}

// IsCowCmd returns true if cow cmd is detected.
func (c *Interpreter) IsCowCmd() bool {
	return c.cmd == cowCmd
}

// IsHelpCmd returns true if help cmd is detected.
func (c *Interpreter) IsHelpCmd() bool {
	return helpCmd.Has(c.cmd)
}

// IsBailCmd returns true if quit cmd is detected.
func (c *Interpreter) IsBailCmd() bool {
	return bailCmd.Has(c.cmd)
}

// IsAliasCmd returns true if alias cmd is detected.
func (c *Interpreter) IsAliasCmd() bool {
	return aliasCmd.Has(c.cmd)
}

// IsXrayCmd returns true if xray cmd is detected.
func (c *Interpreter) IsXrayCmd() bool {
	return xrayCmd.Has(c.cmd)
}

// IsContextCmd returns true if context cmd is detected.
func (c *Interpreter) IsContextCmd() bool {
	return contextCmd.Has(c.cmd)
}

// IsNamespaceCmd returns true if ns cmd is detected.
func (c *Interpreter) IsNamespaceCmd() bool {
	return namespaceCmd.Has(c.cmd)
}

// IsDirCmd returns true if dir cmd is detected.
func (c *Interpreter) IsDirCmd() bool {
	return dirCmd.Has(c.cmd)
}

// IsRBACCmd returns true if rbac cmd is detected.
func (c *Interpreter) IsRBACCmd() bool {
	return c.cmd == canCmd
}

// ContextArg returns context cmd arg.
func (c *Interpreter) ContextArg() (string, bool) {
	if c.IsContextCmd() || strings.Contains(c.line, contextFlag) {
		return c.args[contextKey], true
	}

	return "", false
}

// ResetContextArg deletes context arg.
func (c *Interpreter) ResetContextArg() {
	delete(c.args, contextFlag)
}

// DirArg returns the directory is present.
func (c *Interpreter) DirArg() (string, bool) {
	if !c.IsDirCmd() {
		return "", false
	}
	d, ok := c.args[topicKey]

	return d, ok && d != ""
}

// CowArg returns the cow message.
func (c *Interpreter) CowArg() (string, bool) {
	if !c.IsCowCmd() {
		return "", false
	}
	m, ok := c.args[nsKey]

	return m, ok && m != ""
}

// RBACArgs returns the subject and topic is any.
func (c *Interpreter) RBACArgs() (subject, verb string, ok bool) {
	if !c.IsRBACCmd() {
		return
	}
	tt := rbacRX.FindStringSubmatch(c.line)
	if len(tt) < 3 {
		return
	}
	subject, verb, ok = tt[1], tt[2], true

	return
}

// XrayArgs return the gvr and ns if any.
func (c *Interpreter) XrayArgs() (cmd, namespace string, ok bool) {
	if !c.IsXrayCmd() {
		return
	}
	gvr, ok1 := c.args[topicKey]
	if !ok1 {
		return
	}

	ns, ok2 := c.args[nsKey]
	switch {
	case ok1 && ok2:
		cmd, namespace, ok = gvr, ns, true
	case ok1 && !ok2:
		cmd, namespace, ok = gvr, "", true
	default:
		return
	}

	return
}

// FilterArg returns the current filter if any.
func (c *Interpreter) FilterArg() (string, bool) {
	f, ok := c.args[filterKey]

	return f, ok && f != ""
}

// FuzzyArg returns the fuzzy filter if any.
func (c *Interpreter) FuzzyArg() (string, bool) {
	f, ok := c.args[fuzzyKey]

	return f, ok && f != ""
}

// NSArg returns the current ns if any.
func (c *Interpreter) NSArg() (string, bool) {
	ns, ok := c.args[nsKey]

	return ns, ok && ns != client.BlankNamespace
}

// HasContext returns the current context if any.
func (c *Interpreter) HasContext() (string, bool) {
	ctx, ok := c.args[contextKey]

	return ctx, ok && ctx != ""
}

// LabelsArg return the labels map if any.
func (c *Interpreter) LabelsArg() (map[string]string, bool) {
	ll, ok := c.args[labelKey]

	return ToLabels(ll), ok
}
