import React, { ErrorInfo } from 'react'
import { RefreshControlProps } from 'react-native'
import * as Sentry from 'sentry-expo'
import * as Updates from 'expo-updates'

import { Error as ErrorComponent } from './Error'
import { NetworkError } from './useNetwork'

type Props = {
  error: NetworkError | null
  refreshControl: React.ReactElement<RefreshControlProps>
}
type State = { error: NetworkError | null }

function reportErrors(error: Error, errorInfo: ErrorInfo) {
  console.error(error, errorInfo)
  Sentry.Native.captureException(error)
  Sentry.Native.captureMessage(
    errorInfo.toString(),
    Sentry.Native.Severity.Error
  )
}

export class ErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { error: this.props.error }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    reportErrors(error, errorInfo)
  }

  static getDerivedStateFromError(): State {
    return { error: { type: 'app' } }
  }

  render() {
    const { refreshControl, children } = this.props
    const error = this.props.error || this.state.error
    return error ? (
      <ErrorComponent error={error} refreshControl={refreshControl} />
    ) : (
      children
    )
  }
}

/**
 * Top level error boundary used as a "last resort" on the top level. Reports errors and restarts the app immediately (Default Expo behavior for uncaught errors).
 */
export class TopErrorBoundary extends React.Component<{}, State> {
  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    reportErrors(error, errorInfo)
    Updates.reloadAsync()
  }

  static getDerivedStateFromError(): State {
    return { error: { type: 'app' } }
  }

  render() {
    return this.props.children
  }
}
