// The scale an ERC-20 amount in a dApp's calldata is displayed with, and what
// to display when there is no such scale.
//
// The approval screen decodes `transfer` and `approve` calldata into a
// quantity the user confirms against. That quantity is a base-unit integer,
// and turning it into a number a person can read needs the token's decimals.
// Assuming a scale is how a drain gets confirmed: a `transfer` of 5000000000
// units of a 6-decimal token is 5,000 tokens, but formatted with the ERC-20
// default of 18 it reads `0.0000`, and a user who reads zero signs.
//
// So a scale is either found or the amount is not formatted. Decimals are
// looked for in the bundled token list, then in the tokens the user tracks,
// then in what the block explorer reported for the contract; where none of
// them answers, unknownDecimalsAmount() renders the base-unit integer with the
// unknown scale stated, and no formatUnits() call is reached at all.
//
// The Uniswap decoder's Amount and Min. received lines land on this same
// screen and use these same two functions, so there is one way of resolving a
// scale and one way of saying there is none.
//
// This is the display counterpart to transferAmount.js, which takes the same
// stance on the wallet's own send path: an amount whose scale is unknown or
// disputed is refused rather than guessed at.

// Solidity's decimals() is a uint8, and every source here is ultimately
// reporting that call's result. toDecimals() is that check, shared with the
// send path rather than copied: the bundled list stores numbers, the
// explorer's copy arrives as a string, and a token the user added by hand
// carries whatever lookupTokenInfo() got back, so the accepted types are
// enumerated rather than coerced.
const { toDecimals } = require("./transferAmount");
const { TOKEN_BY_ADDRESS } = require("./tokenList");

// Every decimals the explorer reported for this contract, across all the
// addresses whose balances have been fetched. They describe one contract, so
// they should agree; a set that does not agree is a scale in dispute, and this
// screen has no way to tell which member is the true one.
function explorerDecimals(lower, wallets) {
    let found = null;
    for (const wallet of wallets || []) {
        for (const addr of wallet.addresses || []) {
            for (const tb of addr.tokenBalances || []) {
                if ((tb.address || "").toLowerCase() !== lower) continue;
                const d = toDecimals(tb.decimals);
                if (d === null) continue;
                if (found !== null && found !== d) return null;
                found = d;
            }
        }
    }
    return found;
}

// The decimals to render a token amount with, or null when nothing knows.
// `sources` is { trackedTokens, wallets }, both shaped as they are on `state`.
function resolveTokenDecimals(tokenAddress, sources) {
    const lower = (tokenAddress || "").toLowerCase();
    if (!lower) return null;

    const bundled = TOKEN_BY_ADDRESS.get(lower);
    if (bundled) {
        const d = toDecimals(bundled.decimals);
        if (d !== null) return d;
    }

    const tracked = ((sources && sources.trackedTokens) || []).find(
        (t) => (t.address || "").toLowerCase() === lower,
    );
    if (tracked) {
        const d = toDecimals(tracked.decimals);
        if (d !== null) return d;
    }

    return explorerDecimals(lower, sources && sources.wallets);
}

// What the amount line reads when the scale is unknown. The base units are
// exact and the caveat is part of the same string, so the number on the screen
// cannot be mistaken for a token quantity, and it can never read as zero for a
// transfer that is not zero.
function unknownDecimalsAmount(rawAmount) {
    return String(rawAmount) + " base units (decimals unknown)";
}

module.exports = {
    resolveTokenDecimals,
    unknownDecimalsAmount,
};
