// State management and extension storage persistence.

const { DEFAULT_RPC_URL, DEFAULT_BLOCKSCOUT_URL } = require("./constants");
const { networkById } = require("./networks");
// Dependency-free constant module; safe to pull into a background bundle.
const { RESTORABLE_VIEWS } = require("../popup/restorableViews");

const storageApi =
    typeof browser !== "undefined"
        ? browser.storage.local
        : chrome.storage.local;

const DEFAULT_STATE = {
    hasWallet: false,
    wallets: [],
    trackedTokens: [],
    networkId: "mainnet",
    rpcUrl: DEFAULT_RPC_URL,
    blockscoutUrl: DEFAULT_BLOCKSCOUT_URL,
    lastBalanceRefresh: 0,
    activeAddress: null,
    allowedSites: {},
    deniedSites: {},
    rememberSiteChoice: true,
    showZeroBalanceTokens: true,
    hideSpoofedSymbols: true,
    hideLowHolderTokens: true,
    hideFraudContracts: true,
    hideDustTransactions: true,
    dustThresholdGwei: 100000,
    utcTimestamps: false,
    fraudContracts: [],
    tokenHolderCache: {},
    theme: "system",
    debugMode: false,
};

const state = {
    ...DEFAULT_STATE,
    currentView: null,
    selectedWallet: null,
    selectedAddress: null,
    selectedToken: null,
    viewData: {},
    viewStack: [],
};

// Keep only the leading run of stored views the popup is willing to render.
//
// restoreView() refuses to reopen ONTO a non-restorable view, but the stack
// behind it used to be restored verbatim, so Back could walk onto a screen
// whose content is deliberately never re-rendered — and "show-phrase" has no
// Back control to leave by. Truncating at the first such entry instead of
// splicing it out keeps the result a prefix of the stored stack, so every
// surviving entry's Back target is exactly the one it had; splicing would
// silently re-point the entry above the hole at a different screen.
//
// Filtering happens here on load rather than in saveState(): the live
// in-session stack is legitimate (the screen really is rendered while the
// popup is open), and only a load-side filter also repairs the stacks
// already in storage, including ones written before a view left the set.
function restorableStack(stored, currentView) {
    // A stored stack that is missing or not an array keeps nothing, but it
    // still goes through the never-empty rule below rather than returning
    // early: otherwise a corrupt stack would depend on exactly the goBack()
    // fallback that the explicit ["main"] exists in order not to depend on.
    const source = Array.isArray(stored) ? stored : [];
    const cut = source.findIndex((view) => !RESTORABLE_VIEWS.has(view));
    const kept = cut === -1 ? source.slice() : source.slice(0, cut);
    // A view restored below the root still needs somewhere for Back to go.
    if (
        kept.length === 0 &&
        currentView !== "main" &&
        RESTORABLE_VIEWS.has(currentView)
    ) {
        return ["main"];
    }
    return kept;
}

// Return the network configuration for the currently selected network.
function currentNetwork() {
    return networkById(state.networkId);
}

async function saveState() {
    const persisted = {
        hasWallet: state.hasWallet,
        wallets: state.wallets,
        trackedTokens: state.trackedTokens,
        networkId: state.networkId,
        rpcUrl: state.rpcUrl,
        blockscoutUrl: state.blockscoutUrl,
        lastBalanceRefresh: state.lastBalanceRefresh,
        activeAddress: state.activeAddress,
        allowedSites: state.allowedSites,
        deniedSites: state.deniedSites,
        rememberSiteChoice: state.rememberSiteChoice,
        showZeroBalanceTokens: state.showZeroBalanceTokens,
        hideSpoofedSymbols: state.hideSpoofedSymbols,
        hideLowHolderTokens: state.hideLowHolderTokens,
        hideFraudContracts: state.hideFraudContracts,
        hideDustTransactions: state.hideDustTransactions,
        dustThresholdGwei: state.dustThresholdGwei,
        utcTimestamps: state.utcTimestamps,
        fraudContracts: state.fraudContracts,
        tokenHolderCache: state.tokenHolderCache,
        theme: state.theme,
        debugMode: state.debugMode,
        currentView: state.currentView,
        selectedWallet: state.selectedWallet,
        selectedAddress: state.selectedAddress,
        selectedToken: state.selectedToken,
        viewData: state.viewData,
        viewStack: state.viewStack,
    };
    await storageApi.set({ autistmask: persisted });
}

async function loadState() {
    const result = await storageApi.get("autistmask");
    if (result.autistmask) {
        const saved = result.autistmask;
        state.wallets = saved.wallets || [];
        // Derived, never read from storage: a profile persisted with the flag
        // out of step with the wallet list would otherwise stay broken on
        // every load. Nothing depends on the two disagreeing.
        state.hasWallet = state.wallets.length > 0;
        state.trackedTokens = saved.trackedTokens || [];
        state.networkId = saved.networkId || DEFAULT_STATE.networkId;
        state.rpcUrl = saved.rpcUrl || DEFAULT_STATE.rpcUrl;
        state.blockscoutUrl =
            saved.blockscoutUrl || DEFAULT_STATE.blockscoutUrl;
        state.lastBalanceRefresh = saved.lastBalanceRefresh || 0;
        state.activeAddress = saved.activeAddress || null;
        state.allowedSites =
            saved.allowedSites && !Array.isArray(saved.allowedSites)
                ? saved.allowedSites
                : {};
        state.deniedSites =
            saved.deniedSites && !Array.isArray(saved.deniedSites)
                ? saved.deniedSites
                : {};
        state.rememberSiteChoice =
            saved.rememberSiteChoice !== undefined
                ? saved.rememberSiteChoice
                : true;
        state.showZeroBalanceTokens =
            saved.showZeroBalanceTokens !== undefined
                ? saved.showZeroBalanceTokens
                : true;
        // A profile written before this setting existed has no key for it.
        // It is a safety filter, so absent must load as on, not as undefined.
        state.hideSpoofedSymbols =
            saved.hideSpoofedSymbols !== undefined
                ? saved.hideSpoofedSymbols
                : true;
        state.hideLowHolderTokens =
            saved.hideLowHolderTokens !== undefined
                ? saved.hideLowHolderTokens
                : true;
        state.hideFraudContracts =
            saved.hideFraudContracts !== undefined
                ? saved.hideFraudContracts
                : true;
        state.hideDustTransactions =
            saved.hideDustTransactions !== undefined
                ? saved.hideDustTransactions
                : true;
        state.dustThresholdGwei =
            saved.dustThresholdGwei !== undefined
                ? saved.dustThresholdGwei
                : 100000;
        state.utcTimestamps =
            saved.utcTimestamps !== undefined ? saved.utcTimestamps : false;
        state.fraudContracts = saved.fraudContracts || [];
        state.tokenHolderCache = saved.tokenHolderCache || {};
        state.theme = saved.theme || "system";
        state.debugMode =
            saved.debugMode !== undefined ? saved.debugMode : false;
        state.currentView = saved.currentView || null;
        state.selectedWallet =
            saved.selectedWallet !== undefined ? saved.selectedWallet : null;
        state.selectedAddress =
            saved.selectedAddress !== undefined ? saved.selectedAddress : null;
        state.selectedToken = saved.selectedToken || null;
        state.viewData = saved.viewData || {};
        state.viewStack = restorableStack(saved.viewStack, state.currentView);
    }
}

function currentAddress() {
    if (state.selectedWallet === null || state.selectedAddress === null) {
        return null;
    }
    return state.wallets[state.selectedWallet].addresses[state.selectedAddress];
}

module.exports = {
    state,
    saveState,
    loadState,
    currentAddress,
    currentNetwork,
};
