/* eslint-disable @typescript-eslint/no-explicit-any */
import { Button, Code, Collapse, Group, Paper, rem, Text } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react';
import React, { type PropsWithChildren } from 'react';

import errorManager from './ErrorManager';

let ErrorBoundaryOptions = {
  disabled: false,
  onError: undefined,
} as {
  disabled?: boolean;
  onError?: (error: any, errorInfo: any) => void;
};

export const SetErrorBoundaryOptions = (options: typeof ErrorBoundaryOptions) => {
  ErrorBoundaryOptions = { ...ErrorBoundaryOptions, ...options };
};

export const GetErrorBoundaryOptions = () => {
  return { ...ErrorBoundaryOptions };
};

interface ErrorBoundaryProps extends PropsWithChildren {
  namespace?: string;
  onReportClick?: () => void;
  onResetClick?: () => void;
  onRetryClick?: () => void;
  reportAPI?: string;
}

interface ErrorBoundaryState {
  error: any;
  errorInfo: any;
  reported?: boolean;
  showDetail: boolean;
  timer?: NodeJS.Timeout | undefined;
  try: boolean;
  tryCnt: number;
  wasReset?: boolean;
}

export class ReactErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);

    this.state = {
      error: null,
      errorInfo: null,
      showDetail: false,
      timer: undefined,
      try: false,
      tryCnt: 0,
    };
  }

  componentDidCatch(error: any, errorInfo: any) {
    if (GetErrorBoundaryOptions()?.disabled) {
      throw Error('ErrorBoundary pass through enabled, rethrowing error', {
        cause: error,
        //@ts-ignore
        info: errorInfo,
      });
    }
    // Catch errors in any components below and re-render with error message
    this.setState({
      error,
      errorInfo,
      try: false,
    });
    if (typeof GetErrorBoundaryOptions()?.onError === 'function') {
      GetErrorBoundaryOptions()?.onError?.(error, errorInfo);
    }

    // Report error to error manager (Sentry, custom API, etc.)
    errorManager.reportError(error, errorInfo, {
      componentStack: errorInfo?.componentStack,
      namespace: this.props.namespace,
    });
  }

  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2
            style={{
              color: 'var(--mantine-color-error)',
              display: 'flex',
              gap: '8px',
            }}
          >
            <IconExclamationCircle
              color="var(--mantine-color-error)"
              style={{ height: rem(20), width: rem(20) }}
            />
            A react error has occurred!
          </h2>
          <h4>You can try one of these solutions to get back on track:</h4>
          <ul>
            <li>Report the error to our team</li>
            <li>Hit the keys Ctrl-Shift-R</li>
            <li>Make sure your web browser is up to date</li>
            <li>Clear your browser cache and cookies</li>
            <li>Try using a different web browser</li>
            <li>Reset your layout and settings</li>
            <li>Retry loading the application</li>
          </ul>

          <Group>
            <Button
              color="red"
              my="md"
              onClick={() =>
                this.setState((state) => ({
                  showDetail: !state.showDetail,
                }))
              }
              size="compact-xs"
              variant="outline"
            >
              More
            </Button>
            <Button
              color="blue"
              disabled={this.state.tryCnt > 2 || this.state.try}
              my="md"
              onClick={() => this.retry()}
              size="compact-xs"
              variant="outline"
            >
              Retry {this.state.tryCnt > 0 ? `(${this.state.tryCnt})` : ''}
            </Button>
            <Button
              color="yellow"
              disabled={this.state.wasReset}
              my="md"
              onClick={() => this.reset()}
              size="compact-xs"
              variant="outline"
            >
              Reset Layout or Settings
            </Button>
            <Button
              color="green"
              disabled={this.state.reported}
              my="md"
              onClick={() => this.report()}
              size="compact-xs"
              variant="outline"
            >
              Report
            </Button>
          </Group>

          {this.state.error && (
            <Paper
              p="md"
              shadow="xs"
              style={{
                textAlign: 'justify',
              }}
              w="50%"
              withBorder
            >
              {this.props.namespace && <Text size="sm">In: {this.props.namespace}</Text>}
              <Text size="sm">{this.state.error.toString()}</Text>
            </Paper>
          )}

          <Collapse in={this.state.showDetail}>
            <Code block color="rgba(235, 86, 5, 0.6)" mah="100vh" w="100%">
              {this.state.errorInfo.componentStack}
            </Code>
          </Collapse>
        </div>
      );
    }

    //Retry code, if you do it too many times, it will stop
    if (this.state.try) {
      if (!this.state.timer) {
        const tm = setTimeout(() => {
          clearTimeout(this.state.timer);
          this.setState((state) => ({
            ...state,
            timer: undefined,
            try: false,
          }));
        }, 1000);

        this.setState((state) => ({
          ...state,
          timer: tm,
        }));
      }
      return <div>Retrying {this.state.tryCnt}...</div>;
    }

    // Normally, just render children
    return this.props.children;
  }

  async report() {
    if (this.props?.onReportClick) {
      this.props.onReportClick();
      this.setState(() => ({
        reported: true,
      }));
      return;
    }

    // Manually report error if user clicks report button
    if (this.state.error && this.state.errorInfo) {
      await errorManager.reportError(this.state.error, this.state.errorInfo, {
        componentStack: this.state.errorInfo?.componentStack,
        namespace: this.props.namespace,
        tags: { reportedBy: 'user' },
      });

      this.setState(() => ({
        reported: true,
      }));
    }
  }

  reset() {
    if (this.props?.onResetClick) {
      this.props.onResetClick();
      this.setState(() => ({
        wasReset: true,
      }));
      return;
    }

    this.setState(() => ({
      wasReset: true,
    }));

    window.location.reload();
  }

  retry() {
    this.setState({
      try: true,
      tryCnt: this.state.tryCnt + 1,
    });
    if (this.props?.onRetryClick) {
      this.props.onRetryClick();
      return;
    }
    this.setState({
      error: undefined,
      errorInfo: undefined,
    });
    this.forceUpdate();
  }
}

export default ReactErrorBoundary;
