package cmdlang

import (
	"context"
	"errors"
	"reflect"
)

type CallArgs struct {
	args invocationArgs
}

func (ca CallArgs) Bind(vars ...interface{}) error {
	if len(ca.args.args) != len(vars) {
		return errors.New("wrong number of arguments")
	}

	for i, v := range vars {
		if err := bindArg(v, ca.args.args[i]); err != nil {
			return err
		}
	}
	return nil
}

func (ca CallArgs) HasSwitch(name string) bool {
	if ca.args.kwargs == nil {
		return false
	}

	_, ok := ca.args.kwargs[name]
	return ok
}

func (ca CallArgs) BindSwitch(name string, val interface{}) error {
	if ca.args.kwargs == nil {
		return nil
	}

	vars, ok := ca.args.kwargs[name]
	if !ok || len(*vars) != 1 {
		return nil
	}

	return bindArg(val, (*vars)[0])
}

func (inst *Inst) SetBuiltin(name string, fn func(ctx context.Context, args CallArgs) (any, error)) {
	inst.rootEC.addCmd(name, userBuiltin{fn: fn})
}

type userBuiltin struct {
	fn func(ctx context.Context, args CallArgs) (any, error)
}

func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, error) {
	v, err := u.fn(ctx, CallArgs{args: args})
	if err != nil {
		return nil, err
	}

	return fromGoValue(v)
}

func bindArg(v interface{}, arg object) error {
	switch t := v.(type) {
	case *string:
		*t = arg.String()
	}

	// Check for proxy object
	if po, ok := arg.(proxyObject); ok {
		poValue := reflect.ValueOf(po.p)
		argValue := reflect.ValueOf(v)

		if argValue.Type().Kind() != reflect.Pointer {
			return nil
		} else if !poValue.Type().AssignableTo(argValue.Elem().Type()) {
			return nil
		}

		argValue.Elem().Set(poValue)
	}
	return nil
}