Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });

const node_worker_threads = require('node:worker_threads');
const core = require('@sentry/core');
const debug = require('../../utils/debug.js');
const common = require('./common.js');

// This string is a placeholder that gets overwritten with the worker code.
const base64WorkerScript = ' ';

function log(...args) {
  core.debug.log('[LocalVariables]', ...args);
}

/**
 * Adds local variables to exception frames
 */
const localVariablesAsyncIntegration = core.defineIntegration(((
  integrationOptions = {},
) => {
  function addLocalVariablesToException(exception, localVariables) {
    // Filter out frames where the function name is `new Promise` since these are in the error.stack frames
    // but do not appear in the debugger call frames
    const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise');

    for (let i = 0; i < frames.length; i++) {
      // Sentry frames are in reverse order
      const frameIndex = frames.length - i - 1;

      const frameLocalVariables = localVariables[i];
      const frame = frames[frameIndex];

      if (!frame || !frameLocalVariables) {
        // Drop out if we run out of frames to match up
        break;
      }

      if (
        // We need to have vars to add
        frameLocalVariables.vars === undefined ||
        // We're not interested in frames that are not in_app because the vars are not relevant
        frame.in_app === false ||
        // The function names need to match
        !common.functionNamesMatch(frame.function, frameLocalVariables.function)
      ) {
        continue;
      }

      frame.vars = frameLocalVariables.vars;
    }
  }

  function addLocalVariablesToEvent(event, hint) {
    if (
      hint.originalException &&
      typeof hint.originalException === 'object' &&
      common.LOCAL_VARIABLES_KEY in hint.originalException &&
      Array.isArray(hint.originalException[common.LOCAL_VARIABLES_KEY])
    ) {
      for (const exception of event.exception?.values || []) {
        addLocalVariablesToException(exception, hint.originalException[common.LOCAL_VARIABLES_KEY]);
      }

      hint.originalException[common.LOCAL_VARIABLES_KEY] = undefined;
    }

    return event;
  }

  async function startInspector() {
    // We load inspector dynamically because on some platforms Node is built without inspector support
    const inspector = await import('node:inspector');
    if (!inspector.url()) {
      inspector.open(0);
    }
  }

  function startWorker(options) {
    const worker = new node_worker_threads.Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
      workerData: options,
      // We don't want any Node args to be passed to the worker
      execArgv: [],
      env: { ...process.env, NODE_OPTIONS: undefined },
    });

    process.on('exit', () => {
      // eslint-disable-next-line @typescript-eslint/no-floating-promises
      worker.terminate();
    });

    worker.once('error', (err) => {
      log('Worker error', err);
    });

    worker.once('exit', (code) => {
      log('Worker exit', code);
    });

    // Ensure this thread can't block app exit
    worker.unref();
  }

  return {
    name: 'LocalVariablesAsync',
    async setup(client) {
      const clientOptions = client.getOptions();

      if (!clientOptions.includeLocalVariables) {
        return;
      }

      if (await debug.isDebuggerEnabled()) {
        core.debug.warn('Local variables capture has been disabled because the debugger was already enabled');
        return;
      }

      const options = {
        ...integrationOptions,
        debug: core.debug.isEnabled(),
      };

      startInspector().then(
        () => {
          try {
            startWorker(options);
          } catch (e) {
            core.debug.error('Failed to start worker', e);
          }
        },
        e => {
          core.debug.error('Failed to start inspector', e);
        },
      );
    },
    processEvent(event, hint) {
      return addLocalVariablesToEvent(event, hint);
    },
  };
}) );

exports.base64WorkerScript = base64WorkerScript;
exports.localVariablesAsyncIntegration = localVariablesAsyncIntegration;
