// Copyright 2018-2026 the Deno authors. MIT license.

(function () {
const { core, primordials } = __bootstrap;
const {
  Error,
  ErrorPrototype,
  MathMin,
  NumberIsNaN,
  ObjectPrototypeIsPrototypeOf,
} = primordials;

const { Buffer, kMaxLength } = core.loadExtScript(
  "ext:deno_node/internal/buffer.mjs",
);
const {
  emitAfter,
  emitBefore,
  emitDestroy,
  emitInit,
  executionAsyncId,
  newAsyncId,
} = core.loadExtScript("ext:deno_node/internal/async_hooks.ts");
const {
  validateFunction,
  validateNumber,
} = core.loadExtScript("ext:deno_node/internal/validators.mjs");
const { ERR_OUT_OF_RANGE } = core.loadExtScript(
  "ext:deno_node/internal/errors.ts",
);

const lazyProcess = core.createLazyLoader("node:process");

const MAX_RANDOM_VALUES = 65536;
const kMaxInt32 = 2 ** 31 - 1;
const kMaxPossibleLength = MathMin(kMaxLength, kMaxInt32);
const MAX_SIZE = kMaxPossibleLength;

// Mirrors Node's lib/internal/crypto/random.js assertSize() with
// elementSize = 1, offset = 0, length = Infinity.
function assertSize(size: number): number {
  validateNumber(size, "size");

  if (NumberIsNaN(size) || size > kMaxPossibleLength || size < 0) {
    throw new ERR_OUT_OF_RANGE(
      "size",
      `>= 0 && <= ${kMaxPossibleLength}`,
      size,
    );
  }

  return size >>> 0;
}

function generateRandomBytes(size: number) {
  const bytes = Buffer.allocUnsafeSlow(size);

  //Work around for getRandomValues max generation
  if (size > MAX_RANDOM_VALUES) {
    for (
      let generated = 0;
      generated < size;
      generated += MAX_RANDOM_VALUES
    ) {
      globalThis.crypto.getRandomValues(
        // deno-lint-ignore deno-internal/prefer-primordials -- Buffer.prototype.slice (Node Buffer method, not TypedArray slice)
        bytes.slice(generated, generated + MAX_RANDOM_VALUES),
      );
    }
  } else {
    globalThis.crypto.getRandomValues(bytes);
  }

  return bytes;
}

/**
 * @param size Buffer length, must be equal or greater than zero
 */
function randomBytes(
  size: number,
  cb?: (err: Error | null, buf?: Buffer) => void,
): Buffer | void {
  size = assertSize(size);
  if (cb !== undefined) {
    validateFunction(cb, "callback");
  }
  if (typeof cb === "function") {
    let err: Error | null = null, bytes: Buffer;
    try {
      bytes = generateRandomBytes(size);
    } catch (e) {
      if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, e)) {
        err = e;
      } else {
        err = new Error("[non-error thrown]");
      }
    }

    // Set up async_hooks tracking
    const asyncId = newAsyncId();
    const triggerAsyncId = executionAsyncId();
    const resource = {};
    emitInit(asyncId, "RANDOMBYTESREQUEST", triggerAsyncId, resource);

    const process = lazyProcess().default;
    process.nextTick(() => {
      emitBefore(asyncId);
      try {
        if (err) {
          cb(err);
        } else {
          cb(null, bytes);
        }
      } catch (callbackErr) {
        // If there's an active domain, emit error to it
        if (process.domain && process.domain.listenerCount("error") > 0) {
          process.domain.emit("error", callbackErr);
        } else {
          throw callbackErr;
        }
      } finally {
        emitAfter(asyncId);
        emitDestroy(asyncId);
      }
    });
  } else {
    return generateRandomBytes(size);
  }
}

return { default: randomBytes, randomBytes, MAX_RANDOM_VALUES, MAX_SIZE };
})();
