package cli

import (
	"fmt"
	"time"
)

type Args interface {
	// Get returns the nth argument, or else a blank string
	Get(n int) string
	// First returns the first argument, or else a blank string
	First() string
	// Tail returns the rest of the arguments (not the first one)
	// or else an empty string slice
	Tail() []string
	// Len returns the length of the wrapped slice
	Len() int
	// Present checks if there are any arguments present
	Present() bool
	// Slice returns a copy of the internal slice
	Slice() []string
}

type stringSliceArgs struct {
	v []string
}

func (a *stringSliceArgs) Get(n int) string {
	if len(a.v) > n {
		return a.v[n]
	}
	return ""
}

func (a *stringSliceArgs) First() string {
	return a.Get(0)
}

func (a *stringSliceArgs) Tail() []string {
	if a.Len() >= 2 {
		tail := a.v[1:]
		ret := make([]string, len(tail))
		copy(ret, tail)
		return ret
	}

	return []string{}
}

func (a *stringSliceArgs) Len() int {
	return len(a.v)
}

func (a *stringSliceArgs) Present() bool {
	return a.Len() != 0
}

func (a *stringSliceArgs) Slice() []string {
	ret := make([]string, len(a.v))
	copy(ret, a.v)
	return ret
}

// Argument captures a positional argument that can
// be parsed
type Argument interface {
	// which this argument can be accessed using the given name
	HasName(string) bool

	// Parse the given args and return unparsed args and/or error
	Parse([]string) ([]string, error)

	// The usage template for this argument to use in help
	Usage() string

	// The Value of this Arg
	Get() any
}

// AnyArguments to differentiate between no arguments(nil) vs aleast one
var AnyArguments = []Argument{
	&StringArgs{
		Max: -1,
	},
}

type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct {
	Name        string `json:"name"`      // the name of this argument
	Value       T      `json:"value"`     // the default value of this argument
	Destination *T     `json:"-"`         // the destination point for this argument
	UsageText   string `json:"usageText"` // the usage text to show
	Config      C      `json:"config"`    // config for this argument similar to Flag Config

	value *T
}

func (a *ArgumentBase[T, C, VC]) HasName(s string) bool {
	return s == a.Name
}

func (a *ArgumentBase[T, C, VC]) Usage() string {
	if a.UsageText != "" {
		return a.UsageText
	}

	usageFormat := "%[1]s"
	return fmt.Sprintf(usageFormat, a.Name)
}

func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
	tracef("calling arg%[1] parse with args %[2]", a.Name, s)

	var vc VC
	var t T
	value := vc.Create(a.Value, &t, a.Config)
	a.value = &t

	tracef("attempting arg%[1] parse", &a.Name)
	if len(s) > 0 {
		if err := value.Set(s[0]); err != nil {
			return s, fmt.Errorf("invalid value %q for argument %s: %v", s[0], a.Name, err)
		}
		*a.value = value.Get().(T)
		tracef("set arg%[1] one value", a.Name, *a.value)
	}

	if a.Destination != nil {
		tracef("setting destination")
		*a.Destination = *a.value
	}

	if len(s) > 0 {
		return s[1:], nil
	}
	return s, nil
}

func (a *ArgumentBase[T, C, VC]) Get() any {
	if a.value != nil {
		return *a.value
	}
	return a.Value
}

// ArgumentsBase is a base type for slice arguments
type ArgumentsBase[T any, C any, VC ValueCreator[T, C]] struct {
	Name        string `json:"name"`      // the name of this argument
	Value       T      `json:"value"`     // the default value of this argument
	Destination *[]T   `json:"-"`         // the destination point for this argument
	UsageText   string `json:"usageText"` // the usage text to show
	Min         int    `json:"minTimes"`  // the min num of occurrences of this argument
	Max         int    `json:"maxTimes"`  // the max num of occurrences of this argument, set to -1 for unlimited
	Config      C      `json:"config"`    // config for this argument similar to Flag Config

	values []T
}

func (a *ArgumentsBase[T, C, VC]) HasName(s string) bool {
	return s == a.Name
}

func (a *ArgumentsBase[T, C, VC]) Usage() string {
	if a.UsageText != "" {
		return a.UsageText
	}

	usageFormat := ""
	if a.Min == 0 {
		if a.Max == 1 {
			usageFormat = "[%[1]s]"
		} else {
			usageFormat = "[%[1]s ...]"
		}
	} else {
		usageFormat = "%[1]s [%[1]s ...]"
	}
	return fmt.Sprintf(usageFormat, a.Name)
}

func (a *ArgumentsBase[T, C, VC]) Parse(s []string) ([]string, error) {
	tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
	if a.Max == 0 {
		return s, fmt.Errorf("args %s has max 0, not parsing argument", a.Name)
	}
	if a.Max != -1 && a.Min > a.Max {
		return s, fmt.Errorf("args %s has min[%d] > max[%d], not parsing argument", a.Name, a.Min, a.Max)
	}

	count := 0
	var vc VC
	var t T
	value := vc.Create(a.Value, &t, a.Config)
	a.values = []T{}

	tracef("attempting arg%[1] parse", &a.Name)
	for _, arg := range s {
		if err := value.Set(arg); err != nil {
			return s, fmt.Errorf("invalid value %q for argument %s: %v", arg, a.Name, err)
		}
		tracef("set arg%[1] one value", &a.Name, value.Get().(T))
		a.values = append(a.values, value.Get().(T))
		count++
		if count >= a.Max && a.Max > -1 {
			break
		}
	}
	if count < a.Min {
		return s, fmt.Errorf("sufficient count of arg %s not provided, given %d expected %d", a.Name, count, a.Min)
	}

	if a.Destination != nil {
		tracef("appending destination")
		*a.Destination = a.values // append(*a.Destination, a.values...)
	}

	return s[count:], nil
}

func (a *ArgumentsBase[T, C, VC]) Get() any {
	if a.values != nil {
		return a.values
	}
	return []T{}
}

type (
	FloatArg      = ArgumentBase[float64, NoConfig, floatValue[float64]]
	Float32Arg    = ArgumentBase[float32, NoConfig, floatValue[float32]]
	Float64Arg    = ArgumentBase[float64, NoConfig, floatValue[float64]]
	IntArg        = ArgumentBase[int, IntegerConfig, intValue[int]]
	Int8Arg       = ArgumentBase[int8, IntegerConfig, intValue[int8]]
	Int16Arg      = ArgumentBase[int16, IntegerConfig, intValue[int16]]
	Int32Arg      = ArgumentBase[int32, IntegerConfig, intValue[int32]]
	Int64Arg      = ArgumentBase[int64, IntegerConfig, intValue[int64]]
	StringArg     = ArgumentBase[string, StringConfig, stringValue]
	StringMapArgs = ArgumentBase[map[string]string, StringConfig, StringMap]
	TimestampArg  = ArgumentBase[time.Time, TimestampConfig, timestampValue]
	UintArg       = ArgumentBase[uint, IntegerConfig, uintValue[uint]]
	Uint8Arg      = ArgumentBase[uint8, IntegerConfig, uintValue[uint8]]
	Uint16Arg     = ArgumentBase[uint16, IntegerConfig, uintValue[uint16]]
	Uint32Arg     = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]]
	Uint64Arg     = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]]

	FloatArgs     = ArgumentsBase[float64, NoConfig, floatValue[float64]]
	Float32Args   = ArgumentsBase[float32, NoConfig, floatValue[float32]]
	Float64Args   = ArgumentsBase[float64, NoConfig, floatValue[float64]]
	IntArgs       = ArgumentsBase[int, IntegerConfig, intValue[int]]
	Int8Args      = ArgumentsBase[int8, IntegerConfig, intValue[int8]]
	Int16Args     = ArgumentsBase[int16, IntegerConfig, intValue[int16]]
	Int32Args     = ArgumentsBase[int32, IntegerConfig, intValue[int32]]
	Int64Args     = ArgumentsBase[int64, IntegerConfig, intValue[int64]]
	StringArgs    = ArgumentsBase[string, StringConfig, stringValue]
	TimestampArgs = ArgumentsBase[time.Time, TimestampConfig, timestampValue]
	UintArgs      = ArgumentsBase[uint, IntegerConfig, uintValue[uint]]
	Uint8Args     = ArgumentsBase[uint8, IntegerConfig, uintValue[uint8]]
	Uint16Args    = ArgumentsBase[uint16, IntegerConfig, uintValue[uint16]]
	Uint32Args    = ArgumentsBase[uint32, IntegerConfig, uintValue[uint32]]
	Uint64Args    = ArgumentsBase[uint64, IntegerConfig, uintValue[uint64]]
)

func (c *Command) getArgValue(name string) any {
	tracef("command %s looking for args %s", c.Name, name)
	for _, arg := range c.Arguments {
		if arg.HasName(name) {
			tracef("command %s found args %s", c.Name, name)
			return arg.Get()
		}
	}
	tracef("command %s did not find args %s", c.Name, name)
	return nil
}

func arg[T any](name string, c *Command) T {
	val := c.getArgValue(name)
	if a, ok := val.(T); ok {
		return a
	}
	var zero T
	return zero
}

func (c *Command) StringArg(name string) string {
	return arg[string](name, c)
}

func (c *Command) StringArgs(name string) []string {
	return arg[[]string](name, c)
}

func (c *Command) FloatArg(name string) float64 {
	return arg[float64](name, c)
}

func (c *Command) FloatArgs(name string) []float64 {
	return arg[[]float64](name, c)
}

func (c *Command) Float32Arg(name string) float32 {
	return arg[float32](name, c)
}

func (c *Command) Float32Args(name string) []float32 {
	return arg[[]float32](name, c)
}

func (c *Command) Float64Arg(name string) float64 {
	return arg[float64](name, c)
}

func (c *Command) Float64Args(name string) []float64 {
	return arg[[]float64](name, c)
}

func (c *Command) IntArg(name string) int {
	return arg[int](name, c)
}

func (c *Command) IntArgs(name string) []int {
	return arg[[]int](name, c)
}

func (c *Command) Int8Arg(name string) int8 {
	return arg[int8](name, c)
}

func (c *Command) Int8Args(name string) []int8 {
	return arg[[]int8](name, c)
}

func (c *Command) Int16Arg(name string) int16 {
	return arg[int16](name, c)
}

func (c *Command) Int16Args(name string) []int16 {
	return arg[[]int16](name, c)
}

func (c *Command) Int32Arg(name string) int32 {
	return arg[int32](name, c)
}

func (c *Command) Int32Args(name string) []int32 {
	return arg[[]int32](name, c)
}

func (c *Command) Int64Arg(name string) int64 {
	return arg[int64](name, c)
}

func (c *Command) Int64Args(name string) []int64 {
	return arg[[]int64](name, c)
}

func (c *Command) UintArg(name string) uint {
	return arg[uint](name, c)
}

func (c *Command) Uint8Arg(name string) uint8 {
	return arg[uint8](name, c)
}

func (c *Command) Uint16Arg(name string) uint16 {
	return arg[uint16](name, c)
}

func (c *Command) Uint32Arg(name string) uint32 {
	return arg[uint32](name, c)
}

func (c *Command) Uint64Arg(name string) uint64 {
	return arg[uint64](name, c)
}

func (c *Command) UintArgs(name string) []uint {
	return arg[[]uint](name, c)
}

func (c *Command) Uint8Args(name string) []uint8 {
	return arg[[]uint8](name, c)
}

func (c *Command) Uint16Args(name string) []uint16 {
	return arg[[]uint16](name, c)
}

func (c *Command) Uint32Args(name string) []uint32 {
	return arg[[]uint32](name, c)
}

func (c *Command) Uint64Args(name string) []uint64 {
	return arg[[]uint64](name, c)
}

func (c *Command) TimestampArg(name string) time.Time {
	return arg[time.Time](name, c)
}

func (c *Command) TimestampArgs(name string) []time.Time {
	return arg[[]time.Time](name, c)
}
