package sprig

import (
	"fmt"
	"math"
	"reflect"
	"sort"
)

// Reflection is used in these functions so that slices and arrays of strings,
// ints, and other types not implementing []any can be worked with.
// For example, this is useful if you need to work on the output of regexs.

// list creates a new list (slice) containing the provided arguments.
// It accepts any number of arguments of any type and returns them as a slice.
func list(v ...any) []any {
	return v
}

// push appends an element to the end of a list (slice or array).
// It takes a list and a value, and returns a new list with the value appended.
// This function will panic if the first argument is not a slice or array.
func push(list any, v any) []any {
	l, err := mustPush(list, v)
	if err != nil {
		panic(err)
	}
	return l
}

// mustPush is the implementation of push that returns an error instead of panicking.
// It converts the input list to a slice of any type, then appends the value.
func mustPush(list any, v any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		nl := make([]any, l)
		for i := 0; i < l; i++ {
			nl[i] = l2.Index(i).Interface()
		}
		return append(nl, v), nil
	default:
		return nil, fmt.Errorf("cannot push on type %s", tp)
	}
}

// prepend adds an element to the beginning of a list (slice or array).
// It takes a list and a value, and returns a new list with the value at the start.
// This function will panic if the first argument is not a slice or array.
func prepend(list any, v any) []any {
	l, err := mustPrepend(list, v)
	if err != nil {
		panic(err)
	}
	return l
}

// mustPrepend is the implementation of prepend that returns an error instead of panicking.
// It converts the input list to a slice of any type, then prepends the value.
func mustPrepend(list any, v any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		nl := make([]any, l)
		for i := 0; i < l; i++ {
			nl[i] = l2.Index(i).Interface()
		}
		return append([]any{v}, nl...), nil
	default:
		return nil, fmt.Errorf("cannot prepend on type %s", tp)
	}
}

// chunk divides a list into sub-lists of the specified size.
// It takes a size and a list, and returns a list of lists, each containing
// up to 'size' elements from the original list.
// This function will panic if the second argument is not a slice or array.
func chunk(size int, list any) [][]any {
	l, err := mustChunk(size, list)
	if err != nil {
		panic(err)
	}
	return l
}

// mustChunk is the implementation of chunk that returns an error instead of panicking.
// It divides the input list into chunks of the specified size.
func mustChunk(size int, list any) ([][]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		numChunks := int(math.Floor(float64(l-1)/float64(size)) + 1)
		if numChunks > sliceSizeLimit {
			return nil, fmt.Errorf("number of chunks %d exceeds maximum limit of %d", numChunks, sliceSizeLimit)
		}
		result := make([][]any, numChunks)
		for i := 0; i < numChunks; i++ {
			clen := size
			// Handle the last chunk which might be smaller
			if i == numChunks-1 {
				clen = int(math.Floor(math.Mod(float64(l), float64(size))))
				if clen == 0 {
					clen = size
				}
			}
			result[i] = make([]any, clen)
			for j := 0; j < clen; j++ {
				ix := i*size + j
				result[i][j] = l2.Index(ix).Interface()
			}
		}
		return result, nil

	default:
		return nil, fmt.Errorf("cannot chunk type %s", tp)
	}
}

// last returns the last element of a list (slice or array).
// If the list is empty, it returns nil.
// This function will panic if the argument is not a slice or array.
func last(list any) any {
	l, err := mustLast(list)
	if err != nil {
		panic(err)
	}

	return l
}

// mustLast is the implementation of last that returns an error instead of panicking.
// It returns the last element of the list or nil if the list is empty.
func mustLast(list any) (any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)

		l := l2.Len()
		if l == 0 {
			return nil, nil
		}

		return l2.Index(l - 1).Interface(), nil
	default:
		return nil, fmt.Errorf("cannot find last on type %s", tp)
	}
}

// first returns the first element of a list (slice or array).
// If the list is empty, it returns nil.
// This function will panic if the argument is not a slice or array.
func first(list any) any {
	l, err := mustFirst(list)
	if err != nil {
		panic(err)
	}

	return l
}

// mustFirst is the implementation of first that returns an error instead of panicking.
// It returns the first element of the list or nil if the list is empty.
func mustFirst(list any) (any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)

		l := l2.Len()
		if l == 0 {
			return nil, nil
		}

		return l2.Index(0).Interface(), nil
	default:
		return nil, fmt.Errorf("cannot find first on type %s", tp)
	}
}

// rest returns all elements of a list except the first one.
// If the list is empty, it returns nil.
// This function will panic if the argument is not a slice or array.
func rest(list any) []any {
	l, err := mustRest(list)
	if err != nil {
		panic(err)
	}

	return l
}

// mustRest is the implementation of rest that returns an error instead of panicking.
// It returns all elements of the list except the first one, or nil if the list is empty.
func mustRest(list any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		if l == 0 {
			return nil, nil
		}
		nl := make([]any, l-1)
		for i := 1; i < l; i++ {
			nl[i-1] = l2.Index(i).Interface()
		}
		return nl, nil
	default:
		return nil, fmt.Errorf("cannot find rest on type %s", tp)
	}
}

// initial returns all elements of a list except the last one.
// If the list is empty, it returns nil.
// This function will panic if the argument is not a slice or array.
func initial(list any) []any {
	l, err := mustInitial(list)
	if err != nil {
		panic(err)
	}

	return l
}

// mustInitial is the implementation of initial that returns an error instead of panicking.
// It returns all elements of the list except the last one, or nil if the list is empty.
func mustInitial(list any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		if l == 0 {
			return nil, nil
		}
		nl := make([]any, l-1)
		for i := 0; i < l-1; i++ {
			nl[i] = l2.Index(i).Interface()
		}
		return nl, nil
	default:
		return nil, fmt.Errorf("cannot find initial on type %s", tp)
	}
}

// sortAlpha sorts a list of strings alphabetically.
// If the input is not a slice or array, it returns a single-element slice
// containing the string representation of the input.
func sortAlpha(list any) []string {
	k := reflect.Indirect(reflect.ValueOf(list)).Kind()
	switch k {
	case reflect.Slice, reflect.Array:
		a := strslice(list)
		s := sort.StringSlice(a)
		s.Sort()
		return s
	}
	return []string{strval(list)}
}

// reverse returns a new list with the elements in reverse order.
// This function will panic if the argument is not a slice or array.
func reverse(v any) []any {
	l, err := mustReverse(v)
	if err != nil {
		panic(err)
	}

	return l
}

// mustReverse is the implementation of reverse that returns an error instead of panicking.
// It returns a new list with the elements in reverse order.
func mustReverse(v any) ([]any, error) {
	tp := reflect.TypeOf(v).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(v)
		l := l2.Len()
		// We do not sort in place because the incoming array should not be altered.
		nl := make([]any, l)
		for i := 0; i < l; i++ {
			nl[l-i-1] = l2.Index(i).Interface()
		}
		return nl, nil
	default:
		return nil, fmt.Errorf("cannot find reverse on type %s", tp)
	}
}

// compact returns a new list with all "empty" elements removed.
// An element is considered empty if it's nil, zero, an empty string, or an empty collection.
// This function will panic if the argument is not a slice or array.
func compact(list any) []any {
	l, err := mustCompact(list)
	if err != nil {
		panic(err)
	}
	return l
}

// mustCompact is the implementation of compact that returns an error instead of panicking.
// It returns a new list with all "empty" elements removed.
func mustCompact(list any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		var nl []any
		var item any
		for i := 0; i < l; i++ {
			item = l2.Index(i).Interface()
			if !empty(item) {
				nl = append(nl, item)
			}
		}
		return nl, nil
	default:
		return nil, fmt.Errorf("cannot compact on type %s", tp)
	}
}

// uniq returns a new list with duplicate elements removed.
// The first occurrence of each element is kept.
// This function will panic if the argument is not a slice or array.
func uniq(list any) []any {
	l, err := mustUniq(list)
	if err != nil {
		panic(err)
	}
	return l
}

// mustUniq is the implementation of uniq that returns an error instead of panicking.
// It returns a new list with duplicate elements removed.
func mustUniq(list any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		var dest []any
		var item any
		for i := 0; i < l; i++ {
			item = l2.Index(i).Interface()
			if !inList(dest, item) {
				dest = append(dest, item)
			}
		}
		return dest, nil
	default:
		return nil, fmt.Errorf("cannot find uniq on type %s", tp)
	}
}

// inList checks if a value is present in a list.
// It uses deep equality comparison to check for matches.
// Returns true if the value is found, false otherwise.
func inList(haystack []any, needle any) bool {
	for _, h := range haystack {
		if reflect.DeepEqual(needle, h) {
			return true
		}
	}
	return false
}

// without returns a new list with all occurrences of the specified values removed.
// This function will panic if the first argument is not a slice or array.
func without(list any, omit ...any) []any {
	l, err := mustWithout(list, omit...)
	if err != nil {
		panic(err)
	}
	return l
}

// mustWithout is the implementation of without that returns an error instead of panicking.
// It returns a new list with all occurrences of the specified values removed.
func mustWithout(list any, omit ...any) ([]any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		res := []any{}
		var item any
		for i := 0; i < l; i++ {
			item = l2.Index(i).Interface()
			if !inList(omit, item) {
				res = append(res, item)
			}
		}
		return res, nil
	default:
		return nil, fmt.Errorf("cannot find without on type %s", tp)
	}
}

// has checks if a value is present in a list.
// Returns true if the value is found, false otherwise.
// This function will panic if the second argument is not a slice or array.
func has(needle any, haystack any) bool {
	l, err := mustHas(needle, haystack)
	if err != nil {
		panic(err)
	}
	return l
}

// mustHas is the implementation of has that returns an error instead of panicking.
// It checks if a value is present in a list.
func mustHas(needle any, haystack any) (bool, error) {
	if haystack == nil {
		return false, nil
	}
	tp := reflect.TypeOf(haystack).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(haystack)
		var item any
		l := l2.Len()
		for i := 0; i < l; i++ {
			item = l2.Index(i).Interface()
			if reflect.DeepEqual(needle, item) {
				return true, nil
			}
		}
		return false, nil
	default:
		return false, fmt.Errorf("cannot find has on type %s", tp)
	}
}

// slice extracts a portion of a list based on the provided indices.
// Usage examples:
// $list := [1, 2, 3, 4, 5]
// slice $list     -> list[0:5] = list[:]
// slice $list 0 3 -> list[0:3] = list[:3]
// slice $list 3 5 -> list[3:5]
// slice $list 3   -> list[3:5] = list[3:]
//
// This function will panic if the first argument is not a slice or array.
func slice(list any, indices ...any) any {
	l, err := mustSlice(list, indices...)
	if err != nil {
		panic(err)
	}
	return l
}

// mustSlice is the implementation of slice that returns an error instead of panicking.
// It extracts a portion of a list based on the provided indices.
func mustSlice(list any, indices ...any) (any, error) {
	tp := reflect.TypeOf(list).Kind()
	switch tp {
	case reflect.Slice, reflect.Array:
		l2 := reflect.ValueOf(list)
		l := l2.Len()
		if l == 0 {
			return nil, nil
		}
		// Determine start and end indices
		var start, end int
		if len(indices) > 0 {
			start = toInt(indices[0])
		}
		if len(indices) < 2 {
			end = l
		} else {
			end = toInt(indices[1])
		}
		return l2.Slice(start, end).Interface(), nil
	default:
		return nil, fmt.Errorf("list should be type of slice or array but %s", tp)
	}
}

// concat combines multiple lists into a single list.
// It takes any number of lists and returns a new list containing all elements.
// This function will panic if any argument is not a slice or array.
func concat(lists ...any) any {
	var res []any
	for _, list := range lists {
		tp := reflect.TypeOf(list).Kind()
		switch tp {
		case reflect.Slice, reflect.Array:
			l2 := reflect.ValueOf(list)
			for i := 0; i < l2.Len(); i++ {
				res = append(res, l2.Index(i).Interface())
			}
		default:
			panic(fmt.Sprintf("cannot concat type %s as list", tp))
		}
	}
	return res
}
