package helpers

import (
	"fmt"
	"reflect"

	"github.com/spf13/cast"
)

// strSlice converts a value to a slice of strings, handling various types
// including []string, []any, and other slice types.
//
// Parameters:
//
//	value any - the value to convert to a slice of strings.
//
// Returns:
//
//	[]string - a slice of strings representing the input value.
//
// Example:
//
//	strs := StrSlice([]any{"apple", "banana", "cherry"})
//	fmt.Println(strs) // Output: ["apple", "banana", "cherry"]
func StrSlice(value any) []string {
	if value == nil {
		return []string{}
	}

	// Handle []string type efficiently without reflection.
	if strs, ok := value.([]string); ok {
		return strs
	}

	// For slices of any, convert each element to a string, skipping nil values.
	if interfaces, ok := value.([]any); ok {
		var result []string
		for _, s := range interfaces {
			if s != nil {
				result = append(result, cast.ToString(s))
			}
		}
		return result
	}

	// Use reflection for other slice types to convert them to []string.
	reflectedValue := reflect.ValueOf(value)
	if reflectedValue.Kind() == reflect.Slice || reflectedValue.Kind() == reflect.Array {
		var result []string
		for i := 0; i < reflectedValue.Len(); i++ {
			value := reflectedValue.Index(i).Interface()
			if value != nil {
				result = append(result, cast.ToString(value))
			}
		}
		return result
	}

	// If it's not a slice, array, or nil, return a slice with the string representation of v.
	return []string{cast.ToString(value)}
}

// UntilStep generates a slice of integers from 'start' to 'stop' (exclusive),
// incrementing by 'step'. If 'step' is positive, the sequence increases; if
// negative, it decreases. The function returns an empty slice if the sequence
// does not make logical sense (e.g., positive step when start is greater than
// stop or vice versa).
//
// Parameters:
//
//	start int - the starting point of the sequence.
//	stop int - the endpoint (exclusive) of the sequence.
//	step int - the increment between elements in the sequence.
//
// Returns:
//
//	[]int - a dynamically generated slice of integers based on the input
//	        parameters, or an empty slice if the parameters are inconsistent
//	        with the desired range and step.
//
// Example:
//
//	{{ 0, 10, 2 | untilStep }} // Output: [0 2 4 6 8]
//	{{ 10, 0, -2 | untilStep }} // Output: [10 8 6 4 2]
func UntilStep(start, stop, step int) []int {
	v := []int{}

	if stop < start {
		if step >= 0 {
			return v
		}
		for i := start; i > stop; i += step {
			v = append(v, i)
		}
		return v
	}

	if step <= 0 {
		return v
	}
	for i := start; i < stop; i += step {
		v = append(v, i)
	}
	return v
}

// ToString converts the input value to a string based on its type.
//
// Parameters:
//
//	given any - the value to be converted to a string.
//
// Returns:
//
//	string - the string representation of the input value.
func ToString(v any) string {
	switch v := v.(type) {
	case string:
		return v
	case []byte:
		return string(v)
	case error:
		return v.Error()
	case fmt.Stringer:
		return v.String()
	default:
		return fmt.Sprintf("%v", v)
	}
}

// Empty evaluates the emptiness of the provided value 'given'. It returns
// true if 'given' is considered empty based on its type. This method is
// essential for determining the presence or absence of meaningful value
// across various data types.
//
// Parameters:
//
//	given any - the value to be evaluated for emptiness.
//
// Returns:
//
//	bool - true if 'given' is empty, false otherwise.
//
// This method utilizes the reflect package to inspect the type and value of
// 'given'. Depending on the type, it checks for nil pointers, zero-length
// collections (arrays, slices, maps, and strings), zero values of numeric
// types (integers, floats, complex numbers, unsigned ints), and false for
// booleans.
//
// Example:
//
//	Empty(nil) // Output: true
//	Empty("") // Output: true
//	Empty(0) // Output: true
//	Empty(false) // Output: true
//	Empty(struct{}{}) // Output: false
func Empty(given any) bool {
	g := reflect.ValueOf(given)
	if !g.IsValid() {
		return true
	}

	// Basically adapted from text/template.isTrue
	switch g.Kind() {
	case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
		return g.Len() == 0
	case reflect.Bool:
		return !g.Bool()
	case reflect.Complex64, reflect.Complex128:
		return g.Complex() == 0
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return g.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return g.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return g.Float() == 0
	case reflect.Interface, reflect.Ptr:
		return g.IsNil()
	default:
		return false
	}
}
