import os from 'os'
import path from 'path'
import { PnpmError } from '@pnpm/error'
import normalize from 'normalize-path'
import { type PkgResolutionId } from '@pnpm/resolver-base'

// @ts-expect-error
const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS']
const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
const isFilename = /[.](?:tgz|tar.gz|tar)$/i
const isAbsolutePath = /^[/]|^[A-Za-z]:/

export interface LocalPackageSpec {
  dependencyPath: string
  fetchSpec: string
  id: PkgResolutionId
  type: 'directory' | 'file'
  normalizedPref: string
}

export interface WantedLocalDependency {
  pref: string
  injected?: boolean
}

export function parsePref (
  wd: WantedLocalDependency,
  projectDir: string,
  lockfileDir: string
): LocalPackageSpec | null {
  if (wd.pref.startsWith('link:') || wd.pref.startsWith('workspace:')) {
    return fromLocal(wd, projectDir, lockfileDir, 'directory')
  }
  if (wd.pref.endsWith('.tgz') ||
    wd.pref.endsWith('.tar.gz') ||
    wd.pref.endsWith('.tar') ||
    wd.pref.includes(path.sep) ||
    wd.pref.startsWith('file:') ||
    isFilespec.test(wd.pref)
  ) {
    const type = isFilename.test(wd.pref) ? 'file' : 'directory'
    return fromLocal(wd, projectDir, lockfileDir, type)
  }
  if (wd.pref.startsWith('path:')) {
    const err = new PnpmError('PATH_IS_UNSUPPORTED_PROTOCOL', 'Local dependencies via `path:` protocol are not supported. ' +
      'Use the `link:` protocol for folder dependencies and `file:` for local tarballs')
    // @ts-expect-error
    err['pref'] = wd.pref
    // @ts-expect-error
    err['protocol'] = 'path:'

    throw err
  }
  return null
}

function fromLocal (
  { pref, injected }: WantedLocalDependency,
  projectDir: string,
  lockfileDir: string,
  type: 'file' | 'directory'
): LocalPackageSpec {
  const spec = pref.replace(/\\/g, '/')
    .replace(/^(file|link|workspace):[/]*([A-Za-z]:)/, '$2') // drive name paths on windows
    .replace(/^(file|link|workspace):(?:[/]*([~./]))?/, '$2')

  let protocol!: string
  if (pref.startsWith('file:')) {
    protocol = 'file:'
  } else if (pref.startsWith('link:')) {
    protocol = 'link:'
  } else {
    protocol = type === 'directory' && !injected ? 'link:' : 'file:'
  }
  let fetchSpec!: string
  let normalizedPref!: string
  if (/^~[/]/.test(spec)) {
    // this is needed for windows and for file:~/foo/bar
    fetchSpec = resolvePath(os.homedir(), spec.slice(2))
    normalizedPref = `${protocol}${spec}`
  } else {
    fetchSpec = resolvePath(projectDir, spec)
    if (isAbsolute(spec)) {
      normalizedPref = `${protocol}${spec}`
    } else {
      normalizedPref = `${protocol}${path.relative(projectDir, fetchSpec)}`
    }
  }

  injected = protocol === 'file:'
  const dependencyPath = injected
    ? normalize(path.relative(lockfileDir, fetchSpec))
    : normalize(path.resolve(fetchSpec))
  const id = (
    !injected && (type === 'directory' || projectDir === lockfileDir)
      ? `${protocol}${normalize(path.relative(projectDir, fetchSpec))}`
      : `${protocol}${normalize(path.relative(lockfileDir, fetchSpec))}`
  ) as PkgResolutionId

  return {
    dependencyPath,
    fetchSpec,
    id,
    normalizedPref,
    type,
  }
}

function resolvePath (where: string, spec: string): string {
  if (isAbsolutePath.test(spec)) return spec
  return path.resolve(where, spec)
}

function isAbsolute (dir: string): boolean {
  if (dir[0] === '/') return true
  if (/^[A-Za-z]:/.test(dir)) return true
  return false
}
