#include "installer.hpp"
#include "logs.h"
#include <string>
#include <iostream>
#include "WalletUtils.hpp"
#include "HexCoding.h"
#ifdef ANDROID
#include "TrustWalletCore/TWCoinType.h"
#include "TrustWalletCore/TWAnySigner.h"
#include "TrustWalletCore/TWHDWallet.h"
#include "TrustWalletCore/TWPrivateKey.h"
#include "TrustWalletCore/TWString.h"
#include "TrustWalletCore/TWData.h"
#else
#include <TrustWalletCore/TWCoinType.h>
#include <TrustWalletCore/TWAnySigner.h>
#include <TrustWalletCore/TWHDWallet.h>
#include <TrustWalletCore/TWPrivateKey.h>
#include <TrustWalletCore/TWString.h>
#include <TrustWalletCore/TWData.h>
#endif

using namespace std;
using namespace facebook;

namespace walletcore
{
    TWHDWallet *_wallet;

    function<StringOptional(bool, const char *, const char *)> _getSeedFn;
    function<void(const char *, bool, const char *, const char *)> _commitSeedFn;
    function<void(bool, const char *, const char *)> _clearSeedFn;
    function<BiometricsState()> _getBiometricsState;

    void install(
        jsi::Runtime &rt,
        function<StringOptional(bool, const char *, const char *)> getSeedFn,
        function<void(bool, const char *, const char *)> clearSeedFn,
        function<void(const char *, bool, const char *, const char *)> commitSeedFn,
        function<BiometricsState()> getBiometricsStateNative)
    {
        _clearSeedFn = move(clearSeedFn);
        _getSeedFn = move(getSeedFn);
        _commitSeedFn = move(commitSeedFn);
        _getBiometricsState = move(getBiometricsStateNative);

        // Should not be used in production
        auto createWallet = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "createWallet"),
            2,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (count < 1)
                {
                    throwJSError(rt, "createWallet", "At least the strength param is needed");
                }

                if (!args[0].isNumber())
                {
                    throwJSError(rt, "createWallet", "Argument 0 (strength) must be 128 or 256 integer");
                }

                auto strengthDouble = args[0].asNumber();

                if (strengthDouble != 128 && strengthDouble != 256)
                {
                    throwJSError(rt, "createWallet", "Invalid strength parameter passed, must be 128 or 256 integer");
                }

                string passphrase;
                if (count == 2)
                {
                    if (!args[1].isString())
                    {
                        throwJSError(rt, "createWallet", "Second argument (passphrase) must be a string");
                    }
                    passphrase = args[1].asString(rt).utf8(rt);
                    if (passphrase.length() > 256)
                    {
                        throwJSError(rt, "importWalletFromMnemonic", "Passphrase is too long, max 256 characters");
                    }
                }

                auto twStringPassphrase = TWStringCreateWithUTF8Bytes(passphrase.c_str());
                TWHDWallet *wallet = TWHDWalletCreate(int(strengthDouble), twStringPassphrase);
                TWStringDelete(twStringPassphrase);

                if (wallet == nullptr)
                {
                    throwJSError(rt, "createWallet", "Could not create wallet");
                }

                string mnemonic = TWStringUTF8Bytes(TWHDWalletMnemonic(wallet));
                auto seed = TWHDWalletSeed(wallet);
                auto hex = TWStringCreateWithHexData(seed);
                string seedString = TWStringUTF8Bytes(hex);

                auto entropy = TWHDWalletEntropy(wallet);
                auto entropyHex = TWStringCreateWithHexData(entropy);
                string entropyString = TWStringUTF8Bytes(entropyHex);

                auto res = jsi::Object(rt);
                res.setProperty(rt, "mnemonic", jsi::String::createFromUtf8(rt, mnemonic));
                res.setProperty(rt, "seed", jsi::String::createFromUtf8(rt, seedString));
                res.setProperty(rt, "entropy", jsi::String::createFromUtf8(rt, entropyString));

                TWHDWalletDelete(wallet);
                TWDataDelete(seed);
                TWStringDelete(hex);

                return res;
            });

        auto importWalletFromMnemonic = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "importWalletFromMnemonic"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (count < 1)
                {
                    throwJSError(rt, "importWalletFromMnemonic", "options param is required");
                }

                jsi::Object options = args[0].asObject(rt);

                if (!options.hasProperty(rt, "mnemonic"))
                {
                    throwJSError(rt, "importWalletFromMnemonic", "Mnemonic param is required");
                }

                string mnemonic = options.getProperty(rt, "mnemonic").asString(rt).utf8(rt);

                string passphrase;
                if (options.hasProperty(rt, "passphrase"))
                {
                    passphrase = options.getProperty(rt, "passphrase").asString(rt).utf8(rt);
                    if (passphrase.length() > 256)
                    {
                        throwJSError(rt, "importWalletFromMnemonic", "Passphrase is too long, max 256 characters");
                    }
                }

                bool useBiometrics = false;
                if (options.hasProperty(rt, "useBiometrics"))
                {
                    useBiometrics = options.getProperty(rt, "useBiometrics").getBool();
                }

                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";
                if (options.hasProperty(rt, "promptTitle"))
                {
                    title = options.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                }
                if (options.hasProperty(rt, "promptSubtitle"))
                {
                    subtitle = options.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                }

                auto twStringMnemonic = TWStringCreateWithUTF8Bytes(mnemonic.c_str());
                auto twStringPassphrase = TWStringCreateWithUTF8Bytes(passphrase.c_str());
                TWHDWallet *wallet = TWHDWalletCreateWithMnemonic(twStringMnemonic, twStringPassphrase);
                TWStringDelete(twStringMnemonic);
                TWStringDelete(twStringPassphrase);

                if (wallet == nullptr)
                {
                    throwJSError(rt, "importWalletFromMnemonic", "Could not generate a wallet");
                }

                auto seed = TWHDWalletSeed(wallet);
                auto hex = TWStringCreateWithHexData(seed);
                string seedString = TWStringUTF8Bytes(hex);

                auto entropy = TWHDWalletEntropy(wallet);
                auto entropyHex = TWStringCreateWithHexData(entropy);
                string entropyString = TWStringUTF8Bytes(entropyHex);

                auto resObj = jsi::Object(rt);
                resObj.setProperty(rt, "mnemonic", jsi::String::createFromUtf8(rt, mnemonic));
                resObj.setProperty(rt, "seed", jsi::String::createFromUtf8(rt, seedString));

                _commitSeedFn(entropyString.c_str(), useBiometrics, title.c_str(), subtitle.c_str());

                TWDataDelete(seed);
                TWStringDelete(hex);
                TWStringDelete(entropyHex);

                _wallet = wallet;

                return resObj;
            });

        auto importWalletFromEntropy = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "importWalletFromEntropy"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (count < 1)
                {
                    throwJSError(rt, "importWalletFromEntropy", "Options param is required");
                }

                jsi::Object options = args[0].asObject(rt);

                if (!options.hasProperty(rt, "entropy"))
                {
                    throwJSError(rt, "importWalletFromEntropy", "Entropy param is required");
                }

                string entropy = options.getProperty(rt, "entropy").asString(rt).utf8(rt);

                // TODO: left this validation out just because a proper hex validation function
                // will require boost (same algo used by trustwallet), and I didn't want to spend
                // another couple of days figuring out how to link boost via gradle. Do it at some point.
                // if (!TW::is_valid_hex(entropy))
                // {
                //   throwJSError(rt, "importWalletFromEntropy", "Entropy is not a valid hex string");
                // }

                bool useBiometrics = false;
                if (options.hasProperty(rt, "useBiometrics"))
                {
                    useBiometrics = options.getProperty(rt, "useBiometrics").getBool();
                }

                string passphrase;
                if (options.hasProperty(rt, "passphrase"))
                {
                    passphrase = options.getProperty(rt, "passphrase").asString(rt).utf8(rt);
                    if (passphrase.length() > 256)
                    {
                        throwJSError(rt, "importWalletFromMnemonic", "Passphrase is too long, max 256 characters");
                    }
                }

                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";
                if (options.hasProperty(rt, "promptTitle"))
                {
                    title = options.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                }
                if (options.hasProperty(rt, "promptSubtitle"))
                {
                    subtitle = options.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                }

                auto twStringEntropy = TWStringCreateWithUTF8Bytes(entropy.c_str());
                TWData *data = TWDataCreateWithHexString(twStringEntropy);
                TWStringDelete(twStringEntropy);

                auto twStringPassphrase = TWStringCreateWithUTF8Bytes(passphrase.c_str());
                TWHDWallet *wallet = TWHDWalletCreateWithEntropy(data, twStringPassphrase);
                TWStringDelete(twStringPassphrase);

                if (wallet == nullptr)
                {
                    throwJSError(rt, "importWalletFromEntropy", "Could not generate wallet, invalid hex string or passphrase");
                }

                string mnemonic = TWStringUTF8Bytes(TWHDWalletMnemonic(wallet));
                auto seed = TWHDWalletSeed(wallet);
                auto hex = TWStringCreateWithHexData(seed);
                string seedString = TWStringUTF8Bytes(hex);

                auto objectRes = jsi::Object(rt);
                objectRes.setProperty(rt, "mnemonic", jsi::String::createFromUtf8(rt, mnemonic));
                objectRes.setProperty(rt, "seed", jsi::String::createFromUtf8(rt, seedString));

                _commitSeedFn(entropy.c_str(), useBiometrics, title.c_str(), subtitle.c_str());

                TWDataDelete(seed);
                TWStringDelete(hex);

                _wallet = wallet;

                return objectRes;
            });

        auto getAddressForCoin = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "getAddressForCoin"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    throwJSError(rt, "getAddressForCoin", "No wallet has been imported");
                }

                if (count < 1)
                {
                    throwJSError(rt, "getAddressForCoin", "Wrong number of params");
                }

                if (!args[0].isString())
                {
                    throwJSError(rt, "getAddressForCoin", "Argument 0 must be a string");
                }

                string coinName = args[0].asString(rt).utf8(rt);

                TWCoinType type = getTypeForCoinString(coinName);

                auto address = TWHDWalletGetAddressForCoin(_wallet, type);

                return walletUtf8BytesToString(rt, address);
            });

        auto getAccount = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "getAccount"),
            2,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    throwJSError(rt, "deriveAddressForCoin", "No wallet has been imported");
                }

                if (count < 2)
                {
                    throwJSError(rt, "deriveAddressForCoin", "coin type and derivation path are needed params");
                }

                if (!args[0].isString())
                {
                    throwJSError(rt, "deriveAddressForCoin", "Argument 0 must be a string");
                }

                if (!args[1].isString())
                {
                    throwJSError(rt, "deriveAddressForCoin", "Argument 1 must be a string");
                }

                string coinName = args[0].asString(rt).utf8(rt);
                string derivationPath = args[1].asString(rt).utf8(rt);

                TWCoinType type = getTypeForCoinString(coinName);
                auto twStringDerivationPath = TWStringCreateWithUTF8Bytes(derivationPath.c_str());
                auto privateKey = TWHDWalletGetKey(_wallet, type, twStringDerivationPath);
                TWStringDelete(twStringDerivationPath);

                // TODO: As more coins are added you will need to change this decrypt function to match the coins algo
                auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey, false);

                auto address = TWCoinTypeDeriveAddress(type, privateKey);
                auto hex = TWStringCreateWithHexData(publicKey);
                string publicKeyString = TWStringUTF8Bytes(hex);

                TWPrivateKeyDelete(privateKey);
                TWPublicKeyDelete(publicKey);
                TWStringDelete(hex);

                auto res = jsi::Object(rt);
                res.setProperty(rt, "address", jsi::String::createFromUtf8(rt, TWStringUTF8Bytes(address)));
                res.setProperty(rt, "publicKey", jsi::String::createFromUtf8(rt, publicKeyString));

                return res;
            });

        auto signTransactionForCoin = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "signTransactionForCoin"),
            2,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    throwJSError(rt, "signTransactionForCoin", "No wallet has been imported");
                }

                if (count < 2)
                {
                    throwJSError(rt, "signTransactionForCoin", "coin type and payload path are mandatory");
                }

                if (!args[0].isString())
                {
                    throwJSError(rt, "signTransactionForCoin", "Argument 0 (coinType) must be a string");
                }

                if (!args[1].isString())
                {
                    throwJSError(rt, "signTransactionForCoin", "Argument 1 (transaction) must be a string");
                }

                string coinName = args[0].asString(rt).utf8(rt);
                TWCoinType type = getTypeForCoinString(coinName);

                string transactionStr = args[1].asString(rt).utf8(rt);
                auto transactionJson = TWStringCreateWithUTF8Bytes((transactionStr.c_str()));

                switch (type)
                {
                case TWCoinTypeEthereum:
                {
                    TWPrivateKey *privateKey = TWHDWalletGetKeyForCoin(_wallet, TWCoinTypeEthereum);
                    auto privateKeyData = TWPrivateKeyData(privateKey);
                    auto result = TWAnySignerSignJSON(transactionJson, privateKeyData, TWCoinTypeEthereum);
                    auto signedTransaction = jsi::String::createFromUtf8(rt, TWStringUTF8Bytes(result));
                    TWPrivateKeyDelete(privateKey);
                    TWDataDelete(privateKeyData);
                    TWStringDelete(transactionJson);
                    TWStringDelete(result);
                    return signedTransaction;
                }

                    // TODO @alex don't forget to add cosmos

                default:
                    throwJSError(rt, "signTransactionForCoin", "Coin not supported");
                    break;
                }
                return {};
            });

        auto signData = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "signData"),
            3,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    throwJSError(rt, "signData", "No wallet has been imported");
                }

                if (count < 2)
                {
                    throwJSError(rt, "signData", "coin type and payload are mandatory");
                }

                if (!args[0].isString())
                {
                    throwJSError(rt, "signData", "Argument 0 (coinType) must be a string");
                }

                if (!args[1].isString())
                {
                    throwJSError(rt, "signData", "Argument 1 (payload) must be a string");
                }

                // shared variables with or without derivation path
                string coinName = args[0].asString(rt).utf8(rt);
                string payload = args[1].asString(rt).utf8(rt);
                TWCoinType type = getTypeForCoinString(coinName);
                auto twStringPayload = TWStringCreateWithUTF8Bytes(payload.c_str());
                auto digest = TWDataCreateWithHexString(twStringPayload);
                TWStringDelete(twStringPayload);

                // derivation path was passed
                if (count == 3)
                {
                    if (!args[2].isString())
                    {
                        throwJSError(rt, "signData", "Argument 2 (derivationPath) must be a string");
                    }

                    string derivationPath = args[2].asString(rt).utf8(rt);
                    auto twStringDerivationPath = TWStringCreateWithUTF8Bytes(derivationPath.c_str());
                    auto privateKey = TWHDWalletGetKey(_wallet, type, twStringDerivationPath);
                    TWStringDelete(twStringDerivationPath);
                    TWData *signedHash = TWPrivateKeySign(privateKey, digest, TWCurveSECP256k1);
                    TWPrivateKeyDelete(privateKey);
                    auto hashStr = TWStringCreateWithHexData(signedHash);
                    auto signature = jsi::String::createFromUtf8(rt, TWStringUTF8Bytes(hashStr));
                    TWStringDelete(hashStr);

                    return signature;
                }
                else
                {
                    TWPrivateKey *privateKey = TWHDWalletGetKeyForCoin(_wallet, type);
                    TWData *signedHash = TWPrivateKeySign(privateKey, digest, TWCurveSECP256k1);
                    TWPrivateKeyDelete(privateKey);
                    auto hashStr = TWStringCreateWithHexData(signedHash);
                    auto signature = jsi::String::createFromUtf8(rt, TWStringUTF8Bytes(hashStr));
                    TWStringDelete(hashStr);

                    return signature;
                }
            });

        auto cleanUp = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "cleanUp"),
            0,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    return {};
                }

                _wallet = nullptr;

                return {};
            });

        auto loadWalletFromStorage = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "loadWalletFromStorage"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";
                string passphrase;
                bool useBiometrics = false;
                if (count == 1)
                {
                    jsi::Object params = args[0].asObject(rt);
                    if (params.hasProperty(rt, "promptTitle"))
                    {
                        title = params.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                    }

                    if (params.hasProperty(rt, "promptSubtitle"))
                    {
                        subtitle = params.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                    }

                    if (params.hasProperty(rt, "passphrase"))
                    {
                        passphrase = params.getProperty(rt, "passphrase").asString(rt).utf8(rt);
                    }

                    if (params.hasProperty(rt, "useBiometrics"))
                    {
                        useBiometrics = params.getProperty(rt, "useBiometrics").getBool();
                    }
                }

                StringOptional entropySeed = _getSeedFn(useBiometrics, title.c_str(), subtitle.c_str());
                if (entropySeed.hasValue)
                {
                    string entropyString = entropySeed.value;
                    auto twStringEntropy = TWStringCreateWithUTF8Bytes(entropyString.c_str());
                    TWData *data = TWDataCreateWithHexString(twStringEntropy);
                    auto twStringPassphrase = TWStringCreateWithUTF8Bytes(passphrase.c_str());
                    TWHDWallet *wallet = TWHDWalletCreateWithEntropy(data, twStringPassphrase);

                    if (wallet == nullptr)
                    {
                        throwJSError(rt, "loadWalletFromStorage", "Could not generate wallet, invalid hex string or passphrase");
                    }

                    TWStringDelete(twStringPassphrase);
                    TWStringDelete(twStringEntropy);
                    string mnemonic = TWStringUTF8Bytes(TWHDWalletMnemonic(wallet));
                    auto seed = TWHDWalletSeed(wallet);
                    auto hex = TWStringCreateWithHexData(seed);
                    string seedString = TWStringUTF8Bytes(hex);

                    auto objectRes = jsi::Object(rt);
                    objectRes.setProperty(rt, "mnemonic", jsi::String::createFromUtf8(rt, mnemonic));
                    objectRes.setProperty(rt, "seed", jsi::String::createFromUtf8(rt, seedString));

                    TWDataDelete(seed);
                    TWStringDelete(hex);

                    _wallet = wallet;

                    return true;
                }
                else
                {
                    return false;
                }
            });

        auto getCurrentWalletMnemonic = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "getCurrentWalletMnemonic"),
            0,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                if (_wallet == nullptr)
                {
                    throwJSError(rt, "getCurrentWalletMnemonic", "No wallet has been imported");
                }

                string mnemonic = TWStringUTF8Bytes(TWHDWalletMnemonic(_wallet));

                return jsi::String::createFromUtf8(rt, mnemonic);
            });

        auto deleteWallet = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "deleteWallet"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";
                bool useBiometrics = false;

                if (count == 1)
                {
                    if (!args[0].isObject())
                    {
                        throwJSError(rt, "deleteWallet", "Options param must be an object");
                    }

                    jsi::Object options = args[0].asObject(rt);

                    if (options.hasProperty(rt, "useBiometrics"))
                    {
                        useBiometrics = options.getProperty(rt, "useBiometrics").getBool();
                    }

                    if (options.hasProperty(rt, "promptTitle"))
                    {
                        title = options.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                    }
                    if (options.hasProperty(rt, "promptSubtitle"))
                    {
                        subtitle = options.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                    }
                }

                _wallet = nullptr;
                _clearSeedFn(useBiometrics, title.c_str(), subtitle.c_str());

                return {};
            });

        auto getBiometricsState = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "getBiometricsState"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                BiometricsState state = _getBiometricsState();
                if (state == biometricsStateAvailable)
                {
                    return jsi::String::createFromUtf8(rt, "available");
                }

                if (state == biometricsStateLocked)
                {
                    return jsi::String::createFromUtf8(rt, "available_locked");
                }

                return jsi::String::createFromUtf8(rt, "unavailable");
            });

        auto transferEntropyToBiometricsStorage = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "transferEntropyToBiometricsStorage"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";

                if (count == 1)
                {
                    if (args[0].isObject())
                    {
                        throwJSError(rt, "transferEntropyToNormalStorage", "First parameter is not an object");
                    }

                    jsi::Object options = args[0].asObject(rt);

                    if (options.hasProperty(rt, "promptTitle"))
                    {
                        title = options.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                    }

                    if (options.hasProperty(rt, "promptSubtitle"))
                    {
                        subtitle = options.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                    }
                }

                StringOptional entropySeed = _getSeedFn(false, title.c_str(), subtitle.c_str());

                if (!entropySeed.hasValue)
                {
                    throwJSError(rt, "transferEntropyToNormalStorage", "No entropy seed found!");
                }

                _commitSeedFn(entropySeed.value.c_str(), true, title.c_str(), subtitle.c_str());

                _clearSeedFn(false, title.c_str(), subtitle.c_str());

                return {};
            });

        auto transferEntropyToNormalStorage = jsi::Function::createFromHostFunction(
            rt,
            jsi::PropNameID::forAscii(rt, "transferEntropyToNormalStorage"),
            1,
            [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
            {
                string title = "Unlock wallet";
                string subtitle = "Please authenticate to unlock your wallet.";

                if (count == 1)
                {
                    if (args[0].isObject())
                    {
                        throwJSError(rt, "transferEntropyToNormalStorage", "First parameter is not an object");
                    }

                    jsi::Object options = args[0].asObject(rt);

                    if (options.hasProperty(rt, "promptTitle"))
                    {
                        title = options.getProperty(rt, "promptTitle").asString(rt).utf8(rt);
                    }

                    if (options.hasProperty(rt, "promptSubtitle"))
                    {
                        subtitle = options.getProperty(rt, "promptSubtitle").asString(rt).utf8(rt);
                    }
                }

                StringOptional entropySeed = _getSeedFn(true, title.c_str(), subtitle.c_str());

                if (!entropySeed.hasValue)
                {
                    throwJSError(rt, "transferEntropyToNormalStorage", "No entropy seed found!");
                }

                _commitSeedFn(entropySeed.value.c_str(), false, title.c_str(), subtitle.c_str());

                _clearSeedFn(true, title.c_str(), subtitle.c_str());

                return {};
            });

        // Register global object
        jsi::Object walletCore = jsi::Object(rt);
        walletCore.setProperty(rt, "createWallet", move(createWallet));
        walletCore.setProperty(rt, "importWalletFromMnemonic", move(importWalletFromMnemonic));
        walletCore.setProperty(rt, "importWalletFromEntropy", move(importWalletFromEntropy));
        walletCore.setProperty(rt, "getAddressForCoin", move(getAddressForCoin));
        walletCore.setProperty(rt, "getAccount", move(getAccount));
        walletCore.setProperty(rt, "signTransactionForCoin", move(signTransactionForCoin));
        walletCore.setProperty(rt, "signData", move(signData));
        walletCore.setProperty(rt, "cleanUp", move(cleanUp));
        walletCore.setProperty(rt, "loadWalletFromStorage", move(loadWalletFromStorage));
        walletCore.setProperty(rt, "getCurrentWalletMnemonic", move(getCurrentWalletMnemonic));
        walletCore.setProperty(rt, "deleteWallet", move(deleteWallet));
        walletCore.setProperty(rt, "getBiometricsState", move(getBiometricsState));
        walletCore.setProperty(rt, "transferEntropyToBiometricsStorage", move(transferEntropyToBiometricsStorage));
        walletCore.setProperty(rt, "transferEntropyToNormalStorage", move(transferEntropyToNormalStorage));
        rt.global().setProperty(rt, "WalletCore", move(walletCore));
    }
}
