// ESLint flat config. Static analysis for make check; formatting stays with
// prettier (script/fmt-check), so nothing here touches style.
//
// The sources are CommonJS and are bundled per entrypoint by build.js, so the
// globals differ by tree and are declared per tree below. Getting that wrong in
// either direction defeats the point: too few globals buries a real no-undef in
// false positives, too many hides the next unimported identifier.

const js = require("@eslint/js");
const globals = require("globals");
const backgroundState = require("./script/lib/eslint/noStateSingletonInBackground");
const {
    BACKGROUND_ENTRY_PREFIX,
} = require("./script/lib/forbiddenBundleInputs");

// The extension APIs. MV3 Chrome exposes `chrome`; Firefox exposes both, and
// the code feature-detects between them.
const extensionGlobals = {
    chrome: "readonly",
    browser: "readonly",
};

const commonjs = {
    ecmaVersion: 2024,
    sourceType: "commonjs",
};

module.exports = [
    {
        ignores: ["dist/", "node_modules/"],
    },

    js.configs.recommended,

    {
        rules: {
            // The two rules this config exists for. Both are already
            // error-level in the recommended set; restated so a future
            // recommended-set change cannot silently downgrade them.
            "no-undef": "error",
            // `_`-prefixed arguments are the deliberate "present for the
            // interface, unused here" marker: the popup views share one
            // init(ctx) signature and three of the eight do not read ctx.
            // An unused catch binding is written `catch {`, which the repo
            // already does, so caught errors stay checked.
            "no-unused-vars": ["error", { argsIgnorePattern: "^_" }],

            // Off tree-wide: it requires every rethrow to carry `{ cause }`,
            // at 3 sites today (src/shared/balances.js 207 and 215,
            // tests/e2e/firefox/run.js 131). That is a change to what the
            // wallet's error paths actually throw, and it is a decision of its
            // own rather than a side effect of turning a linter on — so it is
            // off everywhere, including for new code, until that decision is
            // made. Unlike no-useless-assignment below, this is not an
            // accommodation of particular sites and must not be scoped to
            // them.
            "preserve-caught-error": "off",
        },
    },

    // no-useless-assignment stays on everywhere except the two files that
    // wipe decrypted key material: the `password = null` and
    // `decryptedSecret = null` assignments after use are dead by construction
    // — that is what a best-effort wipe is — and the rule's fix is to delete
    // the wipe. 9 sites: approval.js 582, 593, 618, 648, 692, 703, 728, 764
    // and confirmTx.js 459. Everything else in the tree is still checked, so
    // an ordinary dead store elsewhere is still an error.
    {
        files: ["src/popup/views/approval.js", "src/popup/views/confirmTx.js"],
        rules: {
            "no-useless-assignment": "off",
        },
    },

    // Popup and content scripts: page/window context.
    {
        files: ["src/popup/**/*.js", "src/content/**/*.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.browser, ...extensionGlobals },
        },
    },

    // MV3 background: a service worker, with no window and no document.
    //
    // It also may not reach src/shared/state.js. That module's `state` export
    // is a per-bundle singleton loaded once and mutated in place, which is the
    // popup's lifetime and not the worker's: the worker is killed when idle,
    // nothing loads state at module scope, and an unpopulated read used to be
    // served DEFAULT_STATE silently. Five defects came from background code
    // reading or writing it (https://git.eeqj.de/sneak/AutistMask/issues/324),
    // and each point fix added a loadState() that created the next one. The
    // rule below checks reachability through the whole require graph, not just
    // the direct require, because a re-export from any shared module the
    // background already pulls in would put the singleton back in the bundle
    // with no background file naming it.
    //
    // It is not the guarantee: build.js asserts the same prohibition against
    // esbuild's own metafile, from the shared table in
    // script/lib/forbiddenBundleInputs.js. This is the early report.
    //
    // The glob comes from that same file, because build.js uses the prefix to
    // decide which entry points must be listed in the table at all: the two
    // layers must not disagree about which files are "the background".
    {
        files: [`${BACKGROUND_ENTRY_PREFIX}**/*.js`],
        plugins: { background: backgroundState },
        languageOptions: {
            ...commonjs,
            globals: { ...globals.serviceworker, ...extensionGlobals },
        },
        rules: {
            "background/no-state-singleton-in-background": "error",
        },
    },

    // src/shared is bundled into both, so it may only use what both provide:
    // the service worker globals are the intersection, plus the extension APIs.
    {
        files: ["src/shared/**/*.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.serviceworker, ...extensionGlobals },
        },
    },

    // src/shared/ens.js is the documented exception to the line above: its own
    // header says POPUP ONLY, it caches in localStorage, and only popup views
    // require it. Linting it as a service worker would be wrong about the file.
    {
        files: ["src/shared/ens.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.browser, ...extensionGlobals },
        },
    },

    // Unit tests, and the helpers they require: jest on node.
    {
        files: ["tests/**/*.test.js", "tests/support/**/*.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.node, ...globals.jest },
        },
    },

    // The build script is a plain node program.
    {
        files: ["build.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.node },
        },
    },

    // The helpers the script/ entrypoints call: plain node programs too, run
    // from a shell script rather than from yarn, and never bundled.
    {
        files: ["script/lib/**/*.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.node },
        },
    },

    // The e2e harnesses are node programs that also carry, inline, the
    // callbacks they ship into the browser via page.evaluate — so both
    // contexts really are present in the same file and both sets of globals
    // are in scope somewhere in it.
    {
        files: ["tests/e2e/**/*.js"],
        languageOptions: {
            ...commonjs,
            globals: {
                ...globals.node,
                ...globals.browser,
                ...extensionGlobals,
            },
        },
    },

    // This config file itself.
    {
        files: ["eslint.config.js"],
        languageOptions: {
            ...commonjs,
            globals: { ...globals.node },
        },
    },
];
