import { Component, isValidElement } from 'react';

interface ErrorBoundaryPropsWithFallback {
  onError?: (error: Error, info: React.ErrorInfo) => void;
  fallback?: React.ReactElement;
  fallbackRender?: never;
}

interface ErrorBoundaryPropsWithFallbackRender {
  onError?: (error: Error, info: React.ErrorInfo) => void;
  fallback?: never;
  fallbackRender?: (error: Error) => React.ReactNode;
}

export type ErrorBoundaryProps =
  | ErrorBoundaryPropsWithFallback
  | ErrorBoundaryPropsWithFallbackRender;

type ErrorBoundaryState = {
  error: Error | null;
};

export class ErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  state: ErrorBoundaryState = {
    error: null,
  };

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { error };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    // console.log('componentDidCatch', error, info);
    this.props.onError?.(error, info);
  }

  render() {
    const { error } = this.state;
    if (error !== null) {
      if (isValidElement(this.props.fallback)) {
        return this.props.fallback;
      } else if (typeof this.props.fallbackRender === 'function') {
        return this.props.fallbackRender(error);
      } else {
        return error.message;
      }
    }

    return this.props.children;
  }
}

export function withErrorBoundary(
  component: React.ReactNode,
  props?: ErrorBoundaryProps
) {
  return <ErrorBoundary {...props}>{component}</ErrorBoundary>;
}
