/**
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 _ = require('lodash');
/** @typedef {import('../../TagRange')} TagRange */
const tagRangeUtil = require('../models/tag-range/util');
const { PropagationEvent, Signature, CallContext } = require('../models');
const tracker = require('../../tracker');

/** @typedef {import('../../TagRange')} TagRange */

/**
 * @typedef {Object} Metadata
 * @property {string} method name of the propagating method
 * @property {number} offset tracked offset from the start of the result string
 * @property {Object[]} parents events that have tagged or propagated the tracked string
 * @property {TagRange[]} tagRanges tag ranges on the result string we're propagating to
 */

/**
 * Returns any tag ranges present on an argument
 * @param {string} item
 * @returns {TagRange[]}
 */
const getTagRanges = (item) => _.get(tracker.getData(item), 'tagRanges');

/**
 * Returns the event
 * @param {*} item
 */
const getEvent = (item) => _.get(tracker.getData(item), 'event');
/**
 *
 * @param {*} item
 */
const isTracked = (item) => _.get(tracker.getData(item), 'tracked');

const escapeRegExp = (str) => {
  if (str == null || typeof str === 'object') {
    return str;
  }

  return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
};

/**
 * Adds any tag ranges present on a given argument `arg` to the result's metadata.
 * @param {Metadata} metadata
 * @param {string} arg string argument which may have tag ranges attached
 */
const addTagRangesWithOffset = (metadata, arg) => {
  const argData = tracker.getData(arg);
  if (argData) {
    tagRangeUtil.addAllWithOffsetInPlace(
      metadata.tagRanges,
      argData.tagRanges,
      metadata.offset || 0
    );
    metadata.parents.push(argData.event);
  }
};

let propagators;
/**
 * Looks up a propagator's details in `lib/assess/policy/propagators.json`
 * @param {string} method
 * @return {{source: string, target: string}}
 */
const findPropagatorInPolicy = (method) => {
  if (!propagators) ({ propagators } = require('../policy/propagators.json'));
  return propagators[method];
};

/**
 * Create the event to append to the result's metadata.
 * @param {Metadata} metadata
 * @param {Object} data
 * @param {Object} data.obj module or object from which the method is called
 * @param {any[]} data.args args to the method being called
 * @param {string} data.result result of the method being called
 * @return {PropagationEvent} event to append to a part's metadata
 */
const createEvent = ({ tagRanges, method, parents }, data) => {
  const signature = new Signature(method);
  const context = CallContext.create(data);
  const prop = findPropagatorInPolicy(method);
  return new PropagationEvent({
    context,
    parents,
    signature,
    source: prop.source,
    tagRanges,
    target: prop.target
  });
};

/**
 * If metadata contains tag ranges, track the result of the function call and
 * propagate onto the result.
 * @param {Metadata} metadata
 * @param {Object} data
 * @param {string} data.result
 */
const trackResult = (metadata, data) => {
  if (metadata.tagRanges.length) {
    const tracked = tracker.track(data.result);
    if (tracked) {
      tracked.props.tagRanges = metadata.tagRanges;
      tracked.props.event = createEvent(metadata, data);
      data.result = tracked.str;
    }
  }
};

module.exports = {
  // Alias for propagators without the concept of an offset
  addTagRanges: addTagRangesWithOffset,
  addTagRangesWithOffset,
  createEvent,
  escapeRegExp,
  getEvent,
  getTagRanges,
  isTracked,
  trackResult
};
