import { _optionalChain, _optionalChainDelete } from '@sentry/utils';
import { URL } from 'url';
import { defineIntegration, convertIntegrationFnToClass, getCurrentScope } from '@sentry/core';
import { logger, dynamicRequire } from '@sentry/utils';
import { NODE_VERSION } from '../../nodeVersion.js';
import { base64WorkerScript } from './worker-script.js';

const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;

function log(message, ...args) {
  logger.log(`[ANR] ${message}`, ...args);
}

/**
 * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when
 * targeting those versions
 */
function getWorkerThreads() {
  return dynamicRequire(module, 'worker_threads');
}

/**
 * Gets contexts by calling all event processors. This relies on being called after all integrations are setup
 */
async function getContexts(client) {
  let event = { message: 'ANR' };
  const eventHint = {};

  for (const processor of client.getEventProcessors()) {
    if (event === null) break;
    event = await processor(event, eventHint);
  }

  return _optionalChain([event, 'optionalAccess', _2 => _2.contexts]) || {};
}

const INTEGRATION_NAME = 'Anr';

const _anrIntegration = ((options = {}) => {
  return {
    name: INTEGRATION_NAME,
    // TODO v8: Remove this
    setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
    setup(client) {
      if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
        throw new Error('ANR detection requires Node 16.17.0 or later');
      }

      // setImmediate is used to ensure that all other integrations have been setup
      setImmediate(() => _startWorker(client, options));
    },
  };
}) ;

const anrIntegration = defineIntegration(_anrIntegration);

/**
 * Starts a thread to detect App Not Responding (ANR) events
 *
 * ANR detection requires Node 16.17.0 or later
 *
 * @deprecated Use `anrIntegration()` instead.
 */
// eslint-disable-next-line deprecation/deprecation
const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration)

;

// eslint-disable-next-line deprecation/deprecation

/**
 * Starts the ANR worker thread
 */
async function _startWorker(client, _options) {
  const contexts = await getContexts(client);
  const dsn = client.getDsn();

  if (!dsn) {
    return;
  }

  // These will not be accurate if sent later from the worker thread
   _optionalChainDelete([contexts, 'access', _3 => _3.app, 'optionalAccess', _4 => delete _4.app_memory]);
   _optionalChainDelete([contexts, 'access', _5 => _5.device, 'optionalAccess', _6 => delete _6.free_memory]);

  const initOptions = client.getOptions();

  const sdkMetadata = client.getSdkMetadata() || {};
  if (sdkMetadata.sdk) {
    sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
  }

  const options = {
    debug: logger.isEnabled(),
    dsn,
    environment: initOptions.environment || 'production',
    release: initOptions.release,
    dist: initOptions.dist,
    sdkMetadata,
    appRootPath: _options.appRootPath,
    pollInterval: _options.pollInterval || DEFAULT_INTERVAL,
    anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD,
    captureStackTrace: !!_options.captureStackTrace,
    staticTags: _options.staticTags || {},
    contexts,
  };

  if (options.captureStackTrace) {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const inspector = require('inspector');
    if (!inspector.url()) {
      inspector.open(0);
    }
  }

  const { Worker } = getWorkerThreads();

  const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
    workerData: options,
  });

  process.on('exit', () => {
    worker.terminate();
  });

  const timer = setInterval(() => {
    try {
      const currentSession = getCurrentScope().getSession();
      // We need to copy the session object and remove the toJSON method so it can be sent to the worker
      // serialized without making it a SerializedSession
      const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
      // message the worker to tell it the main event loop is still running
      worker.postMessage({ session });
    } catch (_) {
      //
    }
  }, options.pollInterval);
  // Timer should not block exit
  timer.unref();

  worker.on('message', (msg) => {
    if (msg === 'session-ended') {
      log('ANR event sent from ANR worker. Clearing session in this thread.');
      getCurrentScope().setSession(undefined);
    }
  });

  worker.once('error', (err) => {
    clearInterval(timer);
    log('ANR worker error', err);
  });

  worker.once('exit', (code) => {
    clearInterval(timer);
    log('ANR worker exit', code);
  });

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

export { Anr, anrIntegration };
//# sourceMappingURL=index.js.map
