// Recovery phrase display for HD wallets.
//
// The phrase is the secret that owns every address in the wallet, so it is
// handled under four rules:
//
//   1. Only an HD wallet reaches this screen (walletHasRecoveryPhrase).
//   2. Nothing is decrypted, and nothing is written into the DOM, until
//      decryptWithPassword has accepted the password.
//   3. Leaving the screen by any path wipes it, via the onViewLeave hook,
//      and a decrypt still in flight when that happens is discarded
//      instead of written (revealGeneration).
//   4. The phrase never reaches the logger. This module deliberately does
//      not import src/shared/log.js, and the failed-decrypt path reports a
//      fixed sentence rather than the caught error.
//
// The phrase is also never assigned to `state`, so it cannot be persisted
// to extension storage, and "show-phrase" is excluded from RESTORABLE_VIEWS
// so the popup can never reopen onto it.

const {
    $,
    showView,
    showFlash,
    flashCopyFeedback,
    goBack,
    onViewLeave,
    pushCurrentView,
} = require("./helpers");
const { state } = require("../../shared/state");
const { decryptWithPassword } = require("../../shared/vault");
const { walletHasRecoveryPhrase } = require("../../shared/wallet");

const VIEW = "show-phrase";

let walletIndex = null;

// Bumped by every clear(), which is what leaving the screen runs. reveal()
// captures it before awaiting the decrypt and refuses to touch the DOM if
// it has moved: a decrypt still in flight when the screen is left would
// otherwise write the phrase *after* the wipe, with nothing scheduled to
// wipe it again, leaving it in the hidden view for the life of the popup.
let revealGeneration = 0;

// True only if the reveal that captured `generation` is still the live one:
// the screen has not been left, cleared, or re-entered for another wallet
// since it started.
function isCurrentReveal(generation) {
    return (
        generation === revealGeneration &&
        walletIndex !== null &&
        state.currentView === VIEW
    );
}

function fail(message) {
    $("show-phrase-flash").textContent = message;
    $("show-phrase-flash").style.visibility = "visible";
}

// Wipe every trace of the phrase and drop the wallet selection. Safe to
// call when nothing was ever revealed, and safe to call twice.
function clear() {
    walletIndex = null;
    revealGeneration += 1;
    $("show-phrase-value").textContent = "";
    $("show-phrase-password").value = "";
    $("show-phrase-result").classList.add("hidden");
    $("show-phrase-password-section").classList.remove("hidden");
    $("show-phrase-flash").textContent = "";
    $("show-phrase-flash").style.visibility = "hidden";
}

function show(walletIdx) {
    const wallet = state.wallets[walletIdx];
    if (!walletHasRecoveryPhrase(wallet)) {
        showFlash("This wallet does not have a recovery phrase.");
        return;
    }
    clear();
    walletIndex = walletIdx;
    $("show-phrase-wallet-name").textContent =
        wallet.name || "Wallet " + (walletIdx + 1);
    // Pushed here rather than by the caller: this function can return
    // without navigating, and a push that happened anyway would leave an
    // entry on the stack that no screen transition matches.
    pushCurrentView();
    showView(VIEW);
}

async function reveal() {
    const password = $("show-phrase-password").value;
    if (!password) {
        fail("Please enter your password.");
        return;
    }
    if (walletIndex === null) {
        fail("No wallet is selected.");
        return;
    }
    const wallet = state.wallets[walletIndex];
    if (!walletHasRecoveryPhrase(wallet)) {
        fail("This wallet does not have a recovery phrase.");
        return;
    }

    const btn = $("btn-show-phrase-reveal");
    btn.disabled = true;
    btn.classList.add("text-muted");
    const generation = revealGeneration;
    try {
        const phrase = await decryptWithPassword(
            wallet.encryptedSecret,
            password,
        );
        // The only suspension point in this view, and the only place a
        // secret is written: if the screen was left while the decrypt ran,
        // the wipe has already happened and this write must not land.
        if (!isCurrentReveal(generation)) return;
        $("show-phrase-password").value = "";
        $("show-phrase-password-section").classList.add("hidden");
        $("show-phrase-value").textContent = phrase;
        $("show-phrase-result").classList.remove("hidden");
        $("show-phrase-flash").textContent = "";
        $("show-phrase-flash").style.visibility = "hidden";
    } catch {
        if (!isCurrentReveal(generation)) return;
        // Deliberately not the caught error: the message is fixed so that
        // nothing derived from the ciphertext or the attempt can surface.
        fail("That password is incorrect. Please try again.");
    } finally {
        btn.disabled = false;
        btn.classList.remove("text-muted");
    }
}

function init() {
    onViewLeave(VIEW, clear);

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

    $("btn-show-phrase-reveal").addEventListener("click", reveal);

    $("show-phrase-value").addEventListener("click", () => {
        const phrase = $("show-phrase-value").textContent;
        if (!phrase) return;
        navigator.clipboard.writeText(phrase);
        showFlash("Copied!");
        flashCopyFeedback($("show-phrase-value"));
    });
}

module.exports = { init, show };
