////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation                                             //
//                                                                            //
// Use of this source code is governed by a license that can be found in the  //
// LICENSE file.                                                              //
////////////////////////////////////////////////////////////////////////////////

//go:build js && wasm

package worker

import (
	"github.com/hack-pad/safejs"
	"github.com/pkg/errors"

	"gitlab.com/elixxir/wasm-utils/utils"
)

// MessageChannel wraps a Javascript MessageChannel object.
//
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
type MessageChannel struct {
	safejs.Value
}

// NewMessageChannel returns a new MessageChannel object with two new
// MessagePort objects.
//
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel/MessageChannel
func NewMessageChannel() (MessageChannel, error) {
	v, err := jsMessageChannel.New()
	if err != nil {
		return MessageChannel{}, err
	}
	return MessageChannel{v}, nil
}

// CreateMessageChannel creates a new Javascript MessageChannel between two
// workers. The [Channel] tag will be used as the prefix in the name of the
// MessageChannel when printing to logs. The key is used to look up the callback
// registered on the worker to handle the MessageChannel creation.
//
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
func CreateMessageChannel(w1, w2 *Manager, channelName, key string) error {
	// Create a Javascript MessageChannel
	mc, err := NewMessageChannel()
	if err != nil {
		return err
	}
	channelNameJS := utils.CopyBytesToJS([]byte(channelName))
	keyJS := utils.CopyBytesToJS([]byte(key))

	port1, err := mc.Port1()
	if err != nil {
		return errors.Wrap(err, "could not get port1")
	}

	port2, err := mc.Port2()
	if err != nil {
		return errors.Wrap(err, "could not get port2")
	}

	obj1 := map[string]any{
		"port": port1.Value, "channel": channelNameJS, "key": keyJS}
	err = w1.w.PostMessageTransfer(obj1, port1.Value)
	if err != nil {
		return errors.Wrap(err, "failed to send port1")
	}

	obj2 := map[string]any{
		"port": port2.Value, "channel": channelNameJS, "key": keyJS}
	err = w2.w.PostMessageTransfer(obj2, port2.Value)
	if err != nil {
		return errors.Wrap(err, "failed to send port2")
	}

	return nil
}

// Port1 returns the first port of the message channel — the port attached to
// the context that originated the channel.
//
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel/port1
func (mc MessageChannel) Port1() (MessagePort, error) {
	v, err := mc.Get("port1")
	if err != nil {
		return MessagePort{}, err
	}
	return NewMessagePort(v)
}

// Port2 returns the second port of the message channel — the port attached to
// the context at the other end of the channel, which the message is initially
// sent to.
//
// Doc: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel/port2
func (mc MessageChannel) Port2() (MessagePort, error) {
	v, err := mc.Get("port2")
	if err != nil {
		return MessagePort{}, err
	}
	return NewMessagePort(v)
}