// https://github.com/Chyrain/react-error-boundaries/commit/84c809df2d5f89ab47166857841b02f18ab3fa04
// MIT License
// Copyright (c) 2017 Chen HaiYong
import styled from "@emotion/styled"
import React from "react"
import ReactDOM from "react-dom"

interface ErrorBoundaryProps {
  children: React.ReactNode
  onError?: (error: Error, info: React.ErrorInfo) => void
}

export class ErrorBoundary extends React.PureComponent<
  ErrorBoundaryProps,
  {
    hasError: boolean
    error: Error | null
    errorInfo: React.ErrorInfo | null
  }
> {
  constructor(props: ErrorBoundaryProps) {
    super(props)
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
    }
  }

  closeErrorModal = (): void => {
    this.setState({ hasError: false })
  }

  override componentDidCatch(error: Error, info: React.ErrorInfo): void {
    // Update state if error happens
    this.setState({
      hasError: true,
      error,
      errorInfo: info,
    })

    // Report errors here
    const { onError } = this.props
    if (typeof onError === "function") {
      try {
        onError.call(this, error, info)
      } catch {}
    }
  }

  override render(): React.ReactNode {
    // if state contains error and in development environment we render fallback component
    if (this.state.hasError) {
      const { error, errorInfo } = this.state
      return (
        <Fallback
          closeErrorModal={this.closeErrorModal}
          error={error!}
          errorInfo={errorInfo!}
        />
      )
    }

    return this.props.children
  }
}

class Modal extends React.PureComponent<{ children: React.ReactNode }> {
  // Create a div that we'll render the modal into. Because each
  // Modal component has its own element, we can render multiple
  // modal components into the modal container.
  private el = document.createElement("div")

  override componentDidMount(): void {
    // Append the element into the DOM on mount. We'll render
    // into the modal container element (see the HTML tab).
    document.body.appendChild(this.el)
  }

  override componentWillUnmount(): void {
    // Remove the element from the DOM when we unmount
    document.body.removeChild(this.el)
  }

  override render(): React.ReactPortal {
    // Use a portal to render the children into the element
    return ReactDOM.createPortal(
      // Any valid React child: JSX, strings, arrays, etc.
      this.props.children,
      // A DOM element
      this.el,
    )
  }
}

const ErrorBoundaryContainer = styled.div`
  box-sizing: border-box;
  background-color: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(2px);
  color: #fff;
  position: fixed;
  height: 100%;
  width: 100%;
  top: 0;
  left: 0;
  overflow-y: auto;
  padding: 16px 32px;
  z-index: 5;
`
const Button = styled.button`
  position: absolute;
  right: 16px;
  top: 35px;
  cursor: pointer;
  min-width: 40px;
  min-height: 30px;
  border-radius: 5px;
  outline-style: none;
`
const Pre = styled.pre`
  font-family: "SF Mono", monospace, sans-serif;
  white-space: pre-wrap;
  word-wrap: break-word;
  word-break: break-word;
  padding: 0 6px;
  line-height: 1.5em;
`
const Hr = styled.hr`
  border: none;
  border-top: 1px solid #777;
`
const Error = styled(Pre)`
  border-radius: 3px;
  background-color: #290000;
  color: #ff8080;
`
const Warning = styled(Pre)`
  border-radius: 3px;
  background-color: #332b00;
  color: #f1ab27;
`

function parseStack(text?: string) {
  return text
    ? text.replaceAll(/([( ])chrome-extension:\/\/.*?\//g, "$1/").replace(/^\n/, "")
    : null
}

function Fallback(props: {
  error: Error
  errorInfo: React.ErrorInfo
  closeErrorModal: () => void
}) {
  const { error, errorInfo, closeErrorModal } = props

  return (
    <Modal>
      <ErrorBoundaryContainer>
        <Button onClick={closeErrorModal}>Close</Button>
        <div>
          <h2>Something went wrong.</h2>
          <Hr />
          <p>ErrorMessage</p>
          <Error>{error.message}</Error>
          <Hr />
          <p>Stack</p>
          <Error>{parseStack(error.stack)}</Error>
          <Hr />
          <p>ComponentStack</p>
          <Warning>{parseStack(errorInfo.componentStack)}</Warning>
          <Hr />
        </div>
      </ErrorBoundaryContainer>
    </Modal>
  )
}
