/**
 * @file React Error Boundary for catching unhandled render-time errors.
 * @copyright nhcarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */
import { Component, type ErrorInfo, type ReactNode } from "react";
import { logError } from "../utils/logError.js";

interface ErrorBoundaryProperties {
  readonly children: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
}

/**
 * Catches unhandled render-time errors in the React tree, logs them to the
 * backend telemetry service, and renders a fallback UI.
 */
class ErrorBoundary extends Component<
  ErrorBoundaryProperties,
  ErrorBoundaryState
> {
  // eslint-disable-next-line jsdoc/require-jsdoc -- React Error Boundary constructor is standard boilerplate
  public constructor(properties: ErrorBoundaryProperties) {
    super(properties);
    this.state = { hasError: false };
  }

  /**
   * Updates state so the next render shows the fallback UI.
   * @returns The updated error boundary state.
   */
  public static getDerivedStateFromError(): ErrorBoundaryState {
    return { hasError: true };
  }

  /**
   * Logs the error to the backend telemetry service.
   * @param error - The error that was thrown during render.
   * @param info - React error info containing the component stack trace.
   */
  // eslint-disable-next-line @typescript-eslint/class-methods-use-this -- React lifecycle method cannot be static
  public override componentDidCatch(error: Error, info: ErrorInfo): void {
    logError("react_error_boundary", error, info.componentStack);
  }

  /**
   * Renders the fallback UI when an error is caught, otherwise renders children.
   * @returns The JSX element.
   */
  public override render(): ReactNode {
    const { hasError } = this.state;
    const { children } = this.props;

    if (hasError) {
      return (
        <div className="error-screen">
          <p>{"Something went wrong. Please refresh the page."}</p>
        </div>
      );
    }

    return children;
  }
}

export { ErrorBoundary };
