_SIGNAL,
  },
  uvErrmapGet,
  overrideStackTrace,
} = require('internal/errors');
const { signals } = internalBinding('constants').os;
const {
  isArrayBufferDetached: _isArrayBufferDetached,
  privateSymbols: {
    arrow_message_private_symbol,
    decorated_private_symbol,
  },
  sleep: _sleep,
  toUSVString: _toUSVString,
} = internalBinding('util');
const { isNativeError } = internalBinding('types');
const { getOptionValue } = require('internal/options');

const noCrypto = !process.versions.openssl;

const experimentalWarnings = new SafeSet();

const colorRegExp = /\u001b\[\d\d?m/g; // eslint-disable-line no-control-regex

const unpairedSurrogateRe =
  /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
function toUSVString(val) {
  const str = `${val}`;
  // As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are
  // slower than `unpairedSurrogateRe.exec()`.
  const match = RegExpPrototypeExec(unpairedSurrogateRe, str);
  if (!match)
    return str;
  return _toUSVString(str, match.index);
}

let uvBinding;

function lazyUv() {
  uvBinding ??= internalBinding('uv');
  return uvBinding;
}

function removeColors(str) {
  return StringPrototypeReplace(str, colorRegExp, '');
}

function isError(e) {
  // An error could be an instance of Error while not being a native error
  // or could be from a different realm and not be instance of Error but still
  // be a native error.
  return isNativeError(e) || e instanceof Error;
}

// Keep a list of deprecation codes that have been warned on so we only warn on
// each one once.
const codesWarned = new SafeSet();

let validateString;

function getDeprecationWarningEmitter(
  code, msg, deprecated, useEmitSync,
  shouldEmitWarning = () => true,
) {
  let warned = false;
  return function() {
    if (!warned && shouldEmitWarning()) {
      warned = true;
      if (code !== undefined) {
        if (!codesWarned.has(code)) {
          const emitWarning = useEmitSync ?
            require('internal/process/warning').emitWarningSync :
            process.emitWarning;
          emitWarning(msg, 'DeprecationWarning', code, deprecated);
          codesWarned.add(code);
        }
      } else {
        process.emitWarning(msg, 'DeprecationWarning', deprecated);
      }
    }
  };
}

function isPendingDeprecation() {
  return getOptionValue('--pending-deprecation') &&
    !getOptionValue('--no-deprecation');
}

// Internal deprecator for pending --pending-deprecation. This can be invoked
// at snapshot building time as the warning permission is only queried at
// run time.
function pendingDeprecate(fn, msg, code) {
  const emitDeprecationWarning = getDeprecationWarningEmitter(
    code, msg, deprecated, false, isPendingDeprecation,
  );
  function deprecated(...args) {
    emitDeprecationWarning();
    return ReflectApply(fn, this, args);
  }
  return deprecated;
}

// Mark that a method should not be used.
// Returns a modified function which warns once by default.
// If --no-deprecation is set, then it is a no-op.
function deprecate(fn, msg, code, useEmitSync) {
  if (process.noDeprecation === true) {
    return fn;
  }

  // Lazy-load to avoid a circular dependency.
  if (validateString === undefined)
    ({ validateString } = require('internal/validators'));

  if (code !== undefined)
    validateString(code, 'code');

  const emitDeprecationWarning = getDeprecationWarningEmitter(
    code, msg, deprecated, useEmitSync,
  );

  function deprecated(...args) {
    emitDeprecationWarning();
    if (new.target) {
      return ReflectConstruct(fn, args, new.target);
    }
    return ReflectApply(fn, this, args);
  }

  // The wrapper will keep the same prototype as fn to maintain prototype chain
  ObjectSetPrototypeOf(deprecated, fn);
  if (fn.prototype) {
    // Setting this (rather than using Object.setPrototype, as above) ensures
    // that calling the unwrapped constructor gives an instanceof the wrapped
    // constructor.
    deprecated.prototype = fn.prototype;
  }

  return deprecated;
}

function decorateErrorStack(err) {
  if (!(isError(err) && err.stack) || err[decorated_private_symbol])
    return;

  const arrow = err[arrow_message_private_symbol];

  if (arrow) {
    err.stack = arrow + err.stack;
    err[decorated_private_symbol] = true;
  }
}

function assertCrypto() {
  if (noCrypto)
    throw new ERR_NO_CRYPTO();
}

// Return undefined if there is no match.
// Move the "slow cases" to a separate function to make sure this function gets
// inlined properly. That prioritizes the common case.
function normalizeEncoding(enc) {
  if (enc == null || enc === 'utf8' || enc === 'utf-8') return 'utf8';
  return slowCases(enc);
}

function slowCases(enc) {
  switch (enc.length) {
    case 4:
      if (enc === 'UTF8') return 'utf8';
      if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le';
      enc = `${enc}`.toLowerCase();
      if (enc === 'utf8') return 'utf8';
      if (enc === 'ucs2') return 'utf16le';
      break;
    case 3:
      if (enc === 'hex' || enc === 'HEX' ||
          `${enc}`.toLowerCase() === 'hex')
        return 'hex';
      break;
    case 5:
      if (enc === 'ascii') return 'ascii';
      if (enc === 'ucs-2') return 'utf16le';
      if (enc === 'UTF-8') return 'utf8';
      if (enc === 'ASCII') return 'ascii';
      if (enc === 'UCS-2') return 'utf16le';
      enc = `${enc}`.toLowerCase();
      if (enc === 'utf-8') return 'utf8';
      if (enc === 'ascii') return 'ascii';
      if (enc === 'ucs-2') return 'utf16le';
      break;
    case 6:
      if (enc === 'base64') return 'base64';
      if (enc === 'latin1' || enc === 'binary') return 'latin1';
      if (enc === 'BASE64') return 'base64';
      if (enc === 'LATIN1' || enc === 'BINARY') return 'latin1';
      enc = `${enc}`.toLowerCase();
      if (enc === 'base64') return 'base64';
      if (enc === 'latin1' || enc === 'binary') return 'latin1';
      break;
    case 7:
      if (enc === 'utf16le' || enc === 'UTF16LE' ||
          `${enc}`.toLowerCase() === 'utf16le')
        return 'utf16le';
      break;
    case 8:
      if (enc === 'utf-16le' || enc === 'UTF-16LE' ||
        `${enc}`.toLowerCase() === 'utf-16le')
        return 'utf16le';
      break;
    case 9:
      if (enc === 'base64url' || enc === 'BASE64URL' ||
          `${enc}`.toLowerCase() === 'base64url')
        return 'base64url';
      break;
    default:
      if (enc === '') return 'utf8';
  }
}

function emitExperimentalWarning(feature) {
  if (experimentalWarnings.has(feature)) return;
  const msg = `${feature} is an experimental feature and might change at any time`;
  experimentalWarnings.add(feature);
  process.emitWarning(msg, 'ExperimentalWarning');
}

function filterDuplicateStrings(items, low) {
  const map = new SafeMap();
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const key = StringPrototypeToLowerCase(item);
    if (low) {
      map.set(key, key);
    } else {
      map.set(key, item);
    }
  }
  return ArrayPrototypeSort(ArrayFrom(map.values()));
}

function cachedResult(fn) {
  let result;
  return () => {
    if (result === undefined)
      result = fn();
    return ArrayPrototypeSlice(result);
  };
}

// Useful for Wrapping an ES6 Class with a constructor Function that
// does not require the new keyword. For instance:
//   class A { constructor(x) {this.x = x;}}
//   const B = createClassWrapper(A);
//   B() instanceof A // true
//   B() instanceof B // true
function createClassWrapper(type) {
  function fn(...args) {
    return ReflectConstruct(type, args, new.target || type);
  }
  // Mask the wrapper function name and length values
  ObjectDefineProperties(fn, {
    name: { __proto__: null, value: type.name },
    length: { __proto__: null, value: type.length },
  });
  ObjectSetPrototypeOf(fn, type);
  fn.prototype = type.prototype;
  return fn;
}

let signalsToNamesMapping;
function getSignalsToNamesMapping() {
  if (signalsToNamesMapping !== undefined)
    return signalsToNamesMapping;

  signalsToNamesMapping = ObjectCreate(null);
  for (const key in signals) {
    signalsToNamesMapping[signals[key]] = key;
  }

  return signalsToNamesMapping;
}

function convertToValidSignal(signal) {
  if (typeof signal === 'number' && getSignalsToNamesMapping()[signal])
    return signal;

  if (typeof signal === 'string') {
    const signalName = signals[StringPrototypeToUpperCase(signal)];
    if (signalName) return signalName;
  }

  throw new ERR_UNKNOWN_SIGNAL(signal);
}

function getConstructorOf(obj) {
  while (obj) {
    const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
    if (descriptor !== undefined &&
        typeof descriptor.value === 'function' &&
        descriptor.value.name !== '') {
      return descriptor.value;
    }

    obj = ObjectGetPrototypeOf(obj);
  }

  return null;
}

let cachedURL;
let cachedCWD;

/**
 * Get the current working directory while accounting for the possibility that it has been deleted.
 * `process.cwd()` can fail if the parent directory is deleted while the process runs.
 * @returns {URL} The current working directory or the volume root if it cannot be determined.
 */
function getCWDURL() {
  const { sep } = require('path');
  const { pathToFileURL } = require('internal/url');

  let cwd;

  try {
    // The implementation of `process.cwd()` already uses proper cache when it can.
    // It's a relatively cheap call performance-wise for the most common use case.
    cwd = process.cwd();
  } catch {
    cachedURL ??= pathToFileURL(sep);
  }

  if (cwd != null && cwd !== cachedCWD) {
    cachedURL = pathToFileURL(cwd + sep);
    cachedCWD = cwd;
  }

  return cachedURL;
}

function getSystemErrorName(err) {
  const entry = uvErrmapGet(err);
  return entry ? entry[0] : `Unknown system error ${err}`;
}

function getSystemErrorMap() {
  return lazyUv().getErrorMap();
}

const kCustomPromisifiedSymbol = SymbolFor('nodejs.util.promisify.custom');
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');

let validateFunction;

function promisify(original) {
  // Lazy-load to avoid a circular dependency.
  if (validateFunction === undefined)
    ({ validateFunction } = require('internal/validators'));

  validateFunction(original, 'original');

  if (original[kCustomPromisifiedSymbol]) {
    const fn = original[kCustomPromisifiedSymbol];

    validateFunction(fn, 'util.promisify.custom');

    return ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
      __proto__: null,
      value: fn, enumerable: false, writable: false, configurable: true,
    });
  }

  // Names to create an object from in case the callback receives multiple
  // arguments, e.g. ['bytesRead', 'buffer'] for fs.read.
  const argumentNames = original[kCustomPromisifyArgsSymbol];

  function fn(...args) {
    return new Promise((resolve, reject) => {
      ArrayPrototypePush(args, (err, ...values) => {
        if (err) {
          return reject(err);
        }
        if (argumentNames !== undefined && values.length > 1) {
          const obj = {};
          for (let i = 0; i < argumentNames.length; i++)
            obj[argumentNames[i]] = values[i];
          resolve(obj);
        } else {
          resolve(values[0]);
        }
      });
      ReflectApply(original, this, args);
    });
  }

  ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));

  ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
    __proto__: null,
    value: fn, enumerable: false, writable: false, configurable: true,
  });

  const descriptors = ObjectGetOwnPropertyDescriptors(original);
  const propertiesValues = ObjectValues(descriptors);
  for (let i = 0; i < propertiesValues.length; i++) {
    // We want to use null-prototype objects to not rely on globally mutable
    // %Object.prototype%.
    ObjectSetPrototypeOf(propertiesValues[i], null);
  }
  return ObjectDefineProperties(fn, descriptors);
}

promisify.custom = kCustomPromisifiedSymbol;

// The built-in Array#join is slower in v8 6.0
function join(output, separator) {
  let str = '';
  if (output.length !== 0) {
    const lastIndex = output.length - 1;
    for (let i = 0; i < lastIndex; i++) {
      // It is faster not to use a template string here
      str += output[i];
      str += separator;
    }
    str += output[lastIndex];
  }
  return str;
}

// As of V8 6.6, depending on the size of the array, this is anywhere
// between 1.5-10x faster than the two-arg version of Array#splice()
function spliceOne(list, index) {
  for (; index + 1 < list.length; index++)
    list[index] = list[index + 1];
  list.pop();
}

const kNodeModulesRE = /^(.*)[\\/]node_modules[\\/]/;

let getStructuredStack;

function isInsideNodeModules() {
  if (getStructuredStack === undefined) {
    // Lazy-load to avoid a circular dependency.
    const { runInNewContext } = require('vm');
    // Use `runInNewContext()` to get something tamper-proof and
    // side-effect-free. Since this is currently only used for a deprecated API,
    // the perf implications should be okay.
    getStructuredStack = runInNewContext(`(function() {
      try { Error.stackTraceLimit = Infinity; } catch {}
      return function structuredStack() {
        const e = new Error();
        overrideStackTrace.set(e, (err, trace) => trace);
        return e.stack;
      };
    })()`, { overrideStackTrace }, { filename: 'structured-stack' });
  }

  const stack = getStructuredStack();

  // Iterate over all stack frames and look for the first one not coming
  // from inside Node.js itself:
  if (ArrayIsArray(stack)) {
    for (const frame of stack) {
      const filename = frame.getFileName();
      // If a filename does not start with / or contain \,
      // it's likely from Node.js core.
      if (RegExpPrototypeExec(/^\/|\\/, filename) === null)
        continue;
      return RegExpPrototypeExec(kNodeModulesRE, filename) !== null;
    }
  }
  return false;
}

function once(callback) {
  let called = false;
  return function(...args) {
    if (called) return;
    called = true;
    return ReflectApply(callback, this, args);
  };
}

let validateUint32;

function sleep(msec) {
  // Lazy-load to avoid a circular dependency.
  if (validateUint32 === undefined)
    ({ validateUint32 } = require('internal/validators'));

  validateUint32(msec, 'msec');
  _sleep(msec);
}

function createDeferredPromise() {
  let resolve;
  let reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });

  return { promise, resolve, reject };
}

// https://heycam.github.io/webidl/#define-the-operations
function defineOperation(target, name, method) {
  ObjectDefineProperty(target, name, {
    __proto__: null,
    writable: true,
    enumerable: true,
    configurable: true,
    value: method,
  });
}

// https://heycam.github.io/webidl/#es-interfaces
function exposeInterface(target, name, interfaceObject) {
  ObjectDefineProperty(target, name, {
    __proto__: null,
    writable: true,
    enumerable: false,
    configurable: true,
    value: interfaceObject,
  });
}

function defineLazyProperties(target, id, keys, enumerable = true) {
  const descriptors = { __proto__: null };
  let mod;
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    let lazyLoadedValue;
    function set(value) {
      ObjectDefineProperty(target, key, {
        __proto__: null,
        writable: true,
        value,
      });
    }
    ObjectDefineProperty(set, 'name', {
      __proto__: null,
      value: `set ${key}`,
    });
    function get() {
      mod ??= require(id);
      if (lazyLoadedValue === undefined) {
        lazyLoadedValue = mod[key];
        set(lazyLoadedValue);
      }
      return lazyLoadedValue;
    }
    ObjectDefineProperty(get, 'name', {
      __proto__: null,
      value: `get ${key}`,
    });
    descriptors[key] = {
      __proto__: null,
      configurable: true,
      enumerable,
      get,
      set,
    };
  }
  ObjectDefineProperties(target, descriptors);
}

function defineReplaceableLazyAttribute(target, id, keys, writable = true) {
  let mod;
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    let value;
    let setterCalled = false;

    function get() {
      if (setterCalled) {
        return value;
      }
      mod ??= require(id);
      value ??= mod[key];
      return value;
    }

    ObjectDefineProperty(get, 'name', {
      __proto__: null,
      value: `get ${key}`,
    });

    function set(val) {
      setterCalled = true;
      value = val;
    }
    ObjectDefineProperty(set, 'name', {
      __proto__: null,
      value: `set ${key}`,
    });

    ObjectDefineProperty(target, key, {
      __proto__: null,
      enumerable: true,
      configurable: true,
      get,
      set: writable ? set : undefined,
    });
  }
}

function exposeLazyInterfaces(target, id, keys) {
  defineLazyProperties(target, id, keys, false);
}

let _DOMException;
const lazyDOMExceptionClass = () => {
  _DOMException ??= internalBinding('messaging').DOMException;
  return _DOMException;
};

const lazyDOMException = hideStackFrames((message, name) => {
  _DOMException ??= internalBinding('messaging').DOMException;
  return new _DOMException(message, name);
});

const kEnumerableProperty = ObjectCreate(null);
kEnumerableProperty.enumerable = true;
ObjectFreeze(kEnumerableProperty);

const kEmptyObject = ObjectFreeze(ObjectCreate(null));

function filterOwnProperties(source, keys) {
  const filtered = ObjectCreate(null);
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    if (ObjectPrototypeHasOwnProperty(source, key)) {
      filtered[key] = source[key];
    }
  }

  return filtered;
}

/**
 * Mimics `obj[key] = value` but ignoring potential prototype inheritance.
 * @param {any} obj
 * @param {string} key
 * @param {any} value
 * @returns {any}
 */
function setOwnProperty(obj, key, value) {
  return ObjectDefineProperty(obj, key, {
    __proto__: null,
    configurable: true,
    enumerable: true,
    value,
    writable: true,
  });
}

let internalGlobal;
function getInternalGlobal() {
  if (internalGlobal == null) {
    // Lazy-load to avoid a circular dependency.
    const { runInNewContext } = require('vm');
    internalGlobal = runInNewContext('this', undefined, { contextName: 'internal' });
  }
  return internalGlobal;
}

function SideEffectFreeRegExpPrototypeExec(regex, string) {
  const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
  return FunctionPrototypeCall(RegExpFromAnotherRealm.prototype.exec, regex, string);
}

const crossRelmRegexes = new SafeWeakMap();
function getCrossRelmRegex(regex) {
  const cached = crossRelmRegexes.get(regex);
  if (cached) return cached;

  let flagString = '';
  if (RegExpPrototypeGetHasIndices(regex)) flagString += 'd';
  if (RegExpPrototypeGetGlobal(regex)) flagString += 'g';
  if (RegExpPrototypeGetIgnoreCase(regex)) flagString += 'i';
  if (RegExpPrototypeGetMultiline(regex)) flagString += 'm';
  if (RegExpPrototypeGetDotAll(regex)) flagString += 's';
  if (RegExpPrototypeGetUnicode(regex)) flagString += 'u';
  if (RegExpPrototypeGetSticky(regex)) flagString += 'y';

  const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
  const crossRelmRegex = new RegExpFromAnotherRealm(RegExpPrototypeGetSource(regex), flagString);
  crossRelmRegexes.set(regex, crossRelmRegex);
  return crossRelmRegex;
}

function SideEffectFreeRegExpPrototypeSymbolReplace(regex, string, replacement) {
  return getCrossRelmRegex(regex)[SymbolReplace](string, replacement);
}

function SideEffectFreeRegExpPrototypeSymbolSplit(regex, string, limit = undefined) {
  return getCrossRelmRegex(regex)[SymbolSplit](string, limit);
}


function isArrayBufferDetached(value) {
  if (ArrayBufferPrototypeGetByteLength(value) === 0) {
    return _isArrayBufferDetached(value);
  }

  return false;
}

/**
 * Helper function to lazy-load an initialize-once value.
 * @template T Return value of initializer
 * @param {()=>T} initializer Initializer of the lazily loaded value.
 * @returns {()=>T}
 */
function getLazy(initializer) {
  let value;
  let initialized = false;
  return function() {
    if (initialized === false) {
      value = initializer();
      initialized = true;
    }
    return value;
  };
}

// Setup user-facing NODE_V8_COVERAGE environment variable that writes
// ScriptCoverage objects to a specified directory.
function setupCoverageHooks(dir) {
  const cwd = require('internal/process/execution').tryGetCwd();
  const { resolve } = require('path');
  const coverageDirectory = resolve(cwd, dir);
  const { sourceMapCacheToObject } =
    require('internal/source_map/source_map_cache');

  if (process.features.inspector) {
    internalBinding('profiler').setCoverageDirectory(coverageDirectory);
    internalBinding('profiler').setSourceMapCacheGetter(sourceMapCacheToObject);
  } else {
    process.emitWarning('The inspector is disabled, ' +
                        'coverage could not be collected',
                        'Warning');
    return '';
  }
  return coverageDirectory;
}

module.exports = {
  getLazy,
  assertCrypto,
  cachedResult,
  convertToValidSignal,
  createClassWrapper,
  createDeferredPromise,
  decorateErrorStack,
  defineOperation,
  defineLazyProperties,
  defineReplaceableLazyAttribute,
  deprecate,
  emitExperimentalWarning,
  exposeInterface,
  exposeLazyInterfaces,
  filterDuplicateStrings,
  filterOwnProperties,
  getConstructorOf,
  getCWDURL,
  getInternalGlobal,
  getSystemErrorMap,
  getSystemErrorName,
  isArrayBufferDetached,
  isError,
  isInsideNodeModules,
  join,
  lazyDOMException,
  lazyDOMExceptionClass,
  normalizeEncoding,
  once,
  promisify,
  SideEffectFreeRegExpPrototypeExec,
  SideEffectFreeRegExpPrototypeSymbolReplace,
  SideEffectFreeRegExpPrototypeSymbolSplit,
  sleep,
  spliceOne,
  setupCoverageHooks,
  toUSVString,
  removeColors,

  // Symbol used to customize promisify conversion
  customPromisifyArgs: kCustomPromisifyArgsSymbol,

  // Symbol used to provide a custom inspect function for an object as an
  // alternative to using 'inspect'
  customInspectSymbol: SymbolFor('nodejs.util.inspect.custom'),

  // Used by the buffer module to capture an internal reference to the
  // default isEncoding implementation, just in case userland overrides it.
  kIsEncodingSymbol: Symbol('kIsEncodingSymbol'),
  kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol'),

  kEmptyObject,
  kEnumerableProperty,
  setOwnProperty,
  pendingDeprecate,
};
