/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

/**
 * File-specific config overrides and shared config objects.
 *
 * This module contains ESLint configurations that apply to specific file patterns or
 * provide specialized behavior:
 *
 * - dependConfig: eslint-plugin-depend configuration for dependency checking
 * - useProjectService: TypeScript project service for automatic tsconfig discovery
 * - testProjectConfig: Test file configuration with explicit project paths and relaxed rules
 * - internalModulesConfig: import-x/no-internal-modules rule for production code
 * - reactConfig: React and React Hooks plugin configurations
 * - cjsFileConfig: CommonJS file rule overrides
 * - jsNoProject: Disables type-aware parsing for JS and .d.ts files
 * - jsTypeAwareDisable: Disables type-aware rules for JS files
 * - reactRecommendedOverride: React file overrides for recommended config factory
 * - testRecommendedOverride: Test file overrides for recommended config
 * - sharedConfigs: Collection of all shared configs in a config array
 */

import eslintReact from "@eslint-react/eslint-plugin";
import dependPlugin from "eslint-plugin-depend";
import noOnlyTestsPlugin from "eslint-plugin-no-only-tests";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import type { ESLint, Linter } from "eslint";

import { permittedImports, restrictedImportPaths, testFilePatterns } from "../constants.mjs";
import type { FlatConfigArray } from "./base.mjs";

const reactFilePatterns = ["**/*.jsx", "**/*.tsx"] as const;
const reactRecommendedTypeScript = eslintReact.configs[
	"recommended-typescript"
] satisfies Linter.Config;
const reactHooksRecommended = reactHooksPlugin.configs.flat.recommended satisfies Linter.Config;

/**
 * eslint-plugin-depend configuration.
 */
export const dependConfig = {
	plugins: {
		depend: dependPlugin,
	},
	rules: {
		"depend/ban-dependencies": [
			"error",
			{
				allowed: ["axios", "fs-extra"],
			},
		],
	},
} as const satisfies Linter.Config;

/**
 * Use projectService for automatic tsconfig discovery instead of manual project configuration.
 */
export const useProjectService = {
	files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
	languageOptions: {
		parserOptions: {
			// Explicitly clear any inherited project setting to avoid conflicts with projectService.
			// Newer typescript-eslint versions error if both project and projectService are enabled.
			project: null,
			projectService: true,
			// Set tsconfigRootDir to the current working directory so the parser can locate tsconfig
			// files unambiguously. Without this, typescript-eslint tries to auto-detect the root by
			// inspecting the call stack for the nearest eslint.config file. In this monorepo the
			// shared config lives in a different directory than the consuming packages, and VS Code's
			// TypeScript project service can accumulate multiple candidate roots as files from
			// different packages are visited, leading to "multiple candidate TSConfigRootDirs" errors.
			tsconfigRootDir: process.cwd(),
		},
	},
} as const satisfies Linter.Config;

/**
 * Test file configuration with explicit project paths and rule overrides.
 */
export const testProjectConfig = {
	files: ["src/test/**", ...testFilePatterns],
	plugins: {
		"no-only-tests": noOnlyTestsPlugin,
	},
	rules: {
		/**
		 * Disallow .only() in tests (e.g. describe.only, it.only) to prevent accidentally
		 * committing focused tests that would skip the rest of the suite in CI.
		 *
		 * @see https://github.com/levibuzolic/eslint-plugin-no-only-tests
		 */
		"no-only-tests/no-only-tests": "error",
		"@typescript-eslint/no-invalid-this": "off",
		"@typescript-eslint/unbound-method": "off",
		"import-x/no-nodejs-modules": "off",
		"import-x/no-deprecated": "off",
		"@typescript-eslint/consistent-type-exports": "off",
		"@typescript-eslint/consistent-type-imports": "off",
		"@typescript-eslint/no-restricted-imports": [
			"error",
			{
				paths: restrictedImportPaths,
			},
		],
		"import-x/no-internal-modules": [
			"error",
			{
				// Any and all `@fluid*` import paths are allowed in test files.
				// Preferably, external (alpha/beta/public) entrypoints are used
				// for clarity where testing is somewhat whitebox versus validating
				// customer experience.
				allow: ["@fluid*/**", ...permittedImports],
			},
		],
		"import-x/no-extraneous-dependencies": ["error", { devDependencies: true }],
	},
} as const satisfies Linter.Config;

/**
 * Override import-x/no-internal-modules for non-test files to include /legacy imports.
 */
export const internalModulesConfig = {
	files: [
		"**/*.ts",
		"**/*.tsx",
		"**/*.mts",
		"**/*.cts",
		"**/*.js",
		"**/*.jsx",
		"**/*.mjs",
		"**/*.cjs",
	],
	ignores: ["src/test/**", ...testFilePatterns],
	rules: {
		"import-x/no-internal-modules": [
			"error",
			{
				allow: permittedImports,
			},
		],
	},
} as const satisfies Linter.Config;

/**
 * React rules - extends @eslint-react recommended-typescript and react-hooks/recommended.
 *
 * Uses @eslint-react/eslint-plugin instead of eslint-plugin-react for ESLint 10 compatibility.
 * eslint-plugin-react-hooks is kept separately because it provides React Compiler rules
 * and already supports ESLint 10.
 */
export const reactConfig = [
	// @eslint-react recommended-typescript preset
	{
		...reactRecommendedTypeScript,
		files: [...reactFilePatterns],
		rules: {
			...reactRecommendedTypeScript.rules,
			"@eslint-react/dom/no-unsafe-target-blank": "error",
			"@eslint-react/no-children-prop": "error",
			"@eslint-react/no-useless-fragment": "error",
			"@eslint-react/jsx-no-comment-textnodes": "error",
		},
	},
	// react-hooks/recommended rules with custom overrides
	{
		...reactHooksRecommended,
		files: [...reactFilePatterns],
		plugins: {
			...reactHooksRecommended.plugins,
			// reactHooksPlugin.configs.flat does not conform. It is not a `ConfigObject`.
			"react-hooks": reactHooksPlugin as ESLint.Plugin,
		},
		rules: {
			...reactHooksRecommended.rules,
			"react-hooks/immutability": "warn",
			"react-hooks/refs": "warn",
			"react-hooks/rules-of-hooks": "warn",
			"react-hooks/set-state-in-effect": "warn",
			"react-hooks/static-components": "warn",
		},
	},
] as const satisfies FlatConfigArray;

/**
 * CommonJS files can use __dirname and require.
 */
export const cjsFileConfig = {
	files: ["**/*.cts", "**/*.cjs"],
	rules: {
		"unicorn/prefer-module": "off",
	},
} as const satisfies Linter.Config;

/**
 * Disable type-aware parsing for JS files and .d.ts files.
 */
export const jsNoProject = {
	files: ["**/*.js", "**/*.cjs", "**/*.mjs", "**/*.d.ts"],
	languageOptions: { parserOptions: { project: null, projectService: false } },
} as const satisfies Linter.Config;

/**
 * Disable type-required @typescript-eslint rules for pure JS files and .d.ts files.
 */
export const jsTypeAwareDisable = {
	files: ["**/*.js", "**/*.cjs", "**/*.mjs", "**/*.d.ts"],
	rules: {
		"@typescript-eslint/await-thenable": "off",
		"@typescript-eslint/consistent-return": "off",
		"@typescript-eslint/consistent-type-exports": "off",
		"@typescript-eslint/dot-notation": "off",
		"@typescript-eslint/naming-convention": "off",
		"@typescript-eslint/no-array-delete": "off",
		"@typescript-eslint/no-base-to-string": "off",
		"@typescript-eslint/no-confusing-void-expression": "off",
		"@typescript-eslint/no-deprecated": "off",
		"@typescript-eslint/no-duplicate-type-constituents": "off",
		"@typescript-eslint/no-floating-promises": "off",
		"@typescript-eslint/no-for-in-array": "off",
		"@typescript-eslint/no-implied-eval": "off",
		"@typescript-eslint/no-meaningless-void-operator": "off",
		"@typescript-eslint/no-misused-promises": "off",
		"@typescript-eslint/no-mixed-enums": "off",
		"@typescript-eslint/no-redundant-type-constituents": "off",
		"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
		"@typescript-eslint/no-unnecessary-condition": "off",
		"@typescript-eslint/no-unnecessary-qualifier": "off",
		"@typescript-eslint/no-unnecessary-template-expression": "off",
		"@typescript-eslint/no-unnecessary-type-arguments": "off",
		"@typescript-eslint/no-unnecessary-type-assertion": "off",
		"@typescript-eslint/no-unnecessary-type-parameters": "off",
		"@typescript-eslint/no-unsafe-argument": "off",
		"@typescript-eslint/no-unsafe-assignment": "off",
		"@typescript-eslint/no-unsafe-call": "off",
		"@typescript-eslint/no-unsafe-enum-comparison": "off",
		"@typescript-eslint/no-unsafe-member-access": "off",
		"@typescript-eslint/no-unsafe-return": "off",
		"@typescript-eslint/no-unsafe-type-assertion": "off",
		"@typescript-eslint/no-unsafe-unary-minus": "off",
		"@typescript-eslint/non-nullable-type-assertion-style": "off",
		"@typescript-eslint/only-throw-error": "off",
		"@typescript-eslint/prefer-destructuring": "off",
		"@typescript-eslint/prefer-find": "off",
		"@typescript-eslint/prefer-includes": "off",
		"@typescript-eslint/prefer-nullish-coalescing": "off",
		"@typescript-eslint/prefer-optional-chain": "off",
		"@typescript-eslint/prefer-promise-reject-errors": "off",
		"@typescript-eslint/prefer-readonly": "off",
		"@typescript-eslint/prefer-readonly-parameter-types": "off",
		"@typescript-eslint/prefer-reduce-type-parameter": "off",
		"@typescript-eslint/prefer-regexp-exec": "off",
		"@typescript-eslint/prefer-return-this-type": "off",
		"@typescript-eslint/prefer-string-starts-ends-with": "off",
		"@typescript-eslint/promise-function-async": "off",
		"@typescript-eslint/related-getter-setter-pairs": "off",
		"@typescript-eslint/require-array-sort-compare": "off",
		"@typescript-eslint/require-await": "off",
		"@typescript-eslint/restrict-plus-operands": "off",
		"@typescript-eslint/restrict-template-expressions": "off",
		"@typescript-eslint/return-await": "off",
		"@typescript-eslint/strict-boolean-expressions": "off",
		"@typescript-eslint/switch-exhaustiveness-check": "off",
		"@typescript-eslint/unbound-method": "off",
		"@typescript-eslint/use-unknown-in-catch-callback-variable": "off",
	},
} as const satisfies Linter.Config;

/**
 * React file overrides for recommended config factory.
 */
export const reactRecommendedOverride = {
	files: [...reactFilePatterns],
	rules: {
		"unicorn/consistent-function-scoping": "off",
	},
} as const satisfies Linter.Config;

/**
 * Test file overrides for recommended config factory.
 */
export const testRecommendedOverride = {
	// Use of spread operator shouldn't really be needed here. Under VS Code, a
	// complaint is raised that
	//   The type 'readonly [...]' is 'readonly' and cannot be assigned to the mutable type '(string | string[])[]'.ts(4104)
	// without spread. But that doesn't appear in other uses. Use spread to pacify
	// that environment. (Remember mutability is not well checked in TS generally.
	// So an extra copy if safety was needed isn't a problem.)
	files: [...testFilePatterns],
	rules: {
		"unicorn/consistent-function-scoping": "off",
		"unicorn/prefer-module": "off",
	},
} as const satisfies Linter.Config;

/**
 * Full set of shared configuration objects in config array.
 */
export const sharedConfigs = [
	useProjectService,
	testProjectConfig,
	internalModulesConfig,
	...reactConfig,
	cjsFileConfig,
	jsNoProject,
	jsTypeAwareDisable,
] as const satisfies FlatConfigArray;
