/**
 * 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.
 *
 * @flow
 */

import type {CapturedError} from './ReactCapturedValue';

import {showErrorDialog} from './ReactFiberErrorDialog';

export function logCapturedError(capturedError: CapturedError): void {
  const logError = showErrorDialog(capturedError);

  // Allow injected showErrorDialog() to prevent default console.error logging.
  // This enables renderers like ReactNative to better manage redbox behavior.
  if (logError === false) {
    return;
  }

  const error = (capturedError.error: any);
  if (__DEV__) {
    const {
      componentName,
      componentStack,
      errorBoundaryName,
      errorBoundaryFound,
      willRetry,
    } = capturedError;

    // Browsers support silencing uncaught errors by calling
    // `preventDefault()` in window `error` handler.
    // We record this information as an expando on the error.
    if (error != null && error._suppressLogging) {
      if (errorBoundaryFound && willRetry) {
        // The error is recoverable and was silenced.
        // Ignore it and don't print the stack addendum.
        // This is handy for testing error boundaries without noise.
        return;
      }
      // The error is fatal. Since the silencing might have
      // been accidental, we'll surface it anyway.
      // However, the browser would have silenced the original error
      // so we'll print it first, and then print the stack addendum.
      console.error(error);
      // For a more detailed description of this block, see:
      // https://github.com/facebook/react/pull/13384
    }

    const componentNameMessage = componentName
      ? `The above error occurred in the <${componentName}> component:`
      : 'The above error occurred in one of your React components:';

    let errorBoundaryMessage;
    // errorBoundaryFound check is sufficient; errorBoundaryName check is to satisfy Flow.
    if (errorBoundaryFound && errorBoundaryName) {
      if (willRetry) {
        errorBoundaryMessage =
          `React will try to recreate this component tree from scratch ` +
          `using the error boundary you provided, ${errorBoundaryName}.`;
      } else {
        errorBoundaryMessage =
          `This error was initially handled by the error boundary ${errorBoundaryName}.\n` +
          `Recreating the tree from scratch failed so React will unmount the tree.`;
      }
    } else {
      errorBoundaryMessage =
        'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
        'Visit https://fb.me/react-error-boundaries to learn more about error boundaries.';
    }
    const combinedMessage =
      `${componentNameMessage}${componentStack}\n\n` +
      `${errorBoundaryMessage}`;

    // In development, we provide our own message with just the component stack.
    // We don't include the original error message and JS stack because the browser
    // has already printed it. Even if the application swallows the error, it is still
    // displayed by the browser thanks to the DEV-only fake event trick in ReactErrorUtils.
    console.error(combinedMessage);
  } else {
    // In production, we print the error directly.
    // This will include the message, the JS stack, and anything the browser wants to show.
    // We pass the error object instead of custom message so that the browser displays the error natively.
    console.error(error);
  }
}
