import { useCallback, useEffect, useState } from "react";
import { API } from "../config";

type PRScan = {
  id: number;
  pr_number: number;
  repo_name: string;
  pr_title: string;
  pr_url: string;
  source_branch: string;
  target_branch: string;
  author: string;
  state: "open" | "merged" | "closed";
  scan_status: "pending" | "completed";
  issues_count: number;
  security_issues: number;
  created_at: string;
};

type PRDetail = PRScan & {
  scan_result: Record<string, unknown>;
  scan_details_with_code: unknown[];
  ai_review: unknown;
  report_path: string;
};

type ChangedFile = {
  filename: string;
  status: string;
  additions: number;
  deletions: number;
};

type FileContentIssue = {
  line: number;
  severity: string;
  message: string;
  rule?: string;
  scanner?: string;
};

type FileContent = {
  content: string;
  issues: FileContentIssue[];
};

function normalizeFileContent(data: {
  content?: string;
  issues?: Array<{ line?: number; severity?: string; message?: string; description?: string; rule?: string; scanner?: string }>;
  scan_issues?: Array<{ line?: number; severity?: string; message?: string; description?: string; rule?: string; scanner?: string }>;
}): FileContent {
  const raw = data.scan_issues ?? data.issues ?? [];
  const issues: FileContentIssue[] = raw.map((iss) => {
    const line = typeof iss.line === "number" ? iss.line : typeof iss.line === "string" ? parseInt(iss.line, 10) : 1;
    return {
      line: Number.isFinite(line) ? line : 1,
      severity: String(iss.severity ?? "info").toLowerCase(),
      message: String(iss.message ?? iss.description ?? ""),
      rule: iss.rule,
      scanner: iss.scanner,
    };
  });
  return {
    content: data.content ?? "",
    issues,
  };
}

type View = "dashboard" | "pr-list" | "settings";

type PRHistoryItem = {
  pr_number: number;
  error_count?: number;
  warning_count?: number;
  total_issues?: number;
};

type QualityGateProps = {
  view: View;
};

const TREND_SLOTS = 15;
const TREND_PX_PER_PR = 80;
const TREND_CHART_HEIGHT = 220;

function ProblemTrendChart({
  history,
  loading,
}: {
  history: PRHistoryItem[];
  loading: boolean;
}) {
  if (loading) {
    return (
      <div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
        <h3 className="text-base font-extrabold mb-3">问题趋势</h3>
        <div className="text-center py-8 text-txt-muted text-sm">加载趋势数据中...</div>
      </div>
    );
  }
  if (!history || history.length === 0) {
    return (
      <div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
        <h3 className="text-base font-extrabold mb-3">问题趋势</h3>
        <div className="text-center py-8 text-txt-muted text-sm">暂无趋势数据</div>
      </div>
    );
  }

  const n = history.length;
  const pad = Math.max(0, TREND_SLOTS - n);
  const labels = history.map((p) => `#${p.pr_number}`).concat(Array(pad).fill(""));
  const errorData: (number | null)[] = history.map((p) => p.error_count ?? 0).concat(Array(pad).fill(null));
  const warningData: (number | null)[] = history.map((p) => p.warning_count ?? 0).concat(Array(pad).fill(null));

  const width = TREND_SLOTS * TREND_PX_PER_PR;
  const height = TREND_CHART_HEIGHT;
  const padding = { top: 28, right: 24, bottom: 32, left: 40 };
  const chartW = width - padding.left - padding.right;
  const chartH = height - padding.top - padding.bottom;

  const maxVal = Math.max(
    1,
    ...errorData.filter((v): v is number => v != null),
    ...warningData.filter((v): v is number => v != null)
  );
  const yMax = Math.ceil(maxVal / 2) * 2 || 2;
  const yTicks = Array.from({ length: yMax + 1 }, (_, i) => i);

  const xScale = (i: number) => padding.left + (i / (labels.length - 1 || 1)) * chartW;
  const yScale = (v: number) => padding.top + chartH - (v / yMax) * chartH;

  const toPath = (data: (number | null)[]) => {
    const parts: string[] = [];
    for (let i = 0; i < data.length; i++) {
      const v = data[i];
      if (v == null) continue;
      const x = xScale(i);
      const y = yScale(v);
      parts.push(parts.length === 0 ? `M ${x} ${y}` : `L ${x} ${y}`);
    }
    return parts.join(" ");
  };

  const toAreaPath = (data: (number | null)[]) => {
    const points: { i: number; v: number }[] = [];
    data.forEach((v, i) => {
      if (v != null) points.push({ i, v });
    });
    if (points.length === 0) return "";
    const linePath = points.map(({ i, v }) => `${i === 0 ? "M" : "L"} ${xScale(i)} ${yScale(v)}`).join(" ");
    const lastX = xScale(points[points.length - 1].i);
    const baseY = padding.top + chartH;
    const startX = xScale(points[0].i);
    return `${linePath} L ${lastX} ${baseY} L ${startX} ${baseY} Z`;
  };

  const errorPath = toPath(errorData);
  const warningPath = toPath(warningData);
  const errorAreaPath = toAreaPath(errorData);
  const warningAreaPath = toAreaPath(warningData);

  return (
    <div className="rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)] p-4">
      <h3 className="text-base font-extrabold mb-3">问题趋势</h3>
      <div className="overflow-x-auto overflow-y-hidden" style={{ maxWidth: "100%" }}>
        <div style={{ width, minWidth: width, height }}>
          <svg width={width} height={height} className="overflow-visible">
            {/* 网格线 */}
            {yTicks.map((tick, i) => (
              <line
                key={i}
                x1={padding.left}
                y1={yScale(tick)}
                x2={padding.left + chartW}
                y2={yScale(tick)}
                stroke="rgba(0,0,0,0.12)"
                strokeWidth={1}
                strokeDasharray="4 4"
              />
            ))}
            {labels.map((_, i) => (
              <line
                key={i}
                x1={xScale(i)}
                y1={padding.top}
                x2={xScale(i)}
                y2={padding.top + chartH}
                stroke="rgba(0,0,0,0.12)"
                strokeWidth={1}
                strokeDasharray="4 4"
              />
            ))}
            {/* 图例 */}
            <g transform={`translate(${padding.left}, 8)`}>
              <rect x={0} y={2} width={14} height={10} fill="#dc3545" rx={1} />
              <text x={18} y={11} fontSize={11} fill="currentColor" className="text-txt">
                错误
              </text>
              <rect x={70} y={2} width={14} height={10} fill="#ffc107" rx={1} />
              <text x={88} y={11} fontSize={11} fill="currentColor" className="text-txt">
                警告
              </text>
            </g>
            {/* 面积填充 */}
            <path d={errorAreaPath} fill="rgba(220, 53, 69, 0.1)" />
            <path d={warningAreaPath} fill="rgba(255, 193, 7, 0.1)" />
            {/* 折线 */}
            <path d={errorPath} fill="none" stroke="#dc3545" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
            <path d={warningPath} fill="none" stroke="#ffc107" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
            {/* Y 轴刻度 */}
            {yTicks.map((tick) => (
              <text
                key={tick}
                x={padding.left - 8}
                y={yScale(tick) + 4}
                textAnchor="end"
                fontSize={10}
                fill="var(--color-txt-muted, #666)"
              >
                {tick}
              </text>
            ))}
            {/* X 轴刻度 */}
            {labels.map((label, i) => (
              <text
                key={i}
                x={xScale(i)}
                y={height - 8}
                textAnchor="middle"
                fontSize={10}
                fill={label ? "var(--color-txt-muted, #666)" : "transparent"}
              >
                {label || "#"}
              </text>
            ))}
          </svg>
        </div>
      </div>
    </div>
  );
}

const BASE = API.quality;

async function apiGet<T>(path: string): Promise<T> {
  const res = await fetch(`${BASE}${path}`);
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  return res.json();
}

async function apiPost<T>(path: string): Promise<T> {
  const res = await fetch(`${BASE}${path}`, { method: "POST" });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  return res.json();
}

export default function QualityGate({ view }: QualityGateProps) {
  const [prs, setPrs] = useState<PRScan[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [filter, setFilter] = useState<"all" | "open" | "merged" | "closed">("all");

  const [selectedPR, setSelectedPR] = useState<PRDetail | null>(null);
  const [changedFiles, setChangedFiles] = useState<ChangedFile[]>([]);
  const [selectedFile, setSelectedFile] = useState<string>("");
  const [fileContent, setFileContent] = useState<FileContent | null>(null);
  const [modalOpen, setModalOpen] = useState(false);

  const [trendHistory, setTrendHistory] = useState<PRHistoryItem[]>([]);
  const [trendLoading, setTrendLoading] = useState(false);

  const fetchPRs = useCallback(async () => {
    setLoading(true);
    setError("");
    try {
      const query = filter === "all" ? "" : `?state=${filter}`;
      const data = await apiGet<{ prs: PRScan[] } | PRScan[]>(`/prs${query}`);
      setPrs(Array.isArray(data) ? data : data.prs || []);
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setLoading(false);
    }
  }, [filter]);

  useEffect(() => {
    fetchPRs();
  }, [fetchPRs]);

  const fetchTrendHistory = useCallback(async () => {
    setTrendLoading(true);
    try {
      const data = await apiGet<PRHistoryItem[] | { history?: PRHistoryItem[] }>("/prs/history?limit=15");
      const list = Array.isArray(data) ? data : data.history ?? [];
      setTrendHistory(list);
    } catch {
      setTrendHistory([]);
    } finally {
      setTrendLoading(false);
    }
  }, []);

  useEffect(() => {
    if (view === "dashboard") fetchTrendHistory();
  }, [view, fetchTrendHistory]);

  const openPRDetail = useCallback(async (prId: number) => {
    setLoading(true);
    setError("");
    try {
      const [detail, files] = await Promise.all([
        apiGet<PRDetail>(`/prs/${prId}`),
        apiGet<{ files: ChangedFile[] } | ChangedFile[]>(`/prs/${prId}/files`),
      ]);
      setSelectedPR(detail);
      const fileList = Array.isArray(files) ? files : files.files || [];
      setChangedFiles(fileList);
      if (fileList.length > 0) {
        setSelectedFile(fileList[0].filename);
      }
      setModalOpen(true);
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    if (!modalOpen || !selectedPR || !selectedFile) return;
    setFileContent(null);
    apiGet<Parameters<typeof normalizeFileContent>[0]>(`/prs/${selectedPR.id}/file?path=${encodeURIComponent(selectedFile)}`)
      .then((data) => setFileContent(normalizeFileContent(data)))
      .catch(() => setFileContent({ content: "// Failed to load file", issues: [] }));
  }, [modalOpen, selectedPR, selectedFile]);

  const handleMerge = async () => {
    if (!selectedPR) return;
    setLoading(true);
    try {
      await apiPost(`/prs/${selectedPR.id}/merge`);
      setModalOpen(false);
      fetchPRs();
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setLoading(false);
    }
  };

  const handleClose = async () => {
    if (!selectedPR) return;
    setLoading(true);
    try {
      await apiPost(`/prs/${selectedPR.id}/close`);
      setModalOpen(false);
      fetchPRs();
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setLoading(false);
    }
  };

  const stats = {
    pending: prs.filter((p) => p.state === "open").length,
    passed: prs.filter((p) => p.state === "merged").length,
    rejected: prs.filter((p) => p.state === "closed").length,
    totalIssues: prs.reduce((s, p) => s + p.issues_count, 0),
  };

  return (
    <div className="h-full overflow-y-auto p-8">
      {error && (
        <div className="mb-4 px-4 py-2 bg-red-50 text-red-700 text-sm flex justify-between items-center rounded-xl border border-red-200">
          {error}
          <button className="font-bold text-red-400 hover:text-red-600" onClick={() => setError("")}>✕</button>
        </div>
      )}

      {view === "dashboard" && (
        <>
          <h2 className="text-xl font-extrabold mb-6">Overview</h2>

          <div className="grid grid-cols-4 gap-5 mb-8">
            {[
              { label: "Open PRs", value: stats.pending, color: "text-yellow-600", bg: "bg-yellow-50" },
              { label: "Merged", value: stats.passed, color: "text-green-600", bg: "bg-green-50" },
              { label: "Rejected", value: stats.rejected, color: "text-red-600", bg: "bg-red-50" },
              { label: "Total Issues", value: stats.totalIssues, color: "text-magenta", bg: "bg-magenta-50" },
            ].map((s) => (
              <div key={s.label} className={`${s.bg} p-6 border border-border rounded-2xl shadow-[0_4px_16px_rgba(0,0,0,0.04)]`}>
                <div className={`text-3xl font-extrabold ${s.color}`}>{s.value}</div>
                <div className="text-sm font-bold text-txt-muted mt-2">{s.label}</div>
              </div>
            ))}
          </div>

          <div className="mt-8 mb-8">
            <ProblemTrendChart history={trendHistory} loading={trendLoading} />
          </div>

          <h3 className="text-lg font-extrabold mb-4">Recent Pull Requests</h3>
          <PRTable prs={prs.slice(0, 8)} loading={loading} onView={(pr) => openPRDetail(pr.id)} />
        </>
      )}

      {view === "pr-list" && (
        <>
          <div className="flex items-center justify-between mb-6">
            <h2 className="text-xl font-extrabold">Pull Request List</h2>
            <div className="flex gap-2">
              {(["all", "open", "merged", "closed"] as const).map((f) => (
                <button
                  key={f}
                  onClick={() => setFilter(f)}
                  className={`px-3 py-1.5 text-xs font-bold border rounded-lg transition-colors ${
                    filter === f
                      ? "border-magenta text-magenta bg-magenta-50"
                      : "border-border text-txt-muted hover:border-magenta"
                  }`}
                >
                  {f === "all" ? "All" : f === "open" ? "Open" : f === "merged" ? "Merged" : "Closed"}
                </button>
              ))}
              <button className="btn-outline text-xs px-3 py-1.5" onClick={fetchPRs}>
                Refresh
              </button>
            </div>
          </div>
          <PRTable prs={prs} loading={loading} onView={(pr) => openPRDetail(pr.id)} />
        </>
      )}

      {view === "settings" && (
        <div className="max-w-xl">
          <h2 className="text-xl font-extrabold mb-6">Settings</h2>
          <div className="card mb-4">
            <h3 className="font-bold mb-2">Webhook Configuration</h3>
            <p className="text-sm text-txt-muted mb-3">Add this URL to your Gitea repository webhook settings:</p>
            <code className="block bg-surface-muted p-3 text-sm font-mono break-all rounded-lg">
              POST {window.location.origin}/quality-api/webhook/gitea
            </code>
          </div>
          <div className="card">
            <h3 className="font-bold mb-2">Quick Notes</h3>
            <ul className="text-sm text-txt-muted list-disc list-inside space-y-1">
              <li>Supports Gitea Push and Pull Request events</li>
              <li>Runs Pylint, Flake8, ESLint, and Bandit automatically</li>
              <li>Optional AI review using DeepSeek-V3</li>
              <li>Scan summary can be pushed to Feishu channels</li>
            </ul>
          </div>
        </div>
      )}

      {modalOpen && selectedPR && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="bg-white w-[92vw] h-[88vh] flex flex-col rounded-2xl overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.2)]">
            <div className="flex items-center justify-between px-6 py-4 border-b border-border shrink-0">
              <div>
                <h2 className="text-lg font-extrabold">
                  #{selectedPR.pr_number} {selectedPR.pr_title}
                </h2>
                <p className="text-xs text-txt-muted mt-1">
                  {selectedPR.author} · {selectedPR.source_branch} → {selectedPR.target_branch} · {selectedPR.repo_name}
                </p>
              </div>
              <button
                className="w-8 h-8 flex items-center justify-center text-txt-muted hover:text-txt font-bold text-lg"
                onClick={() => setModalOpen(false)}
              >
                ✕
              </button>
            </div>

            <div className="flex flex-1 min-h-0">
              <div className="w-[260px] shrink-0 border-r border-border overflow-y-auto p-4 bg-surface-muted/35">
                <h3 className="text-xs font-bold text-txt-muted uppercase mb-3">Changed Files</h3>
                {changedFiles.map((f) => (
                  <button
                    key={f.filename}
                    className={`block w-full text-left px-3 py-2 text-sm truncate transition-colors rounded-lg ${
                      selectedFile === f.filename
                        ? "bg-magenta-50 text-magenta font-bold"
                        : "text-txt hover:bg-surface-muted"
                    }`}
                    onClick={() => setSelectedFile(f.filename)}
                  >
                    <span className={`inline-block w-4 mr-1 text-xs font-bold ${
                      f.status === "added" ? "text-green-600" : f.status === "removed" ? "text-red-600" : "text-yellow-600"
                    }`}>
                      {f.status === "added" ? "A" : f.status === "removed" ? "D" : "M"}
                    </span>
                    {f.filename.split("/").pop()}
                    <span className="text-xs text-txt-muted ml-1 block truncate">{f.filename}</span>
                  </button>
                ))}
              </div>

              {/* 代码区：完整代码 + 每行右侧缺陷标注，一行一个滚动条（参考 index copy.html） */}
              <div className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
                <div className="flex-1 min-h-0 overflow-auto bg-[#1e1e1e] font-mono text-[13px] leading-[1.5]">
                  {fileContent ? (
                    <div className="min-w-min">
                      {(fileContent.content ?? "").split("\n").map((line, i) => {
                        const lineNum = i + 1;
                        const lineIssues = (fileContent.issues ?? []).filter((iss) => iss.line === lineNum);
                        const hasIssue = lineIssues.length > 0;
                        const reasonText = hasIssue
                          ? lineIssues.map((iss) => (iss.scanner ? `[${iss.scanner}] ` : "") + (iss.message || "")).join("；")
                          : "";
                        const displayText = line === "" ? "\u00A0" : line;
                        return (
                          <div
                            key={i}
                            className={`flex min-h-[1.5em] items-stretch ${hasIssue ? "code-line-has-issue" : ""}`}
                          >
                            {/* 行号区 */}
                            <div className="w-12 min-w-[48px] shrink-0 flex items-center justify-end pr-2 text-[#6c757d] bg-[#252526] select-none">
                              <span className="mr-1">{lineNum}</span>
                              {hasIssue && (
                                <span
                                  className={
                                    lineIssues[0]?.severity === "error" || lineIssues[0]?.severity === "high"
                                      ? "text-[#f14c4c]"
                                      : "text-[#cca700]"
                                  }
                                  title={reasonText}
                                >
                                  ●
                                </span>
                              )}
                            </div>
                            {/* 代码内容 */}
                            <div
                              className={`flex-1 min-w-0 px-3 py-0 whitespace-pre-wrap break-all text-[#d4d4d4] ${
                                hasIssue ? "bg-red-900/20 border-l-2 border-l-red-500 pl-2" : ""
                              }`}
                            >
                              {displayText}
                            </div>
                            {/* 虚线连接区 */}
                            <div className="w-5 min-w-[20px] shrink-0 bg-[#1e1e1e] relative">
                              {hasIssue && (
                                <span
                                  className="absolute left-0 right-0 top-1/2 -mt-px block border-b border-dashed border-red-500/80"
                                  aria-hidden
                                />
                              )}
                            </div>
                            {/* 缺陷标注（与该行对齐，在右侧） */}
                            <div
                              className={`w-[180px] min-w-[180px] shrink-0 py-1.5 px-2 text-[11px] border-l flex items-center ${
                                hasIssue
                                  ? "bg-red-50/90 border-l-2 border-l-red-500 text-red-800"
                                  : "bg-[#252526] border-[#3c3c3c] text-[#9d9d9d]"
                              }`}
                            >
                              {hasIssue && reasonText ? (
                                <>
                                  <span className="text-red-500 mr-1.5 shrink-0">⚠</span>
                                  <span className="break-words leading-snug">{reasonText}</span>
                                </>
                              ) : (
                                <span className="invisible">-</span>
                              )}
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  ) : (
                    <div className="flex items-center justify-center h-full min-h-[200px] text-[#6c757d] text-sm">
                      {selectedFile ? "Loading file..." : "Select a file to inspect"}
                    </div>
                  )}
                </div>
              </div>
            </div>

            <div className="flex justify-end gap-3 px-6 py-4 border-t border-border shrink-0">
              <button className="btn-outline" onClick={() => setModalOpen(false)}>
                Close
              </button>
              <button
                className="bg-red-600 text-white font-bold text-sm px-6 py-2.5 border-none cursor-pointer hover:opacity-90 disabled:opacity-50 rounded-xl"
                onClick={handleClose}
                disabled={loading || selectedPR.state !== "open"}
              >
                Reject
              </button>
              <button
                className="btn-magenta"
                onClick={handleMerge}
                disabled={loading || selectedPR.state !== "open"}
              >
                Approve & Merge
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function PRTable({
  prs,
  loading,
  onView,
}: {
  prs: PRScan[];
  loading: boolean;
  onView: (pr: PRScan) => void;
}) {
  if (loading && prs.length === 0) {
    return <div className="text-center py-8 text-txt-muted text-sm">Loading...</div>;
  }

  if (prs.length === 0) {
    return <div className="text-center py-8 text-txt-muted text-sm">No PR records found</div>;
  }

  return (
    <div className="overflow-x-auto rounded-2xl border border-border bg-white shadow-[0_6px_22px_rgba(0,0,0,0.04)]">
      <table className="w-full text-sm">
        <thead>
          <tr className="bg-surface-muted text-left">
            <th className="px-4 py-3 border-b border-border font-bold">PR#</th>
            <th className="px-4 py-3 border-b border-border font-bold">Title</th>
            <th className="px-4 py-3 border-b border-border font-bold">Repository</th>
            <th className="px-4 py-3 border-b border-border font-bold">Author</th>
            <th className="px-4 py-3 border-b border-border font-bold">Branch</th>
            <th className="px-4 py-3 border-b border-border font-bold">State</th>
            <th className="px-4 py-3 border-b border-border font-bold">Issues</th>
            <th className="px-4 py-3 border-b border-border font-bold">Created</th>
            <th className="px-4 py-3 border-b border-border font-bold">Action</th>
          </tr>
        </thead>
        <tbody>
          {prs.map((pr) => (
            <tr key={pr.id} className="border-b border-border hover:bg-surface-muted/50 transition-colors">
              <td className="px-4 py-3 font-mono text-xs">#{pr.pr_number}</td>
              <td className="px-4 py-3 font-bold max-w-[220px] truncate">{pr.pr_title}</td>
              <td className="px-4 py-3 text-txt-muted text-xs">{pr.repo_name}</td>
              <td className="px-4 py-3 text-txt-muted">{pr.author}</td>
              <td className="px-4 py-3 text-xs font-mono text-txt-muted">
                {pr.source_branch} → {pr.target_branch}
              </td>
              <td className="px-4 py-3">
                <span className={`badge ${
                  pr.state === "open"
                    ? "bg-yellow-100 text-yellow-700"
                    : pr.state === "merged"
                      ? "bg-green-100 text-green-700"
                      : "bg-red-100 text-red-700"
                }`}>
                  {pr.state === "open" ? "Open" : pr.state === "merged" ? "Merged" : "Closed"}
                </span>
              </td>
              <td className="px-4 py-3">
                <span className={`font-bold ${pr.issues_count > 0 ? "text-red-600" : "text-green-600"}`}>
                  {pr.issues_count}
                </span>
              </td>
              <td className="px-4 py-3 text-xs text-txt-muted">
                {new Date(pr.created_at).toLocaleString("en-US")}
              </td>
              <td className="px-4 py-3">
                <button
                  className="text-magenta font-bold text-xs hover:underline"
                  onClick={() => onView(pr)}
                >
                  Inspect →
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
