import type { defineConfig } from 'vitest/config'
import type { CoverageInstrumenter, CoverageProviderModule, InstrumenterOptions, ResolvedCoverageOptions, Vitest } from 'vitest/node'
import { assertType, test } from 'vitest'

type NarrowToTestConfig<T> = T extends { test?: any } ? NonNullable<T['test']> : never
type Configuration = NarrowToTestConfig<(Parameters<typeof defineConfig>[0])>
type Coverage = NonNullable<Configuration['coverage']>

test('providers, built-in', () => {
  assertType<Coverage>({ provider: 'v8' })
  assertType<Coverage>({ provider: 'istanbul' })

  // @ts-expect-error -- String options must be known ones only
  assertType<Coverage>({ provider: 'unknown-provider' })
})

test('providers, custom', () => {
  assertType<Coverage>({
    provider: 'custom',
    customProviderModule: 'custom-provider-module.ts',
  })
})

test('provider options, generic', () => {
  assertType<Coverage>({
    provider: 'v8',
    enabled: true,
    include: ['string'],
    watermarks: {
      functions: [80, 95],
      lines: [80, 95],
    },
    thresholds: {
      '100': true,
      'lines': 1,
      'autoUpdate': true,
      'perFile': true,
      'statements': 100,

      '**/some-file.ts': {
        100: true,
        lines: 12,
        branches: 12,
        functions: 12,
        statements: 12,
        perFile: true,
      },
    },
  })

  assertType<Coverage>({
    provider: 'istanbul',
    enabled: true,
    include: ['string'],
    watermarks: {
      statements: [80, 95],
    },
    thresholds: {
      '100': false,
      'lines': 1,
      'autoUpdate': true,
      'perFile': true,
      'statements': 100,

      '**/some-file.ts': {
        100: false,
        lines: 12,
        branches: 12,
        functions: 12,
        statements: 12,
      },
    },
  })

  assertType<Coverage>({
    provider: 'v8',
    thresholds: {
      lines: 80,
      functions: 80,
      branches: 80,
      statements: 80,
      perFile: {
        lines: 50,
        functions: 50,
        branches: 50,
        statements: 50,
      },
    },
  })

  // Glob patterns accept their own `perFile` (boolean or object).
  assertType<Coverage>({
    provider: 'v8',
    thresholds: {
      '**/some-file.ts': {
        perFile: true,
      },
      '**/other-file.ts': {
        perFile: {
          lines: 50,
        },
      },
      '**/strict.ts': {
        perFile: {
          100: true,
        },
      },
    },
  })

  assertType<Coverage>({
    provider: 'v8',
    thresholds: {
      '**/some-file.ts': {
        perFile: {
          // @ts-expect-error -- per-file threshold values must be numbers
          lines: '50',
        },
      },
    },
  })

  assertType<Coverage>({
    provider: 'istanbul',
    thresholds: {
      lines: 80,
      perFile: {
        100: true,
      },
    },
  })

  assertType<Coverage>({
    provider: 'v8',
    thresholds: {
      perFile: {
        // @ts-expect-error -- per-file threshold values must be numbers
        lines: '50',
      },
    },
  })

  assertType<Coverage>({
    provider: 'v8',
    thresholds: {
      perFile: {
        // @ts-expect-error -- `autoUpdate` is not a per-file option
        autoUpdate: true,
      },
    },
  })
})

test('provider module', () => {
  assertType<CoverageProviderModule>({
    getProvider() {
      return {
        name: 'custom-provider',
        initialize(_: Vitest) {},
        generateCoverage() {},
        resolveOptions(): ResolvedCoverageOptions {
          return {
            provider: 'v8',
            clean: true,
            cleanOnRerun: true,
            enabled: true,
            exclude: ['string'],
            reporter: [['html', {}], ['json', { file: 'string' }]],
            reportsDirectory: 'string',
            reportOnFailure: true,
            allowExternal: true,
            processingConcurrency: 1,
            excludeAfterRemap: false,
            ignoreClassMethods: [],
            skipFull: true,
            watermarks: {
              statements: [80, 95],
              functions: [80, 95],
              branches: [80, 95],
              lines: [80, 95],
            },
            autoAttachSubprocess: false,
          }
        },
        clean(_?: boolean) {},
        onBeforeFilesRun() {},
        onAfterSuiteRun({ coverage: _coverage }) {},
        reportCoverage() {},
        onFileTransform(_code: string, _id: string, ctx) {
          ctx.getCombinedSourcemap()
        },
      }
    },
    takeCoverage() {},
    startCoverage() {},
    stopCoverage() {},
  })
})

test('reporters, single', () => {
  assertType<Coverage>({ reporter: 'clover' })
  assertType<Coverage>({ reporter: 'cobertura' })
  assertType<Coverage>({ reporter: 'html-spa' })
  assertType<Coverage>({ reporter: 'html' })
  assertType<Coverage>({ reporter: 'json-summary' })
  assertType<Coverage>({ reporter: 'json' })
  assertType<Coverage>({ reporter: 'lcov' })
  assertType<Coverage>({ reporter: 'lcovonly' })
  assertType<Coverage>({ reporter: 'none' })
  assertType<Coverage>({ reporter: 'teamcity' })
  assertType<Coverage>({ reporter: 'text-lcov' })
  assertType<Coverage>({ reporter: 'text-summary' })
  assertType<Coverage>({ reporter: 'text' })
  assertType<Coverage>({ reporter: 'custom-reporter' })
})

test('reporters, multiple', () => {
  assertType<Coverage>({
    reporter: [
      'clover',
      'cobertura',
      'html-spa',
      'html',
      'json-summary',
      'json',
      'lcov',
      'lcovonly',
      'none',
      'teamcity',
      'text-lcov',
      'text-summary',
      'text',
    ],
  })

  assertType<Coverage>({ reporter: ['custom-reporter'] })
  assertType<Coverage>({ reporter: ['html', 'json', 'custom-reporter'] })
})

test('reporters, with options', () => {
  assertType<Coverage>({
    reporter: [
      ['clover', { projectRoot: 'string', file: 'string' }],
      ['cobertura', { projectRoot: 'string', file: 'string' }],
      ['html-spa', { metricsToShow: ['branches', 'functions'], verbose: true, subdir: 'string' }],
      ['html', { verbose: true, subdir: 'string' }],
      ['json-summary', { file: 'string' }],
      ['json', { file: 'string' }],
      ['lcov', { projectRoot: 'string', file: 'string' }],
      ['lcovonly', { projectRoot: 'string', file: 'string' }],
      ['none'],
      ['teamcity', { blockName: 'string' }],
      ['text-lcov', { projectRoot: 'string' }],
      ['text-summary', { file: 'string' }],
      ['text', { skipEmpty: true, skipFull: true, maxCols: 1 }],
      ['custom-reporter', { 'someOption': true, 'some-other-custom-option': { width: 123 } }],
    ],
  })

  assertType<Coverage>({
    reporter: [
      ['html', { subdir: 'string' }],
      ['json'],
      ['lcov', { projectRoot: 'string' }],
    ],
  })

  assertType<Coverage>({
    reporter: [
      // @ts-expect-error -- second value should be object even though TS intellisense prompts types of reporters
      ['lcov', 'html-spa'],
    ],
  })
})

test('reporters, mixed variations', () => {
  assertType<Coverage>({
    reporter: [
      'clover',
      'custom-reporter-1',
      ['cobertura'],
      ['custom-reporter-2'],
      ['html-spa', {}],
      ['custom-reporter-3', {}],
      ['html', { verbose: true, subdir: 'string' }],
      ['custom-reporter-4', { some: 'option', width: 123 }],
    ],
  })
})

test('custom instrumenter', () => {
  // Custom instrumenter factory function
  assertType<Coverage>({
    provider: 'istanbul',
    instrumenter: _options => ({
      instrumentSync: (code, _filename, _sourceMap?) => code,
      lastSourceMap: () => ({}),
      lastFileCoverage: () => ({}),
    }),
  })

  // Without instrumenter (default behavior)
  assertType<Coverage>({
    provider: 'istanbul',
  })

  // Verify CoverageInstrumenter type can be used as return type
  const factory: (opts: InstrumenterOptions) => CoverageInstrumenter = _opts => ({
    instrumentSync: code => code,
    lastSourceMap: () => null,
    lastFileCoverage: () => ({}),
  })

  assertType<InstrumenterOptions>({
    coverageVariable: '__VITEST_COVERAGE__',
    coverageGlobalScope: 'globalThis',
    coverageGlobalScopeFunc: false,
    ignoreClassMethods: ['test-method'],
  })

  assertType<Coverage>({
    provider: 'istanbul',
    instrumenter: factory,
  })
})
