text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  /* eslint-disable no-var */
  
  import {
    enableSchedulerDebugging,
    enableProfiling,
  } from './SchedulerFeatureFlags';
  import {
    requestHostCallback,
    requestHostTimeout,
    cancelHostTimeout,
    shouldYieldToHost,
    getCurrentTime,
    forceFrameRate,
    requestPaint,
  } from './SchedulerHostConfig';
  import {push, pop, peek} from './SchedulerMinHeap';
  
  // TODO: Use symbols?
  import {
    ImmediatePriority,
    UserBlockingPriority,
    NormalPriority,
    LowPriority,
    IdlePriority,
  } from './SchedulerPriorities';
  import {
    sharedProfilingBuffer,
    markTaskRun,
    markTaskYield,
    markTaskCompleted,
    markTaskCanceled,
    markTaskErrored,
    markSchedulerSuspended,
    markSchedulerUnsuspended,
    markTaskStart,
    stopLoggingProfilingEvents,
    startLoggingProfilingEvents,
  } from './SchedulerProfiling';
  
  // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
  // Math.pow(2, 30) - 1
  // 0b111111111111111111111111111111
  var maxSigned31BitInt = 1073741823;
  
  // Times out immediately
  var IMMEDIATE_PRIORITY_TIMEOUT = -1;
  // Eventually times out
  var USER_BLOCKING_PRIORITY = 250;
  var NORMAL_PRIORITY_TIMEOUT = 5000;
  var LOW_PRIORITY_TIMEOUT = 10000;
  // Never times out
  var IDLE_PRIORITY = maxSigned31BitInt;
  
  // Tasks are stored on a min heap
  var taskQueue = [];
  var timerQueue = [];
  
  // Incrementing id counter. Used to maintain insertion order.
  var taskIdCounter = 1;
  
  // Pausing the scheduler is useful for debugging.
  var isSchedulerPaused = false;
  
  var currentTask = null;
  var currentPriorityLevel = NormalPriority;
  
  // This is set while performing work, to prevent re-entrancy.
  var isPerformingWork = false;
  
  var isHostCallbackScheduled = false;
  var isHostTimeoutScheduled = false;
  
  function advanceTimers(currentTime) {
    // Check for tasks that are no longer delayed and add them to the queue.
    let timer = peek(timerQueue);
    while (timer !== null) {
      if (timer.callback === null) {
        // Timer was cancelled.
        pop(timerQueue);
      } else if (timer.startTime <= currentTime) {
        // Timer fired. Transfer to the task queue.
        pop(timerQueue);
        timer.sortIndex = timer.expirationTime;
        push(taskQueue, timer);
        if (enableProfiling) {
          markTaskStart(timer, currentTime);
          timer.isQueued = true;
        }
      } else {
        // Remaining timers are pending.
        return;
      }
      timer = peek(timerQueue);
    }
  }
  
  function handleTimeout(currentTime) {
    isHostTimeoutScheduled = false;
    advanceTimers(currentTime);
  
    if (!isHostCallbackScheduled) {
      if (peek(taskQueue) !== null) {
        isHostCallbackScheduled = true;
        requestHostCallback(flushWork);
      } else {
        const firstTimer = peek(timerQueue);
        if (firstTimer !== null) {
          requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
        }
      }
    }
  }
  
  function flushWork(hasTimeRemaining, initialTime) {
    if (enableProfiling) {
      markSchedulerUnsuspended(initialTime);
    }
  
    // We'll need a host callback the next time work is scheduled.
    isHostCallbackScheduled = false;
    if (isHostTimeoutScheduled) {
      // We scheduled a timeout but it's no longer needed. Cancel it.
      isHostTimeoutScheduled = false;
      cancelHostTimeout();
    }
  
    isPerformingWork = true;
    const previousPriorityLevel = currentPriorityLevel;
    try {
      if (enableProfiling) {
        try {
          return workLoop(hasTimeRemaining, initialTime);
        } catch (error) {
          if (currentTask !== null) {
            const currentTime = getCurrentTime();
            markTaskErrored(currentTask, currentTime);
            currentTask.isQueued = false;
          }
          throw error;
        }
      } else {
        // No catch in prod codepath.
        return workLoop(hasTimeRemaining, initialTime);
      }
    } finally {
      currentTask = null;
      currentPriorityLevel = previousPriorityLevel;
      isPerformingWork = false;
      if (enableProfiling) {
        const currentTime = getCurrentTime();
        markSchedulerSuspended(currentTime);
      }
    }
  }
  
  function workLoop(hasTimeRemaining, initialTime) {
    let currentTime = initialTime;
    advanceTimers(currentTime);
    currentTask = peek(taskQueue);
    while (
      currentTask !== null &&
      !(enableSchedulerDebugging && isSchedulerPaused)
    ) {
      if (
        currentTask.expirationTime > currentTime &&
        (!hasTimeRemaining || shouldYieldToHost())
      ) {
        // This currentTask hasn't expired, and we've reached the deadline.
        break;
      }
      const callback = currentTask.callback;
      if (callback !== null) {
        currentTask.callback = null;
        currentPriorityLevel = currentTask.priorityLevel;
        const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
        markTaskRun(currentTask, currentTime);
        const continuationCallback = callback(didUserCallbackTimeout);
        currentTime = getCurrentTime();
        if (typeof continuationCallback === 'function') {
          currentTask.callback = continuationCallback;
          markTaskYield(currentTask, currentTime);
        } else {
          if (enableProfiling) {
            markTaskCompleted(currentTask, currentTime);
            currentTask.isQueued = false;
          }
          if (currentTask === peek(taskQueue)) {
            pop(taskQueue);
          }
        }
        advanceTimers(currentTime);
      } else {
        pop(taskQueue);
      }
      currentTask = peek(taskQueue);
    }
    // Return whether there's additional work
    if (currentTask !== null) {
      return true;
    } else {
      const firstTimer = peek(timerQueue);
      if (firstTimer !== null) {
        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
      }
      return false;
    }
  }
  
  function unstable_runWithPriority(priorityLevel, eventHandler) {
    switch (priorityLevel) {
      case ImmediatePriority:
      case UserBlockingPriority:
      case NormalPriority:
      case LowPriority:
      case IdlePriority:
        break;
      default:
        priorityLevel = NormalPriority;
    }
  
    var previousPriorityLevel = currentPriorityLevel;
    currentPriorityLevel = priorityLevel;
  
    try {
      return eventHandler();
    } finally {
      currentPriorityLevel = previousPriorityLevel;
    }
  }
  
  function unstable_next(eventHandler) {
    var priorityLevel;
    switch (currentPriorityLevel) {
      case ImmediatePriority:
      case UserBlockingPriority:
      case NormalPriority:
        // Shift down to normal priority
        priorityLevel = NormalPriority;
        break;
      default:
        // Anything lower than normal priority should remain at the current level.
        priorityLevel = currentPriorityLevel;
        break;
    }
  
    var previousPriorityLevel = currentPriorityLevel;
    currentPriorityLevel = priorityLevel;
  
    try {
      return eventHandler();
    } finally {
      currentPriorityLevel = previousPriorityLevel;
    }
  }
  
  function unstable_wrapCallback(callback) {
    var parentPriorityLevel = currentPriorityLevel;
    return function() {
      // This is a fork of runWithPriority, inlined for performance.
      var previousPriorityLevel = currentPriorityLevel;
      currentPriorityLevel = parentPriorityLevel;
  
      try {
        return callback.apply(this, arguments);
      } finally {
        currentPriorityLevel = previousPriorityLevel;
      }
    };
  }
  
  function timeoutForPriorityLevel(priorityLevel) {
    switch (priorityLevel) {
      case ImmediatePriority:
        return IMMEDIATE_PRIORITY_TIMEOUT;
      case UserBlockingPriority:
        return USER_BLOCKING_PRIORITY;
      case IdlePriority:
        return IDLE_PRIORITY;
      case LowPriority:
        return LOW_PRIORITY_TIMEOUT;
      case NormalPriority:
      default:
        return NORMAL_PRIORITY_TIMEOUT;
    }
  }
  
  function unstable_scheduleCallback(priorityLevel, callback, options) {
    var currentTime = getCurrentTime();
  
    var startTime;
    var timeout;
    if (typeof options === 'object' && options !== null) {
      var delay = options.delay;
      if (typeof delay === 'number' && delay > 0) {
        startTime = currentTime + delay;
      } else {
        startTime = currentTime;
      }
      timeout =
        typeof options.timeout === 'number'
          ? options.timeout
          : timeoutForPriorityLevel(priorityLevel);
    } else {
      timeout = timeoutForPriorityLevel(priorityLevel);
      startTime = currentTime;
    }
  
    var expirationTime = startTime + timeout;
  
    var newTask = {
      id: taskIdCounter++,
      callback,
      priorityLevel,
      startTime,
      expirationTime,
      sortIndex: -1,
    };
    if (enableProfiling) {
      newTask.isQueued = false;
    }
  
    if (startTime > currentTime) {
      // This is a delayed task.
      newTask.sortIndex = startTime;
      push(timerQueue, newTask);
      if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
        // All tasks are delayed, and this is the task with the earliest delay.
        if (isHostTimeoutScheduled) {
          // Cancel an existing timeout.
          cancelHostTimeout();
        } else {
          isHostTimeoutScheduled = true;
        }
        // Schedule a timeout.
        requestHostTimeout(handleTimeout, startTime - currentTime);
      }
    } else {
      newTask.sortIndex = expirationTime;
      push(taskQueue, newTask);
      if (enableProfiling) {
        markTaskStart(newTask, currentTime);
        newTask.isQueued = true;
      }
      // Schedule a host callback, if needed. If we're already performing work,
      // wait until the next time we yield.
      if (!isHostCallbackScheduled && !isPerformingWork) {
        isHostCallbackScheduled = true;
        requestHostCallback(flushWork);
      }
    }
  
    return newTask;
  }
  
  function unstable_pauseExecution() {
    isSchedulerPaused = true;
  }
  
  function unstable_continueExecution() {
    isSchedulerPaused = false;
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }
  
  function unstable_getFirstCallbackNode() {
    return peek(taskQueue);
  }
  
  function unstable_cancelCallback(task) {
    if (enableProfiling) {
      if (task.isQueued) {
        const currentTime = getCurrentTime();
        markTaskCanceled(task, currentTime);
        task.isQueued = false;
      }
    }
  
    // Null out the callback to indicate the task has been canceled. (Can't
    // remove from the queue because you can't remove arbitrary nodes from an
    // array based heap, only the first one.)
    task.callback = null;
  }
  
  function unstable_getCurrentPriorityLevel() {
    return currentPriorityLevel;
  }
  
  function unstable_shouldYield() {
    const currentTime = getCurrentTime();
    advanceTimers(currentTime);
    const firstTask = peek(taskQueue);
    return (
      (firstTask !== currentTask &&
        currentTask !== null &&
        firstTask !== null &&
        firstTask.callback !== null &&
        firstTask.startTime <= currentTime &&
        firstTask.expirationTime < currentTask.expirationTime) ||
      shouldYieldToHost()
    );
  }
  
  const unstable_requestPaint = requestPaint;
  
  export {
    ImmediatePriority as unstable_ImmediatePriority,
    UserBlockingPriority as unstable_UserBlockingPriority,
    NormalPriority as unstable_NormalPriority,
    IdlePriority as unstable_IdlePriority,
    LowPriority as unstable_LowPriority,
    unstable_runWithPriority,
    unstable_next,
    unstable_scheduleCallback,
    unstable_cancelCallback,
    unstable_wrapCallback,
    unstable_getCurrentPriorityLevel,
    unstable_shouldYield,
    unstable_requestPaint,
    unstable_continueExecution,
    unstable_pauseExecution,
    unstable_getFirstCallbackNode,
    getCurrentTime as unstable_now,
    forceFrameRate as unstable_forceFrameRate,
  };
  
  export const unstable_Profiling = enableProfiling
    ? {
        startLoggingProfilingEvents,
        stopLoggingProfilingEvents,
        sharedProfilingBuffer,
      }
    : null;
  