/**
 * 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";

const { BundleBuilder, createIndexMap } = require("./BundleBuilder");
const composeSourceMaps = require("./composeSourceMaps");
const Consumer = require("./Consumer");
// We need to export this for `metro-symbolicate`
const normalizeSourcePath = require("./Consumer/normalizeSourcePath");
const { generateFunctionMap } = require("./generateFunctionMap");
const Generator = require("./Generator");
// $FlowFixMe[untyped-import] - source-map
const SourceMap = require("source-map");
function fromRawMappingsImpl(isBlocking, onDone, modules, offsetLines) {
  const modulesToProcess = modules.slice();
  const generator = new Generator();
  let carryOver = offsetLines;
  function processNextModule() {
    if (modulesToProcess.length === 0) {
      return true;
    }
    const mod = modulesToProcess.shift();
    const { code, map } = mod;
    if (Array.isArray(map)) {
      addMappingsForFile(generator, map, mod, carryOver);
    } else if (map != null) {
      throw new Error(
        `Unexpected module with full source map found: ${mod.path}`
      );
    }
    carryOver = carryOver + countLines(code);
    return false;
  }
  function workLoop() {
    const time = process.hrtime();
    while (true) {
      const isDone = processNextModule();
      if (isDone) {
        onDone(generator);
        break;
      }
      if (!isBlocking) {
        // Keep the loop running but try to avoid blocking
        // for too long because this is not in a worker yet.
        const diff = process.hrtime(time);
        const NS_IN_MS = 1000000;
        if (diff[1] > 50 * NS_IN_MS) {
          // We've blocked for more than 50ms.
          // This code currently runs on the main thread,
          // so let's give Metro an opportunity to handle requests.
          setImmediate(workLoop);
          break;
        }
      }
    }
  }
  workLoop();
}

/**
 * Creates a source map from modules with "raw mappings", i.e. an array of
 * tuples with either 2, 4, or 5 elements:
 * generated line, generated column, source line, source line, symbol name.
 * Accepts an `offsetLines` argument in case modules' code is to be offset in
 * the resulting bundle, e.g. by some prefix code.
 */
function fromRawMappings(modules, offsetLines = 0) {
  let generator;
  fromRawMappingsImpl(
    true,
    (g) => {
      generator = g;
    },
    modules,
    offsetLines
  );
  if (generator == null) {
    throw new Error("Expected fromRawMappingsImpl() to finish synchronously.");
  }
  return generator;
}
async function fromRawMappingsNonBlocking(modules, offsetLines = 0) {
  return new Promise((resolve) => {
    fromRawMappingsImpl(false, resolve, modules, offsetLines);
  });
}

/**
 * Transforms a standard source map object into a Raw Mappings object, to be
 * used across the bundler.
 */
function toBabelSegments(sourceMap) {
  const rawMappings = [];
  new SourceMap.SourceMapConsumer(sourceMap).eachMapping((map) => {
    rawMappings.push(
      map.originalLine == null || map.originalColumn == null
        ? {
            generated: {
              line: map.generatedLine,
              column: map.generatedColumn,
            },
            source: map.source,
            name: map.name,
          }
        : {
            generated: {
              line: map.generatedLine,
              column: map.generatedColumn,
            },
            original: {
              line: map.originalLine,
              column: map.originalColumn,
            },
            source: map.source,
            name: map.name,
          }
    );
  });
  return rawMappings;
}
function toSegmentTuple(mapping) {
  const { column, line } = mapping.generated;
  const { name, original } = mapping;
  if (original == null) {
    return [line, column];
  }
  if (typeof name !== "string") {
    return [line, column, original.line, original.column];
  }
  return [line, column, original.line, original.column, name];
}
function addMappingsForFile(generator, mappings, module, carryOver) {
  generator.startFile(module.path, module.source, module.functionMap);
  for (let i = 0, n = mappings.length; i < n; ++i) {
    addMapping(generator, mappings[i], carryOver);
  }
  generator.endFile();
}
function addMapping(generator, mapping, carryOver) {
  const n = mapping.length;
  const line = mapping[0] + carryOver;
  // lines start at 1, columns start at 0
  const column = mapping[1];
  if (n === 2) {
    generator.addSimpleMapping(line, column);
  } else if (n === 4) {
    // $FlowIssue[invalid-tuple-arity] Arity is ensured by conidition on length
    const sourceMap = mapping;
    generator.addSourceMapping(line, column, sourceMap[2], sourceMap[3]);
  } else if (n === 5) {
    // $FlowIssue[invalid-tuple-arity] Arity is ensured by conidition on length
    const sourceMap = mapping;
    generator.addNamedSourceMapping(
      line,
      column,
      sourceMap[2],
      sourceMap[3],
      sourceMap[4]
    );
  } else {
    throw new Error(`Invalid mapping: [${mapping.join(", ")}]`);
  }
}
const newline = /\r\n?|\n|\u2028|\u2029/g;
const countLines = (string) => (string.match(newline) || []).length + 1;
module.exports = {
  BundleBuilder,
  composeSourceMaps,
  Consumer,
  createIndexMap,
  generateFunctionMap,
  fromRawMappings,
  fromRawMappingsNonBlocking,
  normalizeSourcePath,
  toBabelSegments,
  toSegmentTuple,
};
