'use strict'

const fs = require('node:fs')
const Module = require('node:module')
const path = require('node:path')
const process = require('node:process')
const logger = require('./@socketsecurity/registry/lib/logger')
const path$1 = require('./@socketsecurity/registry/lib/path')
const constants = require('./constants.js')
const vendor = require('./vendor.js')
const debug = require('./@socketsecurity/registry/lib/debug')
const npm = require('./@socketsecurity/registry/lib/npm')
const words = require('./@socketsecurity/registry/lib/words')
const shadowNpmInject = require('./shadow-npm-inject.js')

const { NODE_MODULES: NODE_MODULES$1, NPM: NPM$1, shadowBinPath } = constants
function findBinPathDetailsSync(binName) {
  const binPaths =
    vendor.libExports$2.sync(binName, {
      all: true,
      nothrow: true
    }) ?? []
  let shadowIndex = -1
  let theBinPath
  for (let i = 0, { length } = binPaths; i < length; i += 1) {
    const binPath = binPaths[i]
    // Skip our bin directory if it's in the front.
    if (path.dirname(binPath) === shadowBinPath) {
      shadowIndex = i
    } else {
      theBinPath = npm.resolveBinPath(binPath)
      break
    }
  }
  return {
    name: binName,
    path: theBinPath,
    shadowed: shadowIndex !== -1
  }
}
function findNpmPathSync(npmBinPath) {
  // Lazily access constants.WIN32.
  const { WIN32 } = constants
  let thePath = npmBinPath
  while (true) {
    const libNmNpmPath = path.join(thePath, 'lib', NODE_MODULES$1, NPM$1)
    // mise puts its npm bin in a path like:
    //   /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/bin/npm.
    // HOWEVER, the location of the npm install is:
    //   /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/lib/node_modules/npm.
    if (
      // Use existsSync here because statsSync, even with { throwIfNoEntry: false },
      // will throw an ENOTDIR error for paths like ./a-file-that-exists/a-directory-that-does-not.
      // See https://github.com/nodejs/node/issues/56993.
      fs.existsSync(libNmNpmPath) &&
      fs
        .statSync(libNmNpmPath, {
          throwIfNoEntry: false
        })
        ?.isDirectory()
    ) {
      thePath = path.join(libNmNpmPath, NPM$1)
    }
    const nmPath = path.join(thePath, NODE_MODULES$1)
    if (
      // npm bin paths may look like:
      //   /usr/local/share/npm/bin/npm
      //   /Users/SomeUsername/.nvm/versions/node/vX.X.X/bin/npm
      //   C:\Users\SomeUsername\AppData\Roaming\npm\bin\npm.cmd
      // OR
      //   C:\Program Files\nodejs\npm.cmd
      //
      // In practically all cases the npm path contains a node_modules folder:
      //   /usr/local/share/npm/bin/npm/node_modules
      //   C:\Program Files\nodejs\node_modules
      fs.existsSync(nmPath) &&
      fs
        .statSync(nmPath, {
          throwIfNoEntry: false
        })
        ?.isDirectory() &&
      // Optimistically look for the default location.
      (path.basename(thePath) === NPM$1 ||
        // Chocolatey installs npm bins in the same directory as node bins.
        (WIN32 && fs.existsSync(path.join(thePath, `${NPM$1}.cmd`))))
    ) {
      return thePath
    }
    const parent = path.dirname(thePath)
    if (parent === thePath) {
      return undefined
    }
    thePath = parent
  }
}
async function getPackageFilesForScan(cwd, inputPaths, supportedFiles, config) {
  debug.debugLog(
    `getPackageFilesForScan: resolving ${inputPaths.length} paths:\n`,
    inputPaths
  )

  // Lazily access constants.spinner.
  const { spinner } = constants
  const patterns = shadowNpmInject.pathsToGlobPatterns(inputPaths)
  spinner.start('Searching for local files to include in scan...')
  const entries = await shadowNpmInject.globWithGitIgnore(patterns, {
    cwd,
    socketConfig: config
  })
  if (debug.isDebug()) {
    spinner.stop()
    debug.debugLog(
      `Resolved ${inputPaths.length} paths to ${entries.length} local paths:\n`,
      entries
    )
    spinner.start('Searching for files now...')
  } else {
    spinner.start(
      `Resolved ${inputPaths.length} paths to ${entries.length} local paths, searching for files now...`
    )
  }
  const packageFiles = await shadowNpmInject.filterGlobResultToSupportedFiles(
    entries,
    supportedFiles
  )
  spinner.successAndStop(
    `Found ${packageFiles.length} local ${words.pluralize('file', packageFiles.length)}`
  )
  debug.debugLog('Absolute paths:\n', packageFiles)
  return packageFiles
}

const { NODE_MODULES, NPM, NPX, SOCKET_CLI_ISSUES_URL } = constants
function exitWithBinPathError(binName) {
  logger.logger.fail(
    `Socket unable to locate ${binName}; ensure it is available in the PATH environment variable`
  )
  // The exit code 127 indicates that the command or binary being executed
  // could not be found.
  // eslint-disable-next-line n/no-process-exit
  process.exit(127)
}
let _npmBinPathDetails
function getNpmBinPathDetails() {
  if (_npmBinPathDetails === undefined) {
    _npmBinPathDetails = findBinPathDetailsSync(NPM)
  }
  return _npmBinPathDetails
}
let _npxBinPathDetails
function getNpxBinPathDetails() {
  if (_npxBinPathDetails === undefined) {
    _npxBinPathDetails = findBinPathDetailsSync(NPX)
  }
  return _npxBinPathDetails
}
let _npmBinPath
function getNpmBinPath() {
  if (_npmBinPath === undefined) {
    _npmBinPath = getNpmBinPathDetails().path
    if (!_npmBinPath) {
      exitWithBinPathError(NPM)
    }
  }
  return _npmBinPath
}
function isNpmBinPathShadowed() {
  return getNpmBinPathDetails().shadowed
}
let _npxBinPath
function getNpxBinPath() {
  if (_npxBinPath === undefined) {
    _npxBinPath = getNpxBinPathDetails().path
    if (!_npxBinPath) {
      exitWithBinPathError(NPX)
    }
  }
  return _npxBinPath
}
function isNpxBinPathShadowed() {
  return getNpxBinPathDetails().shadowed
}
let _npmPath
function getNpmPath() {
  if (_npmPath === undefined) {
    const npmBinPath = getNpmBinPath()
    _npmPath = npmBinPath ? findNpmPathSync(npmBinPath) : undefined
    if (!_npmPath) {
      let message = 'Unable to find npm CLI install directory.'
      if (npmBinPath) {
        message += `\nSearched parent directories of ${path.dirname(npmBinPath)}.`
      }
      message += `\n\nThis is may be a bug with socket-npm related to changes to the npm CLI.\nPlease report to ${SOCKET_CLI_ISSUES_URL}.`
      logger.logger.fail(message)
      // The exit code 127 indicates that the command or binary being executed
      // could not be found.
      // eslint-disable-next-line n/no-process-exit
      process.exit(127)
    }
  }
  return _npmPath
}
let _npmRequire
function getNpmRequire() {
  if (_npmRequire === undefined) {
    const npmPath = getNpmPath()
    const npmNmPath = path.join(npmPath, NODE_MODULES, NPM)
    _npmRequire = Module.createRequire(
      path.join(
        fs.existsSync(npmNmPath) ? npmNmPath : npmPath,
        '<dummy-basename>'
      )
    )
  }
  return _npmRequire
}
let _arboristPkgPath
function getArboristPackagePath() {
  if (_arboristPkgPath === undefined) {
    const pkgName = '@npmcli/arborist'
    const mainPathWithForwardSlashes = path$1.normalizePath(
      getNpmRequire().resolve(pkgName)
    )
    const arboristPkgPathWithForwardSlashes = mainPathWithForwardSlashes.slice(
      0,
      mainPathWithForwardSlashes.lastIndexOf(pkgName) + pkgName.length
    )
    // Lazily access constants.WIN32.
    _arboristPkgPath = constants.WIN32
      ? path.normalize(arboristPkgPathWithForwardSlashes)
      : arboristPkgPathWithForwardSlashes
  }
  return _arboristPkgPath
}
let _arboristClassPath
function getArboristClassPath() {
  if (_arboristClassPath === undefined) {
    _arboristClassPath = path.join(
      getArboristPackagePath(),
      'lib/arborist/index.js'
    )
  }
  return _arboristClassPath
}
let _arboristDepValidPath
function getArboristDepValidPath() {
  if (_arboristDepValidPath === undefined) {
    _arboristDepValidPath = path.join(
      getArboristPackagePath(),
      'lib/dep-valid.js'
    )
  }
  return _arboristDepValidPath
}
let _arboristEdgeClassPath
function getArboristEdgeClassPath() {
  if (_arboristEdgeClassPath === undefined) {
    _arboristEdgeClassPath = path.join(getArboristPackagePath(), 'lib/edge.js')
  }
  return _arboristEdgeClassPath
}
let _arboristNodeClassPath
function getArboristNodeClassPath() {
  if (_arboristNodeClassPath === undefined) {
    _arboristNodeClassPath = path.join(getArboristPackagePath(), 'lib/node.js')
  }
  return _arboristNodeClassPath
}
let _arboristOverrideSetClassPath
function getArboristOverrideSetClassPath() {
  if (_arboristOverrideSetClassPath === undefined) {
    _arboristOverrideSetClassPath = path.join(
      getArboristPackagePath(),
      'lib/override-set.js'
    )
  }
  return _arboristOverrideSetClassPath
}

exports.getArboristClassPath = getArboristClassPath
exports.getArboristDepValidPath = getArboristDepValidPath
exports.getArboristEdgeClassPath = getArboristEdgeClassPath
exports.getArboristNodeClassPath = getArboristNodeClassPath
exports.getArboristOverrideSetClassPath = getArboristOverrideSetClassPath
exports.getNpmBinPath = getNpmBinPath
exports.getNpmRequire = getNpmRequire
exports.getNpxBinPath = getNpxBinPath
exports.getPackageFilesForScan = getPackageFilesForScan
exports.isNpmBinPathShadowed = isNpmBinPathShadowed
exports.isNpxBinPathShadowed = isNpxBinPathShadowed
//# debugId=bdcbda3e-15b7-46f4-9ba0-e5fa9efa7fe8
//# sourceMappingURL=shadow-npm-paths.js.map
