'use client';

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import {
  AlertCircle,
  BarChart3,
  CheckCircle2,
  ChevronDown,
  ClipboardList,
  Download,
  FileCode2,
  FileText,
  GitCompare,
  Info,
  ListChecks,
  Loader2,
  Puzzle,
  RefreshCw,
  Rocket,
  Send,
  ShieldCheck,
  ShieldAlert,
  Trash2,
  Upload,
  X,
  XCircle,
} from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

import {
  deleteSkill,
  disablePublishedSkill,
  downloadSkill,
  getSkillDetail,
  getSkillFile,
  getSkillVersion,
  listSkillCandidates,
  listSkillDrafts,
  listSkills,
  publishSkillDraft,
  recheckSkillDraftSafety,
  regenerateSkillDraft,
  rejectSkillDraft,
  rollbackPublishedSkill,
  submitSkillDraft,
  synthesizeSkillDraft,
  uploadSkill,
} from '@/lib/api';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { SkillDetailView } from '@/components/skills/SkillDetailView';
import type {
  Skill,
  SkillDetailResponse,
  SkillDraft,
  SkillDraftEvalReport,
  SkillDraftSafetyReport,
  SkillFileContent,
  SkillLearningCandidate,
} from '@/types';
import { pickAppText } from '@/lib/i18n/core';
import { useAppI18n } from '@/lib/i18n/provider';
import { containedJsonTextClass, containedLongTextClass } from '@/lib/text-wrapping';

const TERMINAL_DRAFT_STATUSES = new Set(['rejected', 'published', 'disabled', 'archived']);
const REJECTABLE_DRAFT_STATUSES = new Set(['draft', 'in_review', 'approved']);
type SkillsTab = 'published' | 'candidates' | 'drafts';

function normalizeSkillsTab(value: string | null | undefined): SkillsTab {
  if (value === 'candidates' || value === 'drafts') {
    return value;
  }
  return 'published';
}

export default function SkillsPage() {
  const { locale } = useAppI18n();
  const pathname = usePathname();
  const router = useRouter();
  const searchParams = useSearchParams();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  const [skills, setSkills] = useState<Skill[]>([]);
  const [candidates, setCandidates] = useState<SkillLearningCandidate[]>([]);
  const [drafts, setDrafts] = useState<SkillDraft[]>([]);
  const [activeTab, setActiveTab] = useState<SkillsTab>(() => normalizeSkillsTab(searchParams?.get('tab')));
  const [loading, setLoading] = useState(true);
  const [actionId, setActionId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [showUpload, setShowUpload] = useState(false);
  const [ignoredCandidates, setIgnoredCandidates] = useState<Set<string>>(new Set());
  const [selectedSkillName, setSelectedSkillName] = useState<string | null>(null);
  const [skillDetail, setSkillDetail] = useState<SkillDetailResponse | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const [versionLoading, setVersionLoading] = useState(false);
  const [selectedFile, setSelectedFile] = useState<SkillFileContent | null>(null);
  const [fileLoading, setFileLoading] = useState(false);

  const load = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const [skillData, candidateData, draftData] = await Promise.all([
        listSkills(),
        listSkillCandidates().catch(() => []),
        listSkillDrafts().catch(() => []),
      ]);
      setSkills(Array.isArray(skillData) ? skillData : []);
      setCandidates(Array.isArray(candidateData) ? candidateData : []);
      setDrafts(Array.isArray(draftData) ? draftData : []);
    } catch (err: any) {
      setError(err.message || pickAppText(locale, '加载技能失败', 'Failed to load skills'));
    } finally {
      setLoading(false);
    }
  }, [locale]);

  useEffect(() => {
    void load();
  }, [load]);

  useEffect(() => {
    setActiveTab(normalizeSkillsTab(searchParams?.get('tab')));
  }, [searchParams]);

  const changeTab = (value: string) => {
    const nextTab = normalizeSkillsTab(value);
    setActiveTab(nextTab);
    const nextParams = new URLSearchParams(searchParams?.toString());
    if (nextTab === 'published') {
      nextParams.delete('tab');
    } else {
      nextParams.set('tab', nextTab);
    }
    const query = nextParams.toString();
    router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false });
  };

  const runAction = async (id: string, action: () => Promise<unknown>) => {
    setActionId(id);
    setError(null);
    try {
      await action();
      await load();
    } catch (err: any) {
      setError(err.message || t('操作失败', 'Action failed'));
    } finally {
      setActionId(null);
    }
  };

  const openSkillDetail = async (name: string) => {
    setSelectedSkillName(name);
    setSkillDetail(null);
    setSelectedFile(null);
    setDetailLoading(true);
    setError(null);
    try {
      setSkillDetail(await getSkillDetail(name));
    } catch (err: any) {
      setError(err.message || t('加载技能详情失败', 'Failed to load skill details'));
      setSelectedSkillName(null);
    } finally {
      setDetailLoading(false);
    }
  };

  const openSkillVersion = async (version: string) => {
    if (!selectedSkillName || skillDetail?.currentVersion === version) return;
    setVersionLoading(true);
    setSelectedFile(null);
    setError(null);
    try {
      setSkillDetail(await getSkillVersion(selectedSkillName, version));
    } catch (err: any) {
      setError(err.message || t('加载技能版本失败', 'Failed to load skill version'));
    } finally {
      setVersionLoading(false);
    }
  };

  const openSkillFile = async (filePath: string) => {
    if (!selectedSkillName || !skillDetail) return;
    setFileLoading(true);
    setError(null);
    try {
      setSelectedFile(await getSkillFile(selectedSkillName, skillDetail.currentVersion, filePath));
    } catch (err: any) {
      setError(err.message || t('加载文件失败', 'Failed to load file'));
    } finally {
      setFileLoading(false);
    }
  };

  const hiddenCandidateStatuses = new Set(['draft_ready', 'in_review', 'approved', 'rejected', 'superseded', 'published']);
  const visibleCandidates = candidates.filter(
    (candidate) =>
      !ignoredCandidates.has(candidate.candidate_id) &&
      !hiddenCandidateStatuses.has(candidate.status) &&
      !candidate.draft_id
  );
  const visibleDrafts = drafts.filter((draft) => !TERMINAL_DRAFT_STATUSES.has(draft.status));

  if (loading) {
    return (
      <div className="flex items-center justify-center py-20">
        <Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
      </div>
    );
  }

  return (
    <div className="mx-auto w-full max-w-6xl space-y-6 overflow-x-hidden bg-white px-4 py-6 text-black [--background:0_0%_100%] [--card:0_0%_100%] [--card-foreground:0_0%_0%] [--foreground:0_0%_0%] [--muted-foreground:0_0%_0%] [--popover:0_0%_100%] [--popover-foreground:0_0%_0%] [--secondary-foreground:0_0%_0%] sm:px-6">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <h1 className="flex items-center gap-2 text-2xl font-bold">
          <Puzzle className="w-6 h-6" />
          {t('技能', 'Skills')}
        </h1>
        <div className="flex flex-wrap items-center gap-2">
          <Button onClick={() => void load()} variant="outline" size="sm" className="h-11">
            <RefreshCw className="mr-2 h-4 w-4" />
            {t('刷新', 'Refresh')}
          </Button>
          <Button onClick={() => setShowUpload(true)} size="sm" className="h-11">
            <Upload className="mr-2 h-4 w-4" />
            {t('上传技能', 'Upload skill')}
          </Button>
        </div>
      </div>

      {error && (
        <Card className="border-destructive">
          <CardContent className="pt-6">
            <div className="flex items-center gap-2 text-sm text-destructive">
              <AlertCircle className="h-4 w-4" />
              {error}
            </div>
          </CardContent>
        </Card>
      )}

      {showUpload && (
        <UploadSkillForm
          onDone={() => {
            setShowUpload(false);
            void load();
          }}
          onCancel={() => setShowUpload(false)}
          onError={(msg) => setError(msg)}
        />
      )}

      {selectedSkillName && (
        <div className="space-y-4">
          <Button
            variant="ghost"
            size="sm"
            className="h-11"
            onClick={() => {
              setSelectedSkillName(null);
              setSkillDetail(null);
              setSelectedFile(null);
            }}
          >
            {t('返回技能列表', 'Back to skills')}
          </Button>
          {detailLoading || !skillDetail ? (
            <Card>
              <CardContent className="flex justify-center py-16">
                <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
              </CardContent>
            </Card>
          ) : (
            <SkillDetailView
              title={skillDetail.skill.name}
              summary={skillDetail.skill.description}
              currentVersion={skillDetail.currentVersion}
              versions={skillDetail.versions}
              files={skillDetail.files}
              content={skillDetail.content}
              selectedFile={selectedFile}
              loadingFile={fileLoading}
              loadingVersion={versionLoading}
              onSelectVersion={(version) => void openSkillVersion(version)}
              onOpenFile={(filePath) => void openSkillFile(filePath)}
              badges={
                <>
                  <Badge variant={skillDetail.skill.source === 'builtin' ? 'secondary' : 'default'} className="text-xs">
                    {skillDetail.skill.source === 'builtin' ? t('内置', 'Built in') : t('工作区', 'Workspace')}
                  </Badge>
                  <Badge variant={skillDetail.skill.available ? 'default' : 'outline'} className="text-xs">
                    {skillDetail.skill.available ? t('可用', 'Available') : t('不可用', 'Unavailable')}
                  </Badge>
                </>
              }
              actions={
                <div className="flex flex-wrap gap-2">
                  <Button variant="outline" size="sm" className="h-11" onClick={() => downloadSkill(skillDetail.skill.name).catch((err) => setError(err.message))}>
                    <Download className="mr-2 h-4 w-4" />
                    {t('下载', 'Download')}
                  </Button>
                  {skillDetail.skill.source === 'workspace' && (
                    <>
                      <Button variant="outline" size="sm" className="h-11" onClick={() => onRollbackSkill(skillDetail.skill.name)}>
                        <RefreshCw className="mr-2 h-4 w-4" />
                        {t('回滚', 'Rollback')}
                      </Button>
                      <Button
                        variant="outline"
                        size="sm"
                        className="h-11"
                        disabled={Boolean(actionId)}
                        onClick={() => void runAction(`disable:${skillDetail.skill.name}`, () => disablePublishedSkill(skillDetail.skill.name, t('人工禁用', 'Manual disable')))}
                      >
                        <ShieldCheck className="mr-2 h-4 w-4" />
                        {t('禁用', 'Disable')}
                      </Button>
                      <Button
                        variant="outline"
                        size="sm"
                        className="h-11 text-destructive hover:text-destructive"
                        disabled={Boolean(actionId)}
                        onClick={() => void runAction(`delete:${skillDetail.skill.name}`, () => deleteSkill(skillDetail.skill.name)).then(() => {
                          setSelectedSkillName(null);
                          setSkillDetail(null);
                        })}
                      >
                        <Trash2 className="mr-2 h-4 w-4" />
                        {t('删除', 'Delete')}
                      </Button>
                    </>
                  )}
                </div>
              }
              labels={{
                overview: t('说明', 'Overview'),
                files: t('文件', 'Files'),
                versions: t('版本', 'Versions'),
                noReadme: t('暂无说明', 'No overview available'),
                noFiles: t('暂无文件', 'No files'),
                selectFile: t('选择一个文件查看详情', 'Select a file to view details'),
                binaryFile: t('二进制文件暂不预览', 'Binary file preview is not available'),
                current: t('当前', 'Current'),
                size: t('大小', 'Size'),
              }}
            />
          )}
        </div>
      )}

      {!selectedSkillName && (
        <Tabs value={activeTab} onValueChange={changeTab} className="min-w-0 space-y-4">
        <TabsList className="h-auto min-h-11 w-full max-w-full justify-start overflow-x-auto sm:w-auto">
          <TabsTrigger value="published" className="h-10">{t('已发布', 'Published')}</TabsTrigger>
          <TabsTrigger value="candidates" className="h-10">{t('候选', 'Candidates')}</TabsTrigger>
          <TabsTrigger value="drafts" className="h-10">{t('草稿评审', 'Draft review')}</TabsTrigger>
        </TabsList>

        <TabsContent value="published" className="min-w-0">
          <PublishedSkillsTable
            skills={skills}
            onOpen={(name) => void openSkillDetail(name)}
            onDownload={(name) => downloadSkill(name).catch((err) => setError(err.message))}
            onDelete={(name) => void runAction(`delete:${name}`, () => deleteSkill(name))}
            onDisable={(name) =>
              runAction(`disable:${name}`, () => disablePublishedSkill(name, t('人工禁用', 'Manual disable')))
            }
            onRollback={onRollbackSkill}
          />
        </TabsContent>

        <TabsContent value="candidates" className="min-w-0">
          <Card>
            <CardHeader>
              <CardTitle className="text-base">{t('学习候选', 'Learning candidates')}</CardTitle>
            </CardHeader>
            <CardContent>
              {visibleCandidates.length === 0 ? (
                <EmptyState icon={<FileText className="h-8 w-8" />} text={t('暂无学习候选', 'No learning candidates yet')} />
              ) : (
                <div className="space-y-3">
                  {visibleCandidates.map((candidate) => (
                    <CandidateCard
                      key={candidate.candidate_id}
                      candidate={candidate}
                      actionId={actionId}
                      onIgnore={() => setIgnoredCandidates((prev) => new Set(prev).add(candidate.candidate_id))}
                      onSynthesize={() =>
                        runAction(`draft:${candidate.candidate_id}`, () =>
                          synthesizeSkillDraft(candidate.candidate_id)
                        )
                      }
                      onRegenerate={() =>
                        runAction(`regen:${candidate.candidate_id}`, () =>
                          regenerateSkillDraft(candidate.candidate_id)
                        )
                      }
                    />
                  ))}
                </div>
              )}
            </CardContent>
          </Card>
        </TabsContent>

        <TabsContent value="drafts" className="min-w-0">
          <Card>
            <CardHeader>
              <CardTitle className="text-base">{t('草稿、评审与发布', 'Drafts, review, and publish')}</CardTitle>
            </CardHeader>
            <CardContent>
              {visibleDrafts.length === 0 ? (
                <EmptyState icon={<FileText className="h-8 w-8" />} text={t('暂无草稿', 'No drafts yet')} />
              ) : (
                <div className="space-y-4">
                  {visibleDrafts.map((draft) => (
                    <DraftCard
                      key={`${draft.skill_name}:${draft.draft_id}`}
                      draft={draft}
                      actionId={actionId}
                      onSubmit={() =>
                        runAction(`submit:${draft.draft_id}`, () =>
                          submitSkillDraft(draft.skill_name, draft.draft_id)
                        )
                      }
                      onReject={() =>
                        runAction(`reject:${draft.draft_id}`, () =>
                          rejectSkillDraft(draft.skill_name, draft.draft_id)
                        )
                      }
                      onRecheckSafety={() =>
                        runAction(`safety:${draft.draft_id}`, () =>
                          recheckSkillDraftSafety(draft.skill_name, draft.draft_id)
                        )
                      }
                      onPublish={(confirmHighRisk) =>
                        runAction(`publish:${draft.draft_id}`, () =>
                          publishSkillDraft(draft.skill_name, draft.draft_id, '', confirmHighRisk)
                        )
                      }
                    />
                  ))}
                </div>
              )}
            </CardContent>
          </Card>
        </TabsContent>
        </Tabs>
      )}
    </div>
  );

  function onRollbackSkill(name: string) {
    const target = window.prompt(t('回滚到版本，例如 v0001', 'Rollback target version, for example v0001'));
    if (target) {
      void runAction(`rollback:${name}`, () =>
        rollbackPublishedSkill(name, target, t('人工回滚', 'Manual rollback'))
      ).then(() => {
        if (selectedSkillName === name) {
          void openSkillDetail(name);
        }
      });
    }
  }
}

function PublishedSkillsTable({
  skills,
  onOpen,
  onDownload,
  onDelete,
  onDisable,
  onRollback,
}: {
  skills: Skill[];
  onOpen: (name: string) => void;
  onDownload: (name: string) => void;
  onDelete: (name: string) => void;
  onDisable: (name: string) => void;
  onRollback: (name: string) => void;
}) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  return (
    <Card>
      <CardContent className="p-0">
        {skills.length === 0 ? (
          <EmptyState icon={<Puzzle className="h-8 w-8" />} text={t('暂无技能', 'No skills yet')} />
        ) : (
          <>
          <div className="space-y-3 p-3 md:hidden">
            {skills.map((skill) => (
              <div key={`${skill.source}:${skill.name}:card`} className="min-w-0 rounded-lg border border-border bg-white p-4">
                <button
                  type="button"
                  className="block min-h-11 w-full text-left"
                  onClick={() => onOpen(skill.name)}
                >
                  <div className={`text-sm font-semibold ${containedLongTextClass}`}>{skill.name}</div>
                  <div className={`mt-1 text-sm leading-5 text-muted-foreground ${containedLongTextClass}`}>
                    {skill.description || '-'}
                  </div>
                </button>
                <div className="mt-3 flex flex-wrap gap-2">
                  <Badge variant={skill.source === 'builtin' ? 'secondary' : 'default'} className="text-xs">
                    {skill.source === 'builtin' ? t('内置', 'Built in') : t('工作区', 'Workspace')}
                  </Badge>
                  <Badge variant={skill.available ? 'default' : 'outline'} className="text-xs">
                    {skill.available ? t('可用', 'Available') : t('不可用', 'Unavailable')}
                  </Badge>
                </div>
                <div className="mt-3 flex flex-wrap gap-2">
                  <Button variant="outline" size="sm" className="h-11" onClick={() => onDownload(skill.name)}>
                    <Download className="mr-2 h-4 w-4" />
                    {t('下载', 'Download')}
                  </Button>
                  {skill.source === 'workspace' && (
                    <>
                      <Button variant="outline" size="sm" className="h-11" onClick={() => onRollback(skill.name)}>
                        <RefreshCw className="mr-2 h-4 w-4" />
                        {t('回滚', 'Rollback')}
                      </Button>
                      <Button variant="outline" size="sm" className="h-11" onClick={() => onDisable(skill.name)}>
                        <ShieldCheck className="mr-2 h-4 w-4" />
                        {t('禁用', 'Disable')}
                      </Button>
                      <Button variant="outline" size="sm" className="h-11 text-destructive hover:text-destructive" onClick={() => onDelete(skill.name)}>
                        <Trash2 className="mr-2 h-4 w-4" />
                        {t('删除', 'Delete')}
                      </Button>
                    </>
                  )}
                </div>
              </div>
            ))}
          </div>
          <div className="hidden md:block">
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>{t('名称', 'Name')}</TableHead>
                <TableHead>{t('描述', 'Description')}</TableHead>
                <TableHead>{t('来源', 'Source')}</TableHead>
                <TableHead>{t('状态', 'Status')}</TableHead>
                <TableHead className="w-24">{t('操作', 'Actions')}</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {skills.map((skill) => (
                <TableRow
                  key={`${skill.source}:${skill.name}`}
                  className="cursor-pointer"
                  onClick={() => onOpen(skill.name)}
                >
                  <TableCell className={`font-medium ${containedLongTextClass}`}>{skill.name}</TableCell>
                  <TableCell>
                    <span className="block max-w-[360px] truncate text-sm text-muted-foreground">
                      {skill.description}
                    </span>
                  </TableCell>
                  <TableCell>
                    <Badge variant={skill.source === 'builtin' ? 'secondary' : 'default'} className="text-xs">
                      {skill.source === 'builtin' ? t('内置', 'Built in') : t('工作区', 'Workspace')}
                    </Badge>
                  </TableCell>
                  <TableCell>
                    <Badge variant={skill.available ? 'default' : 'outline'} className="text-xs">
                      {skill.available ? t('可用', 'Available') : t('不可用', 'Unavailable')}
                    </Badge>
                  </TableCell>
                  <TableCell>
                    <div className="flex items-center gap-1">
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-11 w-11"
                        aria-label={t('下载', 'Download')}
                        title={t('下载', 'Download')}
                        onClick={(event) => { event.stopPropagation(); onDownload(skill.name); }}
                      >
                        <Download className="h-3.5 w-3.5" />
                      </Button>
                      {skill.source === 'workspace' && (
                        <>
                          <Button
                            variant="ghost"
                            size="icon"
                            className="h-11 w-11"
                            aria-label={t('回滚', 'Rollback')}
                            title={t('回滚', 'Rollback')}
                            onClick={(event) => {
                              event.stopPropagation();
                              onRollback(skill.name);
                            }}
                          >
                            <RefreshCw className="h-3.5 w-3.5" />
                          </Button>
                          <Button
                            variant="ghost"
                            size="icon"
                            className="h-11 w-11"
                            aria-label={t('禁用', 'Disable')}
                            title={t('禁用', 'Disable')}
                            onClick={(event) => {
                              event.stopPropagation();
                              onDisable(skill.name);
                            }}
                          >
                            <ShieldCheck className="h-3.5 w-3.5" />
                          </Button>
                          <Button
                            variant="ghost"
                            size="icon"
                            className="h-11 w-11 text-destructive hover:text-destructive"
                            aria-label={t('删除', 'Delete')}
                            title={t('删除', 'Delete')}
                            onClick={(event) => {
                              event.stopPropagation();
                              onDelete(skill.name);
                            }}
                          >
                            <Trash2 className="h-3.5 w-3.5" />
                          </Button>
                        </>
                      )}
                    </div>
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
          </div>
          </>
        )}
      </CardContent>
    </Card>
  );
}

function CandidateCard({
  candidate,
  actionId,
  onIgnore,
  onSynthesize,
  onRegenerate,
}: {
  candidate: SkillLearningCandidate;
  actionId: string | null;
  onIgnore: () => void;
  onSynthesize: () => Promise<unknown>;
  onRegenerate: () => Promise<unknown>;
}) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  const evidence = candidate.evidence || {};
  const title = candidateTitle(candidate, t);
  const affectedSkills = candidate.draft_skill_name
    ? [candidate.draft_skill_name]
    : candidate.related_skill_names.length > 0
      ? candidate.related_skill_names
      : [];
  const sourceRuns = candidate.source_run_ids || [];
  const sourceSessions = candidate.source_session_ids || [];
  const risk = candidate.risk_level || 'medium';
  const confidence = typeof candidate.confidence === 'number' && candidate.confidence > 0
    ? `${Math.round(candidate.confidence * 100)}%`
    : null;

  return (
    <div className="min-w-0 max-w-full rounded-lg border border-border bg-white p-4">
      <div className="flex flex-wrap items-start justify-between gap-4">
        <div className="min-w-0 flex-1 space-y-3">
          <div className="flex flex-wrap items-center gap-2">
            <Badge variant="outline">{candidateKindLabel(candidate.kind, t)}</Badge>
            <Badge variant="secondary">{candidateStatusLabel(candidate.status, t)}</Badge>
            <Badge variant={risk === 'critical' || risk === 'high' ? 'destructive' : 'outline'}>
              {t('风险', 'Risk')}: {riskLabel(risk, t)}
            </Badge>
            {confidence && <Badge variant="outline">{t('置信度', 'Confidence')}: {confidence}</Badge>}
            {typeof candidate.priority === 'number' && candidate.priority > 0 && (
              <Badge variant="outline">{t('优先级', 'Priority')}: {candidate.priority}</Badge>
            )}
          </div>

          <div>
            <h3 className="break-words text-base font-semibold tracking-normal">{title}</h3>
            <p className={`mt-1 text-sm leading-6 text-muted-foreground ${containedLongTextClass}`}>
              {candidate.reason || t('没有提供候选理由。', 'No candidate reason was provided.')}
            </p>
          </div>

          <div className="grid gap-3 md:grid-cols-3">
            <ReadableFact
              icon={<ClipboardList className="h-4 w-4" />}
              label={t('候选任务', 'Candidate task')}
              value={candidateTaskSummary(candidate, t)}
            />
            <ReadableFact
              icon={<GitCompare className="h-4 w-4" />}
              label={t('影响范围', 'Impact')}
              value={affectedSkills.length > 0 ? affectedSkills.join(', ') : t('新增技能', 'New skill')}
            />
            <ReadableFact
              icon={<ListChecks className="h-4 w-4" />}
              label={t('证据数量', 'Evidence')}
              value={t(
                `${sourceRuns.length} 个运行，${sourceSessions.length} 个会话`,
                `${sourceRuns.length} run(s), ${sourceSessions.length} session(s)`
              )}
            />
          </div>

          {(candidate.evidence_summary || candidate.trigger_reason || candidate.last_error) && (
            <div className="space-y-2 rounded-md border border-border bg-muted/20 p-3 text-sm">
              {candidate.evidence_summary && (
                <p>
                  <span className="font-medium">{t('证据摘要', 'Evidence summary')}:</span>{' '}
                  {candidate.evidence_summary}
                </p>
              )}
              {candidate.trigger_reason && (
                <p>
                  <span className="font-medium">{t('触发原因', 'Trigger')}:</span>{' '}
                  {triggerReasonLabel(candidate.trigger_reason, t)}
                </p>
              )}
              {candidate.last_error && (
                <p className="text-destructive">
                  <span className="font-medium">{t('最近错误', 'Last error')}:</span> {candidate.last_error}
                </p>
              )}
            </div>
          )}

          <div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
            <span className={`font-mono ${containedLongTextClass}`}>{candidate.candidate_id}</span>
            {String(evidence.task_id || '') && <span>{t('任务', 'Task')}: {String(evidence.task_id)}</span>}
            {String(evidence.skill_version || '') && <span>{t('基线版本', 'Base version')}: {String(evidence.skill_version)}</span>}
            {candidate.created_at && <span>{t('创建于', 'Created')}: {formatDateTime(candidate.created_at)}</span>}
          </div>

          <RawDetails title={t('原始候选数据', 'Raw candidate data')} payload={candidate} />
        </div>

        <div className="flex shrink-0 flex-wrap gap-2">
          <Button size="sm" variant="outline" className="h-11" disabled={Boolean(actionId)} onClick={onIgnore}>
            {t('忽略', 'Ignore')}
          </Button>
          <Button
            size="sm"
            className="h-11"
            disabled={Boolean(actionId)}
            onClick={() => void onSynthesize()}
          >
            {actionId === `draft:${candidate.candidate_id}` ? (
              <Loader2 className="mr-2 h-4 w-4 animate-spin" />
            ) : (
              <FileText className="mr-2 h-4 w-4" />
            )}
            {t('生成草稿', 'Synthesize draft')}
          </Button>
          {candidate.draft_id && (
            <Button
              size="sm"
              variant="outline"
              className="h-11"
              disabled={Boolean(actionId)}
              onClick={() => void onRegenerate()}
            >
              <RefreshCw className="mr-2 h-4 w-4" />
              {t('重新生成', 'Regenerate')}
            </Button>
          )}
        </div>
      </div>
    </div>
  );
}

function DraftCard({
  draft,
  actionId,
  onSubmit,
  onReject,
  onRecheckSafety,
  onPublish,
}: {
  draft: SkillDraft;
  actionId: string | null;
  onSubmit: () => Promise<unknown>;
  onReject: () => Promise<unknown>;
  onRecheckSafety: () => Promise<unknown>;
  onPublish: (confirmHighRisk: boolean) => Promise<unknown>;
}) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  const busy = Boolean(actionId);
  const safety = draft.safety_report;
  const evalReport = draft.eval_report;
  const frontmatter = draft.proposed_frontmatter || {};
  const description = String(frontmatter.description || '').trim();
  const toolHints = normalizeStringList(frontmatter.tools);
  const submittedForReview = draft.status === 'in_review' || draft.status === 'approved';
  const isRevision = draft.proposal_kind === 'revise_skill' && Boolean(draft.base_skill);
  const publishBlocked =
    !submittedForReview
    || !safety
    || safety.risk_level === 'critical'
    || (evalReport?.status !== 'skipped_provider_unavailable' && evalReport?.passed === false);
  const isHighRisk = safety?.risk_level === 'high';
  const highRiskReason = [
    ...(safety?.blocked_reasons ?? []),
    ...(safety?.issues ?? []),
    safety?.suggested_fix,
  ].filter(Boolean).join('\n');
  const safetyBlocksReview = Boolean(safety && (!safety.passed || safety.risk_level === 'critical'));
  const submitBlocked = draft.status !== 'draft' || safetyBlocksReview;
  const rejectBlocked = !REJECTABLE_DRAFT_STATUSES.has(draft.status);
  const canPublishLabel = publishBlocked
    ? publishBlockReason(draft, t)
    : isHighRisk
      ? t('高风险草稿，发布前需要再次确认。', 'High-risk draft; publishing requires confirmation.')
      : t('已满足发布门禁。', 'Publish gates are satisfied.');
  const handlePublish = () => {
    if (isHighRisk) {
      const confirmed = window.confirm(
        t('该草稿被标记为高风险。确认发布？', 'This draft is marked high risk. Publish anyway?')
      );
      if (!confirmed) return;
    }
    void onPublish(isHighRisk);
  };
  return (
    <div className="min-w-0 max-w-full rounded-lg border border-border bg-white p-4">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-center gap-2">
            <Badge variant="outline">{candidateKindLabel(draft.proposal_kind, t)}</Badge>
            <Badge variant="secondary">{draftStatusLabel(draft.status, t)}</Badge>
            {safety && (
              <Badge variant={safety.risk_level === 'critical' || safety.risk_level === 'high' ? 'destructive' : 'outline'}>
                {t('安全', 'Safety')}: {safety.passed ? t('通过', 'Pass') : t('阻断', 'Blocked')} · {riskLabel(safety.risk_level, t)}
              </Badge>
            )}
            {evalReport && (
              <Badge variant={evalReport.passed ? 'outline' : 'destructive'}>
                {evalReport.status === 'skipped_provider_unavailable'
                  ? t('评估跳过', 'Eval skipped')
                  : evalReport.passed
                    ? t('评估通过', 'Eval passed')
                    : t('评估失败', 'Eval failed')}
              </Badge>
            )}
          </div>
          <div className="mt-2">
            <div className="text-xs font-medium text-muted-foreground">{t('技能名', 'Skill name')}</div>
            <h3 className={`text-lg font-semibold tracking-normal ${containedLongTextClass}`}>{draft.skill_name}</h3>
          </div>
          <p className={`mt-1 text-sm leading-6 text-muted-foreground ${containedLongTextClass}`}>
            {draft.reason || description || t('没有提供草稿说明。', 'No draft notes were provided.')}
          </p>
          {draft.proposal_kind === 'revise_skill' && draft.base_version && (
            <div className="mt-2 text-sm font-medium text-muted-foreground">
              {draft.skill_name}: {draft.base_version} → {draft.target_version || t('下一版本', 'Next version')}
            </div>
          )}
          <div className="mt-3 grid gap-3 md:grid-cols-4">
            <ReadableFact
              icon={<FileCode2 className="h-4 w-4" />}
              label={t('草稿内容', 'Draft content')}
              value={description || t('未填写技能描述', 'No skill description')}
            />
            <ReadableFact
              icon={<GitCompare className="h-4 w-4" />}
              label={t('基线版本', 'Base version')}
              value={draft.base_version || t('新增技能，无基线', 'New skill, no base')}
            />
            <ReadableFact
              icon={<GitCompare className="h-4 w-4" />}
              label={t('目标版本', 'Target version')}
              value={draft.target_version || '-'}
            />
            <ReadableFact
              icon={<Info className="h-4 w-4" />}
              label={t('来源', 'Source')}
              value={draft.trigger_run_id || draft.trigger_session_id || draft.created_by || '-'}
            />
          </div>
          {isHighRisk && (
            <div className="mt-3 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-xs text-destructive">
              <div className="font-medium">{t('高风险理由来自 safety report', 'High-risk reason from safety report')}</div>
              <pre className="mt-2 whitespace-pre-wrap font-sans">{highRiskReason || t('未提供具体理由', 'No concrete reason provided')}</pre>
            </div>
          )}
          <div className="mt-3 flex flex-wrap gap-2 text-xs text-muted-foreground">
            <span className={`font-mono ${containedLongTextClass}`}>{draft.skill_name}/{draft.draft_id}</span>
            <span>{t('创建者', 'Author')}: {draft.created_by}</span>
            <span>{t('创建于', 'Created')}: {formatDateTime(draft.created_at)}</span>
          </div>
        </div>
        <div className="flex flex-wrap gap-2">
          <Button variant="outline" size="sm" className="h-11" disabled={busy || submitBlocked} onClick={() => void onSubmit()}>
            <Send className="mr-2 h-4 w-4" />
            {t('送审', 'Submit')}
          </Button>
          <Button variant="outline" size="sm" className="h-11" disabled={busy || rejectBlocked} onClick={() => void onReject()}>
            <XCircle className="mr-2 h-4 w-4" />
            {t('拒绝', 'Reject')}
          </Button>
          <Button variant="outline" size="sm" className="h-11" disabled={busy || TERMINAL_DRAFT_STATUSES.has(draft.status)} onClick={() => void onRecheckSafety()}>
            <ShieldCheck className="mr-2 h-4 w-4" />
            {t('复检', 'Recheck')}
          </Button>
          <Button size="sm" className="h-11" disabled={busy || publishBlocked} onClick={handlePublish}>
            <Rocket className="mr-2 h-4 w-4" />
            {draft.proposal_kind === 'revise_skill' ? t('发布修订', 'Publish revision') : t('发布', 'Publish')}
          </Button>
        </div>
      </div>

      <div className="mt-4 grid min-w-0 gap-3 lg:grid-cols-[minmax(0,1fr)_340px]">
        <div className="min-w-0 max-w-full rounded-md border border-border bg-muted/20 p-3 sm:p-4">
          <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
            <div className="flex items-center gap-2 text-sm font-medium">
              <FileText className="h-4 w-4 text-muted-foreground" />
              {isRevision ? t('修改对比', 'Revision comparison') : t('拟发布的技能正文', 'Proposed skill body')}
            </div>
            {toolHints.length > 0 && (
              <div className="flex flex-wrap gap-1">
                {toolHints.map((tool) => (
                  <Badge key={tool} variant="outline" className="font-mono text-[11px]">
                    {tool}
                  </Badge>
                ))}
              </div>
            )}
          </div>
          {isRevision && draft.base_skill ? (
            <RevisionComparison
              baseVersion={draft.base_version || draft.base_skill.version}
              targetVersion={draft.target_version || t('下一版本', 'Next version')}
              baseContent={draft.base_skill.content}
              proposedContent={draft.proposed_content}
            />
          ) : draft.proposed_content.trim() ? (
            <MarkdownPreview content={draft.proposed_content} />
          ) : (
            <p className="text-sm text-muted-foreground">{t('草稿没有正文内容。', 'This draft has no body content.')}</p>
          )}
        </div>

        <div className="min-w-0 space-y-3">
          <GateSummary
            title={t('发布门禁', 'Publish gates')}
            summary={canPublishLabel}
            items={[
              { label: t('草稿已送审', 'Draft submitted'), ok: submittedForReview },
              { label: t('安全报告通过', 'Safety passed'), ok: Boolean(safety?.passed) && safety?.risk_level !== 'critical' },
              {
                label: t('评估未回退', 'No eval regression'),
                ok: !evalReport || evalReport.status === 'skipped_provider_unavailable' || evalReport.passed,
              },
            ]}
          />
          <RawDetails
            title={t('原始草稿内容', 'Raw draft payload')}
            payload={{
              base_skill: draft.base_skill,
              proposed_frontmatter: draft.proposed_frontmatter,
              proposed_content: draft.proposed_content,
              evidence_refs: draft.evidence_refs,
              reviews: draft.reviews,
            }}
          />
        </div>
      </div>

      <div className="mt-3 grid min-w-0 gap-3 md:grid-cols-2">
        <SafetyReportPanel report={safety} />
        <EvalReportPanel report={evalReport} />
      </div>
    </div>
  );
}

function SafetyReportPanel({ report }: { report?: SkillDraftSafetyReport | null }) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  if (!report) {
    return (
      <ReadablePanel
        icon={<ShieldAlert className="h-4 w-4" />}
        title={t('安全报告', 'Safety report')}
        empty={t('还没有安全报告，不能送审或发布。', 'No safety report yet; review and publishing are blocked.')}
      />
    );
  }
  const problems = [...(report.blocked_reasons || []), ...(report.issues || [])];
  return (
    <div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
      <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
        <div className="flex items-center gap-2 text-sm font-medium">
          {report.passed ? <ShieldCheck className="h-4 w-4 text-muted-foreground" /> : <ShieldAlert className="h-4 w-4 text-destructive" />}
          {t('安全报告', 'Safety report')}
        </div>
        <Badge variant={report.passed ? 'outline' : 'destructive'}>
          {report.passed ? t('通过', 'Pass') : t('阻断', 'Blocked')} · {riskLabel(report.risk_level, t)}
        </Badge>
      </div>
      {problems.length > 0 ? (
        <div className="space-y-2">
          <p className="text-xs font-medium text-muted-foreground">{t('发现的问题', 'Findings')}</p>
          <ul className="space-y-1 text-sm leading-6">
            {problems.map((item, index) => (
              <li key={`${item}:${index}`} className="flex gap-2">
                <AlertCircle className="mt-1 h-3.5 w-3.5 shrink-0 text-destructive" />
                <span className={containedLongTextClass}>{item}</span>
              </li>
            ))}
          </ul>
        </div>
      ) : (
        <p className="text-sm text-muted-foreground">
          {t('没有发现阻断项或需要人工注意的问题。', 'No blockers or manual-review issues were found.')}
        </p>
      )}
      {report.suggested_fix && (
        <p className={`mt-3 rounded-md border border-border bg-white p-3 text-sm ${containedLongTextClass}`}>
          <span className="font-medium">{t('建议处理', 'Suggested fix')}:</span> {report.suggested_fix}
        </p>
      )}
      <div className="mt-3 text-xs text-muted-foreground">{formatDateTime(report.created_at)}</div>
      <RawDetails title={t('原始安全报告', 'Raw safety report')} payload={report} />
    </div>
  );
}

function RevisionComparison({
  baseVersion,
  targetVersion,
  baseContent,
  proposedContent,
}: {
  baseVersion: string;
  targetVersion: string;
  baseContent: string;
  proposedContent: string;
}) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  const diff = lineDiffSummary(baseContent, proposedContent);
  return (
    <div className="space-y-3">
      <div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
        <Badge variant="outline">{baseVersion}</Badge>
        <span>→</span>
        <Badge variant="default">{targetVersion}</Badge>
        <span>{t('新增', 'Added')}: {diff.added}</span>
        <span>{t('删除', 'Removed')}: {diff.removed}</span>
        <span>{t('修改', 'Changed')}: {diff.changed}</span>
      </div>
      <div className="grid min-w-0 gap-3 lg:grid-cols-2">
        <DiffPane title={t('当前版本', 'Current version')} content={baseContent} />
        <DiffPane title={t('草稿修订', 'Draft revision')} content={proposedContent} />
      </div>
    </div>
  );
}

function DiffPane({ title, content }: { title: string; content: string }) {
  return (
    <div className="min-w-0 rounded-md border border-border bg-white">
      <div className="border-b border-border px-3 py-2 text-xs font-medium text-muted-foreground">{title}</div>
      <pre className={`max-h-[520px] overflow-auto p-3 text-xs leading-5 ${containedLongTextClass}`}>
        {content.trim() || '-'}
      </pre>
    </div>
  );
}

function lineDiffSummary(baseContent: string, proposedContent: string): { added: number; removed: number; changed: number } {
  const baseLines = baseContent.split(/\r?\n/);
  const proposedLines = proposedContent.split(/\r?\n/);
  const maxLength = Math.max(baseLines.length, proposedLines.length);
  let added = 0;
  let removed = 0;
  let changed = 0;
  for (let index = 0; index < maxLength; index += 1) {
    const baseLine = baseLines[index];
    const proposedLine = proposedLines[index];
    if (baseLine === proposedLine) continue;
    if (baseLine === undefined) {
      added += 1;
    } else if (proposedLine === undefined) {
      removed += 1;
    } else {
      changed += 1;
    }
  }
  return { added, removed, changed };
}

function EvalReportPanel({ report }: { report?: SkillDraftEvalReport | null }) {
  const { locale } = useAppI18n();
  const t = (zh: string, en: string) => pickAppText(locale, zh, en);
  if (!report) {
    return (
      <ReadablePanel
        icon={<BarChart3 className="h-4 w-4" />}
        title={t('评估报告', 'Eval report')}
        empty={t('还没有评估报告。若 provider 不可用，系统会标记为跳过。', 'No eval report yet. If the provider is unavailable, it will be marked as skipped.')}
      />
    );
  }
  if (report.status === 'skipped_provider_unavailable') {
    return (
      <div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
        <div className="mb-2 flex items-center gap-2 text-sm font-medium">
          <BarChart3 className="h-4 w-4 text-muted-foreground" />
          {t('评估报告', 'Eval report')}
        </div>
        <p className="text-sm text-muted-foreground">
          {t('评估被跳过：当前没有可用 provider。这个状态不会阻断发布，但代表没有回放验证。', 'Eval was skipped because no provider was available. This does not block publishing, but no replay validation was run.')}
        </p>
        <RawDetails title={t('原始评估报告', 'Raw eval report')} payload={report} />
      </div>
    );
  }
  const abilitySummary = report.ability_score_summary || {};
  const toolExecutionSummary = report.tool_execution_summary || report.tool_mode_summary || {};
  const caseSelectionSummary = report.case_selection_summary || {};
  const realScore = report.real_score_avg ?? abilitySummary.real_score_avg;
  const syntheticScore = report.synthetic_score_avg ?? abilitySummary.synthetic_score_avg;
  const overallScore = report.overall_score_avg ?? abilitySummary.overall_score_avg ?? report.candidate_score_avg;
  const realCaseCount = toNumber(abilitySummary.real_case_count);
  const syntheticCaseCount = toNumber(abilitySummary.synthetic_case_count);
  const excludedSynthetic = toNumber(caseSelectionSummary.excluded_synthetic_without_validator);
  return (
    <div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
      <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
        <div className="flex items-center gap-2 text-sm font-medium">
          <BarChart3 className="h-4 w-4 text-muted-foreground" />
          {t('评估报告', 'Eval report')}
        </div>
        <Badge variant={report.passed ? 'outline' : 'destructive'}>
          {report.passed ? t('通过', 'Pass') : t('失败', 'Failed')}
        </Badge>
      </div>

      <div className="grid gap-2 sm:grid-cols-3">
        <MetricTile label={t('基线能力均分', 'Baseline ability')} value={formatScore(report.baseline_score_avg)} />
        <MetricTile label={t('候选能力均分', 'Candidate ability')} value={formatScore(report.candidate_score_avg)} />
        <MetricTile
          label={t('变化', 'Delta')}
          value={`${report.score_delta >= 0 ? '+' : ''}${formatScore(report.score_delta)}`}
          tone={report.score_delta < 0 ? 'bad' : report.score_delta > 0 ? 'good' : 'neutral'}
        />
      </div>

      <div className="mt-3 grid gap-2 sm:grid-cols-3">
        <MetricTile label={t('真实案例均分', 'Real avg')} value={formatOptionalScore(realScore)} />
        <MetricTile label={t('模拟案例均分', 'Synthetic avg')} value={formatOptionalScore(syntheticScore)} />
        <MetricTile label={t('总体能力分', 'Overall ability')} value={formatOptionalScore(overallScore)} />
      </div>

      <div className="mt-3 grid gap-2 sm:grid-cols-3">
        <MetricTile label={t('工具执行覆盖', 'Tool execution')} value={formatPercent(toOptionalNumber(toolExecutionSummary.executed) ?? report.execution_coverage)} />
        <MetricTile label={t('替代工具评估', 'Tool surrogate')} value={formatPercent(toOptionalNumber(toolExecutionSummary.surrogate) ?? report.surrogate_coverage)} />
        <MetricTile label={t('置信度', 'Confidence')} value={report.confidence || 'low'} />
      </div>

      <div className="mt-3 grid gap-2 sm:grid-cols-3">
        <ReadableFact icon={<CheckCircle2 className="h-4 w-4" />} label={t('改进', 'Improved')} value={String(report.improved_count)} />
        <ReadableFact icon={<XCircle className="h-4 w-4" />} label={t('回退', 'Regressed')} value={String(report.regression_count)} />
        <ReadableFact icon={<Info className="h-4 w-4" />} label={t('不变', 'Unchanged')} value={String(report.unchanged_count)} />
      </div>

      <div className="mt-3 grid gap-2 sm:grid-cols-3">
        <ReadableFact icon={<Info className="h-4 w-4" />} label={t('真实案例', 'Real cases')} value={String(realCaseCount)} />
        <ReadableFact icon={<Info className="h-4 w-4" />} label={t('模拟案例', 'Synthetic cases')} value={String(syntheticCaseCount)} />
        <ReadableFact icon={<XCircle className="h-4 w-4" />} label={t('无验证器已排除', 'No-validator excluded')} value={String(excludedSynthetic)} />
      </div>

      {report.cases.length > 0 && (
        <div className="mt-3 overflow-hidden rounded-md border border-border bg-white">
          <div className="border-b border-border px-3 py-2 text-xs font-medium text-muted-foreground">
            {t('回放案例', 'Replay cases')}
          </div>
          <div className="space-y-2 p-3 md:hidden">
            {report.cases.map((item, index) => (
              <div key={`${String(item.run_id || index)}:${index}:card`} className="rounded-md border border-border bg-muted/20 p-3 text-xs">
                <div className={`font-mono ${containedLongTextClass}`}>{String(item.run_id || '-')}</div>
                <div className="mt-2 grid grid-cols-3 gap-2">
                  <MetricTile label={t('基线', 'Baseline')} value={formatScore(toNumber(item.baseline_score))} />
                  <MetricTile label={t('候选', 'Candidate')} value={formatScore(toNumber(item.candidate_score))} />
                  <MetricTile label={t('变化', 'Delta')} value={formatSignedScore(toNumber(item.delta))} />
                </div>
                <div className="mt-2 text-muted-foreground">
                  {String(item.synthetic) === 'true' ? t('模拟案例', 'Synthetic case') : t('真实案例', 'Real case')}
                  {item.tier ? ` · ${String(item.tier)}` : ''}
                </div>
              </div>
            ))}
          </div>
          <div className="hidden max-h-48 overflow-auto md:block">
            <table className="w-full text-left text-xs">
              <thead className="bg-muted/40 text-muted-foreground">
                <tr>
                  <th className="px-3 py-2 font-medium">{t('运行', 'Run')}</th>
                  <th className="px-3 py-2 font-medium">{t('来源', 'Source')}</th>
                  <th className="px-3 py-2 font-medium">{t('基线', 'Baseline')}</th>
                  <th className="px-3 py-2 font-medium">{t('候选', 'Candidate')}</th>
                  <th className="px-3 py-2 font-medium">{t('变化', 'Delta')}</th>
                </tr>
              </thead>
              <tbody>
                {report.cases.map((item, index) => (
                  <tr key={`${String(item.run_id || index)}:${index}`} className="border-t border-border">
                    <td className="max-w-[160px] truncate px-3 py-2 font-mono">{String(item.run_id || '-')}</td>
                    <td className="px-3 py-2">
                      {String(item.synthetic) === 'true' ? t('模拟', 'Synthetic') : t('真实', 'Real')}
                      {item.tier ? ` · ${String(item.tier)}` : ''}
                    </td>
                    <td className="px-3 py-2">{formatScore(toNumber(item.baseline_score))}</td>
                    <td className="px-3 py-2">{formatScore(toNumber(item.candidate_score))}</td>
                    <td className="px-3 py-2">{formatSignedScore(toNumber(item.delta))}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
      {Array.isArray(report.case_reports) && report.case_reports.length > 0 ? (
        <RawDetails title={t('Replay case reports', 'Replay case reports')} payload={report.case_reports} />
      ) : null}
      {Object.keys(abilitySummary).length > 0 ? (
        <RawDetails title={t('能力评分汇总', 'Ability score summary')} payload={abilitySummary} />
      ) : null}
      {Object.keys(toolExecutionSummary).length > 0 ? (
        <RawDetails title={t('工具诊断汇总', 'Tool diagnostic summary')} payload={toolExecutionSummary} />
      ) : null}
      {report.preservation_report ? (
        <RawDetails title={t('Preservation report', 'Preservation report')} payload={report.preservation_report} />
      ) : null}
      <div className="mt-3 text-xs text-muted-foreground">{formatDateTime(report.created_at)}</div>
      <RawDetails title={t('原始评估报告', 'Raw eval report')} payload={report} />
    </div>
  );
}

function GateSummary({
  title,
  summary,
  items,
}: {
  title: string;
  summary: string;
  items: Array<{ label: string; ok: boolean }>;
}) {
  return (
    <div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
      <div className="mb-2 flex items-center gap-2 text-sm font-medium">
        <ListChecks className="h-4 w-4 text-muted-foreground" />
        {title}
      </div>
      <p className="mb-3 text-sm leading-6 text-muted-foreground">{summary}</p>
      <div className="space-y-2">
        {items.map((item) => (
          <div key={item.label} className="flex items-center gap-2 text-sm">
            {item.ok ? <CheckCircle2 className="h-4 w-4 text-muted-foreground" /> : <XCircle className="h-4 w-4 text-destructive" />}
            <span>{item.label}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function ReadablePanel({
  icon,
  title,
  empty,
}: {
  icon: React.ReactNode;
  title: string;
  empty: string;
}) {
  return (
    <div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
      <div className="mb-2 flex items-center gap-2 text-sm font-medium">
        {icon}
        {title}
      </div>
      <p className="text-sm text-muted-foreground">{empty}</p>
    </div>
  );
}

function ReadableFact({
  icon,
  label,
  value,
}: {
  icon: React.ReactNode;
  label: string;
  value: string;
}) {
  return (
    <div className="min-w-0 rounded-md border border-border bg-white p-3">
      <div className="mb-1 flex items-center gap-2 text-xs font-medium text-muted-foreground">
        {icon}
        {label}
      </div>
      <div className={`text-sm leading-5 ${containedLongTextClass}`}>{value || '-'}</div>
    </div>
  );
}

function MetricTile({
  label,
  value,
  tone = 'neutral',
}: {
  label: string;
  value: string;
  tone?: 'neutral' | 'good' | 'bad';
}) {
  const toneClass = tone === 'bad' ? 'text-destructive' : 'text-foreground';
  return (
    <div className="min-w-0 rounded-md border border-border bg-white p-3">
      <div className="text-xs font-medium text-muted-foreground">{label}</div>
      <div className={`mt-1 text-lg font-semibold ${toneClass}`}>{value}</div>
    </div>
  );
}

function RawDetails({ title, payload }: { title: string; payload: unknown }) {
  return (
    <details className="mt-3 min-w-0 max-w-full overflow-hidden rounded-md border border-border bg-white">
      <summary className="flex h-11 cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-medium text-muted-foreground">
        {title}
        <ChevronDown className="h-3.5 w-3.5" />
      </summary>
      <pre className={`max-h-72 overflow-auto border-t border-border p-3 text-xs leading-5 ${containedJsonTextClass}`}>
        {JSON.stringify(payload, null, 2)}
      </pre>
    </details>
  );
}

function MarkdownPreview({ content }: { content: string }) {
  return (
    <div className={`prose prose-sm max-w-none text-black prose-a:text-black prose-code:rounded prose-code:bg-white prose-code:px-1 prose-code:py-0.5 prose-code:text-black prose-headings:text-black prose-headings:tracking-normal prose-li:text-black prose-p:text-black prose-pre:border prose-pre:border-border prose-pre:bg-white prose-pre:text-black prose-strong:text-black [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_*]:min-w-0 ${containedLongTextClass}`}>
      <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
    </div>
  );
}

function candidateTitle(candidate: SkillLearningCandidate, t: (zh: string, en: string) => string): string {
  const evidence = candidate.evidence || {};
  const theme = String(evidence.theme || '').trim();
  const taskId = String(evidence.task_id || '').trim();
  const related = candidate.related_skill_names.filter(Boolean).join(', ');
  if (candidate.draft_skill_name) {
    return t(`为 ${candidate.draft_skill_name} 生成草稿`, `Draft for ${candidate.draft_skill_name}`);
  }
  if (candidate.kind === 'new_skill') {
    return theme
      ? t(`把“${theme}”沉淀成新技能`, `Extract "${theme}" into a new skill`)
      : taskId
        ? t(`从任务 ${taskId} 提炼新技能`, `Extract a new skill from task ${taskId}`)
        : t('提炼一个新技能', 'Extract a new skill');
  }
  if (candidate.kind === 'revise_skill') {
    return related
      ? t(`修订技能 ${related}`, `Revise skill ${related}`)
      : t('修订已有技能', 'Revise an existing skill');
  }
  if (candidate.kind === 'merge_skills') {
    return related
      ? t(`合并技能 ${related}`, `Merge skills ${related}`)
      : t('合并相似技能', 'Merge similar skills');
  }
  if (candidate.kind === 'retire_skill') {
    return related
      ? t(`考虑下线技能 ${related}`, `Consider retiring ${related}`)
      : t('考虑下线技能', 'Consider retiring a skill');
  }
  return candidate.reason || candidate.candidate_id;
}

function candidateTaskSummary(candidate: SkillLearningCandidate, t: (zh: string, en: string) => string): string {
  const evidence = candidate.evidence || {};
  const taskId = String(evidence.task_id || '').trim();
  const theme = String(evidence.theme || '').trim();
  const triggerRun = String(evidence.trigger_run_id || '').trim();
  if (taskId) return t(`任务 ${taskId}`, `Task ${taskId}`);
  if (theme) return t(`主题：${theme}`, `Theme: ${theme}`);
  if (triggerRun) return t(`由运行 ${triggerRun} 触发`, `Triggered by run ${triggerRun}`);
  const firstRun = candidate.source_run_ids?.[0];
  if (firstRun) return t(`来自 ${candidate.source_run_ids.length} 个运行`, `From ${candidate.source_run_ids.length} run(s)`);
  return t('系统学习候选', 'System learning candidate');
}

function candidateKindLabel(kind: string, t: (zh: string, en: string) => string): string {
  const labels: Record<string, string> = {
    new_skill: t('新增技能', 'New skill'),
    revise_skill: t('修订技能', 'Revise skill'),
    merge_skills: t('合并技能', 'Merge skills'),
    retire_skill: t('下线技能', 'Retire skill'),
  };
  return labels[kind] || kind;
}

function candidateStatusLabel(status: string, t: (zh: string, en: string) => string): string {
  const labels: Record<string, string> = {
    open: t('待处理', 'Open'),
    queued: t('排队中', 'Queued'),
    synthesizing: t('生成中', 'Synthesizing'),
    draft_ready: t('草稿已就绪', 'Draft ready'),
    safety_failed: t('安全未通过', 'Safety failed'),
    eval_failed: t('评估未通过', 'Eval failed'),
    review_pending: t('等待评审', 'Review pending'),
    approved: t('已批准', 'Approved'),
    rejected: t('已拒绝', 'Rejected'),
    published: t('已发布', 'Published'),
    failed: t('失败', 'Failed'),
    superseded: t('已被替代', 'Superseded'),
  };
  return labels[status] || status;
}

function draftStatusLabel(status: string, t: (zh: string, en: string) => string): string {
  const labels: Record<string, string> = {
    draft: t('草稿', 'Draft'),
    in_review: t('评审中', 'In review'),
    approved: t('已批准', 'Approved'),
    rejected: t('已拒绝', 'Rejected'),
    published: t('已发布', 'Published'),
    disabled: t('已禁用', 'Disabled'),
    archived: t('已归档', 'Archived'),
  };
  return labels[status] || candidateStatusLabel(status, t);
}

function riskLabel(risk: string, t: (zh: string, en: string) => string): string {
  const labels: Record<string, string> = {
    low: t('低', 'Low'),
    medium: t('中', 'Medium'),
    high: t('高', 'High'),
    critical: t('严重', 'Critical'),
  };
  return labels[risk] || risk;
}

function triggerReasonLabel(reason: string, t: (zh: string, en: string) => string): string {
  const labels: Record<string, string> = {
    task_accepted: t('任务已接受', 'Task accepted'),
  };
  return labels[reason] || reason;
}

function publishBlockReason(draft: SkillDraft, t: (zh: string, en: string) => string): string {
  if (draft.status !== 'in_review' && draft.status !== 'approved') {
    return t('草稿还没有送审，不能发布。', 'The draft has not been submitted yet.');
  }
  if (!draft.safety_report) return t('缺少安全报告，不能发布。', 'A safety report is required before publishing.');
  if (draft.safety_report.risk_level === 'critical' || !draft.safety_report.passed) {
    return t('安全报告存在阻断项，不能发布。', 'The safety report has blockers.');
  }
  if (draft.eval_report?.status !== 'skipped_provider_unavailable' && draft.eval_report?.passed === false) {
    return t('评估报告显示回退或未达标，不能发布。', 'The eval report shows a regression or failed score.');
  }
  return t('当前状态不能发布。', 'The current state cannot be published.');
}

function normalizeStringList(value: unknown): string[] {
  if (Array.isArray(value)) {
    return value.map((item) => String(item).trim()).filter(Boolean);
  }
  if (typeof value === 'string') {
    return value.split(',').map((item) => item.trim()).filter(Boolean);
  }
  return [];
}

function formatDateTime(value?: string | null): string {
  if (!value) return '-';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString();
}

function formatScore(value: number): string {
  if (!Number.isFinite(value)) return '-';
  return value.toFixed(2);
}

function formatOptionalScore(value: unknown): string {
  const parsed = toOptionalNumber(value);
  return typeof parsed === 'number' ? formatScore(parsed) : '-';
}

function formatPercent(value?: number | null): string {
  if (typeof value !== 'number' || Number.isNaN(value)) return '0%';
  return `${Math.round(value * 100)}%`;
}

function formatSignedScore(value: number): string {
  if (!Number.isFinite(value)) return '-';
  return `${value >= 0 ? '+' : ''}${value.toFixed(2)}`;
}

function toNumber(value: unknown): number {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function toOptionalNumber(value: unknown): number | null {
  if (value === null || value === undefined || value === '') return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function EmptyState({ icon, text }: { icon: React.ReactNode; text: string }) {
  return (
    <div className="py-12 text-center text-muted-foreground">
      <div className="mx-auto mb-3 flex justify-center opacity-40">{icon}</div>
      <p className="text-sm font-medium">{text}</p>
    </div>
  );
}

function UploadSkillForm({
  onDone,
  onCancel,
  onError,
}: {
  onDone: () => void;
  onCancel: () => void;
  onError: (msg: string) => void;
}) {
  const { locale } = useAppI18n();
  const [uploading, setUploading] = useState(false);
  const fileRef = useRef<HTMLInputElement>(null);

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    const file = fileRef.current?.files?.[0];
    if (!file) return;
    setUploading(true);
    try {
      await uploadSkill(file);
      onDone();
    } catch (err: any) {
      onError(err.message || pickAppText(locale, '上传失败', 'Upload failed'));
    } finally {
      setUploading(false);
    }
  };

  return (
    <Card>
      <CardHeader className="pb-4">
        <div className="flex items-center justify-between">
          <CardTitle className="text-base">{pickAppText(locale, '上传技能', 'Upload skill')}</CardTitle>
          <Button variant="ghost" size="icon" className="h-7 w-7" onClick={onCancel}>
            <X className="h-4 w-4" />
          </Button>
        </div>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div className="space-y-2">
            <label className="text-sm font-medium" htmlFor="skill-zip">
              {pickAppText(locale, '技能压缩包', 'Skill archive')}
            </label>
            <input
              id="skill-zip"
              ref={fileRef}
              type="file"
              accept=".zip"
              className="block w-full cursor-pointer text-sm text-muted-foreground file:mr-4 file:rounded-md file:border-0 file:bg-primary file:px-4 file:py-2 file:text-sm file:font-medium file:text-primary-foreground hover:file:bg-primary/90"
            />
            <p className="text-xs text-muted-foreground">
              {pickAppText(locale, '上传后生成草稿；送审后再运行 safety 和 eval。', 'After upload, a draft is created; safety and eval run after submission.')}
            </p>
          </div>
          <div className="flex justify-end gap-2">
            <Button type="button" variant="outline" onClick={onCancel}>
              {pickAppText(locale, '取消', 'Cancel')}
            </Button>
            <Button type="submit" disabled={uploading}>
              {uploading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Upload className="mr-2 h-4 w-4" />}
              {pickAppText(locale, '上传', 'Upload')}
            </Button>
          </div>
        </form>
      </CardContent>
    </Card>
  );
}
