'use strict';

const common = require('../common');
if (!process.config.variables.node_use_amaro) {
  common.skip('Requires Amaro');
}
const fixtures = require('../common/fixtures');
const file = fixtures.path('get-call-sites.js');

const { getCallSites } = require('node:util');
const { spawnSync } = require('node:child_process');
const assert = require('node:assert');

{
  const callSites = getCallSites();
  assert.ok(callSites.length > 1);
  assert.match(
    callSites[0].scriptName,
    /test-util-getcallsites/,
    'node:util should be ignored',
  );
}

{
  const callSites = getCallSites(3);
  assert.strictEqual(callSites.length, 3);
  assert.match(
    callSites[0].scriptName,
    /test-util-getcallsites/,
    'node:util should be ignored',
  );
}

// frameCount must be an integer
{
  assert.throws(() => {
    const callSites = getCallSites(3.6);
    assert.strictEqual(callSites.length, 3);
  }, common.expectsError({
    code: 'ERR_OUT_OF_RANGE'
  }));
}

{
  assert.throws(() => {
    // Max than kDefaultMaxCallStackSizeToCapture
    getCallSites(201);
  }, common.expectsError({
    code: 'ERR_OUT_OF_RANGE'
  }));
  assert.throws(() => {
    getCallSites(0.5);
  }, common.expectsError({
    code: 'ERR_OUT_OF_RANGE'
  }));
  assert.throws(() => {
    getCallSites(-1);
  }, common.expectsError({
    code: 'ERR_OUT_OF_RANGE'
  }));
  assert.throws(() => {
    getCallSites([]);
  }, common.expectsError({
    code: 'ERR_INVALID_ARG_TYPE'
  }));
  assert.throws(() => {
    getCallSites({}, {});
  }, common.expectsError({
    code: 'ERR_INVALID_ARG_TYPE'
  }));
  assert.throws(() => {
    getCallSites(10, 10);
  }, common.expectsError({
    code: 'ERR_INVALID_ARG_TYPE'
  }));
}

{
  const callSites = getCallSites(1);
  assert.strictEqual(callSites.length, 1);
  assert.match(
    callSites[0].scriptName,
    /test-util-getcallsites/,
    'node:util should be ignored',
  );
}

// ScriptId is a string.
{
  const callSites = getCallSites(1);
  assert.strictEqual(callSites.length, 1);
  assert.strictEqual(typeof callSites[0].scriptId, 'string');
}

// Guarantee [eval] will appear on stacktraces when using -e
{
  const { status, stderr, stdout } = spawnSync(
    process.execPath,
    [
      '-e',
      `const util = require('util');
       const assert = require('assert');
       assert.ok(util.getCallSites().length > 1);
       process.stdout.write(util.getCallSites()[0].scriptName);
      `,
    ],
  );
  assert.strictEqual(status, 0, stderr.toString());
  assert.strictEqual(stdout.toString(), '[eval]');
}

// Guarantee the stacktrace[0] is the filename
{
  const { status, stderr, stdout } = spawnSync(
    process.execPath,
    [file],
  );
  assert.strictEqual(status, 0, stderr.toString());
  assert.strictEqual(stdout.toString(), file);
}

// Error.stackTraceLimit should not influence callsite size
{
  const originalStackTraceLimit = Error.stackTraceLimit;
  Error.stackTraceLimit = 0;
  const callSites = getCallSites();
  assert.notStrictEqual(callSites.length, 0);
  Error.stackTraceLimit = originalStackTraceLimit;
}

{
  // sourceMap must be a boolean
  assert.throws(() => getCallSites({ sourceMap: 1 }), {
    code: 'ERR_INVALID_ARG_TYPE'
  });
  assert.throws(() => getCallSites(1, { sourceMap: 1 }), {
    code: 'ERR_INVALID_ARG_TYPE'
  });

  // Not specifying the sourceMap option should not fail.
  getCallSites({});
  getCallSites(1, {});
}
