////////////////////////////////////////////////////////////////////////////////
// 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 wasm

import (
	"gitlab.com/elixxir/client/channels"
	"syscall/js"

	"gitlab.com/elixxir/client/bindings"
	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
	"gitlab.com/elixxir/xxdk-wasm/utils"
)

////////////////////////////////////////////////////////////////////////////////
// Basic Channel API                                                          //
////////////////////////////////////////////////////////////////////////////////

// ChannelsManager wraps the [bindings.ChannelsManager] object so its methods
// can be wrapped to be Javascript compatible.
type ChannelsManager struct {
	api *bindings.ChannelsManager
}

// newChannelsManagerJS creates a new Javascript compatible object
// (map[string]interface{}) that matches the [ChannelsManager] structure.
func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]interface{} {
	cm := ChannelsManager{api}
	channelsManagerMap := map[string]interface{}{
		// Basic Channel API
		"GetID":         js.FuncOf(cm.GetID),
		"JoinChannel":   js.FuncOf(cm.JoinChannel),
		"GetChannels":   js.FuncOf(cm.GetChannels),
		"LeaveChannel":  js.FuncOf(cm.LeaveChannel),
		"ReplayChannel": js.FuncOf(cm.ReplayChannel),

		// Channel Sending Methods and Reports
		"SendGeneric":      js.FuncOf(cm.SendGeneric),
		"SendAdminGeneric": js.FuncOf(cm.SendAdminGeneric),
		"SendMessage":      js.FuncOf(cm.SendMessage),
		"SendReply":        js.FuncOf(cm.SendReply),
		"SendReaction":     js.FuncOf(cm.SendReaction),
		"GetIdentity":      js.FuncOf(cm.GetIdentity),
		"GetStorageTag":    js.FuncOf(cm.GetStorageTag),
		"SetNickname":      js.FuncOf(cm.SetNickname),
		"DeleteNickname":   js.FuncOf(cm.DeleteNickname),
		"GetNickname":      js.FuncOf(cm.GetNickname),

		// Channel Receiving Logic and Callback Registration
		"RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler),
	}

	return channelsManagerMap
}

// GetID returns the ID for this [ChannelsManager] in the [ChannelsManager]
// tracker.
//
// Returns:
//  - Tracker ID (int).
func (ch *ChannelsManager) GetID(js.Value, []js.Value) interface{} {
	return ch.api.GetID()
}

// GenerateChannelIdentity creates a new private channel identity
// ([channel.PrivateIdentity]). The public component can be retrieved as JSON
// via [GetPublicChannelIdentityFromPrivate].
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int).
//
// Returns:
//  - JSON of [channel.PrivateIdentity] (Uint8Array).
//  - Throws a TypeError if generating the identity fails.
func GenerateChannelIdentity(_ js.Value, args []js.Value) interface{} {
	pi, err := bindings.GenerateChannelIdentity(args[0].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
	}

	return utils.CopyBytesToJS(pi)
}

// GetPublicChannelIdentity constructs a public identity ([channel.Identity])
// from a bytes version and returns it JSON marshaled.
//
// Parameters:
//  - args[0] - Bytes of the public identity ([channel.Identity]) (Uint8Array).
//
// Returns:
//  - JSON of the constructed [channel.Identity] (Uint8Array).
//  - Throws a TypeError if unmarshalling the bytes or marshalling the identity
//    fails.
func GetPublicChannelIdentity(_ js.Value, args []js.Value) interface{} {
	marshaledPublic := utils.CopyBytesToGo(args[0])
	pi, err := bindings.GetPublicChannelIdentity(marshaledPublic)
	if err != nil {
		utils.Throw(utils.TypeError, err)
	}

	return utils.CopyBytesToJS(pi)
}

// GetPublicChannelIdentityFromPrivate returns the public identity
// ([channel.Identity]) contained in the given private identity
// ([channel.PrivateIdentity]).
//
// Parameters:
//  - args[0] - Bytes of the private identity
//    (channel.PrivateIdentity]) (Uint8Array).
//
// Returns:
//  - JSON of the public identity ([channel.Identity]) (Uint8Array).
//  - Throws a TypeError if unmarshalling the bytes or marshalling the identity
//    fails.
func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) interface{} {
	marshaledPrivate := utils.CopyBytesToGo(args[0])
	identity, err := bindings.GetPublicChannelIdentityFromPrivate(
		marshaledPrivate)
	if err != nil {
		utils.Throw(utils.TypeError, err)
	}

	return utils.CopyBytesToJS(identity)
}

// eventModelBuilder adheres to the [bindings.EventModelBuilder] interface.
type eventModelBuilder struct {
	build func(args ...interface{}) js.Value
}

// Build initializes and returns the event model.
func (emb *eventModelBuilder) Build(path string) bindings.EventModel {
	emJs := emb.build(path)
	return &eventModel{
		joinChannel:      utils.WrapCB(emJs, "JoinChannel"),
		leaveChannel:     utils.WrapCB(emJs, "LeaveChannel"),
		receiveMessage:   utils.WrapCB(emJs, "ReceiveMessage"),
		receiveReply:     utils.WrapCB(emJs, "ReceiveReply"),
		receiveReaction:  utils.WrapCB(emJs, "ReceiveReaction"),
		updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"),
	}
}

// NewChannelsManager creates a new [ChannelsManager] from a new private
// identity ([channel.PrivateIdentity]).
//
// This is for creating a manager for an identity for the first time. For
// generating a new one channel identity, use [GenerateChannelIdentity]. To
// reload this channel manager, use [LoadChannelsManager], passing in the
// storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//    using [Cmix.GetID].
//  - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
//    generated by [GenerateChannelIdentity] (Uint8Array).
//  - args[2] - A function that initialises and returns a Javascript object that
//    matches the [bindings.EventModel] interface. The function must match the
//    Build function in [bindings.EventModelBuilder].
//
// Returns:
//  - Javascript representation of the [ChannelsManager] object.
//  - Throws a TypeError if creating the manager fails.
func NewChannelsManager(_ js.Value, args []js.Value) interface{} {
	privateIdentity := utils.CopyBytesToGo(args[1])

	em := &eventModelBuilder{args[2].Invoke}

	cm, err := bindings.NewChannelsManager(args[0].Int(), privateIdentity, em)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return newChannelsManagerJS(cm)
}

// LoadChannelsManager loads an existing [ChannelsManager].
//
// This is for loading a manager for an identity that has already been created.
// The channel manager should have previously been created with
// [NewChannelsManager] and the storage is retrievable with
// [ChannelsManager.GetStorageTag].
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//    using [Cmix.GetID].
//  - args[1] - The storage tag associated with the previously created channel
//    manager and retrieved with [ChannelsManager.GetStorageTag] (string).
//  - args[2] - A function that initialises and returns a Javascript object that
//    matches the [bindings.EventModel] interface. The function must match the
//    Build function in [bindings.EventModelBuilder].
//
// Returns:
//  - Javascript representation of the [ChannelsManager] object.
//  - Throws a TypeError if loading the manager fails.
func LoadChannelsManager(_ js.Value, args []js.Value) interface{} {
	em := &eventModelBuilder{args[2].Invoke}
	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(), em)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return newChannelsManagerJS(cm)
}

// NewChannelsManagerWithIndexedDb creates a new [ChannelsManager] from a new
// private identity ([channel.PrivateIdentity]) and using indexedDb as a backend
// to manage the event model.
//
// This is for creating a manager for an identity for the first time. For
// generating a new one channel identity, use [GenerateChannelIdentity]. To
// reload this channel manager, use [LoadChannelsManagerWithIndexedDb], passing
// in the storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// This function initialises an indexedDb database.
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//    using [Cmix.GetID].
//  - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
//    generated by [GenerateChannelIdentity] (Uint8Array).
//
// Returns a promise:
//  - Resolves to a Javascript representation of the [ChannelsManager] object.
//  - Rejected with an error if loading indexedDb or the manager fails.
func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
	privateIdentity := utils.CopyBytesToGo(args[1])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		emBuilder := func(path string) channels.EventModel {
			em, err := indexedDb.NewWasmEventModel(path)
			if err != nil {
				reject(utils.JsTrace(err))
				return nil
			}

			return em
		}

		cm, err := bindings.NewChannelsManagerGoEventModel(
			args[0].Int(), privateIdentity, emBuilder)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(newChannelsManagerJS(cm))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// LoadChannelsManagerWithIndexedDb loads an existing [ChannelsManager] using
// an existing indexedDb database as a backend to manage the event model.
//
// This is for loading a manager for an identity that has already been created.
// The channel manager should have previously been created with
// [NewChannelsManagerWithIndexedDb] and the storage is retrievable with
// [ChannelsManager.GetStorageTag].
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//    using [Cmix.GetID].
//  - args[1] - The storage tag associated with the previously created channel
//    manager and retrieved with [ChannelsManager.GetStorageTag] (string).
//
// Returns a promise:
//  - Resolves to a Javascript representation of the [ChannelsManager] object.
//  - Rejected with an error if loading indexedDb or the manager fails.
func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		emBuilder := func(path string) channels.EventModel {
			em, err := indexedDb.NewWasmEventModel(path)
			if err != nil {
				reject(utils.JsTrace(err))
				return nil
			}

			return em
		}

		cm, err := bindings.LoadChannelsManagerGoEventModel(
			args[0].Int(), args[1].String(), emBuilder)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(newChannelsManagerJS(cm))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// GenerateChannel is used to create a channel a new channel of which you are
// the admin. It is only for making new channels, not joining existing ones.
//
// It returns a pretty print of the channel and the private key.
//
// The name cannot be longer that __ characters. The description cannot be
// longer than __ and can only use ______ characters.
//
// Parameters:
//  - args[0] - ID of [Cmix] object in tracker (int).
//  - args[1] - The name of the new channel. The name cannot be longer than __
//    characters and must contain only _____ characters. It cannot be changed
//    once a channel is created. (string).
//  - args[2] - The description of a channel. The description cannot be longer
//    than __ characters and must contain only __ characters. It cannot be
//    changed once a channel is created (string).
//
// Returns:
//  - JSON of [bindings.ChannelGeneration], which describes a generated channel.
//    It contains both the public channel info and the private key for the
//    channel in PEM format (Uint8Array).
//  - Throws a TypeError if generating the channel fails.
func GenerateChannel(_ js.Value, args []js.Value) interface{} {
	gen, err := bindings.GenerateChannel(
		args[0].Int(), args[1].String(), args[2].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(gen)
}

// GetChannelInfo returns the info about a channel from its public description.
//
// Parameters:
//  - args[0] - The pretty print of the channel (string).
//
// The pretty print will be of the format:
//  <XXChannel-v1:Test Channel,description:This is a test channel,secrets:pn0kIs6P1pHvAe7u8kUyf33GYVKmkoCX9LhCtvKJZQI=,3A5eB5pzSHyxN09w1kOVrTIEr5UyBbzmmd9Ga5Dx0XA=,0,0,/zChIlLr2p3Vsm2X4+3TiFapoapaTi8EJIisJSqwfGc=>
//
// Returns:
//  - JSON of [bindings.ChannelInfo], which describes all relevant channel info
//    (Uint8Array).
//  - Throws a TypeError if getting the channel info fails.
func GetChannelInfo(_ js.Value, args []js.Value) interface{} {
	ci, err := bindings.GetChannelInfo(args[0].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(ci)
}

// JoinChannel joins the given channel. It will fail if the channel has already
// been joined.
//
// Parameters:
//  - args[0] - A portable channel string. Should be received from another user
//    or generated via GenerateChannel (string).
//
// The pretty print will be of the format:
//  <XXChannel-v1:Test Channel,description:This is a test channel,secrets:pn0kIs6P1pHvAe7u8kUyf33GYVKmkoCX9LhCtvKJZQI=,3A5eB5pzSHyxN09w1kOVrTIEr5UyBbzmmd9Ga5Dx0XA=,0,0,/zChIlLr2p3Vsm2X4+3TiFapoapaTi8EJIisJSqwfGc=>"
//
// Returns:
//  - JSON of [bindings.ChannelInfo], which describes all relevant channel info
//    (Uint8Array).
//  - Throws a TypeError if joining the channel fails.
func (ch *ChannelsManager) JoinChannel(_ js.Value, args []js.Value) interface{} {
	ci, err := ch.api.JoinChannel(args[0].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(ci)
}

// GetChannels returns the IDs of all channels that have been joined.
//
// Returns:
//  - JSON of an array of marshalled [id.ID] (Uint8Array).
//  - Throws a TypeError if getting the channels fails.
//
// JSON Example:
//  {
//    "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID",
//    "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD"
//  }
func (ch *ChannelsManager) GetChannels(js.Value, []js.Value) interface{} {
	channelList, err := ch.api.GetChannels()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(channelList)
}

// LeaveChannel leaves the given channel. It will return an error if the channel
// was not previously joined.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//
// Returns:
//  - Throws a TypeError if the channel does not exist.
func (ch *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])

	err := ch.api.LeaveChannel(marshalledChanId)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// ReplayChannel replays all messages from the channel within the network's
// memory (~3 weeks) over the event model.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//
// Returns:
//  - Throws a TypeError if the replay fails.
func (ch *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])

	err := ch.api.ReplayChannel(marshalledChanId)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

////////////////////////////////////////////////////////////////////////////////
// Channel Sending Methods and Reports                                        //
////////////////////////////////////////////////////////////////////////////////

// SendGeneric is used to send a raw message over a channel. In general, it
// should be wrapped in a function which defines the wire protocol. If the final
// message, before being sent over the wire, is too long, this will return an
// error. Due to the underlying encoding using compression, it isn't possible to
// define the largest payload that can be sent, but it will always be possible
// to send a payload of 802 bytes at minimum. The meaning of validUntil depends
// on the use case.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - args[1] - The message type of the message. This will be a valid
//    [channels.MessageType] (int).
//  - args[2] - The contents of the message (Uint8Array).
//  - args[3] - The lease of the message. This will be how long the message is
//    valid until, in milliseconds. As per the [channels.Manager] documentation,
//    this has different meanings depending on the use case. These use cases may
//    be generic enough that they will not be enumerated here (int).
//  - args[4] - JSON of [xxdk.CMIXParams]. If left empty
//    [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
//  - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//  - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])
	messageType := args[1].Int()
	message := utils.CopyBytesToGo(args[2])
	leaseTimeMS := int64(args[3].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[4])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		sendReport, err := ch.api.SendGeneric(
			marshalledChanId, messageType, message, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendAdminGeneric is used to send a raw message over a channel encrypted with
// admin keys, identifying it as sent by the admin. In general, it should be
// wrapped in a function that defines the wire protocol. If the final message,
// before being sent over the wire, is too long, this will return an error. The
// message must be at most 510 bytes long.
//
// Parameters:
//  - args[0] - The PEM-encode admin RSA private key (Uint8Array).
//  - args[1] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - args[2] - The message type of the message. This will be a valid
//    [channels.MessageType] (int).
//  - args[3] - The contents of the message (Uint8Array).
//  - args[4] - The lease of the message. This will be how long the message is
//    valid until, in milliseconds. As per the [channels.Manager] documentation,
//    this has different meanings depending on the use case. These use cases may
//    be generic enough that they will not be enumerated here (int).
//  - args[5] - JSON of [xxdk.CMIXParams]. If left empty
//    [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
//  - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//  - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) interface{} {
	adminPrivateKey := utils.CopyBytesToGo(args[0])
	marshalledChanId := utils.CopyBytesToGo(args[1])
	messageType := args[2].Int()
	message := utils.CopyBytesToGo(args[3])
	leaseTimeMS := int64(args[4].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[5])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		sendReport, err := ch.api.SendAdminGeneric(adminPrivateKey,
			marshalledChanId, messageType, message, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendMessage is used to send a formatted message over a channel.
// Due to the underlying encoding using compression, it isn't possible to define
// the largest payload that can be sent, but it will always be possible to send
// a payload of 798 bytes at minimum.
//
// The message will auto delete validUntil after the round it is sent in,
// lasting forever if [channels.ValidForever] is used.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - args[1] - The contents of the message (string).
//  - args[2] - The lease of the message. This will be how long the message is
//    valid until, in milliseconds. As per the [channels.Manager] documentation,
//    this has different meanings depending on the use case. These use cases may
//    be generic enough that they will not be enumerated here (int).
//  - args[3] - JSON of [xxdk.CMIXParams]. If left empty
//    [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
//  - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//  - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendMessage(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])
	message := args[1].String()
	leaseTimeMS := int64(args[2].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[3])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		sendReport, err := ch.api.SendMessage(
			marshalledChanId, message, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendReply is used to send a formatted message over a channel.
// Due to the underlying encoding using compression, it isn't possible to define
// the largest payload that can be sent, but it will always be possible to send
// a payload of 766 bytes at minimum.
//
// If the message ID the reply is sent to does not exist, then the other side
// will post the message as a normal message and not a reply.
// The message will auto delete validUntil after the round it is sent in,
// lasting forever if ValidForever is used.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - args[1] - The contents of the message. The message should be at most 510
//    bytes. This is expected to be Unicode, and thus a string data type is
//    expected (string).
//  - args[2] - JSON of [channel.MessageID] of the message you wish to reply to.
//    This may be found in the [bindings.ChannelSendReport] if replying to your
//    own. Alternatively, if reacting to another user's message, you may
//    retrieve it via the [bindings.ChannelMessageReceptionCallback] registered
//    using  RegisterReceiveHandler (Uint8Array).
//  - args[3] - The lease of the message. This will be how long the message is
//    valid until, in milliseconds. As per the [channels.Manager] documentation,
//    this has different meanings depending on the use case. These use cases may
//    be generic enough that they will not be enumerated here (int).
//  - args[4] - JSON of [xxdk.CMIXParams]. If left empty
//    [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
//  - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//  - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendReply(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])
	message := args[1].String()
	messageToReactTo := utils.CopyBytesToGo(args[2])
	leaseTimeMS := int64(args[3].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[4])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		sendReport, err := ch.api.SendReply(marshalledChanId, message,
			messageToReactTo, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendReaction is used to send a reaction to a message over a channel.
// The reaction must be a single emoji with no other characters, and will
// be rejected otherwise.
// Users will drop the reaction if they do not recognize the reactTo message.
//
// Parameters:
//  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - args[1] - The user's reaction. This should be a single emoji with no
//    other characters. As such, a Unicode string is expected (string).
//  - args[2] - JSON of [channel.MessageID] of the message you wish to reply to.
//    This may be found in the [bindings.ChannelSendReport] if replying to your
//    own. Alternatively, if reacting to another user's message, you may
//    retrieve it via the ChannelMessageReceptionCallback registered using
//    RegisterReceiveHandler (Uint8Array).
//  - args[3] - JSON of [xxdk.CMIXParams]. If left empty
//    [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
//  - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//  - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendReaction(_ js.Value, args []js.Value) interface{} {
	marshalledChanId := utils.CopyBytesToGo(args[0])
	reaction := args[1].String()
	messageToReactTo := utils.CopyBytesToGo(args[2])
	cmixParamsJSON := utils.CopyBytesToGo(args[3])

	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
		sendReport, err := ch.api.SendReaction(
			marshalledChanId, reaction, messageToReactTo, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// GetIdentity returns the marshaled public identity ([channel.Identity]) that
// the channel is using.
//
// Returns:
//  - JSON of the [channel.Identity] (Uint8Array).
//  - Throws TypeError if marshalling the identity fails.
func (ch *ChannelsManager) GetIdentity(js.Value, []js.Value) interface{} {
	i, err := ch.api.GetIdentity()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(i)
}

// GetStorageTag returns the storage tag needed to reload the manager.
//
// Returns:
//  - Storage tag (string).
func (ch *ChannelsManager) GetStorageTag(js.Value, []js.Value) interface{} {
	return ch.api.GetStorageTag()
}

// SetNickname sets the nickname for a given channel. The nickname must be valid
// according to [IsNicknameValid].
//
// Parameters:
//  - args[0] - The nickname to set (string).
//  - args[1] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
//  - Throws TypeError if unmarshalling the ID fails or the nickname is invalid.
func (ch *ChannelsManager) SetNickname(_ js.Value, args []js.Value) interface{} {
	err := ch.api.SetNickname(args[0].String(), utils.CopyBytesToGo(args[1]))
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// DeleteNickname deletes the nickname for a given channel.
//
// Parameters:
//  - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
//  - Throws TypeError if deleting the nickname fails.
func (ch *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) interface{} {
	err := ch.api.DeleteNickname(utils.CopyBytesToGo(args[0]))
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// GetNickname returns the nickname set for a given channel. Returns an error if
// there is no nickname set.
//
// Parameters:
//  - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
//  - The nickname (string).
//  - Throws TypeError if the channel has no nickname set.
func (ch *ChannelsManager) GetNickname(_ js.Value, args []js.Value) interface{} {
	nickname, err := ch.api.GetNickname(utils.CopyBytesToGo(args[0]))
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nickname
}

// IsNicknameValid checks if a nickname is valid.
//
// Rules:
//  1. A nickname must not be longer than 24 characters.
//  2. A nickname must not be shorter than 1 character.
//
// Parameters:
//  - args[0] - Nickname to check (string).
//
// Returns:
//  - A Javascript Error object if the nickname is invalid with the reason why.
//  - Null if the nickname is valid.
func IsNicknameValid(_ js.Value, args []js.Value) interface{} {
	err := bindings.IsNicknameValid(args[0].String())
	if err != nil {
		return utils.JsError(err)
	}

	return nil
}

////////////////////////////////////////////////////////////////////////////////
// Channel Receiving Logic and Callback Registration                          //
////////////////////////////////////////////////////////////////////////////////

// channelMessageReceptionCallback wraps Javascript callbacks to adhere to the
// [bindings.ChannelMessageReceptionCallback] interface.
type channelMessageReceptionCallback struct {
	callback func(args ...interface{}) js.Value
}

// Callback returns the context for a channel message.
//
// Parameters:
//  - receivedChannelMessageReport - Returns the JSON of
//   [bindings.ReceivedChannelMessageReport] (Uint8Array).
//  - err - Returns an error on failure (Error).
//
// Returns:
//  - It must return a unique UUID for the message that it can be referenced by
//    later (int).
func (cmrCB *channelMessageReceptionCallback) Callback(
	receivedChannelMessageReport []byte, err error) int {
	uuid := cmrCB.callback(
		utils.CopyBytesToJS(receivedChannelMessageReport), utils.JsTrace(err))

	return uuid.Int()
}

// RegisterReceiveHandler is used to register handlers for non-default message
// types. They can be processed by modules. It is important that such modules
// sync up with the event model implementation.
//
// There can only be one handler per [channels.MessageType], and this will
// return an error on any re-registration.
//
// Parameters:
//  - args[0] - The message type of the message. This will be a valid
//    [channels.MessageType] (int).
//  - args[1] - Javascript object that has functions that implement the
//    [bindings.ChannelMessageReceptionCallback] interface. This callback will
//    be executed when a channel message of the messageType is received.
//
// Returns:
//  - Throws a TypeError if registering the handler fails.
func (ch *ChannelsManager) RegisterReceiveHandler(_ js.Value, args []js.Value) interface{} {
	messageType := args[0].Int()
	listenerCb := &channelMessageReceptionCallback{
		utils.WrapCB(args[1], "Callback")}

	err := ch.api.RegisterReceiveHandler(messageType, listenerCb)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

////////////////////////////////////////////////////////////////////////////////
// Event Model Logic                                                          //
////////////////////////////////////////////////////////////////////////////////

// eventModel wraps Javascript callbacks to adhere to the [bindings.EventModel]
// interface.
type eventModel struct {
	joinChannel      func(args ...interface{}) js.Value
	leaveChannel     func(args ...interface{}) js.Value
	receiveMessage   func(args ...interface{}) js.Value
	receiveReply     func(args ...interface{}) js.Value
	receiveReaction  func(args ...interface{}) js.Value
	updateSentStatus func(args ...interface{}) js.Value
}

// JoinChannel is called whenever a channel is joined locally.
//
// Parameters:
//  - channel - Returns the pretty print representation of a channel (string).
func (em *eventModel) JoinChannel(channel string) {
	em.joinChannel(channel)
}

// LeaveChannel is called whenever a channel is left locally.
//
// Parameters:
//  - ChannelId - Marshalled bytes of the channel [id.ID] (Uint8Array).
func (em *eventModel) LeaveChannel(channelID []byte) {
	em.leaveChannel(utils.CopyBytesToJS(channelID))
}

// ReceiveMessage is called whenever a message is received on a given channel.
// It may be called multiple times on the same message. It is incumbent on the
// user of the API to filter such called by message ID.
//
// Parameters:
//  - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - messageID - The bytes of the [channel.MessageID] of the received message
//    (Uint8Array).
//  - nickname - The nickname of the sender of the message (string).
//  - text - The content of the message (string).
//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
//  - timestamp - Time the message was received; represented as nanoseconds
//    since unix epoch (int).
//  - lease - The number of nanoseconds that the message is valid for (int).
//  - roundId - The ID of the round that the message was received on (int).
//  - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//  Sent      =  0
//  Delivered =  1
//  Failed    =  2
//
// Returns:
//  - A non-negative unique UUID for the message that it can be referenced by
//    later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname,
	text string, identity []byte, timestamp, lease, roundId, status int64) int64 {
	uuid := em.receiveMessage(utils.CopyBytesToJS(channelID),
		utils.CopyBytesToJS(messageID), nickname, text,
		utils.CopyBytesToJS(identity), timestamp, lease, roundId, status)

	return int64(uuid.Int())
}

// ReceiveReply is called whenever a message is received that is a reply on a
// given channel. It may be called multiple times on the same message. It is
// incumbent on the user of the API to filter such called by message ID.
//
// Messages may arrive our of order, so a reply in theory can arrive before the
// initial message. As a result, it may be important to buffer replies.
//
// Parameters:
//  - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - messageID - The bytes of the [channel.MessageID] of the received message
//    (Uint8Array).
//  - reactionTo - The [channel.MessageID] for the message that received a reply
//    (Uint8Array).
//  - senderUsername - The username of the sender of the message (string).
//  - text - The content of the message (string).
//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
//  - timestamp - Time the message was received; represented as nanoseconds
//    since unix epoch (int).
//  - lease - The number of nanoseconds that the message is valid for (int).
//  - roundId - The ID of the round that the message was received on (int).
//  - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//  Sent      =  0
//  Delivered =  1
//  Failed    =  2
//
// Returns:
//  - A non-negative unique UUID for the message that it can be referenced by
//    later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
	senderUsername, text string, identity []byte, timestamp, lease, roundId,
	status int64) int64 {
	uuid := em.receiveReply(utils.CopyBytesToJS(channelID),
		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
		senderUsername, text, utils.CopyBytesToJS(identity),
		timestamp, lease, roundId, status)

	return int64(uuid.Int())
}

// ReceiveReaction is called whenever a reaction to a message is received on a
// given channel. It may be called multiple times on the same reaction. It is
// incumbent on the user of the API to filter such called by message ID.
//
// Messages may arrive our of order, so a reply in theory can arrive before the
// initial message. As a result, it may be important to buffer reactions.
//
// Parameters:
//  - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
//  - messageID - The bytes of the [channel.MessageID] of the received message
//    (Uint8Array).
//  - reactionTo - The [channel.MessageID] for the message that received a reply
//    (Uint8Array).
//  - senderUsername - The username of the sender of the message (string).
//  - reaction - The contents of the reaction message (string).
//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
//  - timestamp - Time the message was received; represented as nanoseconds
//    since unix epoch (int).
//  - lease - The number of nanoseconds that the message is valid for (int).
//  - roundId - The ID of the round that the message was received on (int).
//  - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//  Sent      =  0
//  Delivered =  1
//  Failed    =  2
//
// Returns:
//  - A non-negative unique UUID for the message that it can be referenced by
//    later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveReaction(channelID, messageID, reactionTo []byte,
	senderUsername, reaction string, identity []byte, timestamp, lease, roundId,
	status int64) int64 {
	uuid := em.receiveReaction(utils.CopyBytesToJS(channelID),
		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
		senderUsername, reaction, utils.CopyBytesToJS(identity),
		timestamp, lease, roundId, status)

	return int64(uuid.Int())
}

// UpdateSentStatus is called whenever the sent status of a message has
// changed.
//
// Parameters:
//  - uuid - The unique identifier for the message (int).
//  - messageID - The bytes of the [channel.MessageID] of the received message
//    (Uint8Array).
//  - timestamp - Time the message was received; represented as nanoseconds
//    since unix epoch (int).
//  - roundId - The ID of the round that the message was received on (int).
//  - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//  Sent      =  0
//  Delivered =  1
//  Failed    =  2
func (em *eventModel) UpdateSentStatus(
	uuid int64, messageID []byte, timestamp, roundID, status int64) {
	em.updateSentStatus(
		uuid, utils.CopyBytesToJS(messageID), timestamp, roundID, status)
}