import type { TestFsStructure } from '#test-utils'
import type { ViteUserConfig } from 'vitest/config'
import type { CliOptions, TestUserConfig, VitestOptions } from 'vitest/node'
import { createConsole, runVitest, runVitestCli, useTmpFS } from '#test-utils'
import { playwright } from '@vitest/browser-playwright'
import { preview } from '@vitest/browser-preview'
import { resolve } from 'pathe'
import { describe, expect, onTestFailed, onTestFinished, test, vi } from 'vitest'
import { createVitest, Logger, PluginHarness, resolveConfig } from 'vitest/node'
import { Cli } from '../../../test-utils/cli'

const vitest = vi.defineHelper(async (options: TestUserConfig & { $viteConfig?: ViteUserConfig; $cliConfig?: CliOptions }, vitestOptions: VitestOptions = {}) => {
  const vitest = await createVitest(
    {
      ...options.$cliConfig,
      watch: false,
      config: false,
    },
    {
      ...options.$viteConfig,
      plugins: [
        ...(options.$viteConfig?.plugins || []),
        {
          name: 'ignore-optimize-deps',
          enforce: 'post',
          config: {
            order: 'post',
            handler(config) {
              config.optimizeDeps ??= {}
              config.optimizeDeps.include = []
              config.optimizeDeps.include = []
              config.optimizeDeps.entries = []
              config.optimizeDeps.noDiscovery = true
              config.optimizeDeps.rolldownOptions = {}
              config.optimizeDeps.ignoreOutdatedRequests = true
            },
          },
        },
      ],
      test: options,
    },
    vitestOptions,
  )
  onTestFinished(async () => {
    await vitest.vite.waitForRequestsIdle()
    await vitest.close()
  })
  return vitest
})

async function config(options?: TestUserConfig & { $viteConfig?: ViteUserConfig; $cliConfig?: CliOptions }, pluginHarness?: PluginHarness) {
  const { test: { resolvedProjects } } = await resolveConfig({ config: false, ...options?.$cliConfig }, {
    ...options?.$viteConfig,
    test: options,
  }, pluginHarness)
  return resolvedProjects.filter(p => !p.hidden)
}

async function observePreTransformRequests(options: TestUserConfig = {}) {
  let serverPreTransformRequests: unknown = 'unresolved'
  let clientPreTransformRequests: unknown = 'unresolved'
  const browserEnabled = !!options.browser?.enabled

  await config({
    ...options,
    $viteConfig: {
      plugins: [
        {
          name: 'observe-pre-transform-requests',
          enforce: 'post',
          config: {
            order: 'post',
            handler(config) {
              if (!!config.test?.browser?.enabled === browserEnabled) {
                serverPreTransformRequests = config.server?.preTransformRequests
                clientPreTransformRequests = config.environments?.client?.dev?.preTransformRequests
              }
            },
          },
        },
      ],
    },
  })

  return {
    client: clientPreTransformRequests,
    server: serverPreTransformRequests,
  }
}

test('does not disable pre-transform requests in browser mode', async () => {
  const result = await observePreTransformRequests({
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
      ],
    },
  })

  expect(result).toEqual({
    client: undefined,
    server: undefined,
  })
})

test('disables pre-transform requests in node mode', async () => {
  expect(await observePreTransformRequests()).toEqual({
    client: false,
    server: false,
  })
})

test('assigns names as browsers', async () => {
  const projects = await config({
    browser: {
      enabled: true,
      headless: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
        { browser: 'firefox' },
        { browser: 'webkit' },
      ],
    },
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'chromium',
    'firefox',
    'webkit',
  ])
})

test('filters projects', async () => {
  const projects = await config({
    project: 'chromium',
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
        { browser: 'firefox' },
        { browser: 'webkit' },
      ],
    },
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'chromium',
  ])
})

test('filters projects with a wildcard', async () => {
  const projects = await config({
    project: 'chrom*',
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
        { browser: 'firefox' },
        { browser: 'webkit' },
      ],
    },
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'chromium',
  ])
})

test('assigns names as browsers in a custom project', async () => {
  const projects = await config({
    projects: [
      {
        test: {
          name: 'custom',
          browser: {
            enabled: true,
            headless: true,
            provider: preview(),
            instances: [
              { browser: 'chromium' },
              { browser: 'firefox' },
              { browser: 'webkit' },
              { browser: 'webkit', name: 'hello-world' },
            ],
          },
        },
      },
    ],
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'custom (chromium)',
    'custom (firefox)',
    'custom (webkit)',
    'hello-world',
  ])
})

test('inherits browser options', async () => {
  const fs = useTmpFS({
    './custom-path.html': ``,
    './custom-overridden-path.html': ``,
  })
  const projects = await config({
    setupFiles: ['/test/setup.ts'],
    provide: {
      browser: true,
    } as any,
    browser: {
      enabled: true,
      provider: preview(),
      headless: true,
      screenshotFailures: false,
      testerHtmlPath: fs.resolveFile('./custom-path.html'),
      screenshotDirectory: '/custom-directory',
      viewport: {
        width: 300,
        height: 900,
      },
      locators: {
        testIdAttribute: 'data-tid',
      },
      instances: [
        {
          browser: 'chromium',
          screenshotFailures: true,
        },
        {
          browser: 'firefox',
          screenshotFailures: true,
          locators: {
            testIdAttribute: 'data-custom',
          },
          viewport: {
            width: 900,
            height: 300,
          },
          testerHtmlPath: fs.resolveFile('./custom-overridden-path.html'),
          screenshotDirectory: '/custom-overridden-directory',
        },
      ],
    },
  })
  expect(projects.map(p => p.projectConfig)).toMatchObject([
    {
      name: 'chromium',
      setupFiles: ['/test/setup.ts'],
      provide: {
        browser: true,
      } as any,
      browser: {
        enabled: true,
        headless: true,
        screenshotFailures: true,
        screenshotDirectory: '/custom-directory',
        viewport: {
          width: 300,
          height: 900,
        },
        locators: {
          testIdAttribute: 'data-tid',
        },
        testerHtmlPath: fs.resolveFile('./custom-path.html'),
      },
    },
    {
      name: 'firefox',
      setupFiles: ['/test/setup.ts'],
      provide: {
        browser: true,
      } as any,
      browser: {
        enabled: true,
        headless: true,
        screenshotFailures: true,
        viewport: {
          width: 900,
          height: 300,
        },
        screenshotDirectory: '/custom-overridden-directory',
        locators: {
          testIdAttribute: 'data-custom',
        },
        testerHtmlPath: fs.resolveFile('./custom-overridden-path.html'),
      },
    },
  ])
})

test('coverage provider v8 works correctly in browser mode if instances are filtered', async () => {
  const projects = await config(
    {
      project: 'chromium',
      coverage: {
        enabled: true,
        provider: 'v8',
      },
      browser: {
        enabled: true,
        provider: playwright(),
        instances: [
          { browser: 'chromium' },
          { browser: 'firefox' },
          { browser: 'webkit' },
        ],
      },
    },
  )
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'chromium',
  ])
})

test('coverage provider v8 works correctly in workspaced browser mode if instances are filtered', async () => {
  const projects = await config({
    project: 'browser (chromium)',
    projects: [
      {
        test: {
          name: 'browser',
          browser: {
            enabled: true,
            provider: playwright(),
            instances: [
              { browser: 'chromium' },
              { browser: 'firefox' },
              { browser: 'webkit' },
            ],
          },
        },
      },
    ],
    coverage: {
      enabled: true,
      provider: 'v8',
    },
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'browser (chromium)',
  ])
})

test('browser instances with include/exclude/includeSource option override parent that patterns', async () => {
  const projects = await config({
    include: ['**/*.global.test.{js,ts}', '**/*.shared.test.{js,ts}'],
    exclude: ['**/*.skip.test.{js,ts}'],
    includeSource: ['src/**/*.{js,ts}'],
    browser: {
      enabled: true,
      provider: preview(),
      headless: true,
      instances: [
        { browser: 'chromium' },
        {
          browser: 'firefox',
          include: ['test/firefox-specific.test.ts'],
          exclude: ['test/webkit-only.test.ts', 'test/webkit-extra.test.ts'],
          includeSource: ['src/firefox-compat.ts'],
        },
        {
          browser: 'webkit',
          include: ['test/webkit-only.test.ts', 'test/webkit-extra.test.ts'],
          exclude: ['test/firefox-specific.test.ts'],
          includeSource: ['src/webkit-compat.ts'],
        },
      ],
    },
  })

  // Chromium should inherit parent include/exclude/includeSource patterns (plus default test pattern)
  expect(projects[0].projectConfig.name).toEqual('chromium')
  expect(projects[0].projectConfig.include).toEqual([
    '**/*.global.test.{js,ts}',
    '**/*.shared.test.{js,ts}',
  ])
  expect(projects[0].projectConfig.exclude).toEqual([
    '**/*.skip.test.{js,ts}',
  ])
  expect(projects[0].projectConfig.includeSource).toEqual([
    'src/**/*.{js,ts}',
  ])

  // Firefox should only have its specific include/exclude/includeSource pattern (not parent patterns)
  expect(projects[1].projectConfig.name).toEqual('firefox')
  expect(projects[1].projectConfig.include).toEqual([
    'test/firefox-specific.test.ts',
  ])
  expect(projects[1].projectConfig.exclude).toEqual([
    'test/webkit-only.test.ts',
    'test/webkit-extra.test.ts',
  ])
  expect(projects[1].projectConfig.includeSource).toEqual([
    'src/firefox-compat.ts',
  ])

  // Webkit should only have its specific include/exclude/includeSource patterns (not parent patterns)
  expect(projects[2].projectConfig.name).toEqual('webkit')
  expect(projects[2].projectConfig.include).toEqual([
    'test/webkit-only.test.ts',
    'test/webkit-extra.test.ts',
  ])
  expect(projects[2].projectConfig.exclude).toEqual([
    'test/firefox-specific.test.ts',
  ])
  expect(projects[2].projectConfig.includeSource).toEqual([
    'src/webkit-compat.ts',
  ])
})

test('browser instances with empty include array should get parent include patterns', async () => {
  const projects = await config({
    include: ['**/*.test.{js,ts}'],
    browser: {
      enabled: true,
      provider: preview(),
      headless: true,
      instances: [
        { browser: 'chromium', include: [] },
        { browser: 'firefox' },
      ],
    },
  })

  // Both instances should inherit parent include patterns when include is empty or not specified
  expect(projects[0].projectConfig.include).toEqual(['**/*.test.{js,ts}'])
  expect(projects[1].projectConfig.include).toEqual(['**/*.test.{js,ts}'])
})

test('negation filter excludes all browser instances', async () => {
  const projects = await config({
    project: '!myproject',
    projects: [
      {
        test: {
          name: 'myproject',
          browser: {
            enabled: true,
            provider: playwright(),
            headless: true,
            instances: [
              { browser: 'chromium' },
              { browser: 'firefox' },
              { browser: 'webkit' },
            ],
          },
        },
      },
      {
        test: {
          name: 'other',
        },
      },
    ],
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'other',
  ])
})

test('negation wildcard filter excludes all matching browser instances', async () => {
  const projects = await config({
    project: '!my*',
    projects: [
      {
        test: {
          name: 'myproject',
          browser: {
            enabled: true,
            provider: playwright(),
            headless: true,
            instances: [
              { browser: 'chromium' },
              { browser: 'firefox' },
            ],
          },
        },
      },
      {
        test: {
          name: 'other',
        },
      },
    ],
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'other',
  ])
})

test('filter for the global browser project includes all browser instances', async () => {
  const projects = await config({
    project: 'myproject',
    projects: [
      {
        test: {
          name: 'myproject',
          browser: {
            enabled: true,
            provider: playwright(),
            headless: true,
            instances: [
              { browser: 'chromium' },
              { browser: 'firefox' },
              { browser: 'webkit' },
            ],
          },
        },
      },
      {
        test: {
          name: 'skip',
        },
      },
    ],
  })
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'myproject (chromium)',
    'myproject (firefox)',
    'myproject (webkit)',
  ])
})

test('can enable browser-cli options for multi-project workspace', async () => {
  const projects = await config(
    {
      projects: [
        {
          test: {
            name: 'unit',
          },
        },
        {
          test: {
            browser: {
              enabled: true,
              provider: playwright(),
              instances: [
                { browser: 'chromium', name: 'browser' },
              ],
            },
          },
        },
      ],
      $cliConfig: {
        browser: {
          enabled: true,
          provider: preview(),
          headless: true,
          instances: [],
        },
      },
    },
  )
  expect(projects.map(p => p.projectConfig.name)).toEqual([
    'unit',
    'browser',
  ])

  // unit config
  expect(projects[0].projectConfig.browser.enabled).toBe(false)

  // browser config
  expect(projects[1].projectConfig.browser.enabled).toBe(true)
  expect(projects[1].projectConfig.browser.headless).toBe(true)
})

test('core provider has options if `provider` is playwright', async () => {
  const v = await config({
    browser: {
      enabled: true,
      provider: playwright({ actionTimeout: 1000 }),
      instances: [
        { browser: 'chromium' },
      ],
    },
  })
  expect(v[0].projectConfig.browser.provider?.options).toEqual({
    actionTimeout: 1000,
  })
})

test('core provider has options if `provider` is preview', async () => {
  const v = await config({
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chrome' },
      ],
    },
  })
  expect(v[0].projectConfig.browser.provider?.options).toEqual({})
})

test('provider options can be changed dynamically', async () => {
  const v = await vitest({
    browser: {
      enabled: true,
      provider: playwright({ actionTimeout: 1000 }),
      instances: [
        { browser: 'chromium' },
      ],
    },
    $viteConfig: {
      plugins: [
        {
          name: 'update-options',
          configureVitest({ project }) {
            if (project.config.browser.provider) {
              const options = project.config.browser.provider.options as any
              options.actionTimeout = 400
              options.someDifferentOption = 'test'
            }
          },
        },
      ],
    },
  })
  expect(v.config.browser.provider?.options).toEqual({
    actionTimeout: 400,
    someDifferentOption: 'test',
  })
})

test('provider options can be changed dynamically if no options are specified', async () => {
  const v = await vitest({
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [
        { browser: 'chromium' },
      ],
    },
    $viteConfig: {
      plugins: [
        {
          name: 'update-options',
          configureVitest({ project }) {
            if (project.config.browser.provider) {
              const options = project.config.browser.provider.options as any
              options.actionTimeout = 400
              options.someDifferentOption = 'test'
            }
          },
        },
      ],
    },
  })
  expect(v.config.browser.provider?.options).toEqual({
    actionTimeout: 400,
    someDifferentOption: 'test',
  })
})

test('provider options can be changed dynamically in CLI', async () => {
  const v = await vitest({
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
      ],
    },
    $cliConfig: {
      browser: {
        provider: {
          name: 'playwright',
          _cli: true,
        } as any,
        instances: [],
      },
    },
    $viteConfig: {
      plugins: [
        {
          name: 'update-options',
          configureVitest({ project }) {
            if (project.config.browser.provider) {
              const options = project.config.browser.provider.options as any
              options.actionTimeout = 400
              options.someDifferentOption = 'test'
            }
          },
        },
      ],
    },
  })
  expect(v.config.browser.provider?.options).toEqual({
    actionTimeout: 400,
    someDifferentOption: 'test',
  })
})

test('fileParallelism on the instance works properly', async () => {
  const projects = await config({
    fileParallelism: true,
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [
        {
          browser: 'chromium',
          fileParallelism: false,
        },
        {
          browser: 'firefox',
          fileParallelism: true,
        },
      ],
    },
  })
  expect(projects).toHaveLength(2)
  expect(projects[0].projectConfig.maxWorkers).toBe(1)
  expect(projects[1].projectConfig.maxWorkers).toBe(undefined) // decided dynamically
})

test('detailsPanelPosition defaults to right', async () => {
  const projects = await config({
    browser: {
      enabled: true,
      provider: preview(),
      instances: [
        { browser: 'chromium' },
      ],
    },
  })
  expect(projects[0].projectConfig.browser.detailsPanelPosition).toBe('right')
})

test('detailsPanelPosition from config file is respected', async () => {
  const projects = await config({
    browser: {
      enabled: true,
      provider: preview(),
      detailsPanelPosition: 'bottom',
      instances: [
        { browser: 'chromium' },
      ],
    },
  })
  expect(projects[0].projectConfig.browser.detailsPanelPosition).toBe('bottom')
})

test('CLI option --browser.detailsPanelPosition overrides config', async () => {
  const projects = await config({
    $cliConfig: {
      browser: {
        detailsPanelPosition: 'bottom',
      },
    },
    browser: {
      enabled: true,
      provider: playwright(),
      detailsPanelPosition: 'right',
      instances: [
        { browser: 'chromium' },
      ],
    },
  })
  expect(projects[0].projectConfig.browser.detailsPanelPosition).toBe('bottom')
})

async function getCliConfig(options: TestUserConfig, cli: string[], fs: TestFsStructure = {}) {
  const { root } = useTmpFS({
    ...fs,
    'basic.test.ts': /* ts */`
      import { test } from 'vitest'
      test('basic', () => {
        expect(1).toBe(1)
      })
    `,
    'vitest.config.js': /* ts */ `
      import { playwright } from '@vitest/browser-playwright'
      const config = ${JSON.stringify(options)}
      if (config.projects) {
        config.projects.forEach(p => {
          if (p.test?.browser?.provider) {
            p.test.browser.provider = playwright()
          }
        })
      }
      if (config.browser?.provider) {
        config.browser.provider = playwright()
      }
      export default {
        test: {
          reporters: [
            {
              onInit(vitest) {
                const browser = vitest.config.browser
                const workspace = (p) => ({
                  name: p.name,
                  headless: p.config.browser.headless,
                  browser: p.config.browser.enabled,
                  ui: p.config.browser.ui,
                  locators: p.config.browser.locators,
                })
                console.log(JSON.stringify({
                  browser: {
                    headless: browser.headless,
                    browser: browser.enabled,
                    ui: browser.ui,
                  },
                  workspace: vitest.projects.map(p => {
                    return {
                      ...workspace(p),
                      parent: p._parent ? workspace(p._parent) : null,
                    }
                  })
                }))
                // throw an error to avoid running tests
                throw new Error('stop')
              },
            },
          ],
          ...config,
        }
      }
    `,
  })
  const result = await runVitestCli(
    {
      nodeOptions: {
        env: {
          CI: '',
          GITHUB_ACTIONS: undefined,
        },
      },
    },
    '--root',
    root,
    '--no-watch',
    ...cli,
  )
  onTestFailed(() => {
    console.error(`stdout: ${result.stdout}\nstderr:${result.stderr}`)
  })
  return result
}

describe('[e2e] workspace configs are affected by the CLI options', () => {
  test('nested CLI options correctly override inline workspace options', async () => {
    const vitest = await getCliConfig({
      projects: [
        {
          test: {
            name: 'unit',
          },
        },
        {
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              headless: true,
              provider: playwright(),
              instances: [
                {
                  browser: 'chromium',
                },
              ],
            },
          },
        },
      ],
    }, ['--browser.locators.exact'])

    const config = JSON.parse(vitest.stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'unit',
      headless: false,
      browser: false,
      ui: true,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      locators: {
        exact: true,
      },
    })
  })

  test('UI is not enabled by default in headless config', async () => {
    const vitest = await getCliConfig({
      projects: [
        {
          test: {
            name: 'unit',
          },
        },
        {
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              headless: true,
              provider: playwright(),
              instances: [
                {
                  browser: 'chromium',
                },
              ],
            },
          },
        },
      ],
    }, [])

    const config = JSON.parse(vitest.stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'unit',
      headless: false,
      browser: false,
      ui: true,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      // headless was set in the config
      headless: true,
      browser: true,
      // UI is false because headless is enabled
      ui: false,
      parent: expect.objectContaining({
        name: 'browser',
        headless: true,
        browser: true,
        ui: false,
      }),
    })
  })

  test('CLI options correctly override inline workspace options', async () => {
    const vitest = await getCliConfig({
      projects: [
        {
          test: {
            name: 'unit',
          },
        },
        {
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              headless: true,
              provider: playwright(),
              instances: [
                {
                  browser: 'chromium',
                },
              ],
            },
          },
        },
      ],
    }, ['--browser.headless=false'])

    const config = JSON.parse(vitest.stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'unit',
      headless: false,
      browser: false,
      ui: true,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      // headless was overridden by CLI options
      headless: false,
      browser: true,
      // UI should be true because we always set CI to false,
      // if headless was `true`, ui would be `false`
      ui: true,
      parent: expect.objectContaining({
        name: 'browser',
        headless: false,
        browser: true,
        ui: true,
      }),
    })
  })

  test('CLI options correctly override config file workspace options', async () => {
    const vitest = await getCliConfig(
      {
        projects: [
          {
            test: {
              name: 'unit',
            },
          },
          './vitest.browser.config.ts',
        ],
      },
      ['--browser.headless=false'],
      {
        'vitest.browser.config.ts': {
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              headless: true,
              provider: playwright(),
              instances: [
                {
                  browser: 'chromium',
                },
              ],
            },
          },
        },
      },
    )

    const config = JSON.parse(vitest.stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'unit',
      headless: false,
      browser: false,
      ui: true,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      headless: false,
      browser: true,
      ui: true,
      parent: expect.objectContaining({
        name: 'browser',
        headless: false,
        browser: true,
        ui: true,
      }),
    })
  })

  test('correctly resolves extended project', async () => {
    const { stdout } = await getCliConfig({
      browser: {
        provider: playwright(),
        headless: true,
        instances: [
          { browser: 'chromium' },
        ],
      },
      projects: [
        {
          extends: true,
          test: {
            name: 'node',
          },
        },
        {
          extends: true,
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              provider: preview(),
              instances: [],
            },
          },
        },
      ],
    }, [])

    const config = JSON.parse(stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'node',
      headless: true,
      browser: false,
      ui: false,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      headless: true,
      browser: true,
      ui: false,
      parent: expect.objectContaining({
        name: 'browser',
        headless: true,
        browser: true,
        ui: false,
      }),
    })
  })

  test('correctly overrides extended project', async () => {
    const { stdout } = await getCliConfig({
      browser: {
        provider: playwright(),
        headless: true,
        instances: [
          { browser: 'chromium' },
        ],
      },
      projects: [
        {
          extends: true,
          test: {
            name: 'node',
          },
        },
        {
          extends: true,
          test: {
            name: 'browser',
            browser: {
              enabled: true,
              provider: preview(),
              instances: [],
            },
          },
        },
      ],
    }, ['--browser.headless=false'])

    const config = JSON.parse(stdout)

    expect(config.workspace).toHaveLength(2)
    expect(config.workspace[0]).toMatchObject({
      name: 'node',
      headless: false,
      browser: false,
      ui: true,
      parent: null,
    })

    expect(config.workspace[1]).toMatchObject({
      name: 'browser (chromium)',
      headless: false,
      browser: true,
      ui: true,
      parent: expect.objectContaining({
        name: 'browser',
        headless: false,
        browser: true,
        ui: true,
      }),
    })
  })

  test('CLI options override the config if --browser.enabled is passed down manually', async () => {
    const { stdout } = await getCliConfig({
      browser: {
        enabled: false,
        provider: playwright(),
        headless: true,
        instances: [
          { browser: 'chromium' },
        ],
      },
    }, ['--browser.headless=false', '--browser.enabled'])

    const config = JSON.parse(stdout)
    expect(config).toEqual({
      browser: {
        headless: false,
        browser: true,
        ui: true,
      },
      workspace: [
        expect.objectContaining({
          name: 'chromium',
          headless: false,
          browser: true,
          ui: true,
          parent: expect.objectContaining({
            name: '',
            headless: false,
            browser: true,
            ui: true,
          }),
        }),
      ],
    })
  })
})

test('browser root', async () => {
  process.env.BROWSER_DEFINE_TEST_PROJECT = 'false'
  const { testTree, stderr } = await runVitest({
    root: './fixtures/config/browser-define',
  })
  expect(stderr).toBe('')
  expect(testTree()).toMatchInlineSnapshot(`
    {
      "basic.test.ts": {
        "passes": "passed",
      },
    }
  `)
})

test('browser project', async () => {
  process.env.BROWSER_DEFINE_TEST_PROJECT = 'true'
  const { testTree, stderr } = await runVitest({
    root: './fixtures/config/browser-define',
  })
  expect(stderr).toBe('')
  expect(testTree()).toMatchInlineSnapshot(`
    {
      "basic.test.ts": {
        "passes": "passed",
      },
    }
  `)
})

const customHtmlRoot = resolve(import.meta.dirname, '../../fixtures/config/browser-custom-html')

test('throws an error with non-existing path', async () => {
  const { stderr } = await runVitest({
    root: customHtmlRoot,
    config: './vitest.config.non-existing.ts',
  }, [], { fails: true })
  expect(stderr).toContain(`Tester HTML file "${resolve(customHtmlRoot, './some-non-existing-path')}" doesn't exist.`)
})

test('throws an error and exits if there is an error in the html file hook', async () => {
  const { stderr, exitCode } = await runVitest({
    root: customHtmlRoot,
    config: './vitest.config.error-hook.ts',
  })
  expect(stderr).toContain('Error: expected error in transformIndexHtml')
  expect(stderr).toContain('[vite] Internal server error: expected error in transformIndexHtml')
  expect(exitCode).toBe(1)
})

test('allows correct custom html', async () => {
  const { stderr, stdout, exitCode } = await runVitest({
    root: customHtmlRoot,
    config: './vitest.config.correct.ts',
    reporters: [['default', { summary: false }]],
  })
  expect(stderr).toBe('')
  expect(stdout).toContain('✓ |chromium| browser-basic.test.ts')
  expect(exitCode).toBe(0)
})

test('allows custom transformIndexHtml with custom html file', async () => {
  const { stderr, stdout, exitCode } = await runVitest({
    root: customHtmlRoot,
    config: './vitest.config.custom-transformIndexHtml.ts',
    reporters: [['default', { summary: false }]],
  })
  expect(stderr).toBe('')
  expect(stdout).toContain('✓ |chromium| browser-custom.test.ts')
  expect(exitCode).toBe(0)
})

test('allows custom transformIndexHtml without custom html file', async () => {
  const { stderr, stdout, exitCode } = await runVitest({
    root: customHtmlRoot,
    config: './vitest.config.default-transformIndexHtml.ts',
    reporters: [['default', { summary: false }]],
  })
  expect(stderr).toBe('')
  expect(stdout).toContain('✓ |chromium| browser-custom.test.ts')
  expect(exitCode).toBe(0)
})

test('show a warning if host is exposed', async () => {
  const { stderr, stdout, stdin } = createConsole()
  const harness = new PluginHarness(new Logger(stdout, stderr))
  const cli = new Cli({ stderr, stdin, stdout })

  await config(
    {
      config: false,
      root: './fixtures/basic',
      api: {
        host: 'custom-host',
      },
    },
    harness,
  )

  expect(cli.stderr).toContain(
    'API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set `browser.api.allowExec` manually to hide this warning. See https://vitest.dev/config/api for more details.',
  )
})
