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

type FallbackArgs = { error: Error; reset: () => void };

type ErrorBoundaryProps = {
  children: ReactNode;
  /** Rendered in place of the children once something below has thrown. */
  fallback: (args: FallbackArgs) => ReactNode;
  /**
   * Clear the error whenever any of these changes — pass the pathname to recover on navigation, or the
   * id of the thing being rendered. Without it a boundary latches: the screen stays broken until reload
   * even after the user has navigated somewhere that would render fine.
   */
  resetKeys?: readonly unknown[];
  onError?: (error: Error, info: ErrorInfo) => void;
};

type ErrorBoundaryState = { error: Error | null };

/**
 * The repo had none of these. A single malformed value anywhere below the router took the whole app to a
 * white screen, and the only recovery from a bad stored layout was SQL — which is the wrong thing to ask
 * of someone who has just opened the dashboard to read what happened overnight.
 *
 * Deliberately a render-prop rather than a fixed panel: what "recover" means is different for a screen
 * (go somewhere else) and for one panel inside a workspace (clear the app, keep the rest running).
 */
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  override state: ErrorBoundaryState = { error: null };

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

  override componentDidCatch(error: Error, info: ErrorInfo): void {
    console.error('[error-boundary]', error, info.componentStack);
    this.props.onError?.(error, info);
  }

  override componentDidUpdate(prev: ErrorBoundaryProps): void {
    if (!this.state.error) return;
    const a = prev.resetKeys ?? [];
    const b = this.props.resetKeys ?? [];
    if (a.length !== b.length || a.some((key, i) => !Object.is(key, b[i]))) this.setState({ error: null });
  }

  reset = (): void => this.setState({ error: null });

  override render(): ReactNode {
    const { error } = this.state;
    if (error) return this.props.fallback({ error, reset: this.reset });
    return this.props.children;
  }
}
