// error-boundary.jsx
// React error boundaries. Boundaries MUST be class components — function
// components have no equivalent of getDerivedStateFromError/componentDidCatch.
//
// Why this exists: components are compiled in-browser by Babel-standalone, so a
// render-time throw (e.g. a malformed invoice record reaching a view) surfaces
// only at runtime — and a single uncaught throw blanks the ENTIRE app. These
// boundaries convert that into a contained, recoverable fallback.
//
// Two levels:
//   level="app"  — full-page safety net wrapping <App/>; offers Reload.
//   level="view" — inline panel around one screen, so a crashing view doesn't
//                  take down the TopBar/nav. Auto-resets when `resetKey` changes
//                  (i.e. the user navigated to another view / tab / job).
//
// Deliberately self-contained: no app components, no CSS classes — inline SVG +
// inline styles reading the theme tokens. An error boundary that depended on
// app code could itself fail to render the fallback.
//
// Caveat: boundaries catch errors during render, in lifecycle methods, and in
// constructors — NOT in event handlers or async callbacks. Those still need
// their own try/catch.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleRetry = this.handleRetry.bind(this);
    this.handleReload = this.handleReload.bind(this);
  }

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

  componentDidCatch(error, info) {
    const label = this.props.name || this.props.level || 'app';
    // Surface for debugging now; a hook for real telemetry later (no-op until
    // window.__logError is wired up).
    console.error(`[ErrorBoundary · ${label}]`, error, info && info.componentStack);
    try {
      if (typeof window.__logError === 'function') {
        window.__logError({ scope: label, message: String(error && error.message || error), stack: info && info.componentStack });
      }
    } catch (_) { /* never let logging throw */ }
  }

  componentDidUpdate(prev) {
    // Navigating away (resetKey changed) clears a prior error so the next view
    // renders fresh instead of staying stuck on the fallback.
    if (this.state.error && prev.resetKey !== this.props.resetKey) {
      this.setState({ error: null });
    }
  }

  handleRetry() { this.setState({ error: null }); }
  handleReload() { try { window.location.reload(); } catch (_) {} }

  render() {
    if (!this.state.error) return this.props.children;

    const app = this.props.level === 'app';
    const name = this.props.name;
    const message = String((this.state.error && this.state.error.message) || this.state.error || 'Unknown error');
    const stack = (this.state.error && this.state.error.stack) || '';

    const wrap = {
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: app ? '64px 24px' : '36px 20px',
      minHeight: app ? '100vh' : '200px',
      background: app ? 'var(--bg)' : 'transparent',
      color: 'var(--fg)', boxSizing: 'border-box',
    };
    const card = {
      maxWidth: '480px', width: '100%',
      background: 'var(--surface)', border: '1px solid var(--border)',
      borderRadius: '12px', padding: '22px 22px 18px',
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px',
      textAlign: 'center', boxShadow: 'var(--shadow-2)',
    };
    const title = { fontSize: '15px', fontWeight: 650, margin: 0, color: 'var(--fg)' };
    const sub = { fontSize: '13px', color: 'var(--fg-muted)', margin: 0, lineHeight: 1.5 };
    const row = { display: 'flex', gap: '8px', marginTop: '4px', flexWrap: 'wrap', justifyContent: 'center' };
    const btn = {
      appearance: 'none', font: 'inherit', fontSize: '13px', fontWeight: 600,
      padding: '8px 16px', borderRadius: '8px', cursor: 'pointer', border: '1px solid transparent',
    };
    const primary = { ...btn, background: 'var(--accent)', color: 'var(--accent-fg)' };
    const secondary = { ...btn, background: 'var(--surface-2)', color: 'var(--fg)', borderColor: 'var(--border)' };
    const detailsStyle = { width: '100%', marginTop: '6px', textAlign: 'left' };
    const summaryStyle = { fontSize: '11.5px', color: 'var(--fg-dim)', cursor: 'pointer', userSelect: 'none' };
    const pre = {
      margin: '6px 0 0', padding: '8px 10px', maxHeight: '160px', overflow: 'auto',
      background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: '6px',
      fontSize: '11px', lineHeight: 1.45, color: 'var(--fg-muted)', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
    };

    return (
      <div style={wrap} role="alert">
        <div style={card}>
          <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="var(--warn)"
            strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
            <line x1="12" y1="9" x2="12" y2="13" />
            <line x1="12" y1="17" x2="12.01" y2="17" />
          </svg>
          <h2 style={title}>{app ? 'Something went wrong' : `This ${name || 'view'} hit an error`}</h2>
          <p style={sub}>
            {app
              ? 'The app ran into an unexpected error. Your data is safe — try reloading.'
              : 'The rest of the app is still working — use the navigation above to switch screens, or try again.'}
          </p>
          <div style={row}>
            <button type="button" style={primary} onClick={this.handleRetry}>Try again</button>
            <button type="button" style={secondary} onClick={this.handleReload}>Reload app</button>
          </div>
          <details style={detailsStyle}>
            <summary style={summaryStyle}>Technical details</summary>
            <pre style={pre}>{message}{stack ? `\n\n${stack}` : ''}</pre>
          </details>
        </div>
      </div>
    );
  }
}

if (typeof window !== 'undefined') window.ErrorBoundary = ErrorBoundary;
