import { Component, type ErrorInfo, type ReactNode } from 'react';

interface Props {
  children: ReactNode;
  /** Optional fallback rendered in place of `children` when an error is caught. */
  fallback?: (error: Error) => ReactNode;
}

interface State {
  error: Error | null;
}

/**
 * Catches render errors in its subtree and renders a fallback instead of
 * letting them crash the whole page. Used to isolate failures in part viewers
 * (e.g. WebGL context loss, texture/trace load errors).
 */
export default class ErrorBoundary extends Component<Props, State> {
  override state: State = { error: null };

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

  override componentDidCatch(error: Error, info: ErrorInfo) {
    // Surface the error for debugging without breaking the UI.
    console.error('Part viewer error:', error, info.componentStack);
  }

  override render() {
    if (this.state.error) {
      return this.props.fallback
        ? this.props.fallback(this.state.error)
        : <DefaultFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

function DefaultFallback({ error }: { error: Error }) {
  return (
    <div className="flex h-80 flex-col items-center justify-center gap-2 overflow-auto rounded-lg border border-zinc-800 bg-zinc-900 p-4 text-center">
      <p className="text-sm font-medium text-zinc-200">Couldn't render this part</p>
      <p className="max-w-sm wrap-break-word font-mono text-xs text-zinc-400">
        {error.message}
      </p>
      {error.stack && (
        <pre className="mt-2 max-h-40 w-full overflow-auto whitespace-pre-wrap text-left font-mono text-[10px] leading-relaxed text-zinc-600">
          {error.stack}
        </pre>
      )}
    </div>
  );
}