package jsc

import (
	"net/http"

	F "github.com/sagernet/sing/common/format"

	"github.com/dop251/goja"
)

func IsNil(value goja.Value) bool {
	return value == nil || goja.IsUndefined(value) || goja.IsNull(value)
}

func AssertObject(vm *goja.Runtime, value goja.Value, name string, nilable bool) *goja.Object {
	if IsNil(value) {
		if nilable {
			return nil
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	objectValue, isObject := value.(*goja.Object)
	if !isObject {
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected object, but got ", value)))
	}
	return objectValue
}

func AssertString(vm *goja.Runtime, value goja.Value, name string, nilable bool) string {
	if IsNil(value) {
		if nilable {
			return ""
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	stringValue, isString := value.Export().(string)
	if !isString {
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected string, but got ", value)))
	}
	return stringValue
}

func AssertInt(vm *goja.Runtime, value goja.Value, name string, nilable bool) int64 {
	if IsNil(value) {
		if nilable {
			return 0
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	integerValue, isNumber := value.Export().(int64)
	if !isNumber {
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected integer, but got ", value)))
	}
	return integerValue
}

func AssertBool(vm *goja.Runtime, value goja.Value, name string, nilable bool) bool {
	if IsNil(value) {
		if nilable {
			return false
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	boolValue, isBool := value.Export().(bool)
	if !isBool {
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected boolean, but got ", value)))
	}
	return boolValue
}

func AssertBinary(vm *goja.Runtime, value goja.Value, name string, nilable bool) []byte {
	if IsNil(value) {
		if nilable {
			return nil
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	switch exportedValue := value.Export().(type) {
	case []byte:
		return exportedValue
	case goja.ArrayBuffer:
		return exportedValue.Bytes()
	default:
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected Uint8Array or ArrayBuffer, but got ", value)))
	}
}

func AssertStringBinary(vm *goja.Runtime, value goja.Value, name string, nilable bool) []byte {
	if IsNil(value) {
		if nilable {
			return nil
		}
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	switch exportedValue := value.Export().(type) {
	case string:
		return []byte(exportedValue)
	case []byte:
		return exportedValue
	case goja.ArrayBuffer:
		return exportedValue.Bytes()
	default:
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected string, Uint8Array or ArrayBuffer, but got ", value)))
	}
}

func AssertFunction(vm *goja.Runtime, value goja.Value, name string) goja.Callable {
	if IsNil(value) {
		panic(vm.NewTypeError(F.ToString("invalid argument: missing ", name)))
	}
	functionValue, isFunction := goja.AssertFunction(value)
	if !isFunction {
		panic(vm.NewTypeError(F.ToString("invalid argument: ", name, ": expected function, but got ", value)))
	}
	return functionValue
}

func AssertHTTPHeader(vm *goja.Runtime, value goja.Value, name string) http.Header {
	headersObject := AssertObject(vm, value, name, true)
	if headersObject == nil {
		return nil
	}
	return ObjectToHeaders(vm, headersObject, name)
}
