package funcmap

import (
	"fmt"
	"log"
	"reflect"
)

var ErrEarlyTermination = fmt.Errorf("template was early terminated by calling {{ stop }}")

func Stop() (string, error) {
	return "", ErrEarlyTermination
}

func Lookup(name string, args ...reflect.Value) (interface{}, error) {
	if len(args) == 0 {
		return nil, fmt.Errorf("lookup expects at least 1 argument, got 0")
	}
	zero := reflect.ValueOf("")

	target := args[0]
	log.Printf("lookup %s %v", name, target)
	if len(args) > 1 {
		zero = args[1]
	}
	switch target.Kind() {
	case reflect.Map:
		if target.IsNil() || target.Type().Elem().Kind() != zero.Kind() {
			target = reflect.MakeMap(reflect.MapOf(target.Type().Key(), zero.Type()))
			target.SetMapIndex(reflect.ValueOf(name), zero)
		}

		return target.MapIndex(reflect.ValueOf(name)).Interface(), nil
	case reflect.Pointer:
		field := target.MethodByName(name)
		if field.IsValid() {
			return field.Interface(), nil
		}
	case reflect.Struct:
		field := target.FieldByName(name)
		if field.IsValid() {
			return field.Interface(), nil
		}
	case reflect.Interface:
		method := target.MethodByName(name)
		if method.IsValid() {
			return method.Interface(), nil
		}
	default:
		return nil, fmt.Errorf("cannot lookup %s from type %v", name, target.Type())
	}
	return nil, fmt.Errorf("no such method or field %s", name)
}

func Invoke(name string, target reflect.Value, args ...reflect.Value) (any, error) {
	if name != "" {
		t, err := Lookup(name, target)
		if err != nil {
			return nil, err
		}
		target = reflect.ValueOf(t)
	}
	for i, arg := range args {
		log.Printf("invoke %s arg[%d]=%v", name, i, arg)
	}
	ret := target.Call(args)
	if len(ret) == 0 {
		return nil, nil
	}

	if err, ok := ret[len(ret)-1].Interface().(error); ok && err != nil {
		return nil, err
	}
	for i, r := range ret {
		log.Printf("invoke %s ret[%d]=%v", name, i, r)
	}

	switch len(ret) {
	case 0:
		return nil, nil
	case 1:
		return ret[0].Interface(), nil
	default:
		var rets []any
		for _, r := range ret {
			rets = append(rets, r.Interface())
		}
		return rets, nil
	}

}

func Void(args ...reflect.Value) string {
	return ""
}

func GetFuncMap() map[string]interface{} {
	return map[string]interface{}{
		"lookup":          Lookup,
		"invoke":          Invoke,
		"void":            Void,
		"get":             FuncGet,
		"set":             FuncSet,
		"math":            Math,
		"xml":             EscapeXML,
		"twilio_validate": TwilioValidate,
		"stop":            Stop,
		"trima_img":       TrimaImg,
		"parse_json":      ParseJSON,
		"json":            MarshalJSON,
		"get_auth":        AuthGet,
		"sprintf": func(format string, input ...interface{}) interface{} {
			return fmt.Sprintf(format, input...)
		},
		"http":    HttpRequest,
		"version": Version,
	}
}