// @ts-check
import eslint from "@eslint/js";
import * as regexpPlugin from "eslint-plugin-regexp";
import fs from "fs";
import globals from "globals";
import { createRequire } from "module";
import path from "path";
import tseslint from "typescript-eslint";
import url from "url";

const __filename = url.fileURLToPath(new URL(import.meta.url));
const __dirname = path.dirname(__filename);

const require = createRequire(import.meta.url);

const rulesDir = path.join(__dirname, "scripts", "eslint", "rules");
const ext = ".cjs";
const ruleFiles = fs.readdirSync(rulesDir).filter(p => p.endsWith(ext));

export default tseslint.config(
    {
        files: ["**/*.{ts,tsx,cts,mts,js,cjs,mjs}"],
    },
    {
        ignores: [
            "**/node_modules/**",
            "built/**",
            "tests/**",
            "lib/**",
            "src/lib/*.generated.d.ts",
            "scripts/**/*.js",
            "scripts/**/*.d.*",
            "internal/**",
            "coverage/**",
        ],
    },
    eslint.configs.recommended,
    ...tseslint.configs.recommended,
    ...tseslint.configs.stylistic,
    regexpPlugin.configs.recommended,
    {
        plugins: {
            local: {
                rules: Object.fromEntries(ruleFiles.map(p => {
                    return [p.slice(0, -ext.length), require(path.join(rulesDir, p))];
                })),
            },
        },
    },
    {
        languageOptions: {
            parserOptions: {
                warnOnUnsupportedTypeScriptVersion: false,
            },
            globals: globals.node,
        },
    },
    {
        rules: {
            // eslint
            "dot-notation": "error",
            "eqeqeq": "error",
            "no-caller": "error",
            "no-constant-condition": ["error", { checkLoops: false }],
            "no-eval": "error",
            "no-extra-bind": "error",
            "no-new-func": "error",
            "no-new-wrappers": "error",
            "no-return-await": "error",
            "no-template-curly-in-string": "error",
            "no-throw-literal": "error",
            "no-undef-init": "error",
            "no-var": "error",
            "object-shorthand": "error",
            "prefer-const": "error",
            "prefer-object-spread": "error",
            "unicode-bom": ["error", "never"],

            "no-restricted-syntax": [
                "error",
                {
                    selector: "Literal[raw=null]",
                    message: "Avoid using null; use undefined instead.",
                },
                {
                    selector: "TSNullKeyword",
                    message: "Avoid using null; use undefined instead.",
                },
            ],

            // Enabled in eslint:recommended, but not applicable here
            "no-extra-boolean-cast": "off",
            "no-case-declarations": "off",
            "no-cond-assign": "off",
            "no-control-regex": "off",
            "no-inner-declarations": "off",
            "no-useless-assignment": "off",
            "no-unassigned-vars": "off",
            "preserve-caught-error": "off",

            // @typescript-eslint/eslint-plugin
            "@typescript-eslint/naming-convention": [
                "error",
                { selector: "typeLike", format: ["PascalCase"], filter: { regex: "^(__String|[A-Za-z]+_[A-Za-z]+)$", match: false } },
                { selector: "interface", format: ["PascalCase"], custom: { regex: "^I[A-Z]", match: false }, filter: { regex: "^I(Arguments|TextWriter|O([A-Z][a-z]+[A-Za-z]*)?)$", match: false } },
                { selector: "variable", format: ["camelCase", "PascalCase", "UPPER_CASE"], leadingUnderscore: "allow", filter: { regex: "^(_{1,2}filename|_{1,2}dirname|_+|[A-Za-z]+_[A-Za-z]+)$", match: false } },
                { selector: "function", format: ["camelCase", "PascalCase"], leadingUnderscore: "allow", filter: { regex: "^[A-Za-z]+_[A-Za-z]+$", match: false } },
                { selector: "parameter", format: ["camelCase"], leadingUnderscore: "allow", filter: { regex: "^(_+|[A-Za-z]+_[A-Z][a-z]+)$", match: false } },
                { selector: "method", format: ["camelCase", "PascalCase"], leadingUnderscore: "allow", filter: { regex: "^([0-9]+|[A-Za-z]+_[A-Za-z]+)$", match: false } },
                { selector: "memberLike", format: ["camelCase"], leadingUnderscore: "allow", filter: { regex: "^([0-9]+|[A-Za-z]+_[A-Za-z]+)$", match: false } },
                { selector: "enumMember", format: ["camelCase", "PascalCase"], leadingUnderscore: "allow", filter: { regex: "^[A-Za-z]+_[A-Za-z]+$", match: false } },
                // eslint-disable-next-line no-restricted-syntax
                { selector: "property", format: null },
            ],

            "@typescript-eslint/unified-signatures": "error",
            "no-unused-expressions": "off",
            "@typescript-eslint/no-unused-expressions": ["error", { allowTernary: true }],

            // Rules enabled in typescript-eslint configs that are not applicable here
            "@typescript-eslint/ban-ts-comment": "off",
            "@typescript-eslint/class-literal-property-style": "off",
            "@typescript-eslint/consistent-indexed-object-style": "off",
            "@typescript-eslint/consistent-generic-constructors": "off",
            "@typescript-eslint/no-duplicate-enum-values": "off",
            "@typescript-eslint/no-empty-function": "off",
            "@typescript-eslint/no-namespace": "off",
            "@typescript-eslint/no-non-null-asserted-optional-chain": "off",
            "@typescript-eslint/no-var-requires": "off",
            "@typescript-eslint/no-empty-interface": "off",
            "@typescript-eslint/no-explicit-any": "off",
            "@typescript-eslint/no-empty-object-type": "off", // {} is a totally useful and valid type.
            "@typescript-eslint/no-require-imports": "off",
            "@typescript-eslint/no-unused-vars": [
                "warn",
                {
                    // Ignore: (solely underscores | starting with exactly one underscore)
                    argsIgnorePattern: "^(_+$|_[^_])",
                    varsIgnorePattern: "^(_+$|_[^_])",
                    // Not setting an ignore pattern for caught errors; those can always be safely removed.
                },
            ],
            "@typescript-eslint/no-inferrable-types": "off",

            // Pending https://github.com/typescript-eslint/typescript-eslint/issues/4820
            "@typescript-eslint/prefer-optional-chain": "off",

            // scripts/eslint/rules
            "local/only-arrow-functions": [
                "error",
                {
                    allowNamedFunctions: true,
                    allowDeclarations: true,
                },
            ],
            "local/argument-trivia": "error",
            "local/no-in-operator": "error",
            "local/debug-assert": "error",
            "local/no-keywords": "error",
            "local/jsdoc-format": "error",
            "local/js-extensions": "error",
            "local/no-array-mutating-method-expressions": "error",
        },
    },
    {
        files: ["**/*.mjs", "**/*.mts"],
        rules: {
            // These globals don't exist outside of CJS files.
            "no-restricted-globals": [
                "error",
                { name: "__filename" },
                { name: "__dirname" },
                { name: "require" },
                { name: "module" },
                { name: "exports" },
            ],
        },
    },
    {
        files: ["src/**"],
        languageOptions: {
            parserOptions: {
                tsconfigRootDir: __dirname,
                project: "./src/tsconfig-eslint.json",
            },
        },
    },
    {
        files: ["scripts/**"],
        languageOptions: {
            parserOptions: {
                tsconfigRootDir: __dirname,
                project: "./scripts/tsconfig.json",
            },
        },
    },
    {
        files: ["src/**"],
        rules: {
            "@typescript-eslint/no-unnecessary-type-assertion": "error",
            "no-restricted-globals": [
                "error",
                { name: "setTimeout" },
                { name: "clearTimeout" },
                { name: "setInterval" },
                { name: "clearInterval" },
                { name: "setImmediate" },
                { name: "clearImmediate" },
                { name: "performance" },
            ],
            "local/no-direct-import": "error",
        },
    },
    {
        files: ["src/harness/**", "src/testRunner/**"],
        rules: {
            "no-restricted-globals": "off",
            "regexp/no-super-linear-backtracking": "off",
            "local/no-direct-import": "off",
        },
    },
    {
        files: ["src/**/_namespaces/**"],
        rules: {
            "local/no-direct-import": "off",
        },
    },
    {
        files: ["src/lib/*.d.ts"],
        ...tseslint.configs.disableTypeChecked,
    },
    {
        files: ["src/lib/*.d.ts"],
        languageOptions: {
            globals: {},
        },
        rules: {
            "@typescript-eslint/interface-name-prefix": "off",
            "@typescript-eslint/prefer-function-type": "off",
            "@typescript-eslint/unified-signatures": "off",
            "@typescript-eslint/no-unsafe-function-type": "off",
            "@typescript-eslint/no-wrapper-object-types": "off",
            "@typescript-eslint/no-unused-vars": "off",

            // scripts/eslint/rules
            "local/no-keywords": "off",

            // eslint
            "no-var": "off",
            "no-restricted-globals": "off",
            "no-shadow-restricted-names": "off",
            "no-restricted-syntax": "off",
        },
    },
    {
        files: ["src/lib/es2019.array.d.ts"],
        rules: {
            "@typescript-eslint/array-type": "off",
        },
    },
);
