import { Component } from 'react';

/**
 * Reusable React error boundary. Catches render/lifecycle errors from its
 * children so a single failing subtree (e.g. the 3D viewer) cannot blank the
 * whole application.
 *
 * Props:
 *  - label:      short tag used in dev logs
 *  - fallback:   React node OR function ({ error, info, retry }) => node.
 *                If omitted, renders null on error.
 *  - children
 */
export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null, info: null, generation: 0 };
  }

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

  componentDidCatch(error, info) {
    this.setState({ info });
    if (import.meta.env?.DEV) {
      // eslint-disable-next-line no-console
      console.error(`[ErrorBoundary${this.props.label ? `:${this.props.label}` : ''}]`, error, info?.componentStack);
    }
  }

  retry = () => {
    // Clear the error and bump the generation so children remount fresh
    // (re-triggering loaders / re-initialising WebGL state).
    this.setState((s) => ({ error: null, info: null, generation: s.generation + 1 }));
  };

  render() {
    const { error } = this.state;
    if (error) {
      const { fallback } = this.props;
      if (typeof fallback === 'function') {
        // Render as a genuine component so hook-using fallbacks (e.g.
        // ViewerFallback with useNavigate/useState) do not throw
        // "Invalid Hook Call" when invoked as a plain function.
        const FallbackComponent = fallback;
        return <FallbackComponent error={error} info={this.state.info} retry={this.retry} />;
      }
      if (fallback != null && typeof fallback !== 'function') {
        return fallback;
      }
      return null;
    }
    return <div key={this.state.generation} className="contents">{this.props.children}</div>;
  }
}