import type * as React from 'react';

import HMRClient from './hmr';

if (typeof window !== 'undefined') {
  // Sets up developer tools for web platforms when running in a webview. This ensures that logs are visible in the terminal.
  // We assume full control over the console and send JavaScript logs to Metro.
  const LEVELS = [
    'trace',
    'info',
    'warn',
    'error',
    'log',
    'group',
    'groupCollapsed',
    'groupEnd',
    'debug',
  ] as const;
  LEVELS.forEach((level) => {
    const originalFunction = console[level];
    console[level] = function (...args: any[]) {
      HMRClient.log(level, level === 'error' ? addErrorStacks(args, true) : args);
      originalFunction.apply(console, args);
    };
  });

  window.addEventListener('error', (event) => {
    // Not capturing current stack as it would only point to this function,
    // the stack chain is preserved by the browser.
    HMRClient.log('error', addErrorStacks([event.error]));
  });

  window.addEventListener('unhandledrejection', (event) => {
    // Not capturing current stack as it would only point to this function,
    // the stack chain is preserved by the browser.
    HMRClient.log('error', addErrorStacks([event.reason]));
  });
}

// This is called native on native platforms
HMRClient.setup({ isEnabled: true });

function addErrorStacks(data: unknown[], shouldCaptureCurrentStack = false) {
  const dataWithStacks = [...data];
  let hasStack = false;
  data.forEach((item) => {
    // on native handled in packages/@expo/metro-runtime/src/metroServerLogs.native.ts
    // https://github.com/expo/expo/blob/118528654c982b6df2f4b3e73bbf2ae0b78d84a2/packages/%40expo/metro-runtime/src/metroServerLogs.native.ts#L30
    // this differs from native implementation where error from native modules
    // would not pass instanceof Error check
    if (item instanceof Error && item.stack) {
      hasStack = true;
      dataWithStacks.push(item.stack);
    }
  });

  if (!hasStack && shouldCaptureCurrentStack) {
    // for console.* to point to the call site
    const stack = captureCurrentStack();
    if (typeof stack === 'string') {
      hasStack = true;
      dataWithStacks.push(stack);
    }
  }

  const react = require('react') as typeof React;
  const componentStack = react.captureOwnerStack?.();
  if (componentStack) {
    dataWithStacks.push(componentStack);
  }

  return dataWithStacks;
}

class NamelessError extends Error {
  name = '';
}

function captureCurrentStack() {
  // If you're reading this, look deeper into the call stack to find the actual error source.
  return new NamelessError().stack;
}
