import * as React from 'react';

const changedArray = (a = [], b = []) => {
  return a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]));
};

// 初始状态
const initialState = {
  error: null,
};

class ErrorBoundary extends React.Component {
  state = initialState;
  updatedWithError = false;

  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error, errorInfo) {
    if (this.props.onError) {
      this.props.onError(error, errorInfo.componentStack);
    }
  }

  componentDidUpdate(prevProps) {
    const { error } = this.state;
    const { resetKeys, onResetKeysChange } = this.props;

    if (error !== null && !this.updatedWithError) {
      this.updatedWithError = true;
      return;
    }

    if (error !== null && changedArray(prevProps.resetKeys, resetKeys)) {
      if (onResetKeysChange) {
        onResetKeysChange(prevProps.resetKeys, resetKeys);
      }

      this.reset();
    }
  }

  reset = () => {
    this.updatedWithError = false;
    this.setState(initialState);
  };

  resetErrorBoundary = () => {
    if (this.props.onReset) {
      this.props.onReset();
    }
    this.reset();
  };

  render() {
    const { fallback, FallbackComponent, fallbackRender } = this.props;
    const { error } = this.state;

    if (error !== null) {
      const fallbackProps = {
        error,
        resetErrorBoundary: this.resetErrorBoundary,
      };

      if (React.isValidElement(fallback)) {
        return fallback;
      }
      if (typeof fallbackRender === 'function') {
        return fallbackRender(fallbackProps);
      }
      if (FallbackComponent) {
        return <FallbackComponent {...fallbackProps} />;
      }

      throw new Error(
        'ErrorBoundary 组件需要传入 fallback, fallbackRender, FallbackComponent 其中一个',
      );
    }

    return this.props.children;
  }
}

/**
 * with 写法
 * @param Component 业务组件
 * @param errorBoundaryProps error boundary 的 props
 */
function withErrorBoundary(Component, errorBoundaryProps) {
  const Wrapped = (props) => {
    return (
      <ErrorBoundary {...errorBoundaryProps}>
        <Component {...props} />
      </ErrorBoundary>
    );
  };

  // DevTools 显示的组件名
  const name = Component.displayName || Component.name || 'Unknown';
  Wrapped.displayName = `withErrorBoundary(${name})`;

  return Wrapped;
}

/**
 * 自定义错误的 handler
 * @param givenError
 */
function useErrorHandler(givenError) {
  const [error, setError] = React.useState(null);
  if (givenError) throw givenError;
  if (error) throw error;
  return setError;
}

export { ErrorBoundary, withErrorBoundary, useErrorHandler };
