// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Helpers from '../helpers/helpers.js';
import * as Types from '../types/types.js';

const IDLE_FUNCTION_CALL_NAMES = new Set([
  '(program)',
  '(idle)',
  '(root)',
]);

export function calculateWindow(
    traceBounds: Types.Timing.TraceWindowMicro,
    mainThreadEntries: readonly Types.Events.Event[]): Types.Timing.TraceWindowMicro {
  if (!mainThreadEntries.length) {
    return traceBounds;
  }
  const entriesWithIdleRemoved = mainThreadEntries.filter(entry => {
    if (Types.Events.isProfileCall(entry) &&
        (IDLE_FUNCTION_CALL_NAMES.has(entry.callFrame.functionName) || !entry.callFrame.functionName)) {
      return false;
    }
    return true;
  });

  if (entriesWithIdleRemoved.length === 0) {
    return traceBounds;
  }
  /**
   * Calculates regions of low utilization and returns the index of the event
   * that is the first event that should be included.
   **/
  function findLowUtilizationRegion(startIndex: number, stopIndex: number): number {
    const threshold = 0.1;

    let cutIndex = startIndex;
    const entryAtCut = entriesWithIdleRemoved[cutIndex];
    const timings = Helpers.Timing.eventTimingsMicroSeconds(entryAtCut);
    let cutTime = (timings.startTime + timings.endTime) / 2;
    let usedTime = 0;
    const step = Math.sign(stopIndex - startIndex);
    for (let i = startIndex; i !== stopIndex; i += step) {
      const task = entriesWithIdleRemoved[i];
      const taskTimings = Helpers.Timing.eventTimingsMicroSeconds(task);
      const taskTime = (taskTimings.startTime + taskTimings.endTime) / 2;
      const interval = Math.abs(cutTime - taskTime);
      if (usedTime < threshold * interval) {
        cutIndex = i;
        cutTime = taskTime;
        usedTime = 0;
      }
      usedTime += taskTimings.duration;
    }
    return cutIndex;
  }
  const rightIndex = findLowUtilizationRegion(entriesWithIdleRemoved.length - 1, 0);
  const leftIndex = findLowUtilizationRegion(0, rightIndex);
  const leftTimings = Helpers.Timing.eventTimingsMicroSeconds(entriesWithIdleRemoved[leftIndex]);
  const rightTimings = Helpers.Timing.eventTimingsMicroSeconds(entriesWithIdleRemoved[rightIndex]);

  let leftTime = leftTimings.startTime;
  let rightTime = rightTimings.endTime;
  const zoomedInSpan = rightTime - leftTime;

  if (zoomedInSpan < traceBounds.range * 0.1) {
    // If the area we have chosen to zoom into is less than 10% of the entire
    // span, we bail and show the entire trace. It would not be so useful to
    // the user to zoom in on such a small area; we assume they have
    // purposefully recorded a trace that contains empty periods of time.
    return traceBounds;
  }

  // Adjust the left time down by 5%, and the right time up by 5%, so that
  // we give the range we want to zoom a bit of breathing space. At the
  // same time, ensure that we do not stray beyond the bounds of the
  // min/max time of the entire trace.
  leftTime = Types.Timing.Micro(Math.max(leftTime - 0.05 * zoomedInSpan, traceBounds.min));
  rightTime = Types.Timing.Micro(Math.min(rightTime + 0.05 * zoomedInSpan, traceBounds.max));

  return {
    min: leftTime,
    max: rightTime,
    range: Types.Timing.Micro(rightTime - leftTime),
  };
}
