// Rendering a view the popup lands on without having navigated to it
// forward: on restore, and on Back. In both cases the view may never have
// been rendered in this page load — a reopened popup renders only the
// wallet list and the view it restores onto, so every other view is still
// the blank static template from index.html — so unhiding it is not enough.
//
// Forward navigation renders as it goes and must NOT come through here:
// rendering a second time would re-fetch and clobber whatever the view has
// in flight.
//
// The view modules are injected and nothing here touches the DOM, so the
// dispatch and its data guards can be tested directly; src/popup/index.js
// cannot be required outside a browser.

const { RESTORABLE_VIEWS } = require("../shared/restorableViews");

// The views this page load has rendered.
//
// The Back path cannot otherwise tell its two cases apart. A view the popup
// never rendered is still the blank template from index.html and has to be
// rendered; a view already on the page must NOT be rendered again, because
// a second render re-fetches and overwrites whatever the user has typed
// into it and not yet saved.
//
// Registration is showView() in views/helpers.js, which is the last thing
// every render path runs — restoreView()'s, the Back path's, and every
// forward show(). That is the point of putting it there rather than in the
// individual views: a view added later registers itself with no one having
// to remember it, so this cannot decay.
//
// Module scope is page-load scope: the popup loads this module once per
// page load, and a reopened popup gets a fresh, empty set — which is
// exactly the state that makes the Back path render.
const renderedViews = new Set();

function markViewRendered(view) {
    if (view) renderedViews.add(view);
}

// Begin a fresh page-load scope. The popup gets one by being loaded; the
// unit tests, which simulate several page loads against one module
// instance, ask for one.
function resetRenderedViews() {
    renderedViews.clear();
}

// Home is the exception: Back re-renders it every time, which is what the
// popup did before this router existed (index.js registered
// renderWalletList() as setRenderMain(), and goBack() called it on every
// Back onto "main"). It must stay that way — the wallet list has to reflect
// what changed while the user was away from it, such as a wallet renamed or
// an address removed in Settings — and Home holds no unsaved input to lose.
const ALWAYS_RENDER_ON_BACK = new Set(["main"]);

// Views that render an address the user picked and cannot be rendered
// without one.
const ADDRESS_VIEWS = new Set([
    "address",
    "address-token",
    "receive",
    "transaction",
]);

function needsAddress(view) {
    return ADDRESS_VIEWS.has(view);
}

function hasValidAddress(state) {
    return Boolean(
        state.selectedWallet !== null &&
        state.selectedAddress !== null &&
        state.wallets[state.selectedWallet] &&
        state.wallets[state.selectedWallet].addresses[state.selectedAddress],
    );
}

// Render `view` from persisted state. Each view module shows itself, so a
// true return means the view is both rendered and on screen.
//
// Returns false when the view is not one the popup renders from state, or
// when the state it would render is gone — a token no longer selected, a
// transaction no longer persisted. The caller falls back rather than
// putting an empty template on screen.
function renderView(view, state, views) {
    if (!view || !RESTORABLE_VIEWS.has(view)) return false;
    if (needsAddress(view) && !hasValidAddress(state)) return false;
    if (view === "address-token" && !state.selectedToken) return false;

    const data = state.viewData || {};
    switch (view) {
        case "main":
            views.main.show();
            return true;
        case "address":
            views.addressDetail.show();
            return true;
        case "address-token":
            views.addressToken.show();
            return true;
        case "receive":
            views.receive.show();
            return true;
        case "settings":
            views.settings.show();
            return true;
        case "settings-addtoken":
            views.settingsAddToken.show();
            return true;
        case "confirm-tx":
            if (!data.pendingTx) return false;
            views.confirmTx.restore();
            return true;
        case "transaction":
            if (!data.tx) return false;
            views.transactionDetail.render();
            return true;
        case "wait-tx":
            // Resumes the receipt poll from the persisted broadcast time,
            // and answers false when there is nothing resumable left.
            return Boolean(views.txStatus.restoreWait());
        case "success-tx":
            if (!data.hash) return false;
            views.txStatus.renderSuccess();
            return true;
        case "error-tx":
            if (!data.message) return false;
            views.txStatus.renderError();
            return true;
        default:
            return false;
    }
}

// The Back-path renderer, registered with setBackRenderer() in
// views/helpers.js.
//
// Returns false — leaving goBack() to unhide the view, as it always did —
// in the two cases where the view is known to be on the page already:
//
//   - It is not one the popup renders from persisted state. The restored
//     stack is filtered against RESTORABLE_VIEWS, so such a view can only
//     be on the stack from this page load, where forward navigation
//     rendered it on the way in.
//   - This page load has rendered it. Re-rendering would re-fetch and
//     clobber what it holds; Home is rendered anyway, see above.
//
// What is left is the case the router exists for: a view on the stack that
// this page load has never rendered, whose template is still blank.
function makeBackRenderer(state, views) {
    return function renderBack(view) {
        if (!RESTORABLE_VIEWS.has(view)) return false;
        if (renderedViews.has(view) && !ALWAYS_RENDER_ON_BACK.has(view)) {
            return false;
        }
        if (!renderView(view, state, views)) {
            views.main.show();
        }
        return true;
    };
}

module.exports = {
    renderView,
    makeBackRenderer,
    markViewRendered,
    resetRenderedViews,
};
