////////////////////////////////////////////////////////////////////////////////
// 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/xxdk-wasm/utils"
	"syscall/js"
)

// Request sends a contact request from the user identity in the imported [E2e]
// structure to the passed contact, as well as the passed facts (it will error
// if they are too long).
//
// The other party must accept the request by calling [E2e.Confirm] to be able
// to send messages using [E2e.SendE2E]. When the other party does so, the
// "confirm" callback will get called.
//
// The round the request is initially sent on will be returned, but the request
// will be listed as a critical message, so the underlying cMix client will auto
// resend it in the event of failure.
//
// A request cannot be sent for a contact who has already received a request or
// who is already a partner.
//
// The request sends as a critical message, if the round it sends on fails, it
// will be auto resent by the cMix client.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//   - args[1] - JSON of [fact.FactList] (Uint8Array).
//
// Returns a promise:
//   - Resolves to the ID of the round (int).
//   - Rejected with an error if sending the request fails.
func (e *E2e) Request(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])
	factsListJson := utils.CopyBytesToGo(args[1])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		rid, err := e.api.Request(partnerContact, factsListJson)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(rid)
		}
	}

	return utils.CreatePromise(promiseFn)
}

// Confirm sends a confirmation for a received request. It can only be called
// once. This both sends keying material to the other party and creates a
// channel in the e2e handler, after which e2e messages can be sent to the
// partner using [E2e.SendE2E].
//
// The round the request is initially sent on will be returned, but the request
// will be listed as a critical message, so the underlying cMix client will auto
// resend it in the event of failure.
//
// A confirmation cannot be sent for a contact who has not sent a request or who
// is already a partner. This can only be called once for a specific contact.
// The confirmation sends as a critical message; if the round it sends on fails,
// it will be auto resent by the cMix client.
//
// If the confirmation must be resent, use [E2e.ReplayConfirm].
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//
// Returns a promise:
//   - Resolves to the ID of the round (int).
//   - Rejected with an error if sending the confirmation fails.
func (e *E2e) Confirm(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		rid, err := e.api.Confirm(partnerContact)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(rid)
		}
	}

	return utils.CreatePromise(promiseFn)
}

// Reset sends a contact reset request from the user identity in the imported
// e2e structure to the passed contact, as well as the passed facts (it will
// error if they are too long).
//
// This deletes all traces of the relationship with the partner from e2e and
// create a new relationship from scratch.
//
// The round the reset is initially sent on will be returned, but the request
// will be listed as a critical message, so the underlying cMix client will auto
// resend it in the event of failure.
//
// A request cannot be sent for a contact who has already received a request or
// who is already a partner.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//
// Returns a promise:
//   - Resolves to the ID of the round (int).
//   - Rejected with an error if sending the reset fails.
func (e *E2e) Reset(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		rid, err := e.api.Reset(partnerContact)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(rid)
		}
	}

	return utils.CreatePromise(promiseFn)
}

// ReplayConfirm resends a confirmation to the partner. It will fail to send if
// the send relationship with the partner has already ratcheted.
//
// The confirmation sends as a critical message; if the round it sends on fails,
// it will be auto resent by the cMix client.
//
// This will not be useful if either side has ratcheted.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//
// Returns a promise:
//   - Resolves to the ID of the round (int).
//   - Rejected with an error if resending the confirmation fails.
func (e *E2e) ReplayConfirm(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		rid, err := e.api.ReplayConfirm(partnerContact)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(rid)
		}
	}

	return utils.CreatePromise(promiseFn)
}

// CallAllReceivedRequests will iterate through all pending contact requests and
// replay them on the callbacks.
func (e *E2e) CallAllReceivedRequests(js.Value, []js.Value) any {
	e.api.CallAllReceivedRequests()
	return nil
}

// DeleteRequest deletes sent or received requests for a specific partner ID.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//
// Returns:
//   - Throws TypeError if the deletion fails.
func (e *E2e) DeleteRequest(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])
	err := e.api.DeleteRequest(partnerContact)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// DeleteAllRequests clears all requests from auth storage.
//
// Returns:
//   - Throws TypeError if the deletion fails.
func (e *E2e) DeleteAllRequests(js.Value, []js.Value) any {
	err := e.api.DeleteAllRequests()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// DeleteSentRequests clears all sent requests from auth storage.
//
// Returns:
//   - Throws TypeError if the deletion fails.
func (e *E2e) DeleteSentRequests(js.Value, []js.Value) any {
	err := e.api.DeleteSentRequests()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// DeleteReceiveRequests clears all received requests from auth storage.
//
// Returns:
//   - Throws TypeError if the deletion fails.
func (e *E2e) DeleteReceiveRequests(js.Value, []js.Value) any {
	err := e.api.DeleteReceiveRequests()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// GetReceivedRequest returns a contact if there is a received request for it.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [contact.Contact] (Uint8Array).
//
// Returns:
//   - Marshalled bytes of [contact.Contact] (Uint8Array).
//   - Throws TypeError if getting the received request fails.
func (e *E2e) GetReceivedRequest(_ js.Value, args []js.Value) any {
	partnerContact := utils.CopyBytesToGo(args[0])
	c, err := e.api.GetReceivedRequest(partnerContact)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(c)
}

// VerifyOwnership checks if the received ownership proof is valid.
//
// Parameters:
//   - args[0] - Marshalled bytes of the received [contact.Contact]
//     (Uint8Array).
//   - args[1] - Marshalled bytes of the verified [contact.Contact]
//     (Uint8Array).
//   - args[2] - ID of [E2e] object in tracker (int).
//
// Returns:
//   - Returns true if the ownership is valid (boolean).
//   - Throws TypeError if loading the parameters fails.
func (e *E2e) VerifyOwnership(_ js.Value, args []js.Value) any {
	receivedContact := utils.CopyBytesToGo(args[0])
	verifiedContact := utils.CopyBytesToGo(args[1])
	isValid, err := e.api.VerifyOwnership(
		receivedContact, verifiedContact, args[2].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return isValid
}

// AddPartnerCallback adds a new callback that overrides the generic auth
// callback for the given partner ID.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [id.ID] (Uint8Array).
//   - args[1] - Javascript object that has functions that implement the
//     [bindings.AuthCallbacks] interface.
//
// Returns:
//   - Throws TypeError if the [id.ID] cannot be unmarshalled.
func (e *E2e) AddPartnerCallback(_ js.Value, args []js.Value) any {
	partnerID := utils.CopyBytesToGo(args[0])
	callbacks := newAuthCallbacks(args[1])
	err := e.api.AddPartnerCallback(partnerID, callbacks)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// DeletePartnerCallback deletes the callback that overrides the generic
// auth callback for the given partner ID.
//
// Parameters:
//   - args[0] - Marshalled bytes of the partner [id.ID] (Uint8Array).
//
// Returns:
//   - Throws TypeError if the [id.ID] cannot be unmarshalled.
func (e *E2e) DeletePartnerCallback(_ js.Value, args []js.Value) any {
	partnerID := utils.CopyBytesToGo(args[0])
	err := e.api.DeletePartnerCallback(partnerID)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}