/* eslint-disable no-console */
import { customWrapper } from "@ledgerhq/wallet-api-server";
import { deserializeTransaction } from "@ledgerhq/wallet-api-core";
import { getParentAccount, getMainAccount, makeEmptyTokenAccount, isTokenAccount, } from "@ledgerhq/coin-framework/account/index";
import { getCryptoAssetsStore } from "@ledgerhq/cryptoassets/state";
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
import { getAccountIdFromWalletAccountId, getWalletAPITransactionSignFlowInfos, } from "../converters";
import { getAccountBridge } from "../../bridge";
import { UserRefusedOnDevice } from "@ledgerhq/errors";
import { getEnv } from "@ledgerhq/live-env";
import BigNumber from "bignumber.js";
// Helper function to validate Ethereum address format
function isValidEthereumAddress(address) {
    return /^0x[a-fA-F0-9]{40}$/.test(address);
}
// Helper function to validate all inputs before account creation
function validateInputs(params) {
    const { ethereumAddress, tokenContractAddress, tokenTicker, meta } = params;
    // Validate Ethereum address format
    if (!ethereumAddress) {
        throw new Error("Ethereum address is required");
    }
    if (!isValidEthereumAddress(ethereumAddress)) {
        throw new Error("Invalid Ethereum address format");
    }
    // Validate that at least one token identifier is provided
    if (!tokenContractAddress && !tokenTicker) {
        throw new Error("Either tokenContractAddress or tokenTicker must be provided");
    }
    return { ethereumAddress, tokenContractAddress, tokenTicker, meta };
}
// Helper function to find acreToken by address or token id
async function findAcreToken(tokenContractAddress, tokenTicker) {
    let foundToken;
    // Try to find token by contract address first (if provided)
    if (tokenContractAddress) {
        foundToken = await getCryptoAssetsStore().findTokenByAddressInCurrency(tokenContractAddress, "ethereum");
    }
    else if (tokenTicker) {
        foundToken = await getCryptoAssetsStore().findTokenById(tokenTicker.toLowerCase());
    }
    if (!foundToken) {
        throw new Error(`Token not found. Tried contract address: ${tokenContractAddress || "not provided"}, ticker: ${tokenTicker || "not provided"}`);
    }
    return { token: foundToken, contractAddress: foundToken.contractAddress };
}
// Helper function to generate unique account names with suffixes
// This is clearly a hack as we do not have account name on Account type, we leverage on how many accounts have acreBTC as token sub account to define the name
// This is made to help user to identify different ACRE account but not resilient to token account being wiped, emptied
// (empty subAccount would not been included in the list therefore parent account not considered as an acre account anymore).
function generateUniqueAccountName(existingAccounts, baseName, tokenAddress) {
    const existingAccountWithAcreToken = existingAccounts.flatMap(account => account.subAccounts?.filter(subAccount => subAccount.token.contractAddress.toLowerCase() === tokenAddress.toLowerCase()) || []);
    return existingAccountWithAcreToken.length > 0
        ? `${baseName} ${existingAccountWithAcreToken.length}`
        : baseName;
}
// Helper function to create parent Ethereum account
function createParentAccount(ethereumAddress, ethereumCurrency) {
    return {
        type: "Account",
        id: `js:2:ethereum:${ethereumAddress}:`,
        seedIdentifier: `04${ethereumAddress.slice(2)}`,
        derivationMode: "",
        index: 0,
        freshAddress: ethereumAddress,
        freshAddressPath: "44'/60'/0'/0/0",
        used: false,
        blockHeight: 0,
        creationDate: new Date(),
        balance: new BigNumber(0),
        spendableBalance: new BigNumber(0),
        operationsCount: 0,
        operations: [],
        pendingOperations: [],
        currency: ethereumCurrency,
        lastSyncDate: new Date(),
        swapHistory: [],
        balanceHistoryCache: {
            HOUR: { latestDate: Date.now(), balances: [] },
            DAY: { latestDate: Date.now(), balances: [] },
            WEEK: { latestDate: Date.now(), balances: [] },
        },
        syncHash: "0x00000000",
        subAccounts: [],
        nfts: [],
    };
}
export const handlers = ({ accounts, tracking, manifest, uiHooks: { "custom.acre.messageSign": uiMessageSign, "custom.acre.transactionSign": uiTransactionSign, "custom.acre.transactionBroadcast": uiTransactionBroadcast, "custom.acre.registerAccount": uiRegisterAccount, }, }) => {
    async function signTransaction({ accountId: walletAccountId, rawTransaction, options, tokenCurrency, }) {
        const transaction = deserializeTransaction(rawTransaction);
        tracking.signTransactionRequested(manifest);
        if (!transaction) {
            tracking.signTransactionFail(manifest);
            throw new Error("Transaction required");
        }
        const accountId = getAccountIdFromWalletAccountId(walletAccountId);
        if (!accountId) {
            tracking.signTransactionFail(manifest);
            throw new Error(`accountId ${walletAccountId} unknown`);
        }
        const account = accounts.find(account => account.id === accountId);
        if (!account) {
            tracking.signTransactionFail(manifest);
            throw new Error("Account required");
        }
        const parentAccount = getParentAccount(account, accounts);
        const accountFamily = isTokenAccount(account)
            ? account.token.parentCurrency.family
            : account.currency.family;
        const mainAccount = getMainAccount(account, parentAccount);
        const currency = tokenCurrency
            ? await getCryptoAssetsStore().findTokenById(tokenCurrency)
            : null;
        const signerAccount = currency ? makeEmptyTokenAccount(mainAccount, currency) : account;
        const { canEditFees, liveTx, hasFeesProvided } = getWalletAPITransactionSignFlowInfos({
            walletApiTransaction: transaction,
            account,
        });
        if (accountFamily !== liveTx.family) {
            throw new Error(`Account and transaction must be from the same family. Account family: ${accountFamily}, Transaction family: ${liveTx.family}`);
        }
        const signFlowInfos = {
            canEditFees,
            liveTx,
            hasFeesProvided,
        };
        return new Promise((resolve, reject) => {
            let done = false;
            return uiTransactionSign({
                account: signerAccount,
                parentAccount,
                signFlowInfos,
                options,
                onSuccess: signedOperation => {
                    if (done)
                        return;
                    done = true;
                    tracking.signTransactionSuccess(manifest);
                    resolve(signedOperation);
                },
                onError: error => {
                    if (done)
                        return;
                    done = true;
                    tracking.signTransactionFail(manifest);
                    reject(error);
                },
            });
        });
    }
    return {
        "custom.acre.messageSign": customWrapper(async (params) => {
            if (!params) {
                tracking.signMessageNoParams(manifest);
                // Maybe return an error instead
                return { hexSignedMessage: "" };
            }
            tracking.signMessageRequested(manifest);
            const { accountId: walletAccountId, derivationPath, message, options } = params;
            const accountId = getAccountIdFromWalletAccountId(walletAccountId);
            if (!accountId) {
                tracking.signMessageFail(manifest);
                throw new Error(`accountId ${walletAccountId} unknown`);
            }
            const account = accounts.find(account => account.id === accountId);
            if (account === undefined) {
                tracking.signMessageFail(manifest);
                throw new Error("account not found");
            }
            const path = fromRelativePath(getMainAccount(account).freshAddressPath, derivationPath);
            const formattedMessage = { ...message, path };
            return new Promise((resolve, reject) => {
                let done = false;
                return uiMessageSign({
                    account,
                    message: formattedMessage,
                    options,
                    onSuccess: signature => {
                        if (done)
                            return;
                        done = true;
                        tracking.signMessageSuccess(manifest);
                        resolve({
                            hexSignedMessage: signature.replace("0x", ""),
                        });
                    },
                    onCancel: () => {
                        if (done)
                            return;
                        done = true;
                        tracking.signMessageFail(manifest);
                        reject(new UserRefusedOnDevice());
                    },
                    onError: error => {
                        if (done)
                            return;
                        done = true;
                        tracking.signMessageFail(manifest);
                        reject(error instanceof Error ? error : new Error(String(error)));
                    },
                });
            });
        }),
        "custom.acre.transactionSign": customWrapper(async (params) => {
            if (!params) {
                tracking.signTransactionNoParams(manifest);
                // Maybe return an error instead
                return { signedTransactionHex: "" };
            }
            const signedOperation = await signTransaction(params);
            return {
                signedTransactionHex: Buffer.from(signedOperation.signature).toString("hex"),
            };
        }),
        "custom.acre.transactionSignAndBroadcast": customWrapper(async (params) => {
            if (!params) {
                tracking.signTransactionAndBroadcastNoParams(manifest);
                // Maybe return an error instead
                return { transactionHash: "" };
            }
            const signedOperation = await signTransaction(params);
            if (!signedOperation) {
                tracking.broadcastFail(manifest);
                return Promise.reject(new Error("Transaction required"));
            }
            const { accountId: walletAccountId, tokenCurrency } = params;
            const accountId = getAccountIdFromWalletAccountId(walletAccountId);
            if (!accountId) {
                tracking.broadcastFail(manifest);
                return Promise.reject(new Error(`accountId ${walletAccountId} unknown`));
            }
            const account = accounts.find(account => account.id === accountId);
            if (!account) {
                tracking.broadcastFail(manifest);
                return Promise.reject(new Error("Account required"));
            }
            const currency = tokenCurrency
                ? await getCryptoAssetsStore().findTokenById(tokenCurrency)
                : null;
            const parentAccount = getParentAccount(account, accounts);
            const mainAccount = getMainAccount(account, parentAccount);
            const signerAccount = currency ? makeEmptyTokenAccount(mainAccount, currency) : account;
            const bridge = getAccountBridge(signerAccount, parentAccount);
            const broadcastAccount = getMainAccount(signerAccount, parentAccount);
            let optimisticOperation = signedOperation.operation;
            if (!getEnv("DISABLE_TRANSACTION_BROADCAST")) {
                try {
                    optimisticOperation = await bridge.broadcast({
                        account: broadcastAccount,
                        signedOperation,
                    });
                    tracking.broadcastSuccess(manifest);
                }
                catch (error) {
                    tracking.broadcastFail(manifest);
                    throw error;
                }
            }
            uiTransactionBroadcast &&
                uiTransactionBroadcast(account, parentAccount, mainAccount, optimisticOperation);
            return {
                transactionHash: optimisticOperation.hash,
            };
        }),
        "custom.acre.registerYieldBearingEthereumAddress": customWrapper(async (params) => {
            if (!params) {
                return Promise.reject(new Error("Parameters required"));
            }
            const validatedInputs = validateInputs(params);
            const ethereumCurrency = getCryptoCurrencyById("ethereum");
            const { token: existingToken, contractAddress: finalTokenContractAddress } = await findAcreToken(validatedInputs.tokenContractAddress, validatedInputs.tokenTicker);
            const existingBearingAccount = accounts.find(account => "freshAddress" in account &&
                account.freshAddress === validatedInputs.ethereumAddress);
            const baseName = "Yield-bearing BTC on ACRE";
            // Account already added, skip registration.
            if (existingBearingAccount) {
                return {
                    success: true,
                    accountName: baseName,
                    parentAccountId: existingBearingAccount.id,
                    tokenAccountId: existingBearingAccount.id,
                    ethereumAddress: validatedInputs.ethereumAddress,
                    tokenContractAddress: finalTokenContractAddress,
                    meta: validatedInputs.meta,
                };
            }
            if (uiRegisterAccount) {
                // Create account & manage the case where an ACRE account has been already registered on another Ethereum address
                // Filter to have only root accounts
                const existingParentAccounts = accounts.filter((acc) => acc.type === "Account");
                const accountName = generateUniqueAccountName(existingParentAccounts, baseName, finalTokenContractAddress);
                const parentAccount = createParentAccount(validatedInputs.ethereumAddress, ethereumCurrency);
                const tokenAccount = makeEmptyTokenAccount(parentAccount, existingToken);
                // Add token account as sub-account of parent account
                const parentAccountWithSubAccount = {
                    ...parentAccount,
                    subAccounts: [tokenAccount],
                };
                return new Promise((resolve, reject) => {
                    uiRegisterAccount({
                        parentAccount: parentAccountWithSubAccount,
                        accountName,
                        existingAccounts: existingParentAccounts,
                        onSuccess: () => {
                            resolve({
                                success: true,
                                accountName,
                                parentAccountId: parentAccountWithSubAccount.id,
                                tokenAccountId: tokenAccount.id,
                                ethereumAddress: validatedInputs.ethereumAddress,
                                tokenContractAddress: finalTokenContractAddress,
                                meta: validatedInputs.meta,
                            });
                        },
                        onError: error => {
                            reject(error);
                        },
                    });
                });
            }
            else {
                throw new Error("No account registration UI hook available");
            }
        }),
    };
};
function fromRelativePath(basePath, derivationPath) {
    if (!derivationPath) {
        return basePath;
    }
    const splitPath = basePath.split("'/");
    splitPath[splitPath.length - 1] = derivationPath;
    return splitPath.join("'/");
}
//# sourceMappingURL=server.js.map