import type { Config as FakeTimersConfig } from '@sinonjs/fake-timers'
import type { PrettyFormatOptions } from '@vitest/pretty-format'
import type { SnapshotStateOptions } from '@vitest/snapshot'
import type { Arrayable } from '@vitest/utils'
import type { SerializedDiffOptions } from '@vitest/utils/diff'
import type { AliasOptions, ConfigEnv, DepOptimizationConfig, ResolvedConfig as ResolvedViteConfig, ServerOptions, UserConfig as ViteUserConfig } from 'vite'
import type { ChaiConfig } from '../../integrations/chai/config'
import type { SerializedConfig } from '../../runtime/config'
import type { SequenceHooks, SequenceSetupFiles, SerializableRetry, TestTagDefinition } from '../../runtime/runner/types'
import type { LabelColor, ParsedStack, ProvidedContext, TestError } from '../../types/general'
import type { HappyDOMOptions } from '../../types/happy-dom-options'
import type { JSDOMOptions } from '../../types/jsdom-options'
import type { CliOptions } from '../cli/cli-api'
import type { PoolRunnerInitializer } from '../pools/types'
import type {
  BuiltinReporterOptions,
  BuiltinReporters,
} from '../reporters'
import type { TestCase, TestModule, TestSuite } from '../reporters/reported-tasks'
import type { TestSequencerConstructor } from '../sequencers/types'
import type { VCSProvider } from '../vcs/vcs'
import type { WatcherTriggerPattern } from '../watcher'
import type { BenchmarkUserOptions, ResolvedBenchmarkOptions } from './benchmark'
import type { BrowserConfigOptions, BrowserServerContribution, ResolvedBrowserOptions } from './browser'
import type { CoverageOptions, ResolvedCoverageOptions } from './coverage'
import type { Reporter } from './reporter'

export type { CoverageOptions, ResolvedCoverageOptions }
export type { BenchmarkUserOptions, ResolvedBenchmarkOptions }
export type { RuntimeConfig, SerializedConfig } from '../../runtime/config'
export type { SequenceHooks, SequenceSetupFiles } from '../../runtime/runner/types'
export type { BrowserConfigOptions, BrowserInstanceOption, BrowserScript } from './browser'
export type { CoverageIstanbulOptions, CoverageV8Options } from './coverage'

export type BuiltinEnvironment
  = | 'node'
    | 'jsdom'
    | 'happy-dom'
    | 'edge-runtime'
// Record is used, so user can get intellisense for builtin environments, but still allow custom environments
export type VitestEnvironment
  = | BuiltinEnvironment
    | (string & Record<never, never>)
export type CSSModuleScopeStrategy = 'stable' | 'scoped' | 'non-scoped'

export type ApiConfig = Pick<
  ServerOptions,
  'port' | 'strictPort' | 'host' | 'middlewareMode'
> & {
  /**
   * Allow any write operations from the API server.
   *
   * @default true if `api.host` is exposed to network, false otherwise
   */
  allowWrite?: boolean
  /**
   * Allow running test files via the API.
   * If `api.host` is exposed to network and `allowWrite` is true,
   * anyone connected to the API server can run arbitrary code on your machine.
   *
   * @default true if `api.host` is exposed to network, false otherwise
   */
  allowExec?: boolean
}

export type ResolvedApiConfig = ApiConfig & { token: string; tokenCreated: boolean }

export interface EnvironmentOptions {
  /**
   * jsdom options.
   */
  jsdom?: JSDOMOptions
  happyDOM?: HappyDOMOptions
  [x: string]: unknown
}

export type { HappyDOMOptions, JSDOMOptions }

/**
 * @deprecated
 */
export type VitestRunMode = 'test'

export interface ProjectName {
  label: string
  color?: LabelColor
}

interface SequenceOptions {
  /**
   * Class that handles sorting and sharding algorithm.
   * If you only need to change sorting, you can extend
   * your custom sequencer from `BaseSequencer` from `vitest/node`.
   * @default BaseSequencer
   */
  sequencer?: TestSequencerConstructor
  /**
   * Controls the order in which this project runs its tests when using multiple [projects](/guide/projects).
   *
   * - Projects with the same group order number will run together, and groups are run from lowest to highest.
   * - If you don’t set this option, all projects run in parallel.
   * - If several projects use the same group order, they will run at the same time.
   * @default 0
   */
  groupOrder?: number
  /**
   * Should files and tests run in random order.
   * @default false
   */
  shuffle?:
    | boolean
    | {
      /**
       * Should files run in random order. Long running tests will not start
       * earlier if you enable this option.
       * @default false
       */
      files?: boolean
      /**
       * Should tests run in random order.
       * @default false
       */
      tests?: boolean
    }
  /**
   * Should tests run in parallel.
   * @default false
   */
  concurrent?: boolean
  /**
   * Defines how setup files should be ordered
   * - 'parallel' will run all setup files in parallel
   * - 'list' will run all setup files in the order they are defined in the config file
   * @default 'parallel'
   */
  setupFiles?: SequenceSetupFiles
  /**
   * Seed for the random number generator.
   * @default Date.now()
   */
  seed?: number
  /**
   * Defines how hooks should be ordered
   * - `stack` will order "after" hooks in reverse order, "before" hooks will run sequentially
   * - `list` will order hooks in the order they are defined
   * - `parallel` will run hooks in a single group in parallel
   * @default 'stack'
   */
  hooks?: SequenceHooks
}

export type DepsOptimizationOptions = Omit<
  DepOptimizationConfig,
  'disabled' | 'noDiscovery'
> & {
  enabled?: boolean
}

interface DepsOptions {
  /**
   * Enable dependency optimization. This can improve the performance of your tests.
   */
  optimizer?: Partial<Record<'client' | 'ssr' | ({} & string), DepsOptimizationOptions>>
  web?: {
    /**
     * Should Vitest process assets (.png, .svg, .jpg, etc) files and resolve them like Vite does in the browser.
     *
     * These module will have a default export equal to the path to the asset, if no query is specified.
     *
     * **At the moment, this option only works with `{ pool: 'vmThreads' }`.**
     *
     * @default true
     */
    transformAssets?: boolean
    /**
     * Should Vitest process CSS (.css, .scss, .sass, etc) files and resolve them like Vite does in the browser.
     *
     * If CSS files are disabled with `css` options, this option will just silence UNKNOWN_EXTENSION errors.
     *
     * **At the moment, this option only works with `{ pool: 'vmThreads' }`.**
     *
     * @default true
     */
    transformCss?: boolean
    /**
     * Regexp pattern to match external files that should be transformed.
     *
     * By default, files inside `node_modules` are externalized and not transformed.
     *
     * **At the moment, this option only works with `{ pool: 'vmThreads' }`.**
     *
     * @default []
     */
    transformGlobPattern?: RegExp | RegExp[]
  }

  /**
   * Interpret CJS module's default as named exports
   *
   * @default true
   */
  interopDefault?: boolean

  /**
   * A list of directories relative to the config file that should be treated as module directories.
   *
   * @default ['node_modules']
   */
  moduleDirectories?: string[]
}

type InlineReporter = Reporter
type ReporterName = BuiltinReporters | 'html' | (string & {})
type ReporterWithOptions<Name extends ReporterName = ReporterName>
  = Name extends keyof BuiltinReporterOptions
    ? BuiltinReporterOptions[Name] extends never
      ? [Name, object]
      : [Name, Partial<BuiltinReporterOptions[Name]>]
    : [Name, Record<string, unknown>]

export interface ResolveSnapshotPathHandlerContext { config: SerializedConfig }

export type ResolveSnapshotPathHandler = (
  testPath: string,
  snapExtension: string,
  context: ResolveSnapshotPathHandlerContext,
) => string

export type BuiltinPool
  = | 'browser'
    | 'threads'
    | 'forks'
    | 'vmThreads'
    | 'vmForks'
    | 'typescript'

export type Pool = BuiltinPool | (string & {})

export interface InlineConfig {
  /**
   * Name of the project. Will be used to display in the reporter.
   */
  name?: string | ProjectName

  /**
   * Benchmark options.
   *
   * @default {}
   */
  benchmark?: BenchmarkUserOptions

  /**
   * A list of [glob patterns](https://superchupu.dev/tinyglobby/comparison) that match your test files.
   *
   * @default ['**\/*.{test,spec}.?(c|m)[jt]s?(x)']
   * @see {@link https://vitest.dev/config/include}
   */
  include?: string[]

  /**
   * Exclude globs for test files
   * @default ['**\/node_modules/**', '**\/.git/**']
   */
  exclude?: string[]

  /**
   * Include globs for in-source test files
   *
   * @default []
   */
  includeSource?: string[]

  /**
   * Handling for dependencies inlining or externalizing
   *
   */
  deps?: DepsOptions

  server?: {
    deps?: ServerDepsOptions
    debug?: {
      /**
       * The folder where Vitest stores the contents of transformed
       * test files that can be inspected manually.
       *
       * If `true`, Vitest dumps the files in `.vitest-dump` folder relative to the root of the project.
       *
       * You can also use `VITEST_DEBUG_DUMP` env variable to enable this.
       */
      dump?: string | true
      /**
       * If dump is enabled, should Vitest load the files from there instead of transforming them.
       *
       * You can also use `VITEST_DEBUG_LOAD_DUMP` env variable to enable this.
       */
      load?: boolean
    }
  }

  /**
   * Base directory to scan for the test files
   *
   * @default `config.root`
   */
  dir?: string

  /**
   * Register apis globally
   *
   * @default false
   */
  globals?: boolean

  /**
   * Inject CommonJS module variables (`module`, `exports`, `require`,
   * `__filename`, `__dirname`) into every module processed by Vitest.
   *
   * When disabled, ES modules no longer have access to CommonJS variables,
   * matching how the code runs outside of Vitest. Modules detected to be
   * CommonJS keep these variables because they are part of the module scope.
   * The module type is detected the same way Node.js does it: the file
   * extension wins, then the `type` field in the nearest package.json,
   * then the presence of ESM syntax in the file.
   *
   * This option doesn't affect externalized modules which are always
   * executed by the native runtime.
   *
   * @default true
   */
  injectCjsGlobals?: boolean

  /**
   * Running environment
   *
   * Supports 'node', 'jsdom', 'happy-dom', 'edge-runtime'
   *
   * If used unsupported string, will try to load the package `vitest-environment-${env}`
   *
   * @default 'node'
   */
  environment?: VitestEnvironment

  /**
   * Environment options.
   */
  environmentOptions?: EnvironmentOptions

  /**
   * Run tests in an isolated environment. This option has no effect on vmThreads pool.
   *
   * Disabling this option improves performance if your code doesn't rely on side effects.
   *
   * @default true
   */
  isolate?: boolean

  /**
   * Pass additional arguments to `node` process when spawning the worker.
   *
   * See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information.
   *
   * Set to `process.execArgv` to pass all arguments of the current process.
   *
   * Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103
   *
   * @default [] // no execution arguments are passed
   */
  execArgv?: string[]

  /**
   * Specifies the memory limit for `worker_thread` or `child_process` before they are recycled.
   * If you see memory leaks, try to tinker this value.
   */
  vmMemoryLimit?: string | number

  /**
   * Pool used to run tests in.
   *
   * Supports 'threads', 'forks', 'vmThreads', 'vmForks'
   *
   * @default 'forks'
   */
  pool?: Exclude<Pool, 'browser'> | PoolRunnerInitializer

  /**
   * Maximum number or percentage of workers to run tests in.
   */
  maxWorkers?: number | string

  /**
   * Should all test files run in parallel. Doesn't affect tests running in the same file.
   * Setting this to `false` will override `maxWorkers` option to `1`.
   *
   * @default true
   */
  fileParallelism?: boolean

  /**
   * Options for projects
   */
  projects?: TestProjectConfiguration[]

  /**
   * Update snapshot
   *
   * @default false
   */
  update?: boolean | 'all' | 'new' | 'none'

  /**
   * Watch mode
   *
   * @default !process.env.CI
   */
  watch?: boolean

  /**
   * Project root
   *
   * @default process.cwd()
   */
  root?: string

  /**
   * Custom reporter for output. Can contain one or more built-in reporter names, reporter instances,
   * and/or paths to custom reporters.
   *
   * @default ['default'] (or ['default', 'github-actions'] when `process.env.GITHUB_ACTIONS === 'true'`)
   */
  reporters?:
    | Arrayable<ReporterName | InlineReporter>
    | (
        | (ReporterName | InlineReporter)
        | [ReporterName]
        | ReporterWithOptions
    )[]

  /**
   * Write test results to a file when the --reporter=json` or `--reporter=junit` option is also specified.
   * Also definable individually per reporter by using an object instead.
   */
  outputFile?:
    | string
    | (Partial<Record<BuiltinReporters, string>> & Record<string, string>)

  /**
   * Default timeout of a test in milliseconds
   *
   * @default 5000
   */
  testTimeout?: number

  /**
   * Default timeout of a hook in milliseconds
   *
   * @default 10000
   */
  hookTimeout?: number

  /**
   * Default timeout to wait for close when Vitest shuts down, in milliseconds
   *
   * @default 10000
   */
  teardownTimeout?: number

  /**
   * Silent mode
   *
   * Use `'passed-only'` to see logs from failing tests only.
   *
   * @default false
   */
  silent?: boolean | 'passed-only'

  /**
   * Hide logs for skipped tests
   *
   * @default false
   */
  hideSkippedTests?: boolean

  /**
   * Path to setup files
   */
  setupFiles?: string | string[]

  /**
   * Path to global setup files
   */
  globalSetup?: string | string[]

  /**
   * Glob pattern of file paths that will trigger the whole suite rerun
   *
   * Useful if you are testing calling CLI commands
   *
   * @default ['**\/package.json/**', '**\/{vitest,vite}.config.*\/**']
   */
  forceRerunTriggers?: string[]

  /**
   * Pattern configuration to rerun only the tests that are affected
   * by the changes of specific files in the repository.
   */
  watchTriggerPatterns?: WatcherTriggerPattern[]

  /**
   * Coverage options
   */
  coverage?: CoverageOptions

  /**
   * Run test names with the specified pattern
   */
  testNamePattern?: string | RegExp

  /**
   * Will call `.mockClear()` on all spies before each test
   * @default true
   */
  clearMocks?: boolean

  /**
   * Will call `.mockReset()` on all spies before each test
   * @default false
   */
  mockReset?: boolean

  /**
   * Will call `.mockRestore()` on all spies before each test
   * @default false
   */
  restoreMocks?: boolean

  /**
   * Will restore all global stubs to their original values before each test
   * @default false
   */
  unstubGlobals?: boolean

  /**
   * Will restore all env stubs to their original values before each test
   * @default false
   */
  unstubEnvs?: boolean

  /**
   * Serve API options.
   *
   * When set to true, the default port is 51204.
   *
   * @default false
   */
  api?: boolean | number | ApiConfig

  /**
   * Enable Vitest UI
   *
   * @default false
   */
  ui?: boolean

  /**
   * options for test in a browser environment
   *
   * @default false
   */
  browser?: BrowserConfigOptions

  /**
   * Open UI automatically.
   *
   * @default !process.env.CI
   */
  open?: boolean

  /**
   * Base url for the UI
   *
   * @default '/__vitest__/'
   */
  uiBase?: string

  /**
   * Format options for snapshot testing.
   */
  snapshotFormat?: Omit<PrettyFormatOptions, 'plugins' | 'compareKeys'> & {
    compareKeys?: null | undefined
  }

  /**
   * Path to a module which has a default export of diff config.
   */
  diff?: string | SerializedDiffOptions

  /**
   * Paths to snapshot serializer modules.
   */
  snapshotSerializers?: string[]

  /**
   * Resolve custom snapshot path
   */
  resolveSnapshotPath?: ResolveSnapshotPathHandler

  /**
   * Path to a custom snapshot environment module that has a default export of `SnapshotEnvironment` object.
   */
  snapshotEnvironment?: string

  /**
   * Pass with no tests
   */
  passWithNoTests?: boolean

  /**
   * Allow tests and suites that are marked as only
   *
   * @default !process.env.CI
   */
  allowOnly?: boolean

  /**
   * Show heap usage after each test. Useful for debugging memory leaks.
   */
  logHeapUsage?: boolean

  /**
   * Detect asynchronous resources leaking from the test file.
   *
   * @default false
   */
  detectAsyncLeaks?: boolean

  /**
   * Custom environment variables assigned to `process.env` before running tests.
   */
  env?: Partial<NodeJS.ProcessEnv>

  /**
   * Options for @sinon/fake-timers
   */
  fakeTimers?: FakeTimersConfig

  /**
   * Custom handler for console.log in tests.
   *
   * Return `false` to ignore the log.
   */
  onConsoleLog?: (log: string, type: 'stdout' | 'stderr', entity: TestModule | TestCase | TestSuite | undefined) => boolean | void

  /**
   * Enable stack trace filtering. If absent, all stack trace frames
   * will be shown.
   *
   * Return `false` to omit the frame.
   */
  onStackTrace?: (error: TestError, frame: ParsedStack) => boolean | void

  /**
   * A callback that can return `false` to ignore an unhandled error
   */
  onUnhandledError?: OnUnhandledErrorCallback

  /**
   * Indicates if CSS files should be processed.
   *
   * When excluded, the CSS files will be replaced with empty strings to bypass the subsequent processing.
   *
   * @default { include: [], modules: { classNameStrategy: false } }
   */
  css?:
    | boolean
    | {
      include?: RegExp | RegExp[]
      exclude?: RegExp | RegExp[]
      modules?: {
        classNameStrategy?: CSSModuleScopeStrategy
      }
    }
  /**
   * A number of tests that are allowed to run at the same time marked with `test.concurrent`.
   * @default 5
   */
  maxConcurrency?: number

  /**
   * Options for configuring cache policy.
   * @default { dir: 'node_modules/.vite/vitest/{project-hash}' }
   */
  cache?:
    | false
    | {
      /**
       * @deprecated Use Vite's "cacheDir" instead if you want to change the cache director. Note caches will be written to "cacheDir\/vitest".
       */
      dir: string
    }

  /**
   * Cache transformed modules on the file system and reuse them between reruns
   * and separate Vitest processes, which can significantly speed up cold starts.
   *
   * @default false
   */
  fsModuleCache?: boolean

  /**
   * Directory where the {@link fsModuleCache} is stored. Can be set per project;
   * projects that don't override it fall back to the root's cache directory.
   *
   * By default the cache is stored inside `node_modules` at the workspace root, so
   * that it is naturally invalidated when dependencies are reinstalled.
   *
   * @default 'node_modules/.vitest-cache'
   */
  fsModuleCachePath?: string

  /**
   * Options for configuring the order of running tests.
   */
  sequence?: SequenceOptions

  /**
   * Overrides Vite mode
   * @default 'test'
   */
  mode?: string

  /**
   * Specifies an `Object`, or an `Array` of `Object`,
   * which defines aliases used to replace values in `import` or `require` statements.
   * Will be merged with the default aliases inside `resolve.alias`.
   */
  alias?: AliasOptions

  /**
   * Ignore any unhandled errors that occur
   *
   * @default false
   */
  dangerouslyIgnoreUnhandledErrors?: boolean

  /**
   * Options for configuring typechecking test environment.
   */
  typecheck?: Partial<TypecheckConfig>

  /**
   * The number of milliseconds after which a test is considered slow and reported as such in the results.
   *
   * @default 300
   */
  slowTestThreshold?: number

  /**
   * Path to a custom test runner.
   */
  runner?: string

  /**
   * Debug tests by opening `node:inspector` in worker / child process.
   * Provides similar experience as `--inspect` Node CLI argument.
   *
   * Requires `fileParallelism: false`.
   */
  inspect?: boolean | string

  /**
   * Debug tests by opening `node:inspector` in worker / child process and wait for debugger to connect.
   * Provides similar experience as `--inspect-brk` Node CLI argument.
   *
   * Requires `fileParallelism: false`.
   */
  inspectBrk?: boolean | string

  /**
   * Inspector options. If `--inspect` or `--inspect-brk` is enabled, these options will be passed to the inspector.
   */
  inspector?: {
    /**
     * Enable inspector
     */
    enabled?: boolean
    /**
     * Port to run inspector on
     */
    port?: number
    /**
     * Host to run inspector on
     */
    host?: string
    /**
     * Wait for debugger to connect before running tests
     */
    waitForDebugger?: boolean
  }

  /**
   * Define variables that will be returned from `inject` in the test environment.
   * @example
   * ```ts
   * // vitest.config.ts
   * export default defineConfig({
   *   test: {
   *     provide: {
   *       someKey: 'someValue'
   *     }
   *   }
   * })
   * ```
   * ```ts
   * // test file
   * import { inject } from 'vitest'
   * const value = inject('someKey') // 'someValue'
   * ```
   */
  provide?: Partial<ProvidedContext>

  /**
   * Configuration options for expect() matches.
   */
  expect?: {
    /**
     * Throw an error if tests don't have any expect() assertions.
     */
    requireAssertions?: boolean
    /**
     * Default options for expect.poll()
     */
    poll?: {
      /**
       * Timeout in milliseconds
       * @default 1000
       */
      timeout?: number
      /**
       * Polling interval in milliseconds
       * @default 50
       */
      interval?: number
    }
  }

  /**
   * Modify default Chai config. Vitest uses Chai for `expect` and `assert` matches.
   * https://github.com/chaijs/chai/blob/4.x.x/lib/chai/config.js
   */
  chaiConfig?: ChaiConfig

  /**
   * Sets length limit for formatted values interpolated into generated task titles.
   *
   * This affects values inserted by APIs like `test.each` and `test.for`.
   *
   * @default 40
   */
  taskTitleValueFormatTruncate?: number

  /**
   * Stop test execution when given number of tests have failed.
   */
  bail?: number

  /**
   * Retry configuration for tests.
   * - If a number, specifies how many times to retry failed tests
   * - If an object, allows fine-grained retry control
   *
   * ⚠️ WARNING: Function form is NOT supported in a config file
   * because configurations are serialized when passed to worker threads.
   * Use the function form only in test files directly.
   *
   * @default 0 // Don't retry
   */
  retry?: SerializableRetry

  /**
   * Repeat every test a specific number of times regardless of the result.
   *
   * @default 0 // Don't repeat
   */
  repeats?: number

  /**
   * Show full diff when snapshot fails instead of a patch.
   */
  expandSnapshotDiff?: boolean

  /**
   * By default, Vitest intercepts console output during tests to add context such as the test file and test title.
   * In browser mode, this interception is required to forward logs from the browser DevTools to the terminal.
   * It is also required for console log previews in the Vitest UI.
   * Disabling console interception can be useful when you want to debug code with normal synchronous terminal logging.
   *
   * @default false
   */
  disableConsoleIntercept?: boolean

  /**
   * Always print console stack traces.
   *
   * @default false
   */
  printConsoleTrace?: boolean

  /**
   * Include "location" property inside the test definition
   *
   * @default false
   */
  includeTaskLocation?: boolean

  /**
   * Directory path for storing attachments created by `context.annotate`
   *
   * @default '.vitest/attachments'
   */
  attachmentsDir?: string

  /**
   * Experimental features
   *
   * @experimental
   */
  experimental?: {
    /**
     * {@link https://vitest.dev/guide/open-telemetry}
     */
    openTelemetry?: {
      enabled: boolean
      sdkPath?: string
      browserSdkPath?: string
    }
    /**
     * Configure import duration collection and display.
     *
     * The `limit` option controls how many imports to collect and display.
     * The `print` option controls CLI terminal output.
     * UI can always toggle the breakdown display regardless of `print` setting.
     */
    importDurations?: {
      /**
       * When to print import breakdown to CLI terminal after tests finish.
       * - `true`: Always print
       * - `false`: Never print (default)
       * - `'on-warn'`: Print only when any import exceeds the warn threshold
       * @default false
       */
      print?: boolean | 'on-warn'
      /**
       * Maximum number of imports to collect and display.
       * @default 0 (or 10 if `print` or UI is enabled)
       */
      limit?: number
      /**
       * Fail the test run if any import exceeds the danger threshold.
       * When failing, the breakdown is always printed regardless of `print` setting.
       * @default false
       */
      failOnDanger?: boolean
      /**
       * Duration thresholds in milliseconds for coloring and warnings.
       */
      thresholds?: {
        /**
         * Warning threshold - imports exceeding this are shown in yellow/orange.
         * @default 100
         */
        warn?: number
        /**
         * Danger threshold - imports exceeding this are shown in red.
         * @default 500
         */
        danger?: number
      }
    }

    /**
     * Controls whether Vitest uses Vite's module runner to run the code or fallback to the native `import`.
     *
     * If Node.js cannot process the code, consider registering [module loader](https://nodejs.org/api/module.html#customization-hooks) via `execArgv`.
     * @default true
     */
    viteModuleRunner?: boolean
    /**
     * If module runner is disabled, Vitest uses a module loader to transform files to support
     * `import.meta.vitest` and `vi.mock`.
     *
     * If you don't use these features, you can disable this.
     *
     * This option only affects `loader.load` method, Vitest always defines a `loader.resolve` to populate the module graph.
     */
    nodeLoader?: boolean

    /**
     * Custom provider for detecting changed files. Used with the `--changed` flag
     * to determine which files have been modified.
     *
     * By default, Vitest uses Git to detect changed files. You can provide a custom
     * implementation of the `VCSProvider` interface to use a different version control system.
     */
    vcsProvider?: VCSProvider | string

    /**
     * Parse test specifications before running them.
     * This will apply `.only` flag and test name pattern across all files without running them.
     */
    preParse?: boolean
  }

  /**
   * Define tags available in your test files.
   *
   * If test defines a tag that is not listed here, an error will be thrown.
   */
  tags?: TestTagDefinition[]

  /**
   * Should Vitest throw an error if test has a tag that is not defined in the config.
   * @default true
   */
  strictTags?: boolean

  /**
   * Runs tests that are affected by the changes in the repository, or between specified branch or commit hash
   * Requires initialized git repository
   * @default false
   */
  changed?: boolean | string
}

export interface TypecheckConfig {
  /**
   * Run typechecking tests alongside regular tests.
   */
  enabled?: boolean
  /**
   * When typechecking is enabled, only run typechecking tests.
   */
  only?: boolean
  /**
   * What tools to use for type checking.
   *
   * @default 'tsc'
   */
  checker: 'tsc' | 'vue-tsc' | (string & Record<never, never>)
  /**
   * Pattern for files that should be treated as test files
   *
   * @default ['**\/*.{test,spec}-d.?(c|m)[jt]s?(x)']
   */
  include: string[]
  /**
   * Pattern for files that should not be treated as test files
   *
   * @default ['**\/node_modules/**', '**\/dist/**', '**\/cypress/**', '**\/.{idea,git,cache,output,temp}/**', '**\/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*']
   */
  exclude: string[]
  /**
   * Check JS files that have `@ts-check` comment.
   * If you have it enabled in tsconfig, this will not overwrite it.
   */
  allowJs?: boolean
  /**
   * Do not fail, if Vitest found errors outside the test files.
   */
  ignoreSourceErrors?: boolean
  /**
   * Use TypeScript build mode.
   */
  build?: boolean
  /**
   * Path to tsconfig, relative to the project root.
   */
  tsconfig?: string
  /**
   * Minimum time in milliseconds it takes to spawn the typechecker.
   * @default 10_000
   */
  spawnTimeout?: number
}

export interface UserConfig extends InlineConfig {
  /**
   * Path to the config file.
   *
   * Default resolving to `vitest.config.*`, `vite.config.*`
   *
   * Setting to `false` will disable config resolving.
   */
  config?: string | false | undefined

  /**
   * Do not run tests when Vitest starts.
   *
   * Vitest will only run tests if it's called programmatically or the test file changes.
   *
   * If CLI file filters are passed, standalone mode is ignored.
   */
  standalone?: boolean

  /**
   * Use happy-dom
   */
  dom?: boolean

  /**
   * Run tests that cover a list of source files
   */
  related?: string[] | string

  /**
   * Test suite shard to execute in a format of <index>/<count>.
   * Will divide tests into a `count` numbers, and run only the `indexed` part.
   * Cannot be used with enabled watch.
   * @example --shard=2/3
   */
  shard?: string

  /**
   * Name of the project or projects to run.
   */
  project?: string | string[]

  /**
   * Additional exclude patterns
   */
  cliExclude?: string[]

  /**
   * Override vite config's clearScreen from cli
   */
  clearScreen?: boolean

  /**
   * Directory of blob reports to merge
   * @default '.vitest/blob'
   */
  mergeReports?: string

  /**
   * Delete all Vitest caches, including the `fsModuleCache`.
   * @experimental
   */
  clearCache?: boolean

  /**
   * Tags expression to filter tests to run. Multiple filters will be applied using AND logic.
   * @see {@link https://vitest.dev/guide/test-tags#syntax}
   */
  tagsFilter?: string[]

  /**
   * Log all available tags instead of running tests.
   */
  listTags?: boolean | 'json'

  configLoader?: 'bundle' | 'runner' | 'native'

  /**
   * The `--reporter` argument from the CLI
   */
  reporter?: string | string[]
}

export type OnUnhandledErrorCallback = (error: (TestError | Error) & { type: string }) => boolean | void

export interface ResolvedConfig
  extends Omit<
    Required<UserConfig>,
    | 'project'
    | 'config'
    | 'filters'
    | 'browser'
    | 'coverage'
    | 'testNamePattern'
    | 'related'
    | 'api'
    | 'reporters'
    | 'resolveSnapshotPath'
    | 'benchmark'
    | 'shard'
    | 'cache'
    | 'sequence'
    | 'typecheck'
    | 'runner'
    | 'pool'
    | 'cliExclude'
    | 'diff'
    | 'setupFiles'
    | 'snapshotEnvironment'
    | 'bail'
    | 'name'
    | 'vmMemoryLimit'
    | 'fileParallelism'
    | 'tagsFilter'
    | 'reporter'
  > {
  name: ProjectName['label']
  color?: ProjectName['color']
  base?: string
  diff?: string | SerializedDiffOptions
  bail?: number

  setupFiles: string[]
  snapshotEnvironment?: string

  config?: string
  filters?: string[]
  testNamePattern?: RegExp
  related?: string[]

  coverage: ResolvedCoverageOptions
  snapshotOptions: SnapshotStateOptions

  browser: ResolvedBrowserOptions
  pool: Pool
  poolRunner?: PoolRunnerInitializer

  reporters: (InlineReporter | ReporterWithOptions)[]

  defines: Record<string, any>

  api: ResolvedApiConfig
  cliExclude?: string[]

  project: string[]
  benchmark: ResolvedBenchmarkOptions
  shard?: {
    index: number
    count: number
  }

  cache:
    | {
      /**
       * @deprecated
       */
      dir: string
    }
    | false

  sequence: {
    sequencer: TestSequencerConstructor
    hooks: SequenceHooks
    setupFiles: SequenceSetupFiles
    shuffle?: boolean
    concurrent?: boolean
    seed: number
    groupOrder: number
  }

  typecheck: Omit<TypecheckConfig, 'enabled'> & {
    enabled: boolean
  }
  runner?: string

  maxWorkers: number

  vmMemoryLimit?: UserConfig['vmMemoryLimit']
  dumpDir?: string
  tagsFilter?: string[]
  mergeReportsLabel?: string

  experimental: Omit<Required<UserConfig>['experimental'], 'importDurations'> & {
    importDurations: {
      print: boolean | 'on-warn'
      limit: number
      failOnDanger: boolean
      thresholds: {
        warn: number
        danger: number
      }
    }
  }

  cliOptions: CliOptions
  viteOverrides: ViteUserConfig
  resolvedProjects: ResolvedProjectEntry[]
  /**
   * Browser server contribution captured by the `vitest:browser:loader` plugin
   * during this config's resolution (set only when `browser.enabled`). Used by
   * server creation to build the single Vite server shared by `project.vite` and
   * `project.browser.vite`.
   *
   * @internal
   */
  _browserContribution?: BrowserServerContribution
}

/**
 * A resolved project entry. `viteConfig` may be shared by reference across multiple
 * entries (e.g. browser instances or benchmark variants of the same parent), while
 * `projectConfig` is always a distinct object per entry.
 */
export interface ResolvedProjectEntry {
  viteConfig: ResolvedViteConfig
  projectConfig: ResolvedConfig
  /**
   * Whether test files were found while resolving browser dependencies. This
   * early result is used only to decide whether prewarming is useful; runtime
   * discovery still globs after plugins have configured the server.
   *
   * @internal
   */
  hasTestFiles?: boolean
  /**
   * When set, this entry exists only so browser-instance siblings can attach
   * to a parent that owns the Vite server and (later) the browser provider.
   * The resulting `TestProject` is created and kept alive (so siblings can
   * reference it via `_parent`) but is NOT pushed to `vitest.projects`.
   */
  hidden?: boolean
}

type NonProjectOptions
  = | 'shard'
    | 'watch'
    | 'run'
    | 'cache'
    | 'update'
    | 'reporters'
    | 'outputFile'
    | 'teardownTimeout'
    | 'silent'
    | 'forceRerunTriggers'
    | 'testNamePattern'
    | 'ui'
    | 'open'
    | 'uiBase'
    // TODO: allow snapshot options
    | 'snapshotFormat'
    | 'resolveSnapshotPath'
    | 'passWithNoTests'
    | 'onConsoleLog'
    | 'onStackTrace'
    | 'dangerouslyIgnoreUnhandledErrors'
    | 'slowTestThreshold'
    | 'inspect'
    | 'inspectBrk'
    | 'coverage'
    | 'watchTriggerPatterns'
    | 'tagsFilter' // CLI option only

export interface ServerDepsOptions {
  /**
   * Externalize means that Vite will bpass the package to native Node.
   *
   * Externalized dependencies will not be applied Vite's transformers and resolvers.
   * And does not support HMR on reload.
   *
   * Typically, packages under `node_modules` are externalized.
   */
  external?: (string | RegExp)[]
  /**
   * Vite will process inlined modules.
   *
   * This could be helpful to handle packages that ship `.js` in ESM format (that Node can't handle).
   *
   * If `true`, every dependency will be inlined
   */
  inline?: (string | RegExp)[] | true
  /**
   * Try to guess the CJS version of a package when it's invalid ESM
   * @default false
   */
  fallbackCJS?: boolean
}

export type ProjectConfig = Omit<
  InlineConfig,
  NonProjectOptions
  | 'sequence'
  | 'deps'
> & {
  mode?: string
  sequence?: Omit<SequenceOptions, 'sequencer' | 'seed' | 'shuffle'> & {
    // `shuffle.files` controls cross-project file ordering, which is resolved
    // from the root config only, so projects can only shuffle their own tests.
    shuffle?: boolean | { tests?: boolean }
  }
  deps?: Omit<DepsOptions, 'moduleDirectories'>
}

export type ResolvedProjectConfig = Omit<
  ResolvedConfig,
  // some options cannot be set, but they are inherited from the workspace
  Exclude<NonProjectOptions, 'coverage' | 'watch'>
>

export interface UserWorkspaceConfig extends ViteUserConfig {
  test?: ProjectConfig
}

// TODO: remove types when "workspace" support is removed
export type UserProjectConfigFn = (
  env: ConfigEnv,
) => UserWorkspaceConfig | Promise<UserWorkspaceConfig>
export type UserProjectConfigExport
  = | UserWorkspaceConfig
    | Promise<UserWorkspaceConfig>
    | UserProjectConfigFn

export type TestProjectInlineConfiguration = (UserWorkspaceConfig & {
  /**
   * Relative path to the extendable config. All other options will be merged with this config.
   * If `true`, the project will inherit all options from the root config.
   * @example '../vite.config.ts'
   */
  extends?: string | true
})

export type TestProjectConfiguration
  = string
    | TestProjectInlineConfiguration
    | Promise<UserWorkspaceConfig>
    | UserProjectConfigFn
