// Transaction confirmation view with inline password.
// Shows transaction details, warnings, errors. On Sign & Send,
// reads inline password, decrypts secret, signs and broadcasts.

const {
    parseEther,
    parseUnits,
    formatEther,
    formatUnits,
    Contract,
} = require("ethers");
const {
    $,
    showError,
    hideError,
    showView,
    showFlash,
    flashCopyFeedback,
    addressTitle,
    escapeHtml,
    renderAddressHtml,
    attachCopyHandlers,
    goBack,
} = require("./helpers");
const { state, currentNetwork } = require("../../shared/state");
const { getSignerForAddress } = require("../../shared/wallet");
const { decryptWithPassword } = require("../../shared/vault");
const { formatUsd, getPrice } = require("../../shared/prices");
const { getProvider } = require("../../shared/balances");
const {
    getLocalWarnings,
    getFullWarnings,
} = require("../../shared/addressWarnings");
const { ERC20_ABI, isBurnAddress } = require("../../shared/constants");
const { log } = require("../../shared/log");
const makeBlockie = require("ethereum-blockies-base64");
const txStatus = require("./txStatus");

let pendingTx = null;

function restore() {
    const d = state.viewData;
    if (d && d.pendingTx) {
        show(d.pendingTx);
    }
}

function blockieHtml(address) {
    const src = makeBlockie(address);
    return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
}

function confirmAddressHtml(address, ensName, title) {
    const blockie = blockieHtml(address);
    return (
        `<div class="mb-1">${blockie}</div>` +
        renderAddressHtml(address, { title, ensName })
    );
}

function valueWithUsd(text, usdAmount) {
    if (usdAmount !== null && usdAmount !== undefined && !isNaN(usdAmount)) {
        return text + " (" + formatUsd(usdAmount) + ")";
    }
    return text;
}

function show(txInfo) {
    pendingTx = txInfo;

    const isErc20 = txInfo.token !== "ETH";
    const symbol = isErc20 ? txInfo.tokenSymbol || "?" : "ETH";

    // Transaction type
    if (isErc20) {
        $("confirm-type").textContent =
            "ERC-20 token transfer (" + symbol + ")";
    } else {
        $("confirm-type").textContent = "Native ETH transfer";
    }

    // Token contract section (ERC-20 only)
    const tokenSection = $("confirm-token-section");
    if (isErc20) {
        $("confirm-token-contract").innerHTML = renderAddressHtml(
            txInfo.token,
            {},
        );
        tokenSection.classList.remove("hidden");
        attachCopyHandlers(tokenSection);
    } else {
        tokenSection.classList.add("hidden");
    }

    // From (with blockie)
    const fromTitle = addressTitle(txInfo.from, state.wallets);
    $("confirm-from").innerHTML = confirmAddressHtml(
        txInfo.from,
        null,
        fromTitle,
    );

    // To (with blockie)
    const toTitle = addressTitle(txInfo.to, state.wallets);
    $("confirm-to").innerHTML = confirmAddressHtml(
        txInfo.to,
        txInfo.ensName,
        toTitle,
    );
    $("confirm-to-ens").classList.add("hidden");

    // Amount (with inline USD)
    const ethPrice = getPrice("ETH");
    const tokenPrice = getPrice(symbol);
    const amountNum = parseFloat(txInfo.amount);
    const price = isErc20 ? tokenPrice : ethPrice;
    const amountUsd = price ? amountNum * price : null;
    $("confirm-amount").textContent = valueWithUsd(
        txInfo.amount + " " + symbol,
        amountUsd,
    );

    // Balance (with inline USD)
    if (isErc20) {
        const bal = txInfo.tokenBalance || "0";
        const balUsd = tokenPrice ? parseFloat(bal) * tokenPrice : null;
        $("confirm-balance").textContent = valueWithUsd(
            bal + " " + symbol,
            balUsd,
        );
    } else {
        const bal = txInfo.balance || "0";
        const balUsd = ethPrice ? parseFloat(bal) * ethPrice : null;
        $("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd);
    }

    // Check for warnings (synchronous local checks)
    const localWarnings = getLocalWarnings(txInfo.to, {
        fromAddress: txInfo.from,
    });

    const warningsEl = $("confirm-warnings");
    if (localWarnings.length > 0) {
        warningsEl.innerHTML = localWarnings
            .map(
                (w) =>
                    `<div class="border border-border border-dashed p-2 mb-1 text-xs font-bold">WARNING: ${w.message}</div>`,
            )
            .join("");
        warningsEl.style.visibility = "visible";
    } else {
        warningsEl.innerHTML = "";
        warningsEl.style.visibility = "hidden";
    }

    // Check for errors
    const errors = [];
    if (isErc20) {
        const tokenBal = parseFloat(txInfo.tokenBalance || "0");
        if (parseFloat(txInfo.amount) > tokenBal) {
            errors.push(
                "Insufficient " +
                    symbol +
                    " balance. You have " +
                    txInfo.tokenBalance +
                    " " +
                    symbol +
                    " but are trying to send " +
                    txInfo.amount +
                    " " +
                    symbol +
                    ".",
            );
        }
    } else if (parseFloat(txInfo.amount) > parseFloat(txInfo.balance)) {
        errors.push(
            "Insufficient balance. You have " +
                txInfo.balance +
                " ETH but are trying to send " +
                txInfo.amount +
                " ETH.",
        );
    }

    const errorsEl = $("confirm-errors");
    const sendBtn = $("btn-confirm-send");
    if (errors.length > 0) {
        errorsEl.innerHTML = errors
            .map((e) => `<div class="text-xs">${e}</div>`)
            .join("");
        errorsEl.style.visibility = "visible";
        sendBtn.disabled = true;
        sendBtn.classList.add("text-muted");
    } else {
        errorsEl.innerHTML = "";
        errorsEl.style.visibility = "hidden";
        sendBtn.disabled = false;
        sendBtn.classList.remove("text-muted");
    }

    // Reset password field and error
    $("confirm-tx-password").value = "";
    hideError("confirm-tx-password-error");

    // Gas estimate — show placeholder then fetch async
    $("confirm-fee").style.visibility = "visible";
    $("confirm-fee-amount").textContent = "Estimating...";
    state.viewData = { pendingTx: txInfo };
    showView("confirm-tx");
    attachCopyHandlers("view-confirm-tx");

    // Reset async warnings to hidden (space always reserved, no layout shift)
    $("confirm-recipient-warning").style.visibility = "hidden";
    $("confirm-contract-warning").style.visibility = "hidden";
    $("confirm-burn-warning").style.visibility = "hidden";
    $("confirm-etherscan-warning").style.visibility = "hidden";

    // Show burn warning via reserved element (in addition to inline warning)
    if (isBurnAddress(txInfo.to)) {
        $("confirm-burn-warning").style.visibility = "visible";
    }

    estimateGas(txInfo);
    checkRecipientHistory(txInfo);
}

async function estimateGas(txInfo) {
    try {
        const provider = getProvider(state.rpcUrl);
        const feeData = await provider.getFeeData();
        const gasPrice = feeData.gasPrice;
        let gasLimit;

        if (txInfo.token === "ETH") {
            gasLimit = await provider.estimateGas({
                from: txInfo.from,
                to: txInfo.to,
                value: parseEther(txInfo.amount),
            });
        } else {
            const contract = new Contract(txInfo.token, ERC20_ABI, provider);
            const decimals = await contract.decimals();
            const amount = parseUnits(txInfo.amount, decimals);
            gasLimit = await contract.transfer.estimateGas(txInfo.to, amount, {
                from: txInfo.from,
            });
        }

        const gasCostWei = gasLimit * gasPrice;
        const gasCostEth = formatEther(gasCostWei);
        // Format to 6 significant decimal places
        const parts = gasCostEth.split(".");
        const dec =
            parts.length > 1
                ? parts[1].slice(0, 6).replace(/0+$/, "") || "0"
                : "0";
        const feeStr = parts[0] + "." + dec + " ETH";
        const ethPrice = getPrice("ETH");
        const feeUsd = ethPrice ? parseFloat(gasCostEth) * ethPrice : null;
        $("confirm-fee-amount").textContent = valueWithUsd(feeStr, feeUsd);
    } catch (e) {
        log.errorf("gas estimation failed:", e.message);
        $("confirm-fee-amount").textContent = "Unable to estimate";
    }
}

async function checkRecipientHistory(txInfo) {
    try {
        const provider = getProvider(state.rpcUrl);
        const asyncWarnings = await getFullWarnings(txInfo.to, provider, {
            fromAddress: txInfo.from,
        });
        for (const w of asyncWarnings) {
            if (w.type === "contract") {
                $("confirm-contract-warning").style.visibility = "visible";
            }
            if (w.type === "new-address") {
                $("confirm-recipient-warning").style.visibility = "visible";
            }
            if (w.type === "etherscan-phishing") {
                $("confirm-etherscan-warning").style.visibility = "visible";
            }
        }
    } catch (e) {
        log.errorf("recipient history check failed:", e.message);
    }
}

function init(ctx) {
    $("btn-confirm-send").addEventListener("click", async () => {
        const password = $("confirm-tx-password").value;
        if (!password) {
            showError(
                "confirm-tx-password-error",
                "Please enter your password.",
            );
            return;
        }

        const wallet = state.wallets[state.selectedWallet];
        let decryptedSecret;
        hideError("confirm-tx-password-error");

        try {
            decryptedSecret = await decryptWithPassword(
                wallet.encryptedSecret,
                password,
            );
        } catch (e) {
            showError("confirm-tx-password-error", "Wrong password.");
            return;
        }

        $("btn-confirm-send").disabled = true;
        $("btn-confirm-send").classList.add("text-muted");

        let tx;
        try {
            const signer = getSignerForAddress(
                wallet,
                state.selectedAddress,
                decryptedSecret,
            );
            const provider = getProvider(state.rpcUrl);
            const connectedSigner = signer.connect(provider);

            if (pendingTx.token === "ETH") {
                tx = await connectedSigner.sendTransaction({
                    to: pendingTx.to,
                    value: parseEther(pendingTx.amount),
                });
            } else {
                const contract = new Contract(
                    pendingTx.token,
                    ERC20_ABI,
                    connectedSigner,
                );
                const decimals = await contract.decimals();
                const amount = parseUnits(pendingTx.amount, decimals);
                tx = await contract.transfer(pendingTx.to, amount);
            }

            // Best-effort: clear decrypted secret after use.
            // Note: JS strings are immutable; this nulls the reference but
            // the original string may persist in memory until GC.
            decryptedSecret = null;
            txStatus.showWait(pendingTx, tx.hash);
        } catch (e) {
            decryptedSecret = null;
            const hash = tx ? tx.hash : null;
            txStatus.showError(pendingTx, hash, e.shortMessage || e.message);
        } finally {
            $("btn-confirm-send").disabled = false;
            $("btn-confirm-send").classList.remove("text-muted");
        }
    });

    $("btn-confirm-back").addEventListener("click", () => {
        goBack();
    });
}

module.exports = { init, show, restore };
