const {
    $,
    showView,
    showFlash,
    balanceLinesForAddress,
    isoDate,
    timeAgo,
    addressDotHtml,
    addressTitle,
    escapeHtml,
    displaySymbol,
    truncateMiddle,
    renderAddressHtml,
    attachCopyHandlers,
    pushCurrentView,
} = require("./helpers");
const { state, saveState, currentAddress } = require("../../shared/state");
const { notify } = require("../../shared/browserApi");
const {
    updateSendBalance,
    renderSendTokenSelect,
    resetSendValidation,
} = require("./send");
const { deriveAddressFromXpub } = require("../../shared/wallet");
const { canRemoveAddress } = require("../../shared/walletDelete");
const {
    walletDefect,
    walletDefectHtml,
} = require("../../shared/walletDefects");
const {
    formatUsd,
    formatAddressTotal,
    getPrice,
    getAddressValue,
} = require("../../shared/prices");
const {
    fetchRecentTransactions,
    filterTransactions,
} = require("../../shared/transactions");
const { log } = require("../../shared/log");

function findActiveAddr() {
    for (const w of state.wallets) {
        for (const a of w.addresses) {
            if (a.address === state.activeAddress) return a;
        }
    }
    return null;
}

function renderTotalValue() {
    const el = $("total-value");
    const subEl = $("total-value-sub");
    const priceEl = $("eth-price-display");
    if (!el) return;

    const ethPrice = getPrice("ETH");
    if (priceEl) {
        priceEl.innerHTML = ethPrice
            ? formatUsd(ethPrice) + " USD/ETH"
            : "&nbsp;";
    }

    const addr = findActiveAddr();
    if (!addr) {
        el.innerHTML = "&nbsp;";
        if (subEl) subEl.innerHTML = "&nbsp;";
        return;
    }
    const ethBal = parseFloat(addr.balance || "0");
    const ethStr = ethBal.toFixed(4) + " ETH";
    const ethUsd = ethPrice ? " (" + formatUsd(ethBal * ethPrice) + ")" : "";
    el.textContent = ethStr + ethUsd;

    if (subEl) {
        subEl.innerHTML = formatAddressTotal(getAddressValue(addr)) || "&nbsp;";
    }
}

function renderActiveAddress() {
    const el = $("active-address-display");
    if (!el) return;
    if (state.activeAddress) {
        el.innerHTML = renderAddressHtml(state.activeAddress);
        attachCopyHandlers(el);
    } else {
        el.textContent = "";
    }
}

let homeTxs = [];

function renderHomeTxList(ctx) {
    const list = $("home-tx-list");
    if (!list) return;
    if (homeTxs.length === 0) {
        list.innerHTML =
            '<div class="text-muted text-xs py-1">No transactions found.</div>';
        return;
    }
    let html = "";
    let i = 0;
    for (const tx of homeTxs) {
        // For swap transactions, show the user's own labelled wallet
        // address (the one that initiated the swap) instead of the
        // contract address which is not useful in the list view.
        const counterparty =
            tx.direction === "contract" && tx.directionLabel === "Swap"
                ? tx.from
                : tx.direction === "sent" || tx.direction === "contract"
                  ? tx.to
                  : tx.from;
        // directionLabel is the explorer's own method name for a contract
        // call, title-cased — attacker-chosen for an attacker's contract.
        const dirLabel = escapeHtml(tx.directionLabel);
        const sym = displaySymbol(tx.symbol);
        const amountStr = tx.value
            ? escapeHtml(tx.value + " " + sym)
            : escapeHtml(sym);
        const title = addressTitle(counterparty, state.wallets);
        const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
        const displayAddr = title || truncateMiddle(counterparty, maxAddr);
        const addrStr = escapeHtml(displayAddr);
        const dot = addressDotHtml(counterparty);
        const err = tx.isError ? " (failed)" : "";
        const opacity = tx.isError ? " opacity:0.5;" : "";
        const ago = escapeHtml(timeAgo(tx.timestamp));
        const iso = escapeHtml(isoDate(tx.timestamp));
        html += `<div class="home-tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
        html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
        html += `<div class="flex justify-between"><span class="flex items-center">${dot}${addrStr}</span><span>${amountStr}</span></div>`;
        html += `</div>`;
        i++;
    }
    list.innerHTML = html;
    list.querySelectorAll(".home-tx-row").forEach((row) => {
        row.addEventListener("click", () => {
            const idx = parseInt(row.dataset.tx, 10);
            const tx = homeTxs[idx];
            // Set selectedWallet/selectedAddress so back navigation works
            for (let wi = 0; wi < state.wallets.length; wi++) {
                for (
                    let ai = 0;
                    ai < state.wallets[wi].addresses.length;
                    ai++
                ) {
                    const addr = state.wallets[wi].addresses[ai].address;
                    if (
                        addr.toLowerCase() === tx.from.toLowerCase() ||
                        addr.toLowerCase() === tx.to.toLowerCase()
                    ) {
                        state.selectedWallet = wi;
                        state.selectedAddress = ai;
                        state.selectedToken = null;
                        ctx.showTransactionDetail(tx);
                        return;
                    }
                }
            }
        });
    });
}

async function loadHomeTxs(ctx) {
    const allAddresses = [];
    for (const w of state.wallets) {
        for (const a of w.addresses) {
            allAddresses.push(a.address);
        }
    }
    if (allAddresses.length === 0) return;

    const filters = {
        hideSpoofedSymbols: state.hideSpoofedSymbols,
        hideLowHolderTokens: state.hideLowHolderTokens,
        hideFraudContracts: state.hideFraudContracts,
        hideDustTransactions: state.hideDustTransactions,
        dustThresholdGwei: state.dustThresholdGwei,
        fraudContracts: state.fraudContracts,
    };

    try {
        const fetches = allAddresses.map((addr) =>
            fetchRecentTransactions(addr, state.blockscoutUrl),
        );
        const results = await Promise.all(fetches);

        // Merge, deduplicate by hash, filter, sort, take 25
        const seen = new Set();
        let merged = [];
        for (const txs of results) {
            for (const tx of txs) {
                if (seen.has(tx.hash)) continue;
                seen.add(tx.hash);
                merged.push(tx);
            }
        }

        const filtered = filterTransactions(merged, filters);

        // Persist any newly discovered fraud contracts
        if (filtered.newFraudContracts.length > 0) {
            for (const addr of filtered.newFraudContracts) {
                if (!state.fraudContracts.includes(addr)) {
                    state.fraudContracts.push(addr);
                }
            }
            await saveState();
        }

        merged = filtered.transactions;
        merged.sort((a, b) => b.blockNumber - a.blockNumber);
        homeTxs = merged.slice(0, 25);
        renderHomeTxList(ctx);
    } catch (e) {
        log.errorf("loadHomeTxs failed:", e.message);
        const list = $("home-tx-list");
        if (list) {
            list.innerHTML =
                '<div class="text-muted text-xs py-1">Failed to load transactions.</div>';
        }
    }
}

// The wallet list markup. Pure: it reads state and returns a string, so the
// list can be asserted on without a DOM.
function walletListHtml() {
    let html = "";
    state.wallets.forEach((wallet, wi) => {
        const defect = walletDefect(wallet);
        html += `<div>`;
        html += `<div class="flex justify-between items-center bg-section py-1 px-2" style="margin:0 -0.5rem">`;
        html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${escapeHtml(wallet.name)}</span>`;
        // No "+" on a defective wallet: deriving another address from that
        // xpub would only add one more address the key does not produce
        // under the standard path.
        if (!defect && (wallet.type === "hd" || wallet.type === "xprv")) {
            html += `<button class="btn-add-address border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-wallet="${wi}" title="Add another address to this wallet">+</button>`;
        }
        html += `</div>`;
        html += walletDefectHtml(wallet);

        wallet.addresses.forEach((addr, ai) => {
            html += `<div class="address-row py-1 border-b border-border-light cursor-pointer hover:bg-hover" data-wallet="${wi}" data-address="${ai}">`;
            const isActive = state.activeAddress === addr.address;
            const infoBtn = `<span class="btn-addr-info text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg" style="padding:0" data-wallet="${wi}" data-address="${ai}">[info]</span>`;
            // Only where a wallet can spare the address: a wallet holding a
            // single address has no remove control, because its last address
            // is never removable.
            const removeBtn = canRemoveAddress(wallet)
                ? `<span class="btn-remove-address text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg ml-1" style="padding:0" data-wallet="${wi}" data-address="${ai}" title="Remove this address from the wallet">[x]</span>`
                : "";
            const dot = addressDotHtml(addr.address);
            const titleBold = isActive ? "font-bold" : "";
            html += `<div class="text-xs ${titleBold}">Address ${ai + 1}</div>`;
            if (addr.ensName) {
                // An ENS reverse record is whatever the name owner set it
                // to; renderAddressHtml() escapes its own copy of this and
                // this list was the one that did not.
                html += `<div class="text-xs font-bold flex items-center">${dot}${escapeHtml(addr.ensName)}</div>`;
            }
            html += `<div class="flex text-xs items-center justify-between">`;
            html += `<span class="flex items-center break-all">${addr.ensName ? "" : dot}${escapeHtml(addr.address)}</span>`;
            html += `<span class="flex-shrink-0 ml-1">${infoBtn}${removeBtn}</span>`;
            html += `</div>`;
            const addrTotal = formatAddressTotal(getAddressValue(addr));
            html += `<div class="text-xs text-muted text-right min-h-[1rem]">${addrTotal || "&nbsp;"}</div>`;
            html += balanceLinesForAddress(
                addr,
                state.trackedTokens,
                state.showZeroBalanceTokens,
            );
            html += `</div>`;
        });

        html += `</div>`;
    });
    return html;
}

function render(ctx) {
    const container = $("wallet-list");
    if (state.wallets.length === 0) {
        container.innerHTML =
            '<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
        renderTotalValue();
        renderActiveAddress();
        return;
    }

    container.innerHTML = walletListHtml();

    container.querySelectorAll(".address-row").forEach((row) => {
        row.addEventListener("click", async () => {
            const wi = parseInt(row.dataset.wallet, 10);
            const ai = parseInt(row.dataset.address, 10);
            const addr = state.wallets[wi].addresses[ai].address;
            if (state.activeAddress !== addr) {
                state.activeAddress = addr;
                await saveState();
                render(ctx);
                notify({ type: "AUTISTMASK_ACTIVE_CHANGED" });
            }
        });
    });

    container.querySelectorAll(".btn-addr-info").forEach((btn) => {
        btn.addEventListener("click", (e) => {
            e.stopPropagation();
            state.selectedWallet = parseInt(btn.dataset.wallet, 10);
            state.selectedAddress = parseInt(btn.dataset.address, 10);
            ctx.showAddressDetail();
        });
    });

    container.querySelectorAll(".btn-remove-address").forEach((btn) => {
        btn.addEventListener("click", (e) => {
            e.stopPropagation();
            ctx.showDeleteAddress(
                parseInt(btn.dataset.wallet, 10),
                parseInt(btn.dataset.address, 10),
            );
        });
    });

    container.querySelectorAll(".btn-add-address").forEach((btn) => {
        btn.addEventListener("click", async (e) => {
            e.stopPropagation();
            const wi = parseInt(btn.dataset.wallet, 10);
            const wallet = state.wallets[wi];
            const newAddr = deriveAddressFromXpub(
                wallet.xpub,
                wallet.nextIndex,
            );
            wallet.addresses.push({
                address: newAddr,
                balance: "0.0000",
                tokenBalances: [],
            });
            wallet.nextIndex++;
            await saveState();
            render(ctx);
            ctx.doRefreshAndRender();
        });
    });

    container.querySelectorAll(".wallet-name").forEach((span) => {
        span.addEventListener("click", (e) => {
            e.stopPropagation();
            const wi = parseInt(span.dataset.wallet, 10);
            const wallet = state.wallets[wi];
            const input = document.createElement("input");
            input.type = "text";
            input.value = wallet.name;
            input.className =
                "font-bold border border-border p-0 bg-bg text-fg";
            input.style.width = "100%";
            const save = async () => {
                const val = input.value.trim();
                if (val && val !== wallet.name) {
                    wallet.name = val;
                    await saveState();
                }
                render(ctx);
            };
            input.addEventListener("blur", save);
            input.addEventListener("keydown", (ev) => {
                if (ev.key === "Enter") input.blur();
                if (ev.key === "Escape") {
                    input.value = wallet.name;
                    input.blur();
                }
            });
            span.replaceWith(input);
            input.focus();
            input.select();
        });
    });

    renderTotalValue();
    renderActiveAddress();
    loadHomeTxs(ctx);
}

// The defect of the wallet the selected address belongs to, or null. Call
// after selectActiveAddress().
function selectedWalletDefect() {
    if (state.selectedWallet === null) return null;
    return walletDefect(state.wallets[state.selectedWallet]);
}

function selectActiveAddress() {
    for (let wi = 0; wi < state.wallets.length; wi++) {
        for (let ai = 0; ai < state.wallets[wi].addresses.length; ai++) {
            if (
                state.wallets[wi].addresses[ai].address === state.activeAddress
            ) {
                state.selectedWallet = wi;
                state.selectedAddress = ai;
                return true;
            }
        }
    }
    return false;
}

function init(ctx) {
    $("btn-add-wallet-bottom").addEventListener("click", ctx.showAddWalletView);

    $("btn-main-send").addEventListener("click", () => {
        if (!selectActiveAddress()) {
            showFlash("No active address selected.");
            return;
        }
        // Before the balance check and before any password is asked for: this
        // wallet cannot sign at all, so the send screen is a dead end.
        const defect = selectedWalletDefect();
        if (defect) {
            showFlash(defect.shortMessage);
            return;
        }
        const addr = currentAddress();
        if (!addr.balance || parseFloat(addr.balance) === 0) {
            showFlash("Cannot send \u2014 zero balance.");
            return;
        }
        $("send-to").value = "";
        $("send-amount").value = "";
        $("send-token").classList.remove("hidden");
        $("send-token-static").classList.add("hidden");
        renderSendTokenSelect(addr);
        updateSendBalance();
        resetSendValidation();
        pushCurrentView();
        showView("send");
    });

    $("btn-main-receive").addEventListener("click", () => {
        if (!selectActiveAddress()) {
            showFlash("No active address selected.");
            return;
        }
        ctx.showReceive();
    });
}

module.exports = { init, render, walletListHtml };
