/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 * @oncall react_native
 */

"use strict";

// Capture any uncaughtException listeners already set, see below.
const uncaughtExceptionHandlers = process.listeners("uncaughtException");
const hermesc = require("./emhermesc.js")({
  noInitialRun: true,
  noExitRuntime: true,
  // Do not call console methods
  print: () => {},
  printErr: () => {},
});

// Workaround: Emscripten adds an uncaught exception listener on startup, which
// rethrows and causes node to exit with code 7 and print emhermesc.js (1.4MB)
// to stdout. This removes any newly-set listeners.
//
// Remove when emhermesc.js is rebuilt with NODEJS_CATCH_EXIT=0 (D34790356)
const hermesUncaughtExceptionHandler = process
  .listeners("uncaughtException")
  .find((listener) => !uncaughtExceptionHandlers.includes(listener));
if (hermesUncaughtExceptionHandler != null) {
  process.removeListener("uncaughtException", hermesUncaughtExceptionHandler);
}
const compileToBytecode = hermesc.cwrap("hermesCompileToBytecode", "number", [
  "number",
  "number",
  "string",
  "number",
  "number",
]);
const getError = hermesc.cwrap("hermesCompileResult_getError", "string", [
  "number",
]);
const getBytecodeAddr = hermesc.cwrap(
  "hermesCompileResult_getBytecodeAddr",
  "number",
  ["number"]
);
const getBytecodeSize = hermesc.cwrap(
  "hermesCompileResult_getBytecodeSize",
  "number",
  ["number"]
);
const free = hermesc.cwrap("hermesCompileResult_free", "void", ["number"]);
const props = JSON.parse(
  hermesc.ccall("hermesGetProperties", "string", [], [])
);
function strdup(str) {
  var copy = Buffer.from(str, "utf8");
  var size = copy.length + 1;
  var address = hermesc._malloc(size);
  if (!address) {
    throw new Error("hermesc string allocation error");
  }
  hermesc.HEAP8.set(copy, address);
  hermesc.HEAP8[address + copy.length] = 0;
  return {
    ptr: address,
    size,
  };
}
const align = (offset /* eslint-disable-next-line no-bitwise */) =>
  (offset + props.BYTECODE_ALIGNMENT - 1) & ~(props.BYTECODE_ALIGNMENT - 1);
module.exports.align = align;
module.exports.compile = function (source, { sourceURL, sourceMap }) {
  const buffer =
    typeof source === "string" ? Buffer.from(source, "utf8") : source;
  const address = hermesc._malloc(buffer.length + 1);
  if (!address) {
    throw new Error("Hermesc is out of memory.");
  }
  try {
    hermesc.HEAP8.set(buffer, address);
    hermesc.HEAP8[address + buffer.length] = 0;

    // Strings are passed on the stack by default. Explicitly pass the source map
    // on the heap to avoid problems with large ones.
    const sourceMapNotNull =
      sourceMap !== null && sourceMap !== void 0 ? sourceMap : "";
    const mapOnHeap = strdup(sourceMapNotNull);
    let result;
    try {
      result = compileToBytecode(
        address,
        buffer.length + 1,
        sourceURL,
        mapOnHeap.ptr,
        mapOnHeap.size
      );
    } finally {
      hermesc._free(mapOnHeap.ptr);
    }
    try {
      const error = getError(result);
      if (error) {
        throw new Error(error);
      }
      const bufferFromHBC = Buffer.from(
        hermesc.HEAP8.buffer,
        getBytecodeAddr(result),
        getBytecodeSize(result)
      );
      const bytecode = Buffer.alloc(align(bufferFromHBC.length));
      bufferFromHBC.copy(bytecode, 0);
      return {
        bytecode,
      };
    } finally {
      free(result);
    }
  } finally {
    hermesc._free(address);
  }
};
module.exports.validateBytecodeModule = function (bytecode, offset) {
  if ((bytecode.byteOffset + offset) % props.BYTECODE_ALIGNMENT) {
    throw new Error(
      "Bytecode is not aligned to " + props.BYTECODE_ALIGNMENT + "."
    );
  }
  const fileLength = bytecode.readUInt32LE(offset + props.LENGTH_OFFSET);
  if (
    bytecode.length - offset < props.HEADER_SIZE ||
    bytecode.length - offset < fileLength
  ) {
    throw new Error("Bytecode buffer is too small.");
  }
  if (
    bytecode.readUInt32LE(offset + 0) !== props.MAGIC[0] ||
    bytecode.readUInt32LE(offset + 4) !== props.MAGIC[1]
  ) {
    throw new Error("Bytecode buffer is missing magic value.");
  }
  const version = bytecode.readUInt32LE(offset + 8);
  if (version !== props.VERSION) {
    throw new Error(
      "Bytecode version is " +
        version +
        " but " +
        props.VERSION +
        " is required."
    );
  }
};
module.exports.getFileLength = function (bytecode, offset) {
  return bytecode.readUInt32LE(offset + props.LENGTH_OFFSET);
};
module.exports.VERSION = props.VERSION;
