package maputil

import (
	"fmt"
	"strconv"
	"strings"
)

func CastKeysToStrings(s any) (map[string]any, error) {
	new := map[string]any{}
	switch src := s.(type) {
	case map[any]any:
		for k, v := range src {
			var strK string
			switch typedK := k.(type) {
			case string:
				strK = typedK
			default:
				return nil, fmt.Errorf("unexpected type of key in map: expected string, got %T: value=%v, map=%v", typedK, typedK, src)
			}

			castedV, err := RecursivelyStringifyMapKey(v)
			if err != nil {
				return nil, err
			}

			new[strK] = castedV
		}
	case map[string]any:
		for k, v := range src {
			castedV, err := RecursivelyStringifyMapKey(v)
			if err != nil {
				return nil, err
			}

			new[k] = castedV
		}
	}
	return new, nil
}

func RecursivelyStringifyMapKey(v any) (any, error) {
	var castedV any
	switch typedV := v.(type) {
	case map[any]any, map[string]any:
		tmp, err := CastKeysToStrings(typedV)
		if err != nil {
			return nil, err
		}
		castedV = tmp
	case []any:
		a := []any{}
		for i := range typedV {
			res, err := RecursivelyStringifyMapKey(typedV[i])
			if err != nil {
				return nil, err
			}
			a = append(a, res)
		}
		castedV = a
	default:
		castedV = typedV
	}
	return castedV, nil
}

type arg interface {
	getMap(map[string]any) map[string]any
	set(map[string]any, any)
}

type keyArg struct {
	key string
}

func (a keyArg) getMap(m map[string]any) map[string]any {
	_, ok := m[a.key]
	if !ok {
		m[a.key] = map[string]any{}
	}
	switch t := m[a.key].(type) {
	case map[string]any:
		return t
	default:
		panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
	}
}

func (a keyArg) set(m map[string]any, value any) {
	m[a.key] = value
}

type indexedKeyArg struct {
	key   string
	index int
}

func (a indexedKeyArg) getArray(m map[string]any) []any {
	_, ok := m[a.key]
	if !ok {
		m[a.key] = make([]any, a.index+1)
	}
	switch t := m[a.key].(type) {
	case []any:
		if len(t) <= a.index {
			t2 := make([]any, a.index+1)
			copy(t2, t)
			t = t2
		}
		return t
	default:
		panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
	}
}

func (a indexedKeyArg) getMap(m map[string]any) map[string]any {
	t := a.getArray(m)
	if t[a.index] == nil {
		t[a.index] = map[string]any{}
	}
	switch t := t[a.index].(type) {
	case map[string]any:
		return t
	default:
		panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
	}
}

func (a indexedKeyArg) set(m map[string]any, value any) {
	t := a.getArray(m)
	t[a.index] = value
	m[a.key] = t
}

func getCursor(key string) arg {
	key = strings.ReplaceAll(key, "[", " ")
	key = strings.ReplaceAll(key, "]", " ")
	k := key
	idx := 0

	n, err := fmt.Sscanf(key, "%s %d", &k, &idx)

	if n == 2 && err == nil {
		return indexedKeyArg{
			key:   k,
			index: idx,
		}
	}

	return keyArg{
		key: key,
	}
}

func ParseKey(key string) []string {
	r := []string{}
	part := ""
	escaped := false
	for _, rune := range key {
		split := false
		switch {
		case !escaped && rune == '\\':
			escaped = true
			continue
		case rune == '.':
			split = !escaped
		}
		escaped = false
		if split {
			r = append(r, part)
			part = ""
		} else {
			part += string(rune)
		}
	}
	if len(part) > 0 {
		r = append(r, part)
	}
	return r
}

func Set(m map[string]any, key []string, value string, stringBool bool) {
	if len(key) == 0 {
		panic(fmt.Errorf("bug: unexpected length of key: %d", len(key)))
	}

	for len(key) > 1 {
		m, key = getCursor(key[0]).getMap(m), key[1:]
	}

	getCursor(key[0]).set(m, typedVal(value, stringBool))
}

func typedVal(val string, st bool) any {
	// if st is true, directly return it without casting it
	if st {
		return val
	}

	if strings.EqualFold(val, "true") {
		return true
	}

	if strings.EqualFold(val, "false") {
		return false
	}

	if strings.EqualFold(val, "null") {
		return nil
	}

	// handling of only zero, if val has zero prefix, it will be considered as string
	if strings.EqualFold(val, "0") {
		return int64(0)
	}

	// If this value does not start with zero, try parsing it to an int
	if len(val) != 0 && val[0] != '0' {
		if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
			return iv
		}
	}

	return val
}

// MergeMaps merges two maps with special handling for nested maps and arrays.
func MergeMaps(a, b map[string]interface{}, opts ...MergeOptions) map[string]interface{} {
	arrayStrategy := ArrayMergeStrategySparse
	if len(opts) > 0 {
		arrayStrategy = opts[0].ArrayStrategy
	}

	out := make(map[string]interface{}, len(a))
	for k, v := range a {
		out[k] = v
	}
	for k, v := range b {
		if v == nil {
			// If key doesn't exist in base, add nil (issue #1154).
			// If key exists in base, don't overwrite with nil.
			if _, exists := out[k]; !exists {
				out[k] = nil
			}
			continue
		}
		if v, ok := v.(map[string]interface{}); ok {
			if bv, ok := out[k]; ok {
				if bv, ok := bv.(map[string]interface{}); ok {
					out[k] = MergeMaps(bv, v, opts...)
					continue
				}
			}
		}
		vSlice := toInterfaceSlice(v)
		if vSlice != nil {
			if outVal, exists := out[k]; exists {
				outSlice := toInterfaceSlice(outVal)
				if outSlice != nil {
					out[k] = mergeSlices(outSlice, vSlice, arrayStrategy)
					continue
				}
			}
		}
		out[k] = v
	}
	return out
}

func toInterfaceSlice(v any) []any {
	if slice, ok := v.([]any); ok {
		return slice
	}
	return nil
}

type ArrayMergeStrategy int

const (
	// ArrayMergeStrategySparse uses auto-detection: sparse arrays (with nils) merge
	// element-by-element, complete arrays (no nils) replace entirely.
	ArrayMergeStrategySparse ArrayMergeStrategy = iota
	// ArrayMergeStrategyReplace always replaces arrays entirely.
	ArrayMergeStrategyReplace
	// ArrayMergeStrategyMerge always merges arrays element-by-element (for CLI overrides).
	ArrayMergeStrategyMerge
)

type MergeOptions struct {
	ArrayStrategy ArrayMergeStrategy
}

// mergeSlices merges two slices based on the strategy.
//
// Behavior by strategy:
//   - ArrayMergeStrategyReplace: always returns override as-is
//   - ArrayMergeStrategySparse: auto-detects based on nil values (see below)
//   - ArrayMergeStrategyMerge: always merges element-by-element
//
// For Sparse strategy, auto-detection logic:
//   - If override contains ANY nil values → treated as sparse → merge element-by-element
//   - If override contains NO nil values → treated as complete → replace entirely
//
// Edge cases:
//   - Empty array `[]` has no nils, so it replaces entirely. This is intentional:
//     explicitly setting an empty array should clear the base array.
//   - Explicit `[null, value]` in YAML is treated as sparse (rare but correct).
//
// Recursive behavior: When merging maps within array elements, the same strategy
// is propagated to nested MergeMaps calls, maintaining consistent merge semantics.
func mergeSlices(base, override []any, strategy ArrayMergeStrategy) []any {
	if strategy == ArrayMergeStrategyReplace {
		return override
	}

	if strategy == ArrayMergeStrategySparse {
		isSparse := false
		for _, v := range override {
			if v == nil {
				isSparse = true
				break
			}
		}
		if !isSparse {
			return override
		}
	}

	// Merge element-by-element (for ArrayMergeStrategyMerge or sparse arrays)
	maxLen := len(base)
	if len(override) > maxLen {
		maxLen = len(override)
	}

	result := make([]interface{}, maxLen)
	copy(result, base)

	for i := 0; i < len(override); i++ {
		overrideVal := override[i]
		if overrideVal == nil {
			continue
		}

		if overrideMap, ok := overrideVal.(map[string]any); ok {
			if i < len(base) {
				if baseMap, ok := base[i].(map[string]any); ok {
					result[i] = MergeMaps(baseMap, overrideMap, MergeOptions{ArrayStrategy: strategy})
					continue
				}
			}
		}

		result[i] = overrideVal
	}

	return result
}
