// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception

import Avatar from "@material-ui/core/Avatar";
import Container from "@material-ui/core/Container";
import {
  createStyles,
  Theme,
  WithStyles,
  withStyles,
} from "@material-ui/core/styles";
import BugReportIcon from "@material-ui/icons/BugReport";
import React from "react";

interface State {
  error: any;
}

const styles = (theme: Theme) =>
  createStyles({
    avatar: {
      backgroundColor: theme.palette.secondary.main,
      float: "left",
      marginRight: "1em",
    },
  });

interface Props extends WithStyles<typeof styles> {
  children: React.ReactNode;
}

/**
 * A simple <a href="https://reactjs.org/docs/error-boundaries.html">error
 * boundary</a> meant to go at the top level.
 *
 * The assumption is that any error here is a bug in the UI layer. Components
 * shouldn't throw errors upward even if there are network or server problems.
 *
 * Limitations: as described in the React docs, error boundaries don't catch
 * errors in async code / rejected Promises.
 */
class MoonfireErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { error: null };
  }

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

  componentDidCatch(error: any, errorInfo: React.ErrorInfo) {
    console.error("Uncaught error:", error, errorInfo);
  }

  render() {
    const { classes, children } = this.props;

    if (this.state.error !== null) {
      var error;
      if (this.state.error.stack !== undefined) {
        error = <pre>{this.state.error.stack}</pre>;
      } else if (this.state.error instanceof Error) {
        error = (
          <>
            <pre>{this.state.error.name}</pre>
            <pre>{this.state.error.message}</pre>
          </>
        );
      } else {
        error = <pre>{this.state.error}</pre>;
      }

      return (
        <Container>
          <Avatar className={classes.avatar}>
            <BugReportIcon color="primary" />
          </Avatar>
          <h1>Error</h1>

          <p>
            Sorry! You've found a bug in Moonfire NVR. We need a good bug report
            to get it fixed. Can you help?
          </p>

          <h2>How to report a bug</h2>

          <p>
            Please open{" "}
            <a href="https://github.com/scottlamb/moonfire-nvr/issues">
              Moonfire NVR's issue tracker
            </a>{" "}
            and see if this problem has already been reported.
          </p>

          <h3>Can't find anything?</h3>

          <p>Open a new issue with as much detail as you can:</p>

          <ul>
            <li>the version of Moonfire NVR you're using</li>
            <li>
              your environment, including:
              <ul>
                <li>web browser: Chrome, Firefox, Safari, etc.</li>
                <li>platform: macOS, Windows, Linux, Android, iOS, etc.</li>
                <li>browser extensions</li>
                <li>anything special about your Moonfire NVR setup</li>
              </ul>
            </li>
            <li>all the errors you see in your browser's Javascript console</li>
            <li>steps to reproduce, if possible</li>
          </ul>

          <h3>Already reported?</h3>

          <ul>
            <li>+1 the issue so we know more people are affected.</li>
            <li>add any new details you've noticed.</li>
          </ul>

          <h2>The error</h2>

          {error}
        </Container>
      );
    }
    return children;
  }
}

export default withStyles(styles)(MoonfireErrorBoundary);
