/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {Lane, Lanes} from './ReactFiberLane';
import type {Wakeable} from 'shared/ReactTypes';

import {enableDebugTracing} from 'shared/ReactFeatureFlags';

const nativeConsole: Object = console;
let nativeConsoleLog: null | Function = null;

const pendingGroupArgs: Array<any> = [];
let printedGroupIndex: number = -1;

function formatLanes(laneOrLanes: Lane | Lanes): string {
  return '0b' + (laneOrLanes: any).toString(2).padStart(31, '0');
}

function group(...groupArgs: Array<string>): void {
  pendingGroupArgs.push(groupArgs);

  if (nativeConsoleLog === null) {
    nativeConsoleLog = nativeConsole.log;
    nativeConsole.log = log;
  }
}

function groupEnd(): void {
  pendingGroupArgs.pop();
  while (printedGroupIndex >= pendingGroupArgs.length) {
    nativeConsole.groupEnd();
    printedGroupIndex--;
  }

  if (pendingGroupArgs.length === 0) {
    nativeConsole.log = nativeConsoleLog;
    nativeConsoleLog = null;
  }
}

function log(...logArgs: Array<mixed>): void {
  if (printedGroupIndex < pendingGroupArgs.length - 1) {
    for (let i = printedGroupIndex + 1; i < pendingGroupArgs.length; i++) {
      const groupArgs = pendingGroupArgs[i];
      nativeConsole.group(...groupArgs);
    }
    printedGroupIndex = pendingGroupArgs.length - 1;
  }
  if (typeof nativeConsoleLog === 'function') {
    nativeConsoleLog(...logArgs);
  } else {
    nativeConsole.log(...logArgs);
  }
}

const REACT_LOGO_STYLE =
  'background-color: #20232a; color: #61dafb; padding: 0 2px;';

export function logCommitStarted(lanes: Lanes): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      group(
        `%c⚛%c commit%c (${formatLanes(lanes)})`,
        REACT_LOGO_STYLE,
        '',
        'font-weight: normal;',
      );
    }
  }
}

export function logCommitStopped(): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      groupEnd();
    }
  }
}

const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
// $FlowFixMe[incompatible-type]: Flow cannot handle polymorphic WeakMaps
const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
let wakeableID: number = 0;
function getWakeableID(wakeable: Wakeable): number {
  if (!wakeableIDs.has(wakeable)) {
    wakeableIDs.set(wakeable, wakeableID++);
  }
  return ((wakeableIDs.get(wakeable): any): number);
}

export function logComponentSuspended(
  componentName: string,
  wakeable: Wakeable,
): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      const id = getWakeableID(wakeable);
      const display = (wakeable: any).displayName || wakeable;
      log(
        `%c⚛%c ${componentName} suspended`,
        REACT_LOGO_STYLE,
        'color: #80366d; font-weight: bold;',
        id,
        display,
      );
      wakeable.then(
        () => {
          log(
            `%c⚛%c ${componentName} resolved`,
            REACT_LOGO_STYLE,
            'color: #80366d; font-weight: bold;',
            id,
            display,
          );
        },
        () => {
          log(
            `%c⚛%c ${componentName} rejected`,
            REACT_LOGO_STYLE,
            'color: #80366d; font-weight: bold;',
            id,
            display,
          );
        },
      );
    }
  }
}

export function logLayoutEffectsStarted(lanes: Lanes): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      group(
        `%c⚛%c layout effects%c (${formatLanes(lanes)})`,
        REACT_LOGO_STYLE,
        '',
        'font-weight: normal;',
      );
    }
  }
}

export function logLayoutEffectsStopped(): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      groupEnd();
    }
  }
}

export function logPassiveEffectsStarted(lanes: Lanes): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      group(
        `%c⚛%c passive effects%c (${formatLanes(lanes)})`,
        REACT_LOGO_STYLE,
        '',
        'font-weight: normal;',
      );
    }
  }
}

export function logPassiveEffectsStopped(): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      groupEnd();
    }
  }
}

export function logRenderStarted(lanes: Lanes): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      group(
        `%c⚛%c render%c (${formatLanes(lanes)})`,
        REACT_LOGO_STYLE,
        '',
        'font-weight: normal;',
      );
    }
  }
}

export function logRenderStopped(): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      groupEnd();
    }
  }
}

export function logForceUpdateScheduled(
  componentName: string,
  lane: Lane,
): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      log(
        `%c⚛%c ${componentName} forced update %c(${formatLanes(lane)})`,
        REACT_LOGO_STYLE,
        'color: #db2e1f; font-weight: bold;',
        '',
      );
    }
  }
}

export function logStateUpdateScheduled(
  componentName: string,
  lane: Lane,
  payloadOrAction: any,
): void {
  if (__DEV__) {
    if (enableDebugTracing) {
      log(
        `%c⚛%c ${componentName} updated state %c(${formatLanes(lane)})`,
        REACT_LOGO_STYLE,
        'color: #01a252; font-weight: bold;',
        '',
        payloadOrAction,
      );
    }
  }
}
