/**
Copyright: 2024 Contrast Security, Inc
  Contact: support@contrastsecurity.com
  License: Commercial

  NOTICE: This Software and the patented inventions embodied within may only be
  used as part of Contrast Security’s commercial offerings. Even though it is
  made available through public repositories, use of this Software is subject to
  the applicable End User Licensing Agreement found at
  https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
  between Contrast Security and the End User. The Software may not be reverse
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
  way not consistent with the End User License Agreement.
*/
'use strict';

const {
  AsyncStorage,
  KEYS: { REQUEST }
} = require('../../core/async-storage');
const RequestFactory = require('../../reporter/models/utils/request-factory');
const CleanStack = require('../../util/clean-stack');
const agentEmitter = require('../../agent-emitter');

/** @typedef {import('../../agent').ContrastAgent} Agent */

class BaseFramework {
  /**
   * @param {Object} opts
   * @param {string} opts.id
   * @param {Agent} opts.agent
   */
  constructor({ id, agent } = {}) {
    this.id = id;
    this.agent = agent;
    this.emitter = agentEmitter;
  }

  /**
   * Proxy for the agent configuration.
   * @returns {Object} Agent configuration
   */
  get config() {
    return this.agent.config;
  }

  /**
   * Returns the file and lineNumber of the top-most stackframe representing
   * application code. The result should be a path, relative to the application
   * directory, to the filename of the frame as well as the line number.
   * Formatted as: /path/to/file.js:100
   * -- Note --
   * The current imlementation uses a CleanStack instance in order to generate
   * a stackframe from which to obtain the information. This can be expensive.
   * @returns {string}
   */
  getAppCodeLocation() {
    const limit = Error.stackTraceLimit;
    Error.stackTraceLimit = 50;
    const frame = new CleanStack()
      .build()
      .stack.find(
        (frame) => frame && frame.file && frame.file.indexOf('node_modules') < 0
      );
    Error.stackTraceLimit = limit;

    if (frame) {
      const { file, lineNumber } = frame;
      const relativeFile = (file || '').replace(this.agent.appInfo.appDir, '');
      return `${relativeFile}:${lineNumber || ''}`;
    } else {
      return null;
    }
  }

  /**
   * Common logic for sub-classes to update the context w/ the appropriate model
   * @param {any} request The framework-specific request instance
   * @param {Function} Ctor The framework-specific request model
   */
  static setFrameworkRequest(request, Ctor) {
    const ctxt = AsyncStorage.getContext();
    if (ctxt) {
      ctxt[REQUEST] = RequestFactory.make(request, Ctor);
    }
  }

  /**
   * Adds a key to the framework request model when it exists as a source
   * Instead of instantiating a request model, we will just add a key
   * to the request in the async storage
   *
   * @param {Object} values keys on request model to update
   */
  decorateRequest(values) {
    const ctxt = AsyncStorage.getContext();
    if (ctxt) {
      ctxt.updateRequestModel(values);
    }
  }
}

module.exports = BaseFramework;
