"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ResolvedQuantile = exports.MetricHooks = void 0;
exports.counter = counter;
exports.frequency = frequency;
exports.gauge = gauge;
exports.histogram = histogram;
exports.summary = summary;

var tsplus_module_1 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/core/io/Metrics/MetricState"));

var tsplus_module_2 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/core/io/Metrics/MetricHook"));

var tsplus_module_3 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/data/Tuple/make"));

var tsplus_module_4 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/HashMap/make"));

var tsplus_module_5 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/prelude/Ord/common"));

var tsplus_module_6 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/sort"));

var tsplus_module_7 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/mapWithIndex"));

var tsplus_module_8 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/builder"));

var tsplus_module_9 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/data/Duration"));

var tsplus_module_10 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/isEmpty"));

var tsplus_module_11 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/empty"));

var tsplus_module_12 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/unsafeHead"));

var tsplus_module_13 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/unsafeTail"));

var tsplus_module_14 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/data/Maybe/definition"));

var tsplus_module_15 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/single"));

var tsplus_module_16 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/append"));

var tsplus_module_17 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/reduce"));

var tsplus_module_18 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/map"));

var tsplus_module_19 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/unsafeLast"));

var tsplus_module_20 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/splitWhere"));

var tsplus_module_21 = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@tsplus/stdlib/collections/Chunk/head"));

function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

const MetricHooks = {};
/**
 * @tsplus static ets/Metrics/MetricHooks/Ops counter
 */

exports.MetricHooks = MetricHooks;

function counter(_key) {
  let sum = 0;
  return tsplus_module_2.make(value => {
    sum = sum + value;
  }, () => tsplus_module_1.counter(sum));
}
/**
 * @tsplus static ets/Metrics/MetricHooks/Ops gauge
 */


function gauge(_key, startAt) {
  let value = startAt;
  return tsplus_module_2.make(v => {
    value = v;
  }, () => tsplus_module_1.gauge(value));
}
/**
 * @tsplus static ets/Metrics/MetricHooks/Ops frequency
 */


function frequency(_key) {
  let count = 0;
  const values = new Map();

  const update = word => {
    count = count + 1;
    const slotCount = values.get(word) ?? 0;
    values.set(word, slotCount + 1);
  };

  const snapshot = () => tsplus_module_4.make(...Array.from(values.entries()).map(([k, v]) => tsplus_module_3.make(k, v)));

  return tsplus_module_2.make(update, () => tsplus_module_1.frequency(snapshot()));
}
/**
 * @tsplus static ets/Metrics/MetricHooks/Ops histogram
 */


function histogram(key) {
  const bounds = key.keyType.boundaries.values;
  const size = bounds.length;
  const values = Array(size + 1);
  const boundaries = Array(size);
  let count = 0;
  let sum = 0;
  let min = Number.MAX_VALUE;
  let max = Number.MIN_VALUE;
  tsplus_module_7.mapWithIndex_(tsplus_module_6.sort_(bounds, tsplus_module_5.number), (i, n) => {
    boundaries[i] = n;
  }); // Insert the value into the right bucket with a binary search

  const update = value => {
    let from = 0;
    let to = size;

    while (from !== to) {
      const mid = Math.floor(from + (to - from) / 2);
      const boundary = boundaries[mid];

      if (value <= boundary) {
        to = mid;
      } else {
        from = mid;
      } // The special case when to / from have a distance of one


      if (to === from + 1) {
        if (value <= boundaries[from]) {
          to = from;
        } else {
          from = to;
        }
      }
    }

    values[from] = values[from] + 1;
    count = count + 1;
    sum = sum + value;

    if (value < min) {
      min = value;
    }

    if (value > max) {
      max = value;
    }
  };

  const getBuckets = () => {
    const builder = tsplus_module_8.builder();
    let i = 0;
    let cumulated = 0;

    while (i != size) {
      const boundary = boundaries[i];
      const value = values[i];
      cumulated = cumulated + value;
      builder.append(tsplus_module_3.make(boundary, cumulated));
      i = i + 1;
    }

    return builder.build();
  };

  return tsplus_module_2.make(update, () => tsplus_module_1.histogram(getBuckets(), count, min, max, sum));
}
/**
 * @tsplus static ets/Metrics/MetricHooks/Ops summary
 */


function summary(key) {
  const {
    error,
    maxAge,
    maxSize,
    quantiles
  } = key.keyType;
  const sortedQuantiles = tsplus_module_6.sort_(quantiles, tsplus_module_5.number);
  const values = Array(maxSize);
  let head = 0;
  let count = 0;
  let sum = 0;
  let min = Number.MAX_VALUE;
  let max = Number.MIN_VALUE; // Just before the snapshot we filter out all values older than maxAge

  const snapshot = now => {
    const builder = tsplus_module_8.builder(); // If the buffer is not full yet it contains valid items at the 0..last
    // indices and null values at the rest of the positions.
    //
    // If the buffer is already full then all elements contains a valid
    // measurement with timestamp.
    //
    // At any given point in time we can enumerate all the non-null elements in
    // the buffer and filter them by timestamp to get a valid view of a time
    // window.
    //
    // The order does not matter because it gets sorted before passing to
    // `calculateQuantiles`.

    let i = 0;

    while (i !== maxSize - 1) {
      const item = values[i];

      if (item != null) {
        const {
          tuple: [t, v]
        } = item;
        const age = new tsplus_module_9.Duration(now - t);

        if (age.millis >= 0 && tsplus_module_9.lowerThenOrEqual_(age, maxAge)) {
          builder.append(v);
        }
      }

      i = i + 1;
    }

    return calculateQuantiles(error, sortedQuantiles, tsplus_module_6.sort_(builder.build(), tsplus_module_5.number));
  };

  const observe = (value, timestamp) => {
    if (maxSize > 0) {
      head = head + 1;
      const target = head % maxSize;
      values[target] = tsplus_module_3.make(timestamp, value);
    }

    count = count + 1;
    sum = sum + value;

    if (value < min) {
      min = value;
    }

    if (value > max) {
      max = value;
    }
  };

  return tsplus_module_2.make(({
    tuple: [value, timestamp]
  }) => observe(value, timestamp), () => tsplus_module_1.summary(error, snapshot(Date.now()), count, min, max, sum));
}

class ResolvedQuantile {
  constructor(
  /**
   * The quantile that shall be resolved.
   */
  quantile,
  /**
   * `Some<number>` if a value for the quantile could be found, otherwise
   * `None`.
   */
  value,
  /**
   * How many samples have been consumed prior to this quantile.
   */
  consumed,
  /**
   * The rest of the samples after the quantile has been resolved.
   */
  rest) {
    this.quantile = quantile;
    this.value = value;
    this.consumed = consumed;
    this.rest = rest;
  }

}

exports.ResolvedQuantile = ResolvedQuantile;

function calculateQuantiles(error, sortedQuantiles, sortedSamples) {
  // The number of samples examined
  const sampleCount = sortedSamples.length;

  if (tsplus_module_10.isEmpty(sortedQuantiles)) {
    return tsplus_module_11.empty();
  }

  const head = tsplus_module_12.unsafeHead(sortedQuantiles);
  const tail = tsplus_module_13.unsafeTail(sortedQuantiles);
  const resolved = tsplus_module_17.reduce_(tail, tsplus_module_15.single(resolveQuantile(error, sampleCount, tsplus_module_14.none, 0, head, sortedSamples)), (acc, quantile) => {
    const h = tsplus_module_12.unsafeHead(acc);
    return tsplus_module_16.append_(acc, resolveQuantile(error, sampleCount, h.value, h.consumed, quantile, h.rest));
  });
  return tsplus_module_18.map_(resolved, rq => tsplus_module_3.make(rq.quantile, rq.value));
}
/**
 * @tsplus tailRec
 */


function resolveQuantile(error, sampleCount, current, consumed, quantile, rest) {
  var error_1 = error,
      sampleCount_1 = sampleCount,
      current_1 = current,
      consumed_1 = consumed,
      quantile_1 = quantile,
      rest_1 = rest;
  var error_2 = error,
      sampleCount_2 = sampleCount,
      current_2 = current,
      consumed_2 = consumed,
      quantile_2 = quantile,
      rest_2 = rest;

  while (1) {
    // If the remaining list of samples is empty, there is nothing more to resolve
    if (tsplus_module_10.isEmpty(rest_1)) {
      return new ResolvedQuantile(quantile_1, tsplus_module_14.none, consumed_1, tsplus_module_11.empty());
    } // If the quantile is the 100% quantile, we can take the maximum of all the
    // remaining values as the result


    if (quantile_1 === 1) {
      return new ResolvedQuantile(quantile_1, tsplus_module_14.some(tsplus_module_19.unsafeLast(rest_1)), consumed_1 + rest_1.length, tsplus_module_11.empty());
    } // Split into two chunks - the first chunk contains all elements of the same
    // value as the chunk head


    const sameHead = tsplus_module_20.splitWhere_(rest_1, n => n > tsplus_module_12.unsafeHead(rest_1)); // How many elements do we want to accept for this quantile

    const desired = quantile_1 * sampleCount_1; // The error margin

    const allowedError = error_1 / 2 * desired; // Taking into account the elements consumed from the samples so far and the
    // number of same elements at the beginning of the chunk, calculate the number
    // of elements we would have if we selected the current head as result

    const candConsumed = consumed_1 + sameHead.get(0).length;
    const candError = Math.abs(candConsumed - desired); // If we haven't got enough elements yet, recurse

    if (candConsumed < desired - allowedError) {
      error_2 = error_1;
      sampleCount_2 = sampleCount_1;
      current_2 = tsplus_module_21.head(rest_1);
      consumed_2 = candConsumed;
      quantile_2 = quantile_1;
      rest_2 = sameHead.get(1);
      error_1 = error_2;
      sampleCount_1 = sampleCount_2;
      current_1 = current_2;
      consumed_1 = consumed_2;
      quantile_1 = quantile_2;
      rest_1 = rest_2;
      continue;
    } // If we have too many elements, select the previous value and hand back the
    // the rest as leftover


    if (candConsumed > desired + allowedError) {
      return new ResolvedQuantile(quantile_1, current_1, consumed_1, rest_1);
    } // If we are in the target interval, select the current head and hand back the leftover after dropping all elements
    // from the sample chunk that are equal to the current head


    switch (current_1._tag) {
      case "None":
        {
          error_2 = error_1;
          sampleCount_2 = sampleCount_1;
          current_2 = tsplus_module_21.head(rest_1);
          consumed_2 = candConsumed;
          quantile_2 = quantile_1;
          rest_2 = sameHead.get(1);
          error_1 = error_2;
          sampleCount_1 = sampleCount_2;
          current_1 = current_2;
          consumed_1 = consumed_2;
          quantile_1 = quantile_2;
          rest_1 = rest_2;
          continue;
        }

      case "Some":
        {
          const prevError = Math.abs(desired - current_1.value);

          if (candError < prevError) {
            error_2 = error_1;
            sampleCount_2 = sampleCount_1;
            current_2 = tsplus_module_21.head(rest_1);
            consumed_2 = candConsumed;
            quantile_2 = quantile_1;
            rest_2 = sameHead.get(1);
            error_1 = error_2;
            sampleCount_1 = sampleCount_2;
            current_1 = current_2;
            consumed_1 = consumed_2;
            quantile_1 = quantile_2;
            rest_1 = rest_2;
            continue;
          }

          return new ResolvedQuantile(quantile_1, tsplus_module_14.some(current_1.value), consumed_1, rest_1);
        }
    }
  }
}
//# sourceMappingURL=MetricHooks.js.map