"use client"

/**
 * Grades/Diagnostic 模块通用 Widget 边界组件。
 *
 * 组合三个能力：
 * 1. Error Boundary — 隔离故障域，单个 Widget 抛错不影响其他区块
 * 2. Suspense — 流式渲染时显示骨架屏，避免白屏等待
 * 3. Skeleton — 与 Widget 尺寸匹配的占位
 *
 * 用法：
 * ```tsx
 * <WidgetBoundary title="成绩趋势">
 *   <GradeTrendChart data={data} />
 * </WidgetBoundary>
 * ```
 */

import { Component, Suspense, type ReactNode } from "react"
import { AlertCircle } from "lucide-react"
import { Button } from "@/shared/components/ui/button"
import { Skeleton } from "@/shared/components/ui/skeleton"

interface WidgetBoundaryProps {
  children: ReactNode
  /** Widget 标题（用于错误提示和 aria-label） */
  title?: string
  /** 骨架屏高度（默认 200px） */
  skeletonHeight?: number
  /** 自定义错误描述 */
  fallbackDescription?: string
  /** 重试按钮文案 */
  retryLabel?: string
}

interface WidgetBoundaryState {
  hasError: boolean
}

class WidgetErrorBoundary extends Component<
  Pick<
    WidgetBoundaryProps,
    "title" | "fallbackDescription" | "retryLabel" | "children"
  >,
  WidgetBoundaryState
> {
  constructor(props: WidgetBoundaryProps) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(): WidgetBoundaryState {
    return { hasError: true }
  }

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

  render(): ReactNode {
    if (this.state.hasError) {
      const title = this.props.title ?? "区块"
      return (
        <div
          role="alert"
          aria-live="assertive"
          className="flex h-full min-h-[200px] flex-col items-center justify-center gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-6 text-center"
        >
          <AlertCircle className="h-8 w-8 text-destructive" aria-hidden="true" />
          <div className="space-y-1">
            <p className="text-sm font-medium text-foreground">
              {title}加载失败
            </p>
            <p className="text-xs text-muted-foreground">
              {this.props.fallbackDescription ?? "请重试或刷新页面"}
            </p>
          </div>
          <Button
            size="sm"
            variant="outline"
            onClick={this.handleReset}
            aria-label={`重试加载${title}`}
          >
            {this.props.retryLabel ?? "重试"}
          </Button>
        </div>
      )
    }

    return this.props.children
  }
}

function WidgetSkeleton({
  height,
  title,
}: {
  height: number
  title?: string
}): ReactNode {
  return (
    <div
      role="status"
      aria-label={`${title ?? "区块"}加载中`}
      aria-live="polite"
      className="space-y-3 p-4"
      style={{ minHeight: height }}
    >
      <Skeleton className="h-6 w-1/3" />
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-2/3" />
      <Skeleton className="h-32 w-full" />
    </div>
  )
}

export function WidgetBoundary({
  children,
  title,
  skeletonHeight = 200,
  fallbackDescription,
  retryLabel,
}: WidgetBoundaryProps): ReactNode {
  return (
    <WidgetErrorBoundary
      title={title}
      fallbackDescription={fallbackDescription}
      retryLabel={retryLabel}
    >
      <Suspense
        fallback={
          <WidgetSkeleton height={skeletonHeight} title={title} />
        }
      >
        {children}
      </Suspense>
    </WidgetErrorBoundary>
  )
}
