////////////////////////////////////////////////////////////////////////////////
// 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/v4/bindings"
	"gitlab.com/elixxir/wasm-utils/exception"
	"gitlab.com/elixxir/wasm-utils/utils"
	"syscall/js"
)

////////////////////////////////////////////////////////////////////////////////
// Structs and Interfaces                                                     //
////////////////////////////////////////////////////////////////////////////////

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

// newBackupJS creates a new Javascript compatible object (map[string]any) tha
// matches the [Backup] structure.
func newBackupJS(api *bindings.Backup) map[string]any {
	b := Backup{api}
	backupMap := map[string]any{
		"StopBackup":      js.FuncOf(b.StopBackup),
		"IsBackupRunning": js.FuncOf(b.IsBackupRunning),
		"AddJson":         js.FuncOf(b.AddJson),
	}

	return backupMap
}

// updateBackupFunc wraps Javascript callbacks to adhere to the
// [bindings.UpdateBackupFunc] interface.
type updateBackupFunc struct {
	updateBackup func(args ...any) js.Value
}

// UpdateBackup is a function callback that returns new backups.
//
// Parameters:
//   - encryptedBackup - Returns the bytes of the encrypted backup (Uint8Array).
func (ubf *updateBackupFunc) UpdateBackup(encryptedBackup []byte) {
	ubf.updateBackup(utils.CopyBytesToJS(encryptedBackup))
}

////////////////////////////////////////////////////////////////////////////////
// Client functions                                                           //
////////////////////////////////////////////////////////////////////////////////

// NewCmixFromBackup initializes a new e2e storage from an encrypted backup.
// Users of this function should delete the storage directory on error. Users of
// this function should call LoadCmix as normal once this call succeeds.
//
// Parameters:
//   - args[0] - JSON of the NDF ([ndf.NetworkDefinition]) (string).
//   - args[1] - Storage directory (string).
//   - args[2] - Backup passphrase (string).
//   - args[3] - Session password (Uint8Array).
//   - args[4] - Backup file contents (Uint8Array).
//
// Returns:
//   - JSON of [bindings.BackupReport] (Uint8Array).
//   - Throws an error if creating [Cmix] from backup fails.
func NewCmixFromBackup(_ js.Value, args []js.Value) any {
	ndfJSON := args[0].String()
	storageDir := args[1].String()
	backupPassphrase := args[2].String()
	sessionPassword := utils.CopyBytesToGo(args[3])
	backupFileContents := utils.CopyBytesToGo(args[4])

	report, err := bindings.NewCmixFromBackup(ndfJSON, storageDir,
		backupPassphrase, sessionPassword, backupFileContents)
	if err != nil {
		exception.ThrowTrace(err)
		return nil
	}

	return utils.CopyBytesToJS(report)
}

////////////////////////////////////////////////////////////////////////////////
// Backup functions                                                           //
////////////////////////////////////////////////////////////////////////////////

// InitializeBackup creates a bindings-layer [Backup] object.
//
// Parameters:
//   - args[0] - ID of [E2e] object in tracker (int).
//   - args[1] - ID of [UserDiscovery] object in tracker (int).
//   - args[2] - [Backup] passphrase provided by the user (string). Used to
//     decrypt the backup.
//   - args[3] - The callback to be called when a backup is triggered. Must be
//     Javascript object that has functions that implement the
//     [bindings.UpdateBackupFunc] interface.
//
// Returns:
//   - Javascript representation of the [Backup] object.
//   - Throws an error if initializing the [Backup] fails.
func InitializeBackup(_ js.Value, args []js.Value) any {
	cb := &updateBackupFunc{utils.WrapCB(args[3], "UpdateBackup")}
	api, err := bindings.InitializeBackup(
		args[0].Int(), args[1].Int(), args[2].String(), cb)
	if err != nil {
		exception.ThrowTrace(err)
		return nil
	}

	return newBackupJS(api)
}

// ResumeBackup resumes the backup processes with a new callback.
// Call this function only when resuming a backup that has already been
// initialized or to replace the callback.
// To start the backup for the first time or to use a new password, use
// [InitializeBackup].
//
// Parameters:
//   - args[0] - ID of [E2e] object in tracker (int).
//   - args[1] - ID of [UserDiscovery] object in tracker (int).
//   - args[2] - The callback to be called when a backup is triggered. Must be
//     Javascript object that has functions that implement the
//     [bindings.UpdateBackupFunc] interface. This will replace any callback
//     that has been passed into [InitializeBackup].
//
// Returns:
//   - Javascript representation of the [Backup] object.
//   - Throws an error if initializing the [Backup] fails.
func ResumeBackup(_ js.Value, args []js.Value) any {
	cb := &updateBackupFunc{utils.WrapCB(args[2], "UpdateBackup")}
	api, err := bindings.ResumeBackup(args[0].Int(), args[1].Int(), cb)
	if err != nil {
		exception.ThrowTrace(err)
		return nil
	}

	return newBackupJS(api)
}

// StopBackup stops the backup processes and deletes the user's password from
// storage. To enable backups again, call [InitializeBackup].
//
// Returns:
//   - Throws an error if stopping the backup fails.
func (b *Backup) StopBackup(js.Value, []js.Value) any {
	err := b.api.StopBackup()
	if err != nil {
		exception.ThrowTrace(err)
		return nil
	}

	return nil
}

// IsBackupRunning returns true if the backup has been initialized and is
// running. Returns false if it has been stopped.
//
// Returns:
//   - If the backup is running (boolean).
func (b *Backup) IsBackupRunning(js.Value, []js.Value) any {
	return b.api.IsBackupRunning()
}

// AddJson stores the argument within the [Backup] structure.
//
// Parameters:
//   - args[0] - JSON to store (string).
func (b *Backup) AddJson(_ js.Value, args []js.Value) any {
	b.api.AddJson(args[0].String())
	return nil
}