'use strict';

// Flags: --expose-gc

const common = require('../common');
common.skipIfInspectorDisabled();

const assert = require('assert');
const vm = require('vm');
const { Session } = require('inspector');

const session = new Session();
session.connect();

function notificationPromise(method) {
  return new Promise((resolve) => session.once(method, resolve));
}

async function testContextCreatedAndDestroyed() {
  console.log('Testing context created/destroyed notifications');
  {
    const mainContextPromise =
        notificationPromise('Runtime.executionContextCreated');

    session.post('Runtime.enable', assert.ifError);
    const contextCreated = await mainContextPromise;
    const { name, origin, auxData } = contextCreated.params.context;
    if (common.isSunOS || common.isWindows) {
      // uv_get_process_title() is unimplemented on Solaris-likes, it returns
      // an empty string.  On the Windows CI buildbots it returns
      // "Administrator: Windows PowerShell[42]" because of a GetConsoleTitle()
      // quirk. Not much we can do about either, just verify that it contains
      // the PID.
      assert.strictEqual(name.includes(`[${process.pid}]`), true);
    } else {
      let expects = `${process.argv0}[${process.pid}]`;
      if (!common.isMainThread) {
        expects = `Worker[${require('worker_threads').threadId}]`;
      }
      assert.strictEqual(expects, name);
    }
    assert.strictEqual(origin, '',
                       JSON.stringify(contextCreated));
    assert.strictEqual(auxData.isDefault, true,
                       JSON.stringify(contextCreated));
  }

  {
    const vmContextCreatedPromise =
        notificationPromise('Runtime.executionContextCreated');

    let contextDestroyed = null;
    session.once('Runtime.executionContextDestroyed',
                 (notification) => contextDestroyed = notification);

    vm.runInNewContext('1 + 1');

    const contextCreated = await vmContextCreatedPromise;
    const { id, name, origin, auxData } = contextCreated.params.context;
    assert.strictEqual(name, 'VM Context 1',
                       JSON.stringify(contextCreated));
    assert.strictEqual(origin, '',
                       JSON.stringify(contextCreated));
    assert.strictEqual(auxData.isDefault, false,
                       JSON.stringify(contextCreated));

    // GC is unpredictable...
    console.log('Checking/waiting for GC.');
    while (!contextDestroyed)
      global.gc();
    console.log('Context destroyed.');

    assert.strictEqual(contextDestroyed.params.executionContextId, id,
                       JSON.stringify(contextDestroyed));
  }

  {
    const vmContextCreatedPromise =
        notificationPromise('Runtime.executionContextCreated');

    let contextDestroyed = null;
    session.once('Runtime.executionContextDestroyed',
                 (notification) => contextDestroyed = notification);

    vm.runInNewContext('1 + 1', {}, {
      contextName: 'Custom context',
      contextOrigin: 'https://origin.example'
    });

    const contextCreated = await vmContextCreatedPromise;
    const { name, origin, auxData } = contextCreated.params.context;
    assert.strictEqual(name, 'Custom context',
                       JSON.stringify(contextCreated));
    assert.strictEqual(origin, 'https://origin.example',
                       JSON.stringify(contextCreated));
    assert.strictEqual(auxData.isDefault, false,
                       JSON.stringify(contextCreated));

    // GC is unpredictable...
    console.log('Checking/waiting for GC again.');
    while (!contextDestroyed)
      global.gc();
    console.log('Other context destroyed.');
  }

  {
    const vmContextCreatedPromise =
        notificationPromise('Runtime.executionContextCreated');

    let contextDestroyed = null;
    session.once('Runtime.executionContextDestroyed',
                 (notification) => contextDestroyed = notification);

    vm.createContext({}, { origin: 'https://nodejs.org' });

    const contextCreated = await vmContextCreatedPromise;
    const { name, origin, auxData } = contextCreated.params.context;
    assert.strictEqual(name, 'VM Context 2',
                       JSON.stringify(contextCreated));
    assert.strictEqual(origin, 'https://nodejs.org',
                       JSON.stringify(contextCreated));
    assert.strictEqual(auxData.isDefault, false,
                       JSON.stringify(contextCreated));

    // GC is unpredictable...
    console.log('Checking/waiting for GC a third time.');
    while (!contextDestroyed)
      global.gc();
    console.log('Context destroyed once again.');
  }

  {
    const vmContextCreatedPromise =
        notificationPromise('Runtime.executionContextCreated');

    let contextDestroyed = null;
    session.once('Runtime.executionContextDestroyed',
                 (notification) => contextDestroyed = notification);

    vm.createContext({}, { name: 'Custom context 2' });

    const contextCreated = await vmContextCreatedPromise;
    const { name, auxData } = contextCreated.params.context;
    assert.strictEqual(name, 'Custom context 2',
                       JSON.stringify(contextCreated));
    assert.strictEqual(auxData.isDefault, false,
                       JSON.stringify(contextCreated));

    // GC is unpredictable...
    console.log('Checking/waiting for GC a fourth time.');
    while (!contextDestroyed)
      global.gc();
    console.log('Context destroyed a fourth time.');
  }
}

async function testBreakpointHit() {
  console.log('Testing breakpoint is hit in a new context');
  session.post('Debugger.enable', assert.ifError);

  const pausedPromise = notificationPromise('Debugger.paused');
  vm.runInNewContext('debugger', {});
  await pausedPromise;
}

testContextCreatedAndDestroyed().then(common.mustCall(testBreakpointHit));
