package convert

import (
	"errors"
	"fmt"
	"reflect"

	"go.starlark.net/starlark"
)

var ErrInvalidType = errors.New("unknown type")

func Convert(v any) (starlark.Value, error) {
	if v == nil {
		return starlark.None, nil
	}
	val := reflect.ValueOf(v)
	kind := val.Kind()
	for kind == reflect.Pointer || kind == reflect.Interface {
		val = val.Elem()
	}
	return convert(val)
}

func convert(val reflect.Value) (starlark.Value, error) {
	// Check for invalid or nil values
	if !val.IsValid() {
		return starlark.None, nil
	}
	
	// Check for zero value (uninitialized)
	if val == (reflect.Value{}) {
		return starlark.None, nil
	}
	
	switch val.Kind() {
	case reflect.Interface:
		if val.IsNil() {
			return starlark.None, nil
		}
		return convert(val.Elem())
	case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
		return starlark.MakeInt64(val.Int()), nil
	case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
		return starlark.MakeUint64(val.Uint()), nil
	case reflect.Float64, reflect.Float32:
		return starlark.Float(val.Float()), nil
	case reflect.Bool:
		return starlark.Bool(val.Bool()), nil
	case reflect.String:
		return starlark.String(val.String()), nil
	case reflect.Slice, reflect.Array:
		if val.IsNil() {
			return starlark.None, nil
		}
		return convertSlice(val)
	case reflect.Map:
		if val.IsNil() {
			return starlark.None, nil
		}
		return convertMap(val)
	case reflect.Ptr:
		if val.IsNil() {
			return starlark.None, nil
		}
		return convert(val.Elem())
	default:
		return nil, fmt.Errorf("%w: %s", ErrInvalidType, val.Type())
	}
}

func convertSlice(val reflect.Value) (starlark.Value, error) {
	// Detect byte slice
	if val.Type().Elem().Kind() == reflect.Uint8 {
		return starlark.Bytes(val.Bytes()), nil
	}

	elems := make([]starlark.Value, val.Len())

	for i := 0; i < val.Len(); i++ {
		elem, err := convert(val.Index(i))
		if err != nil {
			return nil, err
		}
		elems[i] = elem
	}

	return starlark.NewList(elems), nil
}

func convertMap(val reflect.Value) (starlark.Value, error) {
	dict := starlark.NewDict(val.Len())
	iter := val.MapRange()
	for iter.Next() {
		k, err := convert(iter.Key())
		if err != nil {
			return nil, err
		}

		v, err := convert(iter.Value())
		if err != nil {
			return nil, err
		}

		err = dict.SetKey(k, v)
		if err != nil {
			return nil, err
		}
	}
	return dict, nil
}
