// ESLint flat config for JavaScript files
// https://eslint.org/docs/latest/use/configure/configuration-files-new

import nounsanitized from "eslint-plugin-no-unsanitized";
import chaiFriendly from "eslint-plugin-chai-friendly";
import regexp from "eslint-plugin-regexp";

// Recognized safe escape/sanitize methods for no-unsanitized plugin.
// Implementations:
//   escapeHtml, esc — security/xss-protection.js (escapes & < > " ')
//   DOMPurify.sanitize — app.js imports dompurify, exposed as window.DOMPurify
//   sanitizeHtml/sanitizeHTML — security/xss-protection.js / utils/sanitizer.js
const escapeConfig = {
  escape: {
    methods: [
      "escapeHtml",
      "esc",
      "DOMPurify.sanitize",
      "window.DOMPurify.sanitize",
      "sanitizeHtml",
      "sanitizeHTML",
      "window.escapeHtml",
      "window.sanitizeHtml",
      "window.XSSProtection.escapeHtml",
    ],
  },
};

export default [
  // Global ignores — must be a standalone object (no "files" key) for ESLint flat config
  {
    ignores: [
      "**/node_modules/**",
      "**/static/dist/**",
      "tests/ldr-news-dev-files/**",
      "dist/**",
      "build/**",
      "**/*.min.js",
    ],
  },
  // Catch regex anti-patterns and ReDoS hazards. Recommended preset only —
  // ~67 rules, all the safety-relevant ones already in the project's verified
  // zero/near-zero violation profile.
  regexp.configs["flat/recommended"],
  {
    // Apply to all JavaScript files
    files: ["**/*.js", "**/*.mjs"],

    plugins: {
      "no-unsanitized": nounsanitized,
      "chai-friendly": chaiFriendly,
    },

    rules: {
      "no-undef": "error", // globals enumerated below in languageOptions.globals
      "no-unused-vars": ["warn", {
        "args": "after-used",
        "argsIgnorePattern": "^_",
        "varsIgnorePattern": "^_",
        "caughtErrors": "all",
        "caughtErrorsIgnorePattern": "^_",
        "destructuredArrayIgnorePattern": "^_",
        "ignoreRestSiblings": true,
      }],

      // --- High priority: real bugs found in codebase ---
      "no-var": "error",
      "no-prototype-builtins": "error",
      "prefer-const": "warn",

      // --- Medium priority ---
      "eqeqeq": ["error", "always", { "null": "ignore" }],

      // --- Zero-cost safety (no violations, pure prevention) ---
      "no-eval": "error",
      "no-implied-eval": "error",
      "no-new-func": "error",

      // XSS prevention - detect unescaped data in innerHTML/outerHTML/document.write
      "no-unsanitized/property": ["error", escapeConfig],
      "no-unsanitized/method": ["error", escapeConfig],

      // --- Zero-cost safety bundle (all zero or trivial violations at time of enabling) ---
      // Duplicate detection
      "no-dupe-args": "error",
      "no-dupe-keys": "error",
      "no-dupe-else-if": "error",
      "no-duplicate-case": "error",
      // Dead / unreachable code
      "no-unreachable": "error",
      "no-useless-catch": "error",
      "no-useless-concat": "error",
      "no-useless-escape": "error",
      "no-empty": ["error", { "allowEmptyCatch": true }],
      // Real-bug catchers
      "no-cond-assign": "error",
      "no-constant-condition": "error",
      "no-func-assign": "error",
      "no-invalid-regexp": "error",
      "no-self-assign": "error",
      "no-self-compare": "error",
      "no-unsafe-finally": "error",
      "no-unsafe-negation": "error",
      "use-isnan": "error",
      "valid-typeof": "error",
      // Code discipline
      "no-debugger": "error",
      "no-redeclare": "error",
      "no-throw-literal": "error",

      // --- Zero-cost safety bundle v2 ---
      // Correctness / real-bug catchers
      "no-async-promise-executor": "error",
      "no-class-assign": "error",
      "no-const-assign": "error",
      "no-import-assign": "error",
      "no-setter-return": "error",
      "no-this-before-super": "error",
      "no-with": "error",
      "no-octal": "error",
      "no-octal-escape": "error",
      "default-param-last": "error",
      "no-multi-assign": "error",
      // Dead / pointless code
      "no-extra-bind": "error",
      "no-extra-semi": "error",
      "no-lone-blocks": "error",
      "no-sequences": "error",
      "no-useless-call": "error",
      "no-useless-computed-key": "error",
      "no-useless-constructor": "error",
      "no-useless-rename": "error",
      "no-useless-return": "error",
      "no-void": "error",
      "no-labels": "error",
      "no-unneeded-ternary": "error",
      // Prefer literals / modern idioms
      "no-new-object": "error",
      "no-array-constructor": "error",
      "prefer-numeric-literals": "error",
      "prefer-object-spread": "error",
      "prefer-promise-reject-errors": "error",
      "prefer-spread": "error",
      "prefer-rest-params": "error",
      "prefer-exponentiation-operator": "error",
      // Safety discipline
      "no-iterator": "error",
      "no-proto": "error",
      "no-extend-native": "error",
      "default-case-last": "error",
      // Formatting (acting as formatter — no Prettier for JS in this repo)
      "no-trailing-spaces": "error",
      "no-mixed-spaces-and-tabs": "error",

      // --- Zero-cost safety bundle v3 ---
      // Correctness / real-bug catchers
      "no-implicit-globals": "error",
      "no-new-native-nonconstructor": "error",
      "no-new-require": "error",
      "no-new-wrappers": "error",
      "no-nonoctal-decimal-escape": "error",
      "no-shadow-restricted-names": "error",
      "no-unmodified-loop-condition": "error",
      "no-unreachable-loop": "error",
      "no-useless-backreference": "error",
      "no-duplicate-imports": "error",
      // Prefer modern idioms
      "prefer-regex-literals": "error",
      "no-object-constructor": "error",
      "logical-assignment-operators": ["error", "always"],
      "operator-assignment": "error",
      // Dead / pointless code
      "no-undef-init": "error",
      "no-template-curly-in-string": "error",
      "no-multi-str": "error",
      "no-confusing-arrow": ["error", { "allowParens": true }],
      // Accessors & classes
      "grouped-accessor-pairs": "error",
      "no-unused-private-class-members": "error",
      // Style discipline
      "func-name-matching": "error",
      "require-yield": "error",
      "symbol-description": "error",
      "yoda": "error",

      // --- Zero-cost safety bundle v4 ---
      // Correctness / real-bug catchers
      "accessor-pairs": "error",
      "getter-return": "error",
      "no-dupe-class-members": "error",
      "no-ex-assign": "error",
      "no-fallthrough": "error",
      "no-global-assign": "error",
      "no-misleading-character-class": "error",
      "no-regex-spaces": "error",
      "no-sparse-arrays": "error",
      "no-unexpected-multiline": "error",
      "no-unsafe-optional-chaining": "error",
      // Dead / pointless code
      "no-div-regex": "error",
      "no-empty-character-class": "error",
      "no-empty-pattern": "error",
      "no-empty-static-block": "error",
      "no-extra-label": "error",
      "no-inner-declarations": "error",
      // Exhaustiveness
      "default-case": "error",
      "new-cap": ["error", { "newIsCapExceptions": ["jsPDFLib"] }],

      // --- Zero-cost safety bundle v5 ---
      "no-constant-binary-expression": "error",
      "no-compare-neg-zero": "error",
      "for-direction": "error",
      "no-buffer-constructor": "error",
      "no-path-concat": "error",

      // Prefer ES2015+ object literal shorthand
      "object-shorthand": "error",

      // Catch dead stores: `let x = 1; x = 2;` where the first 1 is never read
      "no-useless-assignment": "error",

      // Forbid variable shadowing — clearer reads, fewer accidental rebinds
      "no-shadow": "error",

      // Catch `return a = b` (almost always unintentional) and lexical declarations
      // floating in switch cases (which leak across cases without block scoping)
      "no-return-assign": "error",
      "no-case-declarations": "error",

      // Always pass radix to parseInt — explicit base avoids surprise
      "radix": "error",

      // Readability: drop pointless `else` after `return`, fold `else { if }` into `else if`
      "no-else-return": "error",
      "no-lonely-if": "error",

      // Catch bare expression statements like `foo.bar;` (probably meant `foo.bar()`).
      // Chai-friendly variant exempts `expect(x).to.be.true` style assertions.
      "no-unused-expressions": "off",
      "chai-friendly/no-unused-expressions": "error",

      // Force SafeLogger usage in browser code — raw console.* leaks
      // sensitive data to client-side logs in production. SafeLogger
      // (security/safe-logger.js) sanitises output. Tests and the
      // SafeLogger module itself are exempted via overrides below.
      "no-console": "error",

      // Bug-detection trio:
      // - consistent-return: every return path returns the same shape so
      //   callers don't have to special-case undefined
      // - no-loop-func: closures inside a loop that capture mutable loop
      //   state — classic stale-reference hazard
      // - require-atomic-updates: state updated based on a pre-await read
      //   is racy if the variable can change during the await
      "consistent-return": "error",
      "no-loop-func": "error",
      "require-atomic-updates": "error",
    },

    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module",
      globals: {
        // Browser globals
        window: "readonly",
        document: "readonly",
        console: "readonly",
        setTimeout: "readonly",
        setInterval: "readonly",
        clearTimeout: "readonly",
        clearInterval: "readonly",
        fetch: "readonly",
        URL: "readonly",
        URLSearchParams: "readonly",
        FormData: "readonly",
        Blob: "readonly",
        File: "readonly",
        FileReader: "readonly",
        localStorage: "readonly",
        sessionStorage: "readonly",
        navigator: "readonly",
        location: "readonly",
        history: "readonly",
        CustomEvent: "readonly",
        Event: "readonly",
        EventTarget: "readonly",
        HTMLElement: "readonly",
        Element: "readonly",
        Node: "readonly",
        NodeList: "readonly",
        MutationObserver: "readonly",
        ResizeObserver: "readonly",
        IntersectionObserver: "readonly",
        requestAnimationFrame: "readonly",
        cancelAnimationFrame: "readonly",
        requestIdleCallback: "readonly",
        performance: "readonly",
        // Browser dialogs and built-in APIs
        alert: "readonly",
        confirm: "readonly",
        prompt: "readonly",
        AbortController: "readonly",
        getComputedStyle: "readonly",
        XMLHttpRequest: "readonly",
        Image: "readonly",
        Notification: "readonly",
        CSS: "readonly",
        // The deprecated `event` magic global — IE-era leftover. Some inline
        // template handlers still use it; mark readonly so the rule doesn't
        // flag them while we phase that out.
        event: "readonly",
        // Project globals — all exposed via window.<name> = ... at module init.
        // SafeLogger: security/safe-logger.js (sanitises console output)
        // URLBuilder, URLS, URLValidator: URL helpers loaded in base.html
        // ResearchStates: config/constants.js — research-status state machine
        // safeFetch: utils/safe-fetch.js — fetch wrapper with URL validation
        // escapeHtml, sanitizeHtml: security/xss-protection.js — XSS escaping
        // LDR_CONSTANTS: config/constants.js — shared frontend constants
        // DeleteManager: deletion/delete_manager.js — deletion UI helper
        SafeLogger: "readonly",
        URLBuilder: "readonly",
        URLS: "readonly",
        URLValidator: "readonly",
        ResearchStates: "readonly",
        safeFetch: "readonly",
        escapeHtml: "readonly",
        sanitizeHtml: "readonly",
        LDR_CONSTANTS: "readonly",
        DeleteManager: "readonly",
        // Top-level helper in services/ui.js used as a fallback before
        // window.escapeHtml is loaded.
        escapeHtmlFallback: "readonly",
        // Helpers exposed via window.X = ... in components/news.js
        showModal: "readonly",
        hideModal: "readonly",
        showEmptyState: "readonly",
        showLoadingState: "readonly",
        showErrorState: "readonly",
        showMessage: "readonly",
        formatTimeAgo: "readonly",
        createTag: "readonly",
        debounce: "readonly",
        // Markdown helper in services/ui.js
        renderMarkdown: "readonly",
        // Page-injected globals from Jinja template inline scripts
        COLLECTION_ID: "readonly",
        DEFAULT_LIBRARY_COLLECTION_ID: "readonly",
        COLLECTIONS_DATA: "readonly",
        // Third-party libs loaded via <script> tags in base.html / per-page templates
        bootstrap: "readonly",
        Chart: "readonly",
        DOMPurify: "readonly",
        marked: "readonly",
        Prism: "readonly",
        jsPDF: "readonly",
        html2canvas: "readonly",
        io: "readonly",
        // Node.js globals (for config files)
        module: "readonly",
        require: "readonly",
        process: "readonly",
        __dirname: "readonly",
        __filename: "readonly",
        exports: "readonly",
        Buffer: "readonly",
        global: "readonly",
      },
    },
  },

  // SafeLogger is the canonical console.* wrapper — it MUST use raw
  // console internally. Same pattern the pre-commit hook applies.
  {
    files: ["**/security/safe-logger.js"],
    rules: {
      "no-console": "off",
    },
  },

  // Tests run in Node (Puppeteer/Playwright/vitest harnesses) where
  // SafeLogger isn't loaded. Plain console.* is the right choice there.
  // Matches the pre-commit hook's exclude pattern.
  {
    files: [
      "tests/**/*.js",
      "tests/**/*.mjs",
      "**/*.test.js",
      "**/*.spec.js",
    ],
    rules: {
      "no-console": "off",
    },
    languageOptions: {
      globals: {
        // Test framework globals (vitest / jest / mocha-style)
        describe: "readonly",
        it: "readonly",
        test: "readonly",
        expect: "readonly",
        vi: "readonly",
        jest: "readonly",
        beforeEach: "readonly",
        afterEach: "readonly",
        beforeAll: "readonly",
        afterAll: "readonly",
        before: "readonly",
        after: "readonly",
        // Browser globals used inside page.evaluate(...) callbacks — they
        // run in the browser context, not Node.
        $: "readonly",                 // jQuery, when loaded on the page
        KeyboardEvent: "readonly",
        MouseEvent: "readonly",
        Response: "readonly",
        CSSRule: "readonly",
        // Page-defined functions referenced from page.evaluate() with a
        // `typeof X === 'function'` guard
        handleTestRun: "readonly",
      },
    },
  },
];
