// see the comment in config-helper.ts for why this doesn't use /ts-eslint
import type { TSESLint } from '@typescript-eslint/utils';
import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';

import pluginBase from '@typescript-eslint/eslint-plugin';
import rawPlugin from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/raw-plugin';
import { addCandidateTSConfigRootDir } from '@typescript-eslint/typescript-estree';

import type {
  CompatibleConfig,
  CompatibleConfigArray,
  CompatibleParser,
  CompatiblePlugin,
} from './compatibility-types';

import { config } from './config-helper';
import { getTSConfigRootDirFromStack } from './getTSConfigRootDirFromStack';

export type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';

export const parser: CompatibleParser =
  rawPlugin.parser as CompatibleParser satisfies FlatConfig.Parser;

/*
we could build a plugin object here without the `configs` key - but if we do
that then we create a situation in which
```
require('typescript-eslint').plugin !== require('@typescript-eslint/eslint-plugin')
```

This is bad because it means that 3rd party configs would be required to use
`typescript-eslint` or else they would break a user's config if the user either
used `tseslint.configs.recommended` et al or
```
{
  plugins: {
    '@typescript-eslint': tseslint.plugin,
  },
}
```

This might be something we could consider okay (eg 3rd party flat configs must
use our new package); however legacy configs consumed via `@eslint/eslintrc`
would never be able to satisfy this constraint and thus users would be blocked
from using them.
*/
export const plugin: CompatiblePlugin =
  pluginBase satisfies FlatConfig.Plugins['string'];

export const configs = createConfigsGetters({
  /**
   * Enables each the rules provided as a part of typescript-eslint. Note that many rules are not applicable in all codebases, or are meant to be configured.
   * @see {@link https://typescript-eslint.io/users/configs#all}
   */
  all: rawPlugin.flatConfigs['flat/all'] as CompatibleConfigArray,

  /**
   * A minimal ruleset that sets only the required parser and plugin options needed to run typescript-eslint.
   * We don't recommend using this directly; instead, extend from an earlier recommended rule.
   * @see {@link https://typescript-eslint.io/users/configs#base}
   */
  base: rawPlugin.flatConfigs['flat/base'] as CompatibleConfig,

  /**
   * A utility ruleset that will disable type-aware linting and all type-aware rules available in our project.
   * @see {@link https://typescript-eslint.io/users/configs#disable-type-checked}
   */
  disableTypeChecked: rawPlugin.flatConfigs[
    'flat/disable-type-checked'
  ] as CompatibleConfig,

  /**
   * This is a compatibility ruleset that:
   * - disables rules from eslint:recommended which are already handled by TypeScript.
   * - enables rules that make sense due to TS's typechecking / transpilation.
   * @see {@link https://typescript-eslint.io/users/configs/#eslint-recommended}
   */
  eslintRecommended: rawPlugin.flatConfigs[
    'flat/eslint-recommended'
  ] as CompatibleConfig,

  /**
   * Recommended rules for code correctness that you can drop in without additional configuration.
   * @see {@link https://typescript-eslint.io/users/configs#recommended}
   */
  recommended: rawPlugin.flatConfigs[
    'flat/recommended'
  ] as CompatibleConfigArray,

  /**
   * Contains all of `recommended` along with additional recommended rules that require type information.
   * @see {@link https://typescript-eslint.io/users/configs#recommended-type-checked}
   */
  recommendedTypeChecked: rawPlugin.flatConfigs[
    'flat/recommended-type-checked'
  ] as CompatibleConfigArray,

  /**
   * A version of `recommended` that only contains type-checked rules and disables of any corresponding core ESLint rules.
   * @see {@link https://typescript-eslint.io/users/configs#recommended-type-checked-only}
   */
  recommendedTypeCheckedOnly: rawPlugin.flatConfigs[
    'flat/recommended-type-checked-only'
  ] as CompatibleConfigArray,

  /**
   * Contains all of `recommended`, as well as additional strict rules that can also catch bugs.
   * @see {@link https://typescript-eslint.io/users/configs#strict}
   */
  strict: rawPlugin.flatConfigs['flat/strict'] as CompatibleConfigArray,

  /**
   * Contains all of `recommended`, `recommended-type-checked`, and `strict`, along with additional strict rules that require type information.
   * @see {@link https://typescript-eslint.io/users/configs#strict-type-checked}
   */
  strictTypeChecked: rawPlugin.flatConfigs[
    'flat/strict-type-checked'
  ] as CompatibleConfigArray,

  /**
   * A version of `strict` that only contains type-checked rules and disables of any corresponding core ESLint rules.
   * @see {@link https://typescript-eslint.io/users/configs#strict-type-checked-only}
   */
  strictTypeCheckedOnly: rawPlugin.flatConfigs[
    'flat/strict-type-checked-only'
  ] as CompatibleConfigArray,

  /**
   * Rules considered to be best practice for modern TypeScript codebases, but that do not impact program logic.
   * @see {@link https://typescript-eslint.io/users/configs#stylistic}
   */
  stylistic: rawPlugin.flatConfigs['flat/stylistic'] as CompatibleConfigArray,

  /**
   * Contains all of `stylistic`, along with additional stylistic rules that require type information.
   * @see {@link https://typescript-eslint.io/users/configs#stylistic-type-checked}
   */
  stylisticTypeChecked: rawPlugin.flatConfigs[
    'flat/stylistic-type-checked'
  ] as CompatibleConfigArray,

  /**
   * A version of `stylistic` that only contains type-checked rules and disables of any corresponding core ESLint rules.
   * @see {@link https://typescript-eslint.io/users/configs#stylistic-type-checked-only}
   */
  stylisticTypeCheckedOnly: rawPlugin.flatConfigs[
    'flat/stylistic-type-checked-only'
  ] as CompatibleConfigArray,
}) satisfies Record<string, FlatConfig.Config | FlatConfig.ConfigArray>;

function createConfigsGetters<T extends object>(values: T): T {
  const configs = {};

  Object.defineProperties(
    configs,
    Object.fromEntries(
      Object.entries(values).map(([key, value]: [string, unknown]) => [
        key,
        {
          enumerable: true,
          get: () => {
            const candidateRootDir = getTSConfigRootDirFromStack();
            if (candidateRootDir) {
              addCandidateTSConfigRootDir(candidateRootDir);
            }

            return value;
          },
        },
      ]),
    ),
  );

  return configs as T;
}

export type Config = TSESLint.FlatConfig.ConfigFile;

/*
we do both a default and named exports to allow people to use this package from
both CJS and ESM in very natural ways.

EG it means that all of the following are valid:

```ts
import tseslint from 'typescript-eslint';

export default tseslint.config(
  ...tseslint.configs.recommended,
);
```
```ts
import { config, parser, plugin } from 'typescript-eslint';

export default config(
  {
    languageOptions: { parser }
    plugins: { ts: plugin },
  }
);
```
```ts
const tseslint = require('typescript-eslint');

module.exports = tseslint.config(
  ...tseslint.configs.recommended,
);
```
```ts
const { config, parser, plugin } = require('typescript-eslint');

module.exports = config(
  {
    languageOptions: { parser }
    plugins: { ts: plugin },
  }
);
```
*/
export default {
  config,
  configs,
  parser,
  plugin,
};

export {
  // eslint-disable-next-line @typescript-eslint/no-deprecated
  config,
  type ConfigWithExtends,
  type InfiniteDepthConfigWithExtends,
  type ConfigArray,
} from './config-helper';

// https://github.com/typescript-eslint/typescript-eslint/issues/12134
export type {
  CompatibleConfig,
  CompatibleConfigArray,
  CompatibleParser,
  CompatiblePlugin,
} from './compatibility-types';
