import * as React from 'react'
import { Button } from './Button'
import { ErrorState } from './ErrorState'
import { RefreshSignal } from './RefreshSignal'
import { SkeletonGuard } from './SkeletonGuard'

export interface QueryLike<T> {
  data: T | undefined
  isPending: boolean
  isError: boolean
  refetch: () => void
  isFetching?: boolean
  isStale?: boolean
  isPaused?: boolean
  error?: unknown
  dataUpdatedAt?: number
}

export interface QueryBoundaryProps<T> {
  query: QueryLike<T>
  skeleton: React.ReactNode
  children: (data: T) => React.ReactNode
  errorFallback?: (retry: () => void, error: unknown) => React.ReactNode
  staleErrorFallback?: (retry: () => void, error: unknown) => React.ReactNode
  showRefreshing?: boolean
}

function defaultErrorMessage(error: unknown): string {
  return error instanceof Error && error.message ? error.message : 'Failed to load.'
}

function defaultErrorFallback(retry: () => void, error: unknown): React.ReactNode {
  return (
    <ErrorState
      variant="inline"
      title="Something went wrong"
      message={defaultErrorMessage(error)}
      action={<Button onClick={retry}>Retry</Button>}
    />
  )
}

function defaultStaleErrorFallback(retry: () => void, error: unknown): React.ReactNode {
  return (
    <div className="relative mt-2 flex items-center gap-3 pt-2 text-sm text-danger">
      <RefreshSignal variant="error" />
      <span>{defaultErrorMessage(error)}</span>
      <Button size="sm" onClick={retry}>
        Retry
      </Button>
    </div>
  )
}

function useMounted(): boolean {
  const [mounted, setMounted] = React.useState(false)
  React.useEffect(() => {
    setMounted(true)
  }, [])
  return mounted
}

export function QueryBoundary<T>({
  query,
  skeleton,
  children,
  errorFallback = defaultErrorFallback,
  staleErrorFallback = defaultStaleErrorFallback,
  showRefreshing = true,
}: QueryBoundaryProps<T>) {
  const mounted = useMounted()
  const { data, isPending, isError, refetch, isFetching, isStale, isPaused, error } = query

  const settledRef = React.useRef(true)
  const [announce, setAnnounce] = React.useState('')

  React.useEffect(() => {
    if (isFetching) {
      settledRef.current = false
      return
    }
    if (!settledRef.current) {
      settledRef.current = true
      if (data !== undefined) setAnnounce('updated')
    }
  }, [isFetching, data])

  const effectiveData = mounted ? data : undefined
  const effectivePending = mounted ? isPending : true

  const liveRegion = (
    <div role="status" className="sr-only">
      {announce}
    </div>
  )

  if (effectiveData !== undefined && !isError) {
    const showRefreshHint =
      showRefreshing === true &&
      isFetching === true &&
      (typeof isStale === 'boolean' ? isStale : true) &&
      !isPaused

    return (
      <div aria-busy={!!isFetching} className="relative">
        {liveRegion}
        {showRefreshHint ? <RefreshSignal /> : null}
        {isPaused ? (
          <div role="status" className="mb-1 text-xs text-fg-muted">
            Showing cached data
          </div>
        ) : null}
        {children(effectiveData)}
      </div>
    )
  }

  if (effectiveData !== undefined && isError) {
    return (
      <div aria-busy={!!isFetching} className="relative">
        {liveRegion}
        {children(effectiveData)}
        {staleErrorFallback(refetch, error)}
      </div>
    )
  }

  if (effectiveData === undefined && effectivePending) {
    return (
      <div aria-busy="true">
        <SkeletonGuard pending>{skeleton}</SkeletonGuard>
      </div>
    )
  }

  return (
    <div ref={(el) => el?.focus()} tabIndex={-1} role="alert">
      {errorFallback(refetch, error)}
    </div>
  )
}
