package opt

import (
	"reflect"
	"strings"
)

type Completion struct {
	Value       string
	Description string
}

func (c *CmdSpec) unseenFlags(arg string) []Completion {
	var flags []Completion
	for i := 0; i < len(c.opts); i++ {
		spec := &c.opts[i]
		if !spec.appliesToAlias(c.name) || spec.seen {
			continue
		}
		switch spec.kind {
		case flag, option:
			if spec.short != "" && strings.HasPrefix(spec.short, arg) {
				flags = append(flags, Completion{
					Value:       spec.short + " ",
					Description: spec.description,
				})
			}
			if spec.long != "" && strings.HasPrefix(spec.long, arg) {
				flags = append(flags, Completion{
					Value:       spec.long + " ",
					Description: spec.description,
				})
			}
		}
	}
	return flags
}

func (c *CmdSpec) nextPositional() *optSpec {
	var spec *optSpec
	for p := len(c.positionals) - 1; p >= 0; p-- {
		spec = &c.opts[c.positionals[p]]
		if !spec.appliesToAlias(c.name) {
			continue
		}
		if spec.seenValue && (p+1) < len(c.positionals) {
			// first "unseen" positional argument
			return &c.opts[c.positionals[p+1]]
		}
	}
	return spec
}

func (s *optSpec) getCompletions(arg string) []Completion {
	if s.complete.IsValid() {
		in := []reflect.Value{reflect.ValueOf(arg)}
		out := s.complete.Call(in)
		if res, ok := out[0].Interface().([]string); ok {
			var completions []Completion
			for _, value := range res {
				value, desc, found := strings.Cut(value, "\n")
				if !found {
					desc = s.description
				}
				completions = append(completions, Completion{
					Value:       value,
					Description: desc,
				})
			}
			return completions
		}
	}
	return nil
}

func (c *CmdSpec) GetCompletions(args *Args) ([]Completion, string) {
	if args.Count() == 0 || (args.Count() == 1 && args.TrailingSpace() == "") {
		return nil, ""
	}

	var completions []Completion
	var prefix string
	var flags []Completion
	var last *seenArg
	var spec *optSpec

	c, _ = c.parseArgs(args.Clone())

	if len(c.seen) > 0 {
		last = c.seen[len(c.seen)-1]
	}

	if args.TrailingSpace() != "" {
		// Complete new argument
		prefix = args.String()
		if last != nil && !last.spec.seenValue {
			spec = last.spec
		}
		if spec == nil {
			// Last argument was not a flag that required a value.
			// Complete for the next unseen positional argument.
			flags = c.unseenFlags("")
			spec = c.nextPositional()
		}
		if spec != nil {
			completions = spec.getCompletions("")
		}
	} else {
		// Complete current argument
		arg := args.Cut(1)[0]
		prefix = args.String() + " "

		if last != nil && last.indexes[len(last.indexes)-1] == args.Count() {
			s := last.spec
			f := s.long + "="
			switch {
			case (s.kind == flag || s.kind == option) && (s.short == arg || s.long == arg):
				// Current argument is precisely a flag.
				spec = nil
				completions = []Completion{{Value: arg + " ", Description: s.description}}
			case s.kind == option && f != "=" && strings.HasPrefix(arg, f):
				// Current argument is a long flag in the format:
				//       --flag=value
				// Strip the prefix and complete the value.
				prefix += f
				arg = strings.TrimPrefix(arg, f)
				fallthrough
			default:
				spec = s
			}
		} else {
			// Current argument was not identified, attempt
			// completion from the next unseen positional.
			spec = c.nextPositional()
		}
		if spec != nil {
			completions = spec.getCompletions(arg)
		}
		if strings.HasPrefix(arg, "-") {
			flags = c.unseenFlags(arg)
		}
	}

	if flags != nil {
		completions = append(completions, flags...)
	}

	return completions, prefix
}
