// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

//go:build js && wasm
// +build js,wasm

package webrtc

import (
	"fmt"
	"syscall/js"
)

// awaitPromise accepts a js.Value representing a Promise. If the promise
// resolves, it returns (result, nil). If the promise rejects, it returns
// (js.Undefined, error). awaitPromise has a synchronous-like API but does not
// block the JavaScript event loop.
func awaitPromise(promise js.Value) (js.Value, error) {
	resultsChan := make(chan js.Value)
	errChan := make(chan js.Error)

	thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		go func() {
			resultsChan <- args[0]
		}()
		return js.Undefined()
	})
	defer thenFunc.Release()

	catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		go func() {
			errChan <- js.Error{args[0]}
		}()
		return js.Undefined()
	})
	defer catchFunc.Release()

	promise.Call("then", thenFunc).Call("catch", catchFunc)

	select {
	case result := <-resultsChan:
		return result, nil
	case err := <-errChan:
		return js.Undefined(), err
	}
}

func valueToUint16Pointer(val js.Value) *uint16 {
	if val.IsNull() || val.IsUndefined() {
		return nil
	}
	convertedVal := uint16(val.Int())
	return &convertedVal
}

func valueToStringPointer(val js.Value) *string {
	if val.IsNull() || val.IsUndefined() {
		return nil
	}
	stringVal := val.String()
	return &stringVal
}

func stringToValueOrUndefined(val string) js.Value {
	if val == "" {
		return js.Undefined()
	}
	return js.ValueOf(val)
}

func uint8ToValueOrUndefined(val uint8) js.Value {
	if val == 0 {
		return js.Undefined()
	}
	return js.ValueOf(val)
}

func interfaceToValueOrUndefined(val interface{}) js.Value {
	if val == nil {
		return js.Undefined()
	}
	return js.ValueOf(val)
}

func valueToStringOrZero(val js.Value) string {
	if val.IsUndefined() || val.IsNull() {
		return ""
	}
	return val.String()
}

func valueToUint8OrZero(val js.Value) uint8 {
	if val.IsUndefined() || val.IsNull() {
		return 0
	}
	return uint8(val.Int())
}

func valueToUint16OrZero(val js.Value) uint16 {
	if val.IsNull() || val.IsUndefined() {
		return 0
	}
	return uint16(val.Int())
}

func valueToUint32OrZero(val js.Value) uint32 {
	if val.IsNull() || val.IsUndefined() {
		return 0
	}
	return uint32(val.Int())
}

func valueToStrings(val js.Value) []string {
	result := make([]string, val.Length())
	for i := 0; i < val.Length(); i++ {
		result[i] = val.Index(i).String()
	}
	return result
}

func stringPointerToValue(val *string) js.Value {
	if val == nil {
		return js.Undefined()
	}
	return js.ValueOf(*val)
}

func uint16PointerToValue(val *uint16) js.Value {
	if val == nil {
		return js.Undefined()
	}
	return js.ValueOf(*val)
}

func boolPointerToValue(val *bool) js.Value {
	if val == nil {
		return js.Undefined()
	}
	return js.ValueOf(*val)
}

func stringsToValue(strings []string) js.Value {
	val := make([]interface{}, len(strings))
	for i, s := range strings {
		val[i] = s
	}
	return js.ValueOf(val)
}

func stringEnumToValueOrUndefined(s string) js.Value {
	if s == "unknown" {
		return js.Undefined()
	}
	return js.ValueOf(s)
}

// Converts the return value of recover() to an error.
func recoveryToError(e interface{}) error {
	switch e := e.(type) {
	case error:
		return e
	default:
		return fmt.Errorf("recovered with non-error value: (%T) %s", e, e)
	}
}

func uint8ArrayValueToBytes(val js.Value) []byte {
	result := make([]byte, val.Length())
	js.CopyBytesToGo(result, val)

	return result
}
