import {
  App,
  ComponentOptions,
  defineComponent,
  nextTick,
  ref,
  reactive,
  PropType
} from 'vue'
import './index.less'

const prefix = 'error'

type This = Options & ComponentOptions

interface Options {
  handler?: () => void
  error: boolean
}

interface Methods extends Options {
  handleError: (
    this: This,
    error: boolean,
    runtimeProps: RuntimeErrorComponentProps
  ) => void
  close: () => void
}

interface RuntimeErrorComponentProps {
  title: string
  message: string | unknown
}

export function errorHandle(app: App): void {
  app.config.errorHandler = (err, vm, info) => {
    nextTick(() => {
      if (vm && vm.$root) {
        const ErrorBoundary = vm.$root.$refs.ErrorBoundary as unknown as Methods
        ErrorBoundary.handleError(true, {
          title: info,
          message: err
        })
      }
    })
  }
}

export const RuntimeErrorComponent = defineComponent({
  props: {
    title: {
      type: String as PropType<string>,
      required: true
    },
    message: {
      type: Object as PropType<RuntimeErrorComponentProps>,
      required: true
    }
  },
  render(this: RuntimeErrorComponentProps & ComponentOptions) {
    const { title, message } = this.$props
    return (
      <div class={`${prefix}`}>
        <div class={`${prefix}-boundary`}>
          <h1 class={`${prefix}-boundary-title`}>{title}</h1>
          <div class={`${prefix}-boundary-content`}>{message.message}</div>
          <pre class={`${prefix}-boundary-content`}>{message.stack}</pre>
        </div>
        <div
          class={`${prefix}-close`}
          onClick={() => {
            this.$parent.close()
          }}
        >
          <icon icon="cross" size={24}></icon>
        </div>
      </div>
    )
  }
})

export const ErrorBoundary = defineComponent<Options>({
  name: 'ErrorBoundary',
  components: {
    RuntimeErrorComponent
  },
  setup() {
    const error = ref(false)
    const runtimeProps = reactive({})
    return {
      error,
      runtimeProps
    }
  },
  methods: {
    handleError(
      this: This,
      error: boolean,
      runtimeProps: RuntimeErrorComponentProps
    ) {
      this.error = error
      this.runtimeProps = runtimeProps
    },
    close() {
      this.error = false
    }
  } as Methods,
  render(this: This) {
    const { error, runtimeProps } = this
    return (
      <>
        {error && (
          <RuntimeErrorComponent
            title={runtimeProps.title}
            message={runtimeProps.message}
          ></RuntimeErrorComponent>
        )}
        {this.$slots.default && this.$slots.default()}
      </>
    )
  }
})