import type {
  BrowserProvider,
  ResolvedBrowserOptions,
  ResolvedConfig,
  TestProject,
} from 'vitest/node'

import { defaultKeyMap } from '@testing-library/user-event/dist/esm/keyboard/keyMap.js'
import { parseKeyDef as tlParse } from '@testing-library/user-event/dist/esm/keyboard/parseKeyDef.js'
import { basename, dirname, relative, resolve } from 'pathe'
import { isFileLoadingAllowed } from 'vitest/node'

declare enum DOM_KEY_LOCATION {
  STANDARD = 0,
  LEFT = 1,
  RIGHT = 2,
  NUMPAD = 3,
}

interface keyboardKey {
  /** Physical location on a keyboard */
  code?: string
  /** Character or functional key descriptor */
  key?: string
  /** Location on the keyboard for keys with multiple representation */
  location?: DOM_KEY_LOCATION
  /** Does the character in `key` require/imply AltRight to be pressed? */
  altGr?: boolean
  /** Does the character in `key` require/imply a shiftKey to be pressed? */
  shift?: boolean
}

export function parseKeyDef(text: string): {
  keyDef: keyboardKey
  releasePrevious: boolean
  releaseSelf: boolean
  repeat: number
}[] {
  return tlParse(defaultKeyMap, text)
}

export function replacer(code: string, values: Record<string, string>): string {
  return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _)
}

export function resolveScreenshotPath(
  testPath: string,
  name: string,
  config: ResolvedConfig,
  customPath: string | undefined,
): string {
  if (customPath) {
    return resolve(dirname(testPath), customPath)
  }
  const dir = dirname(testPath)
  const base = basename(testPath)
  if (config.browser.screenshotDirectory) {
    return resolve(
      config.browser.screenshotDirectory,
      relative(config.root, dir),
      base,
      name,
    )
  }
  return resolve(dir, '__screenshots__', base, name)
}

export async function getBrowserProvider(
  options: ResolvedBrowserOptions,
  project: TestProject,
): Promise<BrowserProvider> {
  const browser = project.config.browser.name
  const name = project.name ? `[${project.name}] ` : ''
  if (!browser) {
    throw new Error(
      `${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`,
    )
  }
  if (options.provider == null) {
    throw new Error(`Browser Mode requires the "provider" to always be specified.`)
  }
  const supportedBrowsers = options.provider.supportedBrowser || []
  if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
    throw new Error(
      `${name}Browser "${browser}" is not supported by the browser provider "${
        options.provider.name
      }". Supported browsers: ${supportedBrowsers.join(', ')}.`,
    )
  }
  if (typeof options.provider.providerFactory !== 'function') {
    throw new TypeError(`The "${name}" browser provider does not provide a "providerFactory" function. Received ${typeof options.provider.providerFactory}.`)
  }
  return options.provider.providerFactory(project)
}

export function slash(path: string): string {
  return path.replace(/\\/g, '/').replace(/\/+/g, '/')
}

export function assertBrowserFileAccess(project: TestProject, path: string): void {
  const normalized = slash(path)
  if (
    !isFileLoadingAllowed(project.vite.config, normalized)
    && !isFileLoadingAllowed(project.vitest.vite.config, normalized)
  ) {
    throw new Error(
      `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`,
    )
  }
}

export function assertBrowserApiWrite(project: TestProject, path: string): void {
  if (!project.config.api.allowWrite || !project.vitest.config.api.allowWrite) {
    throw new Error(
      `Cannot modify file "${path}". File writing is disabled because the server is exposed to the internet, see https://vitest.dev/config/browser/api.`,
    )
  }
}
