import { useCallback, useEffect, useMemo, useRef, useState, type DragEvent, type KeyboardEvent, type SyntheticEvent } from "react";
import { apiClient } from "../api/client";
import type {
  AssistantConversationItem,
  AssistantAnnotationRecord,
  AssistantSelectionChip,
  AsyncEvalRunJob,
  AutoGenHistoryRecord,
  AutoGenMode,
  AutoRunAnnotationListItem,
  AutoRunAnnotationRecord,
  AutoRunCaseSummary,
  AutoRunDetailResponse,
  AutoRunDialogMessage,
  AutoRunDialogResponse,
  AutoRunDomainCoverage,
  AutoRunHistoryResponse,
  AutoRunPostAnalysisResponse,
  AutoRunSummary,
  ConnectionState,
  ManualCaseDecision,
  PromptState
} from "../state/types";
import { AssistantPanel } from "./AssistantPanel";
import { ConnectionPanel } from "./ConnectionPanel";
import { JsonView } from "./JsonView";
import { PanelFrame } from "./PanelFrame";
import { PromptPanel } from "./PromptPanel";

interface AutoRunsHistoryPanelProps {
  connection: ConnectionState;
  modelOptions: string[];
  modelsBusy: boolean;
  connectionStatus: string;
  connectionBusy: boolean;
  onConnectionChange: (next: ConnectionState) => void;
  onReloadModels: () => Promise<void> | void;
  onSaveLocalConfig: () => void;
  onTestConnection: () => Promise<void> | void;
  prompts: PromptState;
  onPromptsChange: (next: PromptState) => void;
  promptPresets: Array<{
    id: string;
    name: string;
    prompt_version: string;
    systemPrompt: string;
    developerPrompt: string;
    domainPrompt: string;
    schemaNotes?: string;
    fewShotExamples?: string;
  }>;
  selectedPresetId: string;
  onSelectPreset: (id: string) => void;
  onLoadPreset: () => void;
  onSavePreset: () => void;
  onResetDefaults: () => void;
  onDiffPrevious: () => void;
  presetName: string;
  onPresetNameChange: (name: string) => void;
  diffSummary: string;
  assistantPromptVersion: string;
  decompositionPromptVersion: string;
  showSettingsMode: boolean;
  showAutoRunsMode: boolean;
  showAssistantMode: boolean;
  showProgressMode: boolean;
  showCommentsMode: boolean;
  onLog?: (message: string) => void;
}

type UseMockFilter = "any" | "true" | "false";
type AutoGenPersonalityId = string;

interface AutoGenPersonalityDefinition {
  id: AutoGenPersonalityId;
  label: string;
  domain: string;
  defaultPrompt: string;
}

interface AutoRunsFilters {
  fromLocal: string;
  toLocal: string;
  target: string;
  mode: string;
  useMock: UseMockFilter;
  promptContains: string;
  limit: number;
}

interface CommentModalState {
  open: boolean;
  caseId: string;
  caseMessageIndex: number;
  messageIndex: number;
  rating: number;
  comment: string;
  manualCaseDecision: ManualCaseDecision;
  annotationAuthor: string;
  saving: boolean;
  error: string;
}

interface AssistantLiveCommentModalState {
  open: boolean;
  messageIndex: number;
  rating: number;
  comment: string;
  annotationAuthor: string;
  saving: boolean;
  error: string;
}

interface AssistantLiveSaveModalState {
  open: boolean;
  title: string;
  saving: boolean;
  error: string;
}

interface SavedSessionQuestionDeleteModalState {
  open: boolean;
  generationId: string;
  questionIndex: number;
  questionText: string;
  saving: boolean;
  error: string;
}

interface AutoGenDeleteModalState {
  open: boolean;
  generationId: string;
  title: string;
  saving: boolean;
  error: string;
}

interface AutoGenSettingsState {
  mode: AutoGenMode;
  count: number;
  personalityId: AutoGenPersonalityId;
  personalityPrompts: Record<string, string>;
  persistToEvalCases: boolean;
  generatedBy: string;
}

const DEFAULT_FILTERS: AutoRunsFilters = {
  fromLocal: "",
  toLocal: "",
  target: "all",
  mode: "all",
  useMock: "any",
  promptContains: "",
  limit: 120
};

const DEFAULT_MANUAL_DECISION: ManualCaseDecision = "needs_dialog_policy_fix";
const ALL_CASES_ID = "__all__";
const LIVE_RUN_ID_PREFIX = "__live__:";

const AUTORUNS_UI_CONFIG_KEY = "ndc_autoruns_ui_config_v1";
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];

function buildAssistantLiveFollowupMessage(inputValue: string, selectedChip: AssistantSelectionChip | null): string {
  const trimmedInput = inputValue.trim();
  if (!trimmedInput) {
    return "";
  }
  if (!selectedChip) {
    return trimmedInput;
  }

  const normalizedInput = trimmedInput.toLowerCase();
  const selectionAnchor = selectedChip.anchor_text.trim();
  const normalizedSelection = selectionAnchor.toLowerCase();
  if (normalizedSelection && normalizedInput.includes(normalizedSelection)) {
    return trimmedInput;
  }

  return `По выбранному объекту "${selectionAnchor}": ${trimmedInput}`;
}

const AUTOGEN_PERSONALITIES: AutoGenPersonalityDefinition[] = [
  {
    id: "general",
    label: "Общий контур",
    domain: "",
    defaultPrompt:
      "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."
  }
];

function buildDefaultPersonalityPrompts(
  personalities: AutoGenPersonalityDefinition[] = AUTOGEN_PERSONALITIES
): Record<string, string> {
  return personalities.reduce((acc, item) => {
    acc[item.id] = item.defaultPrompt;
    return acc;
  }, {} as Record<string, string>);
}

interface AutoRunsUiConfig {
  filters?: Partial<AutoRunsFilters>;
  analysisDate?: string;
  autogenPersonalityPromptHeight?: number;
  groupsExpanded?: {
    filters?: boolean;
    generationContext?: boolean;
    autogen?: boolean;
    savedSessions?: boolean;
  };
  autoGenSettings?: {
    mode?: AutoGenMode;
    count?: number;
    personalityId?: string;
    personalityPrompts?: Record<string, string>;
    persistToEvalCases?: boolean;
    generatedBy?: string;
  };
  annotationDecisionFilter?: ManualCaseDecision | "all";
  hideResolvedAnnotations?: boolean;
}

type UnifiedCommentListItem =
  | {
      source: "autorun";
      key: string;
      updated_at: string;
      rating: number;
      autorun: AutoRunAnnotationListItem;
      assistant: null;
    }
  | {
      source: "assistant_live";
      key: string;
      updated_at: string;
      rating: number;
      autorun: null;
      assistant: AssistantAnnotationRecord;
    };

const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = {
  mode: "codex_creative",
  count: 24,
  personalityId: "general",
  personalityPrompts: buildDefaultPersonalityPrompts(),
  persistToEvalCases: true,
  generatedBy: "manual_reviewer"
};

function normalizeAnalysisDateInput(value: string): string {
  const normalized = String(value ?? "").trim();
  return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : "";
}

function clampAutogenPromptHeight(value: number | null | undefined): number {
  const numeric = typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : 160;
  return Math.max(110, Math.min(520, numeric));
}

function dateToInputValue(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hour = String(date.getHours()).padStart(2, "0");
  const minute = String(date.getMinutes()).padStart(2, "0");
  return `${year}-${month}-${day}T${hour}:${minute}`;
}

function defaultFromDateValue(): string {
  const date = new Date();
  date.setDate(date.getDate() - 14);
  return dateToInputValue(date);
}

function localInputToIso(value: string): string | undefined {
  if (!value.trim()) return undefined;
  const parsed = Date.parse(value);
  if (!Number.isFinite(parsed)) return undefined;
  return new Date(parsed).toISOString();
}

function formatDateTime(iso: string | null): string {
  if (!iso) return "нет данных";
  const parsed = Date.parse(iso);
  if (!Number.isFinite(parsed)) return iso;
  return new Date(parsed).toLocaleString("ru-RU");
}

function formatDialogStepTag(message: AutoRunDialogMessage): string | null {
  const localIndex =
    typeof message.case_message_index === "number"
      ? message.case_message_index
      : typeof message.message_index === "number"
        ? message.message_index
        : null;
  if (localIndex === null || localIndex < 0) {
    return null;
  }
  const turnNumber = Math.floor(localIndex / 2) + 1;
  const turnLabel = String(turnNumber).padStart(3, "0");
  const roleLabel = message.role === "assistant" ? "ответ" : "вопрос";
  return `${turnLabel} ${roleLabel}`;
}

function formatAutoGenModeLabel(mode: AutoGenMode): string {
  if (mode === "saved_user_sessions") {
    return "Пользовательские сессии";
  }
  return mode;
}

function isAgentSemanticGeneration(item: AutoGenHistoryRecord | null | undefined): boolean {
  if (!item) {
    return false;
  }
  if (item.context?.agent_run === true) {
    return true;
  }
  if (item.context?.saved_case_set_kind === "agent_semantic_scenario") {
    return true;
  }
  return typeof item.title === "string" && item.title.trim().toUpperCase().startsWith("AGENT");
}

function formatAutoGenGenerationTitle(item: AutoGenHistoryRecord): string {
  const fallback = item.title ?? formatDateTime(item.created_at);
  if (isAgentSemanticGeneration(item) && !fallback.trim().toUpperCase().startsWith("AGENT")) {
    return `AGENT | ${fallback}`;
  }
  return fallback;
}

function buildSavedSessionDefaultTitle(items: AssistantConversationItem[]): string {
  const lastMessage = items[items.length - 1];
  const timestamp = formatDateTime(lastMessage?.created_at ?? new Date().toISOString());
  return `Ручная сессия ${timestamp}`;
}

function toPercent(closed: number, total: number): number {
  if (total <= 0) return 0;
  return Math.max(0, Math.min(100, Number(((closed / total) * 100).toFixed(1))));
}

function formatScore(value: number | null): string {
  if (typeof value !== "number") return "нет данных";
  return `${value.toFixed(1)}%`;
}

function formatShortTarget(value: string): string {
  if (value === "assistant_stage1") return "assistant/s1";
  if (value === "assistant_stage2") return "assistant/s2";
  if (value === "assistant_p0") return "assistant/p0";
  return value;
}

function trendLabel(value: "up" | "down" | "flat"): string {
  if (value === "up") return "Рост";
  if (value === "down") return "Регресс";
  return "Без изменений";
}

function renderRatingDots(rating: number): string {
  const safe = Math.max(1, Math.min(5, Math.round(rating)));
  return `${"●".repeat(safe)}${"○".repeat(5 - safe)}`;
}

function renderCoverageRows(items: AutoRunDomainCoverage[]) {
  if (items.length === 0) {
    return <p className="muted">Покрытие доменов пока не сформировано.</p>;
  }

  return (
    <div className="autoruns-coverage-list">
      {items.map((item) => {
        const percent = toPercent(item.closed_cases, item.total_cases);
        return (
          <div key={item.domain} className="autoruns-coverage-item">
            <div className="autoruns-coverage-head">
              <strong>{item.domain}</strong>
              <span>
                {item.closed_cases}/{item.total_cases} ({percent}%)
              </span>
            </div>
            <div className="autoruns-coverage-bar">
              <div style={{ width: `${percent}%` }} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

function toLiveRunId(jobId: string): string {
  return `${LIVE_RUN_ID_PREFIX}${jobId}`;
}

function isLiveRunId(runId: string): boolean {
  return runId.startsWith(LIVE_RUN_ID_PREFIX);
}

function jobIdFromLiveRunId(runId: string): string {
  return runId.startsWith(LIVE_RUN_ID_PREFIX) ? runId.slice(LIVE_RUN_ID_PREFIX.length) : "";
}

function buildLiveRunSummary(job: AsyncEvalRunJob): AutoRunSummary {
  const timestamp = job.report_summary?.run_timestamp ?? job.created_at;
  const openCases = Math.max(0, job.total_cases - job.completed_cases);
  const liveRunId = toLiveRunId(job.job_id);
  return {
    run_id: liveRunId,
    eval_target: job.eval_target,
    run_timestamp: timestamp,
    mode: "single-pass-strict",
    llm_provider: null,
    model: null,
    use_mock: null,
    analysis_date: job.report_summary?.analysis_date ?? job.analysis_date ?? null,
    prompt_version: null,
    schema_version: null,
    suite_id: job.case_set_file,
    cases_total: job.total_cases,
    requests_total: null,
    report_path: `async_job:${job.job_id}`,
    score_index: job.report_summary?.score_index ?? null,
    blocking_failures: 0,
    quality_failures: 0,
    closed_cases: job.completed_cases,
    open_cases: openCases,
    domain_coverage: [
      {
        domain: "runtime",
        total_cases: job.total_cases,
        closed_cases: job.completed_cases
      }
    ]
  };
}

function buildLiveRunDetail(job: AsyncEvalRunJob, requestedCaseId: string): {
  detail: AutoRunDetailResponse;
  dialog: AutoRunDialogResponse;
  caseId: string;
} {
  const summary = buildLiveRunSummary(job);
  const cases = job.cases.map<AutoRunCaseSummary>((item) => ({
    case_id: item.case_id,
    domain: null,
    query_class: null,
    status: item.status === "completed" ? "closed" : item.status === "failed" ? "open" : "unknown",
    score_index: null,
    trace_id: null,
    reply_type: null,
    session_id: `${job.run_id}-${item.case_id}`,
    dialog_available: item.messages.length > 0,
    commented_count: 0,
    latest_annotation_at: null,
    avg_rating: null,
    checks: null,
    metric_subscores: null
  }));

  const hasCase = requestedCaseId !== ALL_CASES_ID && cases.some((item) => item.case_id === requestedCaseId);
  const caseId = hasCase ? requestedCaseId : cases.length > 0 ? ALL_CASES_ID : "";

  const detail: AutoRunDetailResponse = {
    ok: true,
    run: summary,
    coverage: {
      closed_cases: job.completed_cases,
      open_cases: Math.max(0, job.total_cases - job.completed_cases),
      domain_coverage: [
        {
          domain: "runtime",
          total_cases: job.total_cases,
          closed_cases: job.completed_cases
        }
      ]
    },
    cases,
    annotations_summary: { total: 0 },
    report: job.report_summary
      ? {
          run_id: job.report_summary.run_id,
          run_timestamp: job.report_summary.run_timestamp,
          score_index: job.report_summary.score_index,
          cases_total: job.report_summary.cases_total,
          analysis_date: job.report_summary.analysis_date ?? job.analysis_date ?? null
        }
      : {}
  };

  const messages: AutoRunDialogMessage[] = [];
  let globalIndex = 0;
  if (caseId === ALL_CASES_ID) {
    for (const caseItem of job.cases) {
      for (let index = 0; index < caseItem.messages.length; index += 1) {
        const message = caseItem.messages[index];
        messages.push({
          ...message,
          message_index: globalIndex,
          case_id: caseItem.case_id,
          case_message_index: index,
          commented: false,
          annotation: null
        });
        globalIndex += 1;
      }
    }
  } else if (caseId) {
    const selected = job.cases.find((item) => item.case_id === caseId) ?? null;
    for (let index = 0; index < (selected?.messages.length ?? 0); index += 1) {
      const message = selected?.messages[index];
      if (!message) continue;
      messages.push({
        ...message,
        message_index: index,
        case_id: caseId,
        case_message_index: index,
        commented: false,
        annotation: null
      });
    }
  }

  const dialog: AutoRunDialogResponse = {
    ok: true,
    run_id: summary.run_id,
    case_id: caseId,
    source: "assistant_session",
    session_id: caseId === ALL_CASES_ID ? `${job.run_id}::__all__` : `${job.run_id}-${caseId}`,
    messages,
    decomposition: [],
    assistant_mode: {
      status: job.status,
      completed_cases: job.completed_cases,
      total_cases: job.total_cases
    },
    annotations: []
  };

  return {
    detail,
    dialog,
    caseId
  };
}

function CommentBubbleIcon({ commented }: { commented: boolean }) {
  const className = commented ? "comment-icon-svg commented" : "comment-icon-svg";
  return (
    <svg className={className} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <path d="M5 6.5h14v9H11.5l-4.5 3v-3H5z" />
    </svg>
  );
}

function CommentResolvedIcon({ resolved }: { resolved: boolean }) {
  return (
    <svg className={resolved ? "resolve-icon-svg resolved" : "resolve-icon-svg"} viewBox="0 0 16 16" aria-hidden="true" focusable="false">
      <circle cx="8" cy="8" r="6.2" />
      {resolved ? <path d="M5.1 8.2 7.2 10.3 11 6.5" /> : null}
    </svg>
  );
}

function CopyOutlineIcon() {
  return (
    <svg className="autoruns-copy-icon-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <rect x="9" y="9" width="11" height="11" rx="2.2" />
      <path d="M15 7V5.8a1.8 1.8 0 0 0-1.8-1.8H5.8A1.8 1.8 0 0 0 4 5.8v7.4A1.8 1.8 0 0 0 5.8 15H7" />
    </svg>
  );
}

function QuestionGripIcon() {
  return (
    <svg className="autoruns-question-grip-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
      <circle cx="4" cy="4" r="1" />
      <circle cx="8" cy="4" r="1" />
      <circle cx="12" cy="4" r="1" />
      <circle cx="4" cy="8" r="1" />
      <circle cx="8" cy="8" r="1" />
      <circle cx="12" cy="8" r="1" />
      <circle cx="4" cy="12" r="1" />
      <circle cx="8" cy="12" r="1" />
      <circle cx="12" cy="12" r="1" />
    </svg>
  );
}

function CardChevronIcon({ expanded }: { expanded: boolean }) {
  return (
    <svg
      className={expanded ? "autoruns-card-chevron-svg expanded" : "autoruns-card-chevron-svg"}
      viewBox="0 0 16 16"
      aria-hidden="true"
      focusable="false"
    >
      <path d="M3.5 6.2 8 10.4l4.5-4.2" />
    </svg>
  );
}

function CardLaunchIcon() {
  return (
    <svg className="autoruns-card-launch-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
      <path d="M5 3.8 12 8l-7 4.2Z" />
    </svg>
  );
}

function CardStopIcon() {
  return (
    <svg className="autoruns-card-stop-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
      <rect x="4.2" y="4.2" width="7.6" height="7.6" rx="0.8" />
    </svg>
  );
}

function GroupChevronIcon({ expanded }: { expanded: boolean }) {
  return (
    <svg className={expanded ? "autoruns-group-chevron-svg expanded" : "autoruns-group-chevron-svg"} viewBox="0 0 16 16" aria-hidden="true" focusable="false">
      <path d="M3.5 6.2 8 10.4l4.5-4.2" />
    </svg>
  );
}

export function AutoRunsHistoryPanel({
  connection,
  modelOptions,
  modelsBusy,
  connectionStatus,
  connectionBusy,
  onConnectionChange,
  onReloadModels,
  onSaveLocalConfig,
  onTestConnection,
  prompts,
  onPromptsChange,
  promptPresets,
  selectedPresetId,
  onSelectPreset,
  onLoadPreset,
  onSavePreset,
  onResetDefaults,
  onDiffPrevious,
  presetName,
  onPresetNameChange,
  diffSummary,
  assistantPromptVersion,
  decompositionPromptVersion,
  showSettingsMode,
  showAutoRunsMode,
  showAssistantMode,
  showProgressMode,
  showCommentsMode,
  onLog
}: AutoRunsHistoryPanelProps) {
  const [filters, setFilters] = useState<AutoRunsFilters>({
    ...DEFAULT_FILTERS,
    fromLocal: defaultFromDateValue()
  });
  const [analysisDate, setAnalysisDate] = useState("");
  const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
  const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
  const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
  const [annotations, setAnnotations] = useState<AutoRunAnnotationListItem[]>([]);
  const [annotationDecisionFilter, setAnnotationDecisionFilter] = useState<ManualCaseDecision | "all">("all");
  const [hideResolvedAnnotations, setHideResolvedAnnotations] = useState(false);
  const [manualDecisionSchema, setManualDecisionSchema] = useState<Record<string, unknown> | null>(null);
  const [availableManualDecisions, setAvailableManualDecisions] = useState<ManualCaseDecision[]>([]);
  const [selectedAnnotationId, setSelectedAnnotationId] = useState("");
  const [selectedRunId, setSelectedRunId] = useState("");
  const [selectedCaseId, setSelectedCaseId] = useState("");
  const [autogenPersonalities, setAutogenPersonalities] = useState<AutoGenPersonalityDefinition[]>(AUTOGEN_PERSONALITIES);
  const [autoGenSettings, setAutoGenSettings] = useState<AutoGenSettingsState>(DEFAULT_AUTOGEN_SETTINGS);
  const [autoGenHistory, setAutoGenHistory] = useState<AutoGenHistoryRecord[]>([]);
  const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState("");
  const [expandedSavedSessionGenerationId, setExpandedSavedSessionGenerationId] = useState("");
  const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState<string[]>([]);
  const [generatedQuestionsBusy, setGeneratedQuestionsBusy] = useState(false);
  const [editingQuestionIndex, setEditingQuestionIndex] = useState<number | null>(null);
  const [editingQuestionDraft, setEditingQuestionDraft] = useState("");
  const [draggingQuestionIndex, setDraggingQuestionIndex] = useState<number | null>(null);
  const [dragOverQuestionIndex, setDragOverQuestionIndex] = useState<number | null>(null);
  const [activeAsyncJob, setActiveAsyncJob] = useState<AsyncEvalRunJob | null>(null);
  const [postAnalysis, setPostAnalysis] = useState<AutoRunPostAnalysisResponse | null>(null);
  const [autoGenBusy, setAutoGenBusy] = useState(false);
  const [autogenRunBusy, setAutogenRunBusy] = useState(false);
  const [postAnalysisBusy, setPostAnalysisBusy] = useState(false);
  const [autogenHistoryBusy, setAutogenHistoryBusy] = useState(false);
  const [historyBusy, setHistoryBusy] = useState(false);
  const [detailBusy, setDetailBusy] = useState(false);
  const [dialogBusy, setDialogBusy] = useState(false);
  const [annotationsBusy, setAnnotationsBusy] = useState(false);
  const [annotationResolutionBusyId, setAnnotationResolutionBusyId] = useState("");
  const [errorText, setErrorText] = useState("");
  const [assistantLiveSessionId, setAssistantLiveSessionId] = useState("");
  const [assistantLiveConversation, setAssistantLiveConversation] = useState<AssistantConversationItem[]>([]);
  const [assistantLiveAnnotations, setAssistantLiveAnnotations] = useState<AssistantAnnotationRecord[]>([]);
  const [assistantLiveInput, setAssistantLiveInput] = useState("");
  const [assistantLiveSelectedChip, setAssistantLiveSelectedChip] = useState<AssistantSelectionChip | null>(null);
  const [assistantLiveUseMock, setAssistantLiveUseMock] = useState(false);
  const [assistantLiveBusy, setAssistantLiveBusy] = useState(false);
  const [assistantLiveStatus, setAssistantLiveStatus] = useState("");
  const [assistantLiveError, setAssistantLiveError] = useState("");
  const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
  const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
  const [autogenPersonalityPromptHeight, setAutogenPersonalityPromptHeight] = useState(160);
  const [runningAutogenGenerationId, setRunningAutogenGenerationId] = useState("");
  const [autogenStopBusy, setAutogenStopBusy] = useState(false);
  const [filtersGroupExpanded, setFiltersGroupExpanded] = useState(true);
  const [generationContextGroupExpanded, setGenerationContextGroupExpanded] = useState(true);
  const [autogenGroupExpanded, setAutogenGroupExpanded] = useState(true);
  const [savedSessionsGroupExpanded, setSavedSessionsGroupExpanded] = useState(true);
  const [commentModal, setCommentModal] = useState<CommentModalState>({
    open: false,
    caseId: "",
    caseMessageIndex: -1,
    messageIndex: -1,
    rating: 3,
    comment: "",
    manualCaseDecision: DEFAULT_MANUAL_DECISION,
    annotationAuthor: "manual_reviewer",
    saving: false,
    error: ""
  });
  const [assistantLiveCommentModal, setAssistantLiveCommentModal] = useState<AssistantLiveCommentModalState>({
    open: false,
    messageIndex: -1,
    rating: 3,
    comment: "",
    annotationAuthor: "manual_reviewer",
    saving: false,
    error: ""
  });
  const [assistantLiveSaveModal, setAssistantLiveSaveModal] = useState<AssistantLiveSaveModalState>({
    open: false,
    title: "",
    saving: false,
    error: ""
  });
  const [savedSessionQuestionDeleteModal, setSavedSessionQuestionDeleteModal] = useState<SavedSessionQuestionDeleteModalState>({
    open: false,
    generationId: "",
    questionIndex: -1,
    questionText: "",
    saving: false,
    error: ""
  });
  const [autoGenDeleteModal, setAutoGenDeleteModal] = useState<AutoGenDeleteModalState>({
    open: false,
    generationId: "",
    title: "",
    saving: false,
    error: ""
  });

  const initialLoadDoneRef = useRef(false);
  const asyncJobPollTimerRef = useRef<number | null>(null);
  const questionEditorRef = useRef<HTMLInputElement | null>(null);
  const isSavedUserSessionsMode = autoGenSettings.mode === "saved_user_sessions";
  const selectedPersonality = useMemo(
    () => autogenPersonalities.find((item) => item.id === autoGenSettings.personalityId) ?? autogenPersonalities[0] ?? AUTOGEN_PERSONALITIES[0],
    [autoGenSettings.personalityId, autogenPersonalities]
  );
  const visibleAutoGenHistory = useMemo(
    () => autoGenHistory.filter((item) => item.mode === autoGenSettings.mode),
    [autoGenHistory, autoGenSettings.mode]
  );
  const selectedAutogenGeneration = useMemo(
    () => visibleAutoGenHistory.find((item) => item.generation_id === selectedAutogenGenerationId) ?? visibleAutoGenHistory[0] ?? null,
    [selectedAutogenGenerationId, visibleAutoGenHistory]
  );

  const visibleAnnotations = useMemo(
    () => (hideResolvedAnnotations ? annotations.filter((item) => !item.resolved) : annotations),
    [annotations, hideResolvedAnnotations]
  );
  const selectedAnnotation = visibleAnnotations.find((item) => item.annotation_id === selectedAnnotationId) ?? null;
  const modalMessage = dialog?.messages.find((item) => item.message_index === commentModal.messageIndex) ?? null;
  const modalQuestion = useMemo(() => {
    if (!dialog || commentModal.messageIndex < 0) return null;
    for (let index = commentModal.messageIndex - 1; index >= 0; index -= 1) {
      const candidate = dialog.messages[index];
      if (candidate?.role === "user") {
        return candidate;
      }
    }
    return null;
  }, [commentModal.messageIndex, dialog]);
  const assistantLiveAnnotationsByMessageId = useMemo(() => {
    const map = new Map<string, AssistantAnnotationRecord>();
    for (const item of assistantLiveAnnotations) {
      if (item.message_id) {
        map.set(item.message_id, item);
      }
    }
    return map;
  }, [assistantLiveAnnotations]);
  const assistantLiveCommentModalMessage =
    assistantLiveCommentModal.messageIndex >= 0 ? assistantLiveConversation[assistantLiveCommentModal.messageIndex] ?? null : null;
  const assistantLiveCommentModalQuestion = useMemo(() => {
    if (assistantLiveCommentModal.messageIndex < 0) return null;
    for (let index = assistantLiveCommentModal.messageIndex - 1; index >= 0; index -= 1) {
      const candidate = assistantLiveConversation[index];
      if (candidate?.role === "user") {
        return candidate;
      }
    }
    return null;
  }, [assistantLiveCommentModal.messageIndex, assistantLiveConversation]);

  const unifiedVisibleAnnotations = useMemo<UnifiedCommentListItem[]>(() => {
    const autorunItems: UnifiedCommentListItem[] = visibleAnnotations.map((item) => ({
      source: "autorun",
      key: `autorun:${item.annotation_id}`,
      updated_at: item.updated_at,
      rating: item.rating,
      autorun: item,
      assistant: null
    }));
    const assistantItems: UnifiedCommentListItem[] = assistantLiveAnnotations.map((item) => ({
      source: "assistant_live",
      key: `assistant:${item.annotation_id}`,
      updated_at: item.updated_at,
      rating: item.rating,
      autorun: null,
      assistant: item
    }));
    return [...autorunItems, ...assistantItems].sort((left, right) => Date.parse(right.updated_at) - Date.parse(left.updated_at));
  }, [assistantLiveAnnotations, visibleAnnotations]);

  const annotationsAverageRating = useMemo(() => {
    if (unifiedVisibleAnnotations.length === 0) return null;
    const avg = unifiedVisibleAnnotations.reduce((acc, item) => acc + item.rating, 0) / unifiedVisibleAnnotations.length;
    return Number(avg.toFixed(2));
  }, [unifiedVisibleAnnotations]);

  const runSelectItems = useMemo(() => {
    const list = [...(history?.items ?? [])];
    if (activeAsyncJob) {
      list.unshift(buildLiveRunSummary(activeAsyncJob));
    }
    if (selectedRunId && !list.some((item) => item.run_id === selectedRunId) && runDetail?.run) {
      list.unshift(runDetail.run);
    }
    return list;
  }, [activeAsyncJob, history?.items, runDetail?.run, selectedRunId]);

  const log = useCallback(
    (message: string) => {
      onLog?.(`[autoruns] ${message}`);
    },
    [onLog]
  );

  const loadAssistantLiveAnnotationsForSession = useCallback(
    async (sessionIdRaw: string) => {
      const sessionId = String(sessionIdRaw ?? "").trim();
      if (!sessionId) {
        setAssistantLiveAnnotations([]);
        return;
      }
      try {
        const payload = await apiClient.loadAssistantAnnotations({
          session_id: sessionId,
          limit: 400
        });
        setAssistantLiveAnnotations(payload.items ?? []);
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        log(`Assistant live annotations load error: ${message}`);
      }
    },
    [log]
  );

  const closeAssistantLiveCommentModal = useCallback((options?: { force?: boolean }) => {
    setAssistantLiveCommentModal((prev) => {
      if (prev.saving && !options?.force) {
        return prev;
      }
      return {
        open: false,
        messageIndex: -1,
        rating: 3,
        comment: "",
        annotationAuthor: "manual_reviewer",
        saving: false,
        error: ""
      };
    });
  }, []);

  const closeAssistantLiveSaveModal = useCallback((options?: { force?: boolean }) => {
    setAssistantLiveSaveModal((prev) => {
      if (prev.saving && !options?.force) {
        return prev;
      }
      return {
        open: false,
        title: "",
        saving: false,
        error: ""
      };
    });
  }, []);

  const closeSavedSessionQuestionDeleteModal = useCallback((options?: { force?: boolean }) => {
    setSavedSessionQuestionDeleteModal((prev) => {
      if (prev.saving && !options?.force) {
        return prev;
      }
      return {
        open: false,
        generationId: "",
        questionIndex: -1,
        questionText: "",
        saving: false,
        error: ""
      };
    });
  }, []);

  const closeAutoGenDeleteModal = useCallback((options?: { force?: boolean }) => {
    setAutoGenDeleteModal((prev) => {
      if (prev.saving && !options?.force) {
        return prev;
      }
      return {
        open: false,
        generationId: "",
        title: "",
        saving: false,
        error: ""
      };
    });
  }, []);

  const copyIdentifierToClipboard = useCallback(
    async (event: React.SyntheticEvent, valueRaw: string, label: string) => {
      event.stopPropagation();
      event.preventDefault();
      const value = String(valueRaw ?? "").trim();
      if (!value) {
        return;
      }
      try {
        if (navigator?.clipboard?.writeText) {
          await navigator.clipboard.writeText(value);
        } else {
          const textarea = document.createElement("textarea");
          textarea.value = value;
          textarea.setAttribute("readonly", "true");
          textarea.style.position = "fixed";
          textarea.style.opacity = "0";
          document.body.appendChild(textarea);
          textarea.select();
          document.execCommand("copy");
          document.body.removeChild(textarea);
        }
        log(`${label} copied: ${value}`);
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`Копирование ${label}: ${message}`);
        log(`copy ${label} error: ${message}`);
      }
    },
    [log]
  );

  function startAssistantLiveStatusTicker(): () => void {
    let index = 0;
    setAssistantLiveStatus(ASSISTANT_STAGES[0]);
    const timer = window.setInterval(() => {
      index = Math.min(index + 1, ASSISTANT_STAGES.length - 1);
      setAssistantLiveStatus(ASSISTANT_STAGES[index]);
    }, 650);
    return () => window.clearInterval(timer);
  }

  const resetAssistantLiveSession = useCallback(() => {
    setAssistantLiveSessionId("");
    setAssistantLiveConversation([]);
    setAssistantLiveAnnotations([]);
    setAssistantLiveInput("");
    setAssistantLiveSelectedChip(null);
    setAssistantLiveStatus("");
    setAssistantLiveError("");
    closeAssistantLiveCommentModal({ force: true });
    log("Live-чат ассистента в истории автопрогонов сброшен.");
  }, [closeAssistantLiveCommentModal, log]);

  const sendAssistantLiveMessage = useCallback(async () => {
    const userMessage = buildAssistantLiveFollowupMessage(assistantLiveInput, assistantLiveSelectedChip);
    if (!userMessage) {
      return;
    }

    setAssistantLiveBusy(true);
    setAssistantLiveError("");
    setAssistantLiveInput("");
    setAssistantLiveConversation((prev) => [
      ...prev,
      {
        message_id: `autoruns-live-${Date.now()}`,
        session_id: assistantLiveSessionId || "pending",
        role: "user",
        text: userMessage,
        reply_type: null,
        created_at: new Date().toISOString(),
        trace_id: null,
        debug: null
      }
    ]);

    const stopTicker = startAssistantLiveStatusTicker();
    try {
      const response = await apiClient.sendAssistantMessage({
        connection,
        prompts,
        userMessage,
        sessionId: assistantLiveSessionId || undefined,
        promptVersion: assistantPromptVersion,
        useMock: assistantLiveUseMock
      });
      setAssistantLiveSessionId(response.session_id);
      setAssistantLiveConversation(response.conversation);
      await loadAssistantLiveAnnotationsForSession(response.session_id);
      setAssistantLiveStatus("Ответ готов");
      log(`Live-ответ ассистента получен: trace=${response.debug.trace_id}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantLiveError(message);
      setAssistantLiveStatus("Ошибка ассистента");
      log(`Live-чат ассистента: ошибка отправки сообщения: ${message}`);
    } finally {
      stopTicker();
      setAssistantLiveBusy(false);
    }
  }, [
    assistantLiveInput,
    assistantLiveSelectedChip,
    assistantLiveSessionId,
    assistantLiveUseMock,
    assistantPromptVersion,
    connection,
    loadAssistantLiveAnnotationsForSession,
    log,
    prompts
  ]);

  const openAssistantLiveSaveModal = useCallback(() => {
    if (!assistantLiveSessionId.trim() || assistantLiveConversation.length === 0) {
      setAssistantLiveError("Сначала получите хотя бы один ответ в живой сессии ассистента.");
      return;
    }
    setAssistantLiveError("");
    setAssistantLiveSaveModal({
      open: true,
      title: buildSavedSessionDefaultTitle(assistantLiveConversation),
      saving: false,
      error: ""
    });
  }, [assistantLiveConversation, assistantLiveSessionId]);

  const submitAssistantLiveSaveModal = useCallback(async () => {
    const sessionId = assistantLiveSessionId.trim();
    const title = assistantLiveSaveModal.title.trim();
    if (!sessionId) {
      setAssistantLiveSaveModal((prev) => ({ ...prev, error: "Активная сессия ассистента не найдена." }));
      return;
    }
    if (!title) {
      setAssistantLiveSaveModal((prev) => ({ ...prev, error: "Укажите название сессии." }));
      return;
    }

    setAssistantLiveSaveModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const promptFingerprint = [
        prompts.systemPrompt,
        prompts.developerPrompt,
        prompts.domainPrompt,
        prompts.schemaNotes,
        prompts.fewShotExamples
      ].join("||");
      const payload = await apiClient.saveAutoRunAssistantSession({
        session_id: sessionId,
        title,
        generated_by: autoGenSettings.generatedBy.trim() || undefined,
        context: {
          llm_provider: connection.llmProvider,
          model: connection.model,
          assistant_prompt_version: assistantPromptVersion,
          decomposition_prompt_version: decompositionPromptVersion,
          prompt_fingerprint: promptFingerprint
        }
      });
      setAutoGenHistory((prev) => [payload.generation, ...prev.filter((item) => item.generation_id !== payload.generation.generation_id)]);
      setAutoGenSettings((prev) => ({ ...prev, mode: "saved_user_sessions" }));
      setSelectedAutogenGenerationId(payload.generation.generation_id);
      closeAssistantLiveSaveModal({ force: true });
      log(`Живая сессия сохранена в автопрогоны: ${payload.generation.generation_id}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantLiveSaveModal((prev) => ({ ...prev, saving: false, error: message }));
      log(`Assistant live save error: ${message}`);
    }
  }, [
    assistantLiveSaveModal.title,
    assistantLiveSessionId,
    assistantPromptVersion,
    autoGenSettings.generatedBy,
    closeAssistantLiveSaveModal,
    connection.llmProvider,
    connection.model,
    decompositionPromptVersion,
    log,
    prompts.developerPrompt,
    prompts.domainPrompt,
    prompts.fewShotExamples,
    prompts.schemaNotes,
    prompts.systemPrompt
  ]);

  const commitLimitInput = useCallback(
    (raw: string) => {
      const normalized = raw.trim();
      if (!normalized) {
        setLimitInput(String(filters.limit));
        return;
      }
      if (!/^\d+$/.test(normalized)) {
        setLimitInput(String(filters.limit));
        return;
      }
      const parsed = Number.parseInt(normalized, 10);
      if (!Number.isFinite(parsed)) {
        setLimitInput(String(filters.limit));
        return;
      }
      const next = Math.max(1, Math.min(500, parsed));
      if (next !== filters.limit) {
        setFilters((prev) => ({ ...prev, limit: next }));
      }
      setLimitInput(String(next));
    },
    [filters.limit]
  );

  const commitAutogenCountInput = useCallback(
    (raw: string) => {
      const normalized = raw.trim();
      if (!normalized) {
        setAutogenCountInput(String(autoGenSettings.count));
        return;
      }
      if (!/^\d+$/.test(normalized)) {
        setAutogenCountInput(String(autoGenSettings.count));
        return;
      }
      const parsed = Number.parseInt(normalized, 10);
      if (!Number.isFinite(parsed)) {
        setAutogenCountInput(String(autoGenSettings.count));
        return;
      }
      const next = Math.max(1, Math.min(200, parsed));
      if (next !== autoGenSettings.count) {
        setAutoGenSettings((prev) => ({ ...prev, count: next }));
      }
      setAutogenCountInput(String(next));
    },
    [autoGenSettings.count]
  );

  const commitAutogenPromptHeight = useCallback((height: number) => {
    setAutogenPersonalityPromptHeight(clampAutogenPromptHeight(height));
  }, []);

  const captureAutogenPromptHeight = useCallback(
    (event: SyntheticEvent<HTMLTextAreaElement>) => {
      const nextHeight = event.currentTarget.offsetHeight;
      if (Number.isFinite(nextHeight) && nextHeight > 0) {
        commitAutogenPromptHeight(nextHeight);
      }
    },
    [commitAutogenPromptHeight]
  );

  const loadAnnotations = useCallback(async () => {
    setAnnotationsBusy(true);
    try {
      const payload = await apiClient.loadAutoRunAnnotations({
        limit: 800,
        manual_case_decision: annotationDecisionFilter
      });
      setAnnotations(payload.items);
      setManualDecisionSchema(payload.manual_case_decision_schema ?? null);
      setAvailableManualDecisions(payload.available_manual_case_decisions ?? []);
      setSelectedAnnotationId((prev) => {
        if (payload.items.length === 0) return "";
        if (payload.items.some((item) => item.annotation_id === prev)) return prev;
        return payload.items[0].annotation_id;
      });
    } catch (error) {
      log(`Annotations load error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setAnnotationsBusy(false);
    }
  }, [annotationDecisionFilter, log]);

  const loadAutoGenHistory = useCallback(async () => {
    setAutogenHistoryBusy(true);
    try {
      const payload = await apiClient.loadAutoRunAutogenHistory({ limit: 180 });
      setAutoGenHistory(payload.items);
    } catch (error) {
      log(`Autogen history load error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setAutogenHistoryBusy(false);
    }
  }, [log]);

  const loadAutoGenPersonalityCatalog = useCallback(async () => {
    try {
      const payload = await apiClient.loadAutoRunAutogenPersonalityCatalog();
      const normalized = payload.items
        .map((item) => ({
          id: String(item.id ?? "").trim(),
          label: String(item.label ?? "").trim(),
          domain: typeof item.domain === "string" ? item.domain.trim() : "",
          defaultPrompt: String(item.default_prompt ?? "").trim()
        }))
        .filter((item) => item.id.length > 0 && item.label.length > 0);

      if (normalized.length === 0) {
        return;
      }

      setAutogenPersonalities(
        normalized.map((item) => ({
          id: item.id,
          label: item.label,
          domain: item.domain || "",
          defaultPrompt:
            item.defaultPrompt ||
            "Генерируй реалистичные вопросы бухгалтера по выбранному профилю. Не выдумывай непокрытые возможности."
        }))
      );
    } catch (error) {
      log(`Autogen personality catalog load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }, [log]);

  const loadPostAnalysis = useCallback(async () => {
    setPostAnalysisBusy(true);
    try {
      const payload = await apiClient.loadAutoRunPostAnalysis({
        run_id: selectedRunId && !isLiveRunId(selectedRunId) ? selectedRunId : undefined,
        limit_per_queue: 30,
        annotation_limit: 1500,
        from: localInputToIso(filters.fromLocal),
        to: localInputToIso(filters.toLocal),
        target: filters.target,
        mode: filters.mode,
        use_mock: filters.useMock,
        prompt_contains: filters.promptContains.trim() || undefined
      });
      setPostAnalysis(payload);
    } catch (error) {
      log(`Post-analysis load error: ${error instanceof Error ? error.message : String(error)}`);
      setPostAnalysis(null);
    } finally {
      setPostAnalysisBusy(false);
    }
  }, [filters.fromLocal, filters.mode, filters.promptContains, filters.target, filters.toLocal, filters.useMock, log, selectedRunId]);

  const generateAutogenBatch = useCallback(async () => {
    setAutoGenBusy(true);
    setErrorText("");
    try {
      if (autoGenSettings.mode === "saved_user_sessions") {
        throw new Error("Пользовательские сессии сохраняются из живого чата, а не генерируются автоматически.");
      }
      const activePersonalityPrompt = autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? "";
      const promptFingerprint = [
        prompts.systemPrompt,
        prompts.developerPrompt,
        prompts.domainPrompt,
        prompts.schemaNotes,
        prompts.fewShotExamples
      ]
        .join("\n")
        .slice(0, 900);
      const payload = await apiClient.generateAutoRunQuestions({
        mode: autoGenSettings.mode,
        count: autoGenSettings.count,
        domain: selectedPersonality.domain || undefined,
        persist_to_eval_cases: autoGenSettings.persistToEvalCases,
        generated_by: autoGenSettings.generatedBy.trim() || undefined,
        llm: {
          llm_provider: connection.llmProvider,
          api_key: connection.apiKey,
          model: connection.model,
          base_url: connection.baseUrl,
          temperature: connection.temperature,
          max_output_tokens: connection.maxOutputTokens
        },
        context: {
          llm_provider: connection.llmProvider,
          model: connection.model,
          assistant_prompt_version: assistantPromptVersion,
          decomposition_prompt_version: decompositionPromptVersion,
          prompt_fingerprint: promptFingerprint,
          autogen_personality_id: selectedPersonality.id,
          autogen_personality_prompt: activePersonalityPrompt.trim() || undefined
        }
      });
      log(
        `Generated ${payload.generation.count} questions (${payload.generation.mode}) id=${payload.generation.generation_id}` +
          (payload.generation.saved_case_set_file ? ` saved=${payload.generation.saved_case_set_file}` : "")
      );
      setSelectedAutogenGenerationId(payload.generation.generation_id);
      setEditableGeneratedQuestions([...(payload.generation.questions ?? [])]);
      await loadAutoGenHistory();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setErrorText(`Автогенерация: ${message}`);
      log(`Autogen generate error: ${message}`);
    } finally {
      setAutoGenBusy(false);
    }
  }, [
    assistantPromptVersion,
    autoGenSettings.count,
    autoGenSettings.generatedBy,
    autoGenSettings.mode,
    autoGenSettings.personalityId,
    autoGenSettings.personalityPrompts,
    autoGenSettings.persistToEvalCases,
    connection.apiKey,
    connection.baseUrl,
    connection.llmProvider,
    connection.maxOutputTokens,
    connection.model,
    connection.temperature,
    decompositionPromptVersion,
    loadAutoGenHistory,
    log,
    prompts.developerPrompt,
    prompts.domainPrompt,
    prompts.fewShotExamples,
    prompts.schemaNotes,
    prompts.systemPrompt,
    selectedPersonality.domain,
    selectedPersonality.id
  ]);

  const loadCaseDialog = useCallback(
    async (runId: string, caseId: string) => {
      if (isLiveRunId(runId)) {
        const liveJobId = jobIdFromLiveRunId(runId);
        if (activeAsyncJob && activeAsyncJob.job_id === liveJobId) {
          const live = buildLiveRunDetail(activeAsyncJob, caseId);
          setSelectedRunId(runId);
          setSelectedCaseId(live.caseId);
          setDialog(live.dialog);
          return;
        }
        setDialog(null);
        return;
      }
      setDialogBusy(true);
      try {
        const payload = await apiClient.loadAutoRunCaseDialog(runId, caseId);
        setDialog(payload);
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`Диалог кейса: ${message}`);
        setDialog(null);
        log(`Dialog load error for ${runId}/${caseId}: ${message}`);
      } finally {
        setDialogBusy(false);
      }
    },
    [activeAsyncJob, log]
  );

  const loadRunDetail = useCallback(
    async (runId: string, preferredCaseId?: string) => {
      if (isLiveRunId(runId)) {
        const liveJobId = jobIdFromLiveRunId(runId);
        if (activeAsyncJob && activeAsyncJob.job_id === liveJobId) {
          const live = buildLiveRunDetail(activeAsyncJob, preferredCaseId ?? ALL_CASES_ID);
          setSelectedRunId(runId);
          setSelectedCaseId(live.caseId);
          setRunDetail(live.detail);
          setDialog(live.dialog);
          return;
        }
        setSelectedRunId(runId);
        setSelectedCaseId("");
        setRunDetail(null);
        setDialog(null);
        return;
      }
      setDetailBusy(true);
      try {
        const payload = await apiClient.loadAutoRunDetail(runId);
        setRunDetail(payload);
        const nextCaseId =
          (preferredCaseId &&
          (preferredCaseId === ALL_CASES_ID || payload.cases.some((item) => item.case_id === preferredCaseId))
            ? preferredCaseId
            : "") ||
          (payload.cases.length > 0 ? ALL_CASES_ID : "") ||
          "";
        setSelectedRunId(runId);
        setSelectedCaseId(nextCaseId);
        if (nextCaseId) {
          await loadCaseDialog(runId, nextCaseId);
        } else {
          setDialog(null);
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`Детализация прогона: ${message}`);
        setRunDetail(null);
        setDialog(null);
        log(`Run detail load error for ${runId}: ${message}`);
      } finally {
        setDetailBusy(false);
      }
    },
    [activeAsyncJob, loadCaseDialog, log]
  );

  const loadHistory = useCallback(
    async (options?: { keepSelection?: boolean; preferredRunId?: string; preferredCaseId?: string }) => {
      setHistoryBusy(true);
      setErrorText("");
      try {
        const payload = await apiClient.loadAutoRunsHistory({
          from: localInputToIso(filters.fromLocal),
          to: localInputToIso(filters.toLocal),
          target: filters.target,
          mode: filters.mode,
          use_mock: filters.useMock,
          prompt_contains: filters.promptContains.trim() || undefined,
          limit: filters.limit
        });
        setHistory(payload);
        if (payload.items.length === 0) {
          setSelectedRunId("");
          setSelectedCaseId("");
          setRunDetail(null);
          setDialog(null);
          return;
        }

        const keepSelection = options?.keepSelection ?? true;
        const preferredRunId = options?.preferredRunId ?? "";
        const preferredCaseId = options?.preferredCaseId ?? "";
        const nextRunId =
          keepSelection && preferredRunId && payload.items.some((item) => item.run_id === preferredRunId)
            ? preferredRunId
            : payload.items[0].run_id;
        await loadRunDetail(nextRunId, keepSelection ? preferredCaseId : undefined);
        void loadPostAnalysis();
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`История прогонов: ${message}`);
        log(`History load error: ${message}`);
      } finally {
        setHistoryBusy(false);
      }
    },
    [
      filters.fromLocal,
      filters.limit,
      filters.mode,
      filters.promptContains,
      filters.target,
      filters.toLocal,
      filters.useMock,
      loadPostAnalysis,
      loadRunDetail,
      log
    ]
  );

  const stopAsyncJobPolling = useCallback(() => {
    if (asyncJobPollTimerRef.current !== null) {
      window.clearTimeout(asyncJobPollTimerRef.current);
      asyncJobPollTimerRef.current = null;
    }
  }, []);

  const pollAsyncJobStatus = useCallback(
    async (jobId: string) => {
      try {
        const payload = await apiClient.loadEvalRunAsyncStatus(jobId);
        setActiveAsyncJob(payload.job);
        const liveRunId = toLiveRunId(jobId);
        if (selectedRunId === liveRunId) {
          const live = buildLiveRunDetail(payload.job, selectedCaseId || ALL_CASES_ID);
          setRunDetail(live.detail);
          setDialog(live.dialog);
          setSelectedCaseId(live.caseId);
        }

        if (payload.job.status === "completed") {
          stopAsyncJobPolling();
          setAutogenRunBusy(false);
          setAutogenStopBusy(false);
          setRunningAutogenGenerationId("");
          const finalRunId = payload.job.report_summary?.run_id ?? payload.job.run_id;
          await loadHistory({
            keepSelection: true,
            preferredRunId: finalRunId || selectedRunId,
            preferredCaseId: ALL_CASES_ID
          });
          await loadAutoGenHistory();
          setActiveAsyncJob(null);
          return;
        }

        if (payload.job.status === "failed") {
          stopAsyncJobPolling();
          setAutogenRunBusy(false);
          setAutogenStopBusy(false);
          setRunningAutogenGenerationId("");
          setErrorText(`Запуск прогонов: ${payload.job.error ?? "неизвестная ошибка"}`);
          log(`Autogen async run failed: ${payload.job.error ?? "unknown error"}`);
          return;
        }

        if (payload.job.status === "canceled") {
          stopAsyncJobPolling();
          setAutogenRunBusy(false);
          setAutogenStopBusy(false);
          setRunningAutogenGenerationId("");
          setActiveAsyncJob(null);
          await loadHistory({ keepSelection: false });
          await loadAutoGenHistory();
          log(`Autogen async run canceled: job=${payload.job.job_id}`);
          return;
        }

        stopAsyncJobPolling();
        asyncJobPollTimerRef.current = window.setTimeout(() => {
          void pollAsyncJobStatus(jobId);
        }, 500);
      } catch (error) {
        stopAsyncJobPolling();
        setAutogenRunBusy(false);
        setAutogenStopBusy(false);
        setRunningAutogenGenerationId("");
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`Запуск прогонов: ${message}`);
        log(`Autogen async status error: ${message}`);
      }
    },
    [loadAutoGenHistory, loadHistory, log, selectedCaseId, selectedRunId, stopAsyncJobPolling]
  );

  const runAutogenCampaign = useCallback(async (generationOverride?: AutoGenHistoryRecord, questionsOverride?: string[]) => {
    stopAsyncJobPolling();
    setAutogenRunBusy(true);
    setErrorText("");
    try {
      const generation = generationOverride ?? selectedAutogenGeneration;
      if (!generation) {
        throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов.");
      }

      const sourceQuestions =
        questionsOverride ??
        (selectedAutogenGeneration?.generation_id === generation.generation_id ? editableGeneratedQuestions : generation.questions);
      const questionsForRun = sourceQuestions
        .map((item) => item.trim())
        .filter((item) => item.length > 0);
      if (questionsForRun.length === 0) {
        throw new Error("Нет вопросов для запуска: список пустой после ручного редактирования.");
      }

      const useMockForRun = filters.useMock === "true";
      const effectiveAnalysisDate = normalizeAnalysisDateInput(analysisDate);
      const useScenarioReplay = generation.mode === "saved_user_sessions";
      const payload = await apiClient.startEvalRunAsync({
        connection,
        prompts,
        promptVersion: assistantPromptVersion,
        mode: "single-pass-strict",
        caseSetFile: useScenarioReplay ? undefined : generation.saved_case_set_file ?? undefined,
        useMock: useMockForRun,
        evalTarget: "assistant_stage1",
        questions: useScenarioReplay ? undefined : questionsForRun,
        scenarioQuestions: useScenarioReplay ? questionsForRun : undefined,
        scenarioTitle: useScenarioReplay ? generation.title ?? undefined : undefined,
        analysisDate: useScenarioReplay ? undefined : effectiveAnalysisDate || undefined
      });

      const liveJob = payload.job;
      setRunningAutogenGenerationId(generation.generation_id);
      setAutogenStopBusy(false);
      setActiveAsyncJob(liveJob);
      const liveRunId = toLiveRunId(liveJob.job_id);
      const live = buildLiveRunDetail(liveJob, ALL_CASES_ID);
      setSelectedRunId(liveRunId);
      setSelectedCaseId(live.caseId);
      setRunDetail(live.detail);
      setDialog(live.dialog);

      log(
        `Запущен async-прогон job=${liveJob.job_id}, run_id=${liveJob.run_id}, вопросов=${questionsForRun.length}` +
          (generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "") +
          (useScenarioReplay
            ? ", replay_mode=saved_user_session_scenario"
            : effectiveAnalysisDate
              ? `, analysis_date=${effectiveAnalysisDate}`
              : ", analysis_date=current_state")
      );
      void pollAsyncJobStatus(liveJob.job_id);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setErrorText(`Запуск прогонов: ${message}`);
      log(`Autogen run error: ${message}`);
      setAutogenRunBusy(false);
      setAutogenStopBusy(false);
      setRunningAutogenGenerationId("");
    }
  }, [
    analysisDate,
    assistantPromptVersion,
    connection,
    editableGeneratedQuestions,
    filters.useMock,
    log,
    pollAsyncJobStatus,
    prompts,
    selectedAutogenGeneration,
    stopAsyncJobPolling
  ]);

  const stopAutogenCampaign = useCallback(async () => {
    const jobId = activeAsyncJob?.job_id ?? "";
    if (!jobId) {
      setAutogenRunBusy(false);
      setAutogenStopBusy(false);
      setRunningAutogenGenerationId("");
      setActiveAsyncJob(null);
      stopAsyncJobPolling();
      return;
    }
    setAutogenStopBusy(true);
    setErrorText("");
    try {
      const payload = await apiClient.cancelEvalRunAsync(jobId);
      stopAsyncJobPolling();
      setActiveAsyncJob(null);
      setAutogenRunBusy(false);
      setAutogenStopBusy(false);
      setRunningAutogenGenerationId("");
      await loadHistory({ keepSelection: false });
      await loadAutoGenHistory();
      log(`Autogen async run stopped: job=${payload.job.job_id}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAutogenStopBusy(false);
      setErrorText(`Остановка прогона: ${message}`);
      log(`Autogen stop error: ${message}`);
    }
  }, [activeAsyncJob, loadAutoGenHistory, loadHistory, log, stopAsyncJobPolling]);

  const openCommentModal = useCallback((message: AutoRunDialogMessage) => {
    if (message.role !== "assistant") return;
    const resolvedCaseId = message.case_id ?? selectedCaseId;
    const resolvedCaseMessageIndex = message.case_message_index ?? message.message_index;
    setCommentModal({
      open: true,
      caseId: resolvedCaseId,
      caseMessageIndex: resolvedCaseMessageIndex,
      messageIndex: message.message_index,
      rating: message.annotation?.rating ?? 3,
      comment: message.annotation?.comment ?? "",
      manualCaseDecision: message.annotation?.manual_case_decision ?? DEFAULT_MANUAL_DECISION,
      annotationAuthor: message.annotation?.annotation_author ?? autoGenSettings.generatedBy,
      saving: false,
      error: ""
    });
  }, [autoGenSettings.generatedBy, selectedCaseId]);

  const closeCommentModal = useCallback((options?: { force?: boolean }) => {
    setCommentModal((prev) => {
      if (prev.saving && !options?.force) return prev;
      return {
        open: false,
        caseId: "",
        caseMessageIndex: -1,
        messageIndex: -1,
        rating: 3,
        comment: "",
        manualCaseDecision: DEFAULT_MANUAL_DECISION,
        annotationAuthor: autoGenSettings.generatedBy,
        saving: false,
        error: ""
      };
    });
  }, [autoGenSettings.generatedBy]);

  const submitCommentModal = useCallback(async () => {
    const targetRunId = selectedRunId;
    const targetCaseId = commentModal.caseId;
    const targetCaseMessageIndex = commentModal.caseMessageIndex;
    if (!targetRunId || !targetCaseId || targetCaseMessageIndex < 0) return;
    if (isLiveRunId(targetRunId)) {
      setCommentModal((prev) => ({ ...prev, error: "Комментарий можно сохранить после завершения прогона." }));
      return;
    }
    if (!commentModal.comment.trim()) {
      setCommentModal((prev) => ({ ...prev, error: "Добавьте комментарий." }));
      return;
    }
    setCommentModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      await apiClient.saveAutoRunAnnotation({
        run_id: targetRunId,
        case_id: targetCaseId,
        message_index: targetCaseMessageIndex,
        rating: commentModal.rating,
        comment: commentModal.comment.trim(),
        manual_case_decision: commentModal.manualCaseDecision,
        annotation_author: commentModal.annotationAuthor.trim() || undefined
      });

      closeCommentModal({ force: true });
      void Promise.all([loadRunDetail(targetRunId, selectedCaseId), loadAnnotations(), loadPostAnalysis()]).catch((error) => {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`Обновление после комментария: ${message}`);
        log(`Comment refresh error: ${message}`);
      });
    } catch (error) {
      setCommentModal((prev) => ({
        ...prev,
        saving: false,
        error: error instanceof Error ? error.message : String(error)
      }));
    }
  }, [
    closeCommentModal,
    commentModal.annotationAuthor,
    commentModal.caseId,
    commentModal.caseMessageIndex,
    commentModal.comment,
    commentModal.manualCaseDecision,
    commentModal.rating,
    loadAnnotations,
    loadPostAnalysis,
    loadRunDetail,
    log,
    selectedCaseId,
    selectedRunId
  ]);

  const canCommentAssistantLiveMessage = useCallback((item: AssistantConversationItem): boolean => item.role === "assistant", []);

  const isAssistantLiveMessageCommented = useCallback(
    (item: AssistantConversationItem): boolean => item.role === "assistant" && assistantLiveAnnotationsByMessageId.has(item.message_id),
    [assistantLiveAnnotationsByMessageId]
  );

  const openAssistantLiveCommentModal = useCallback(
    (item: AssistantConversationItem, index: number) => {
      if (item.role !== "assistant") {
        return;
      }
      const sessionIdFromState = assistantLiveSessionId.trim();
      const sessionIdFromItem = String(item.session_id ?? "").trim();
      const resolvedSessionId = sessionIdFromState || sessionIdFromItem;
      if (!resolvedSessionId) {
        setAssistantLiveError("Сначала получите ответ ассистента в активной сессии.");
        return;
      }
      if (!sessionIdFromState && sessionIdFromItem) {
        setAssistantLiveSessionId(sessionIdFromItem);
      }
      const existing = assistantLiveAnnotationsByMessageId.get(item.message_id) ?? null;
      setAssistantLiveError("");
      setAssistantLiveCommentModal({
        open: true,
        messageIndex: index,
        rating: existing?.rating ?? 3,
        comment: existing?.comment ?? "",
        annotationAuthor: existing?.annotation_author ?? "manual_reviewer",
        saving: false,
        error: ""
      });
    },
    [assistantLiveAnnotationsByMessageId, assistantLiveSessionId]
  );

  const submitAssistantLiveCommentModal = useCallback(async () => {
    if (assistantLiveCommentModal.messageIndex < 0) {
      return;
    }
    if (!assistantLiveCommentModal.comment.trim()) {
      setAssistantLiveCommentModal((prev) => ({ ...prev, error: "Добавьте комментарий." }));
      return;
    }

    const modalMessage = assistantLiveConversation[assistantLiveCommentModal.messageIndex] ?? null;
    const sessionId =
      assistantLiveSessionId.trim() || (modalMessage?.role === "assistant" ? String(modalMessage.session_id ?? "").trim() : "");
    if (!sessionId) {
      setAssistantLiveCommentModal((prev) => ({ ...prev, error: "Сессия ассистента не найдена." }));
      return;
    }

    setAssistantLiveCommentModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const payload = await apiClient.saveAssistantAnnotation({
        session_id: sessionId,
        message_index: assistantLiveCommentModal.messageIndex,
        rating: assistantLiveCommentModal.rating,
        comment: assistantLiveCommentModal.comment.trim(),
        annotation_author: assistantLiveCommentModal.annotationAuthor.trim() || undefined
      });
      setAssistantLiveAnnotations((prev) => {
        const next = [...prev];
        const index = next.findIndex((item) => item.annotation_id === payload.annotation.annotation_id);
        if (index >= 0) {
          next[index] = payload.annotation;
        } else {
          next.unshift(payload.annotation);
        }
        return next.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
      });
      closeAssistantLiveCommentModal({ force: true });
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantLiveCommentModal((prev) => ({ ...prev, saving: false, error: message }));
    }
  }, [
    assistantLiveCommentModal.annotationAuthor,
    assistantLiveCommentModal.comment,
    assistantLiveCommentModal.messageIndex,
    assistantLiveCommentModal.rating,
    assistantLiveConversation,
    assistantLiveSessionId,
    closeAssistantLiveCommentModal
  ]);

  const requestDeleteSavedSessionQuestion = useCallback(
    (questionIndex: number) => {
      if (!selectedAutogenGeneration || selectedAutogenGeneration.mode !== "saved_user_sessions") {
        return;
      }
      const questionText = editableGeneratedQuestions[questionIndex] ?? "";
      setSavedSessionQuestionDeleteModal({
        open: true,
        generationId: selectedAutogenGeneration.generation_id,
        questionIndex,
        questionText,
        saving: false,
        error: ""
      });
    },
    [editableGeneratedQuestions, selectedAutogenGeneration]
  );

  const submitSavedSessionQuestionDelete = useCallback(async () => {
    const generationId = savedSessionQuestionDeleteModal.generationId;
    const questionIndex = savedSessionQuestionDeleteModal.questionIndex;
    if (!generationId || questionIndex < 0) {
      return;
    }

    const nextQuestions = editableGeneratedQuestions.filter((_, index) => index !== questionIndex);
    if (nextQuestions.length === 0) {
      setSavedSessionQuestionDeleteModal((prev) => ({
        ...prev,
        error: "Нельзя удалить последний вопрос из сохраненной сессии."
      }));
      return;
    }

    setSavedSessionQuestionDeleteModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const payload = await apiClient.updateAutoRunAutogenQuestions({
        generation_id: generationId,
        questions: nextQuestions
      });
      setAutoGenHistory((prev) =>
        prev.map((item) => (item.generation_id === generationId ? payload.generation : item))
      );
      setEditableGeneratedQuestions(payload.generation.questions);
      closeSavedSessionQuestionDeleteModal({ force: true });
      log(`Обновлена сохраненная сессия: ${generationId}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setSavedSessionQuestionDeleteModal((prev) => ({ ...prev, saving: false, error: message }));
      log(`Saved session question delete error: ${message}`);
    }
  }, [
    closeSavedSessionQuestionDeleteModal,
    editableGeneratedQuestions,
    log,
    savedSessionQuestionDeleteModal.generationId,
    savedSessionQuestionDeleteModal.questionIndex
  ]);

  const updateGeneratedQuestions = useCallback(
    async (nextQuestions: string[], options?: { successLog?: string; revertQuestions?: string[] }) => {
      const generationId = selectedAutogenGeneration?.generation_id ?? "";
      const revertQuestions = options?.revertQuestions ?? editableGeneratedQuestions;

      setEditableGeneratedQuestions(nextQuestions);
      if (!generationId) {
        return true;
      }

      setGeneratedQuestionsBusy(true);
      try {
        const payload = await apiClient.updateAutoRunAutogenQuestions({
          generation_id: generationId,
          questions: nextQuestions
        });
        setAutoGenHistory((prev) =>
          prev.map((item) => (item.generation_id === generationId ? payload.generation : item))
        );
        setEditableGeneratedQuestions([...(payload.generation.questions ?? [])]);
        if (options?.successLog) {
          log(options.successLog);
        }
        return true;
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setEditableGeneratedQuestions(revertQuestions);
        setErrorText(`Вопросы к запуску: ${message}`);
        log(`Autogen questions update error: ${message}`);
        return false;
      } finally {
        setGeneratedQuestionsBusy(false);
      }
    },
    [editableGeneratedQuestions, log, selectedAutogenGeneration]
  );

  const startQuestionEdit = useCallback(
    (questionIndex: number) => {
      setEditingQuestionIndex(questionIndex);
      setEditingQuestionDraft(editableGeneratedQuestions[questionIndex] ?? "");
    },
    [editableGeneratedQuestions]
  );

  const stopQuestionEdit = useCallback(() => {
    setEditingQuestionIndex(null);
    setEditingQuestionDraft("");
  }, []);

  const commitQuestionEdit = useCallback(
    async (questionIndex: number | null) => {
      if (questionIndex === null) {
        return;
      }

      const currentQuestion = editableGeneratedQuestions[questionIndex] ?? "";
      const nextText = editingQuestionDraft.trim();
      if (!nextText || nextText === currentQuestion) {
        stopQuestionEdit();
        return;
      }

      const nextQuestions = editableGeneratedQuestions.map((item, index) => (index === questionIndex ? nextText : item));
      const saved = await updateGeneratedQuestions(nextQuestions, {
        successLog: `Список вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
        revertQuestions: editableGeneratedQuestions
      });
      if (saved) {
        stopQuestionEdit();
      }
    },
    [editableGeneratedQuestions, editingQuestionDraft, selectedAutogenGeneration, stopQuestionEdit, updateGeneratedQuestions]
  );

  const handleQuestionEditorBlur = useCallback(() => {
    void commitQuestionEdit(editingQuestionIndex);
  }, [commitQuestionEdit, editingQuestionIndex]);

  const handleQuestionEditorKeyDown = useCallback(
    (event: KeyboardEvent<HTMLInputElement>) => {
      if (event.key === "Enter") {
        event.preventDefault();
        void commitQuestionEdit(editingQuestionIndex);
        return;
      }
      if (event.key === "Escape") {
        event.preventDefault();
        stopQuestionEdit();
      }
    },
    [commitQuestionEdit, editingQuestionIndex, stopQuestionEdit]
  );

  const handleAddGeneratedQuestion = useCallback(async () => {
    const nextQuestions = [...editableGeneratedQuestions, "Новый вопрос"];
    const nextIndex = nextQuestions.length - 1;
    const saved = await updateGeneratedQuestions(nextQuestions, {
      successLog: `В список добавлен вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
      revertQuestions: editableGeneratedQuestions
    });
    if (saved) {
      setEditingQuestionIndex(nextIndex);
      setEditingQuestionDraft(nextQuestions[nextIndex]);
    }
  }, [editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]);

  const handleDeleteGeneratedQuestion = useCallback(
    async (questionIndex: number) => {
      if (editableGeneratedQuestions.length <= 1) {
        setErrorText("В списке должен остаться хотя бы один вопрос.");
        return;
      }

      const nextQuestions = editableGeneratedQuestions.filter((_, index) => index !== questionIndex);
      const saved = await updateGeneratedQuestions(nextQuestions, {
        successLog: `Из списка удален вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
        revertQuestions: editableGeneratedQuestions
      });
      if (!saved) {
        return;
      }

      setEditingQuestionIndex((prev) => {
        if (prev === null) return prev;
        if (prev === questionIndex) return null;
        if (prev > questionIndex) return prev - 1;
        return prev;
      });
      setEditingQuestionDraft("");
    },
    [editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
  );

  const handleQuestionDragStart = useCallback(
    (event: DragEvent<HTMLButtonElement>, questionIndex: number) => {
      if (generatedQuestionsBusy) {
        event.preventDefault();
        return;
      }

      setDraggingQuestionIndex(questionIndex);
      setDragOverQuestionIndex(questionIndex);
      event.dataTransfer.effectAllowed = "move";
      event.dataTransfer.setData("text/plain", String(questionIndex));
    },
    [generatedQuestionsBusy]
  );

  const handleQuestionDragOver = useCallback(
    (event: DragEvent<HTMLDivElement>, questionIndex: number) => {
      event.preventDefault();
      if (dragOverQuestionIndex !== questionIndex) {
        setDragOverQuestionIndex(questionIndex);
      }
      event.dataTransfer.dropEffect = "move";
    },
    [dragOverQuestionIndex]
  );

  const handleQuestionDrop = useCallback(
    async (event: DragEvent<HTMLDivElement>, questionIndex: number) => {
      event.preventDefault();
      const fromIndex = draggingQuestionIndex;
      setDragOverQuestionIndex(null);
      setDraggingQuestionIndex(null);
      if (fromIndex === null || fromIndex === questionIndex) {
        return;
      }

      const nextQuestions = [...editableGeneratedQuestions];
      const [movedQuestion] = nextQuestions.splice(fromIndex, 1);
      nextQuestions.splice(questionIndex, 0, movedQuestion);
      await updateGeneratedQuestions(nextQuestions, {
        successLog: `Порядок вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
        revertQuestions: editableGeneratedQuestions
      });
    },
    [draggingQuestionIndex, editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
  );

  const handleQuestionDragEnd = useCallback(() => {
    setDraggingQuestionIndex(null);
    setDragOverQuestionIndex(null);
  }, []);

  const toggleSavedSessionQuestions = useCallback((generationId: string) => {
    setSelectedAutogenGenerationId(generationId);
    setExpandedSavedSessionGenerationId((prev) => (prev === generationId ? "" : generationId));
  }, []);

  const openAutoGenDeleteModal = useCallback((item: AutoGenHistoryRecord) => {
    setAutoGenDeleteModal({
      open: true,
      generationId: item.generation_id,
      title: item.title ?? `${formatAutoGenModeLabel(item.mode)} ${formatDateTime(item.created_at)}`,
      saving: false,
      error: ""
    });
  }, []);

  const submitAutoGenDeleteModal = useCallback(async () => {
    const generationId = autoGenDeleteModal.generationId.trim();
    if (!generationId) {
      return;
    }

    setAutoGenDeleteModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const payload = await apiClient.deleteAutoRunAutogenHistoryRecord(generationId);
      setAutoGenHistory((prev) => prev.filter((item) => item.generation_id !== payload.generation_id));
      closeAutoGenDeleteModal({ force: true });
      log(
        `Удален набор автопрогона: ${payload.generation_id}` +
          (payload.deleted_files.length > 0 ? `, files=${payload.deleted_files.length}` : "")
      );
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAutoGenDeleteModal((prev) => ({ ...prev, saving: false, error: message }));
      log(`Autogen record delete error: ${message}`);
    }
  }, [autoGenDeleteModal.generationId, closeAutoGenDeleteModal, log]);

  const applyLocalAnnotationPatch = useCallback((annotation: AutoRunAnnotationRecord) => {
    setAnnotations((prev) =>
      prev.map((item) =>
        item.annotation_id === annotation.annotation_id
          ? {
              ...item,
              ...annotation
            }
          : item
      )
    );
    setDialog((prev) => {
      if (!prev) return prev;
      return {
        ...prev,
        annotations: prev.annotations.map((item) => (item.annotation_id === annotation.annotation_id ? annotation : item)),
        messages: prev.messages.map((item) => {
          if (!item.annotation || item.annotation.annotation_id !== annotation.annotation_id) {
            return item;
          }
          return {
            ...item,
            commented: true,
            annotation
          };
        })
      };
    });
  }, []);

  const toggleAnnotationResolved = useCallback(
    async (annotation: AutoRunAnnotationRecord, nextResolved: boolean) => {
      if (!annotation.annotation_id) return;
      if (isLiveRunId(annotation.run_id)) {
        setErrorText("\u0421\u0442\u0430\u0442\u0443\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u0432.");
        return;
      }
      setAnnotationResolutionBusyId(annotation.annotation_id);
      try {
        const payload = await apiClient.updateAutoRunAnnotation({
          annotation_id: annotation.annotation_id,
          resolved: nextResolved,
          resolved_by: autoGenSettings.generatedBy || undefined
        });
        applyLocalAnnotationPatch(payload.annotation);
        void loadPostAnalysis();
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        setErrorText(`\u0421\u043c\u0435\u043d\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043a\u0435\u0439\u0441\u0430: ${message}`);
        log(`Annotation resolve toggle error: ${message}`);
      } finally {
        setAnnotationResolutionBusyId("");
      }
    },
    [applyLocalAnnotationPatch, autoGenSettings.generatedBy, loadPostAnalysis, log]
  );

  const openAnnotationContext = useCallback(
    async (annotation: AutoRunAnnotationRecord) => {
      setSelectedAnnotationId(annotation.annotation_id);
      await loadRunDetail(annotation.run_id, annotation.case_id);
      if (!history?.items.some((item) => item.run_id === annotation.run_id)) {
        setErrorText("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.");
      }
    },
    [history?.items, loadRunDetail]
  );

  useEffect(() => {
    if (initialLoadDoneRef.current) return;
    initialLoadDoneRef.current = true;
    void loadHistory({ keepSelection: false });
    void loadAutoGenHistory();
    void loadAutoGenPersonalityCatalog();
    void loadPostAnalysis();
  }, [loadAutoGenHistory, loadAutoGenPersonalityCatalog, loadHistory, loadPostAnalysis]);

  useEffect(() => {
    if (!initialLoadDoneRef.current) return;
    void loadAnnotations();
  }, [annotationDecisionFilter, loadAnnotations]);

  useEffect(() => {
    setSelectedAnnotationId((prev) => {
      if (visibleAnnotations.length === 0) return "";
      if (visibleAnnotations.some((item) => item.annotation_id === prev)) return prev;
      return visibleAnnotations[0].annotation_id;
    });
  }, [visibleAnnotations]);

  useEffect(() => {
    setSelectedAutogenGenerationId((prev) => {
      if (visibleAutoGenHistory.length === 0) return "";
      if (prev && visibleAutoGenHistory.some((item) => item.generation_id === prev)) return prev;
      return visibleAutoGenHistory[0].generation_id;
    });
  }, [visibleAutoGenHistory]);

  useEffect(() => {
    if (!selectedAutogenGeneration) {
      setEditableGeneratedQuestions([]);
      stopQuestionEdit();
      setDraggingQuestionIndex(null);
      setDragOverQuestionIndex(null);
      return;
    }
    setEditableGeneratedQuestions([...selectedAutogenGeneration.questions]);
    stopQuestionEdit();
    setDraggingQuestionIndex(null);
    setDragOverQuestionIndex(null);
  }, [selectedAutogenGeneration, stopQuestionEdit]);

  useEffect(() => {
    if (editingQuestionIndex === null) {
      return;
    }
    const timer = window.setTimeout(() => {
      questionEditorRef.current?.focus();
      questionEditorRef.current?.select();
    }, 0);
    return () => window.clearTimeout(timer);
  }, [editingQuestionIndex]);

  useEffect(() => {
    if (!isSavedUserSessionsMode) {
      setExpandedSavedSessionGenerationId("");
      return;
    }
    if (
      expandedSavedSessionGenerationId &&
      !visibleAutoGenHistory.some((item) => item.generation_id === expandedSavedSessionGenerationId)
    ) {
      setExpandedSavedSessionGenerationId("");
    }
  }, [expandedSavedSessionGenerationId, isSavedUserSessionsMode, visibleAutoGenHistory]);

  useEffect(() => {
    setLimitInput(String(filters.limit));
  }, [filters.limit]);

  useEffect(() => {
    setAutogenCountInput(String(autoGenSettings.count));
  }, [autoGenSettings.count]);

  useEffect(() => {
    if (!assistantLiveSessionId.trim()) {
      setAssistantLiveAnnotations([]);
      return;
    }
    void loadAssistantLiveAnnotationsForSession(assistantLiveSessionId);
  }, [assistantLiveSessionId, loadAssistantLiveAnnotationsForSession]);

  useEffect(() => {
    if (!activeAsyncJob) return;
    const liveRunId = toLiveRunId(activeAsyncJob.job_id);
    if (selectedRunId !== liveRunId) return;
    const live = buildLiveRunDetail(activeAsyncJob, selectedCaseId || ALL_CASES_ID);
    setRunDetail(live.detail);
    setDialog(live.dialog);
    setSelectedCaseId(live.caseId);
  }, [activeAsyncJob, selectedCaseId, selectedRunId]);

  useEffect(() => {
    return () => {
      stopAsyncJobPolling();
    };
  }, [stopAsyncJobPolling]);

  useEffect(() => {
    if (autogenPersonalities.length === 0) return;
    setAutoGenSettings((prev) => {
      let changed = false;
      const nextPrompts: Record<string, string> = { ...prev.personalityPrompts };
      for (const item of autogenPersonalities) {
        if (typeof nextPrompts[item.id] !== "string" || nextPrompts[item.id].trim().length === 0) {
          nextPrompts[item.id] = item.defaultPrompt;
          changed = true;
        }
      }

      let nextPersonalityId = prev.personalityId;
      if (!autogenPersonalities.some((item) => item.id === prev.personalityId)) {
        nextPersonalityId = autogenPersonalities[0].id;
        changed = true;
      }

      if (!changed) return prev;
      return {
        ...prev,
        personalityId: nextPersonalityId,
        personalityPrompts: nextPrompts
      };
    });
  }, [autogenPersonalities]);

  useEffect(() => {
    const raw = localStorage.getItem(AUTORUNS_UI_CONFIG_KEY);
    if (!raw) return;
    try {
      const parsed = JSON.parse(raw) as AutoRunsUiConfig;
      if (parsed.filters) {
        const savedFilters = parsed.filters;
        setFilters((prev) => ({
          ...prev,
          ...savedFilters,
          limit: typeof savedFilters.limit === "number" ? Math.max(1, Math.min(500, savedFilters.limit)) : prev.limit
        }));
      }
      if (typeof parsed.analysisDate === "string") {
        setAnalysisDate(normalizeAnalysisDateInput(parsed.analysisDate));
      }
      if (typeof parsed.autogenPersonalityPromptHeight === "number") {
        setAutogenPersonalityPromptHeight(clampAutogenPromptHeight(parsed.autogenPersonalityPromptHeight));
      }
      if (parsed.groupsExpanded) {
        if (typeof parsed.groupsExpanded.filters === "boolean") {
          setFiltersGroupExpanded(parsed.groupsExpanded.filters);
        }
        if (typeof parsed.groupsExpanded.generationContext === "boolean") {
          setGenerationContextGroupExpanded(parsed.groupsExpanded.generationContext);
        }
        if (typeof parsed.groupsExpanded.autogen === "boolean") {
          setAutogenGroupExpanded(parsed.groupsExpanded.autogen);
        }
        if (typeof parsed.groupsExpanded.savedSessions === "boolean") {
          setSavedSessionsGroupExpanded(parsed.groupsExpanded.savedSessions);
        }
      }
      if (parsed.autoGenSettings) {
        setAutoGenSettings((prev) => {
          const nextPrompts: Record<string, string> = {
            ...prev.personalityPrompts
          };
          const incomingPrompts = parsed.autoGenSettings?.personalityPrompts ?? {};
          for (const [key, value] of Object.entries(incomingPrompts)) {
            if (typeof value === "string" && key.trim().length > 0) {
              nextPrompts[key.trim()] = value;
            }
          }
          const nextPersonalityId =
            typeof parsed.autoGenSettings?.personalityId === "string" && parsed.autoGenSettings.personalityId.trim().length > 0
              ? parsed.autoGenSettings.personalityId.trim()
              : prev.personalityId;
          return {
            ...prev,
            mode:
              parsed.autoGenSettings?.mode === "codex_creative" ||
              parsed.autoGenSettings?.mode === "qwen_seed" ||
              parsed.autoGenSettings?.mode === "saved_user_sessions"
                ? parsed.autoGenSettings.mode
                : prev.mode,
            count:
              typeof parsed.autoGenSettings?.count === "number"
                ? Math.max(1, Math.min(200, parsed.autoGenSettings.count))
                : prev.count,
            personalityId: nextPersonalityId,
            personalityPrompts: nextPrompts,
            persistToEvalCases:
              typeof parsed.autoGenSettings?.persistToEvalCases === "boolean"
                ? parsed.autoGenSettings.persistToEvalCases
                : prev.persistToEvalCases,
            generatedBy:
              typeof parsed.autoGenSettings?.generatedBy === "string"
                ? parsed.autoGenSettings.generatedBy
                : prev.generatedBy
          };
        });
      }
      if (
        parsed.annotationDecisionFilter === "all" ||
        (typeof parsed.annotationDecisionFilter === "string" && parsed.annotationDecisionFilter.length > 0)
      ) {
        setAnnotationDecisionFilter(parsed.annotationDecisionFilter as ManualCaseDecision | "all");
      }
      if (typeof parsed.hideResolvedAnnotations === "boolean") {
        setHideResolvedAnnotations(parsed.hideResolvedAnnotations);
      }
    } catch {
      // ignore broken local cache
    }
  }, []);

  const saveUiConfig = useCallback(() => {
    const payload: AutoRunsUiConfig = {
      filters,
      analysisDate,
      autogenPersonalityPromptHeight,
      groupsExpanded: {
        filters: filtersGroupExpanded,
        generationContext: generationContextGroupExpanded,
        autogen: autogenGroupExpanded,
        savedSessions: savedSessionsGroupExpanded
      },
      autoGenSettings: {
        mode: autoGenSettings.mode,
        count: autoGenSettings.count,
        personalityId: autoGenSettings.personalityId,
        personalityPrompts: autoGenSettings.personalityPrompts,
        persistToEvalCases: autoGenSettings.persistToEvalCases,
        generatedBy: autoGenSettings.generatedBy
      },
      annotationDecisionFilter,
      hideResolvedAnnotations
    };
    localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
  }, [
    analysisDate,
    annotationDecisionFilter,
    autoGenSettings,
    autogenGroupExpanded,
    autogenPersonalityPromptHeight,
    filters,
    filtersGroupExpanded,
    generationContextGroupExpanded,
    hideResolvedAnnotations,
    savedSessionsGroupExpanded
  ]);

  useEffect(() => {
    const onSave = () => {
      saveUiConfig();
      log("Сохранены настройки панели автопрогонов.");
    };
    window.addEventListener(AUTORUNS_SAVE_EVENT, onSave as EventListener);
    return () => {
      window.removeEventListener(AUTORUNS_SAVE_EVENT, onSave as EventListener);
    };
  }, [log, saveUiConfig]);

  return (
    <PanelFrame
      className="autoruns-frame"
      title=""
      hideHeader
    >
      <div className="autoruns-columns">
        {showSettingsMode ? (
          <section className="autoruns-col autoruns-settings-col">
            <div className="autoruns-col-header">
              <h3>Настройки</h3>
            </div>
            <div className="autoruns-settings-stack">
              <ConnectionPanel
                embedded
                value={connection}
                modelOptions={modelOptions}
                modelsBusy={modelsBusy}
                onChange={onConnectionChange}
                onReloadModels={onReloadModels}
                onSaveLocalConfig={onSaveLocalConfig}
                onTestConnection={onTestConnection}
                lastStatus={connectionStatus}
                busy={connectionBusy}
              />
              <PromptPanel
                embedded
                value={prompts}
                onChange={onPromptsChange}
                presets={promptPresets}
                selectedPresetId={selectedPresetId}
                onSelectPreset={onSelectPreset}
                onLoadPreset={onLoadPreset}
                onSavePreset={onSavePreset}
                onResetDefaults={onResetDefaults}
                onDiffPrevious={onDiffPrevious}
                presetName={presetName}
                onPresetNameChange={onPresetNameChange}
                diffSummary={diffSummary}
              />
            </div>
          </section>
        ) : null}

        {showAutoRunsMode ? (
          <section className="autoruns-col">
          <div className="autoruns-col-header">
            <h3>Автопрогоны</h3>
          </div>

              <div className="autoruns-group-heading">
                <h4>Настройки выборки</h4>
                <button
                  type="button"
                  className="autoruns-group-toggle"
                  onClick={() => setFiltersGroupExpanded((prev) => !prev)}
                  aria-label={filtersGroupExpanded ? "Скрыть группу настройки выборки" : "Показать группу настройки выборки"}
                  title={filtersGroupExpanded ? "Скрыть группу" : "Показать группу"}
                >
                  <GroupChevronIcon expanded={filtersGroupExpanded} />
                </button>
              </div>
              {filtersGroupExpanded ? (
                <>
              <div className="autoruns-form-grid">
                <label>
                  Дата с
                  <input
                    type="datetime-local"
                    value={filters.fromLocal}
                    onChange={(event) => setFilters((prev) => ({ ...prev, fromLocal: event.target.value }))}
                  />
                </label>
                <label>
                  Дата по
                  <input
                    type="datetime-local"
                    value={filters.toLocal}
                    onChange={(event) => setFilters((prev) => ({ ...prev, toLocal: event.target.value }))}
                  />
                </label>
                <label>
                  Целевой контур
                  <select value={filters.target} onChange={(event) => setFilters((prev) => ({ ...prev, target: event.target.value }))}>
                    <option value="all">все</option>
                    {(history?.available.targets ?? []).map((item) => (
                      <option key={item} value={item}>
                        {item}
                      </option>
                    ))}
                  </select>
                </label>
                <label>
                  Режим
                  <select value={filters.mode} onChange={(event) => setFilters((prev) => ({ ...prev, mode: event.target.value }))}>
                    <option value="all">все</option>
                    {(history?.available.modes ?? []).map((item) => (
                      <option key={item} value={item}>
                        {item}
                      </option>
                    ))}
                  </select>
                </label>
                <label>
                  Использовать mock
                  <select
                    value={filters.useMock}
                    onChange={(event) => setFilters((prev) => ({ ...prev, useMock: event.target.value as UseMockFilter }))}
                  >
                    <option value="any">любой</option>
                    <option value="true">да</option>
                    <option value="false">нет</option>
                  </select>
                </label>
                <label>
                  Лимит
                  <input
                    type="number"
                    min={1}
                    max={500}
                    value={limitInput}
                    onChange={(event) => {
                      const raw = event.target.value;
                      if (raw === "" || /^\d+$/.test(raw)) {
                        setLimitInput(raw);
                      }
                    }}
                    onBlur={(event) => commitLimitInput(event.target.value)}
                    onKeyDown={(event) => {
                      if (event.key === "Enter") {
                        commitLimitInput((event.target as HTMLInputElement).value);
                      }
                    }}
                  />
                </label>
                <label className="full-width">
                  Версия промпта содержит
                  <input
                    value={filters.promptContains}
                    onChange={(event) => setFilters((prev) => ({ ...prev, promptContains: event.target.value }))}
                    placeholder="normalizer_v2_0_2 / address_query_runtime_v1"
                    list="autoruns-prompt-versions"
                  />
                </label>
              </div>
              <datalist id="autoruns-prompt-versions">
                {(history?.available.prompt_versions ?? []).map((item) => (
                  <option key={item} value={item} />
                ))}
              </datalist>

              <div className="button-row">
                <button type="button" disabled={historyBusy} onClick={() => void loadHistory({ keepSelection: false })}>
                  {historyBusy ? "Обновляю..." : "Применить"}
                </button>
                <button
                  type="button"
                  className="tab"
                  onClick={() => {
                    setFilters({
                      ...DEFAULT_FILTERS,
                      fromLocal: defaultFromDateValue()
                    });
                    setErrorText("");
                  }}
                >
                  Сбросить фильтры
                </button>
              </div>
                </>
              ) : null}

              <div className="autoruns-group-heading">
                <h4>Контур генерации</h4>
                <button
                  type="button"
                  className="autoruns-group-toggle"
                  onClick={() => setGenerationContextGroupExpanded((prev) => !prev)}
                  aria-label={generationContextGroupExpanded ? "Скрыть группу контура генерации" : "Показать группу контура генерации"}
                  title={generationContextGroupExpanded ? "Скрыть группу" : "Показать группу"}
                >
                  <GroupChevronIcon expanded={generationContextGroupExpanded} />
                </button>
              </div>
              {generationContextGroupExpanded ? (
              <div className="autoruns-meta-list">
                <div>
                  <span>Провайдер:</span>
                  <strong>{connection.llmProvider}</strong>
                </div>
                <div>
                  <span>Модель:</span>
                  <strong>{connection.model || "нет данных"}</strong>
                </div>
                <div>
                  <span>Промпт ассистента:</span>
                  <strong>{assistantPromptVersion}</strong>
                </div>
                <div>
                  <span>Промпт декомпозиции:</span>
                  <strong>{decompositionPromptVersion}</strong>
                </div>
              </div>
              ) : null}

              <div className="autoruns-group-heading">
                <h4>Автопрогоны</h4>
                <button
                  type="button"
                  className="autoruns-group-toggle"
                  onClick={() => setAutogenGroupExpanded((prev) => !prev)}
                  aria-label={autogenGroupExpanded ? "Скрыть группу автопрогонов" : "Показать группу автопрогонов"}
                  title={autogenGroupExpanded ? "Скрыть группу" : "Показать группу"}
                >
                  <GroupChevronIcon expanded={autogenGroupExpanded} />
                </button>
              </div>
              {autogenGroupExpanded ? (
                <>
              <div className="autoruns-form-grid">
                <label>
                  Режимы
                  <select
                    value={autoGenSettings.mode}
                    onChange={(event) => setAutoGenSettings((prev) => ({ ...prev, mode: event.target.value as AutoGenMode }))}
                  >
                    <option value="codex_creative">codex_creative</option>
                    <option value="qwen_seed">qwen_seed</option>
                    <option value="saved_user_sessions">Пользовательские сессии</option>
                  </select>
                </label>
                {!isSavedUserSessionsMode ? (
                  <>
                    <label>
                      Кол-во
                      <input
                        type="number"
                        min={1}
                        max={200}
                        value={autogenCountInput}
                        onChange={(event) => {
                          const raw = event.target.value;
                          if (raw === "" || /^\d+$/.test(raw)) {
                            setAutogenCountInput(raw);
                          }
                        }}
                        onBlur={(event) => commitAutogenCountInput(event.target.value)}
                        onKeyDown={(event) => {
                          if (event.key === "Enter") {
                            commitAutogenCountInput((event.target as HTMLInputElement).value);
                          }
                        }}
                      />
                    </label>
                    <label>
                      Личность автогенерации
                      <select
                        value={autoGenSettings.personalityId}
                        onChange={(event) =>
                          setAutoGenSettings((prev) => ({
                            ...prev,
                            personalityId: event.target.value as AutoGenPersonalityId
                          }))
                        }
                      >
                        {autogenPersonalities.map((item) => (
                          <option key={item.id} value={item.id}>
                            {item.label}
                          </option>
                        ))}
                      </select>
                    </label>
                    <label>
                      Кто генерирует
                      <input
                        value={autoGenSettings.generatedBy}
                        onChange={(event) => setAutoGenSettings((prev) => ({ ...prev, generatedBy: event.target.value }))}
                        placeholder="manual_reviewer"
                      />
                    </label>
                    <label className="full-width">
                      Промпт личности
                      <textarea
                        className="autoruns-personality-prompt"
                        value={autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? ""}
                        onChange={(event) =>
                          setAutoGenSettings((prev) => ({
                            ...prev,
                            personalityPrompts: {
                              ...prev.personalityPrompts,
                              [prev.personalityId]: event.target.value
                            }
                          }))
                        }
                        placeholder="Текст промпта для выбранной личности автогенерации"
                        style={{ height: `${autogenPersonalityPromptHeight}px` }}
                        onMouseUp={captureAutogenPromptHeight}
                        onTouchEnd={captureAutogenPromptHeight}
                      />
                    </label>
                    <label className="checkbox-row">
                      <input
                        type="checkbox"
                        checked={autoGenSettings.persistToEvalCases}
                        onChange={(event) => setAutoGenSettings((prev) => ({ ...prev, persistToEvalCases: event.target.checked }))}
                      />
                      Сохранять кейс-сет в `eval_cases`
                    </label>
                  </>
                ) : null}
              </div>

              {!isSavedUserSessionsMode ? (
                <div className="autoruns-form-grid">
                  <label>
                    Дата анализа (срез)
                    <input
                      type="date"
                      value={analysisDate}
                      onChange={(event) => setAnalysisDate(normalizeAnalysisDateInput(event.target.value))}
                    />
                  </label>
                  <div className="button-row">
                    <button type="button" className="tab" disabled={!analysisDate} onClick={() => setAnalysisDate("")}>
                      Сбросить дату среза
                    </button>
                  </div>
                </div>
              ) : null}

              <div className="button-row">
                {!isSavedUserSessionsMode ? (
                  <>
                    <button type="button" disabled={autoGenBusy} onClick={() => void generateAutogenBatch()}>
                      {autoGenBusy ? "Генерирую..." : "Сгенерировать пачку"}
                    </button>
                    <button type="button" className="tab" disabled={autogenHistoryBusy} onClick={() => void loadAutoGenHistory()}>
                      {autogenHistoryBusy ? "Обновляю..." : "Обновить историю"}
                    </button>
                  </>
                ) : null}
                <button
                  type="button"
                  className="autoruns-run-launch-btn"
                  style={isSavedUserSessionsMode ? { display: "none" } : undefined}
                  disabled={
                    autogenStopBusy ||
                    (!autogenRunBusy && (editableGeneratedQuestions.length === 0 || !selectedAutogenGeneration))
                  }
                  onClick={() => void (autogenRunBusy ? stopAutogenCampaign() : runAutogenCampaign())}
                >
                  {autogenRunBusy ? (autogenStopBusy ? "Останавливаю..." : "Остановить прогон") : "Запустить прогон"}
                </button>
              </div>

              <div className="autoruns-form-grid">
                <label className="full-width">
                  {isSavedUserSessionsMode ? "Сохраненная сессия" : "Кейс-сет для запуска"}
                  <select
                    value={selectedAutogenGenerationId}
                    onChange={(event) => setSelectedAutogenGenerationId(event.target.value)}
                    disabled={visibleAutoGenHistory.length === 0}
                  >
                    {visibleAutoGenHistory.length === 0 ? (
                      <option value="">
                        {isSavedUserSessionsMode ? "нет сохраненных сессий" : "нет генераций"}
                      </option>
                    ) : null}
                    {visibleAutoGenHistory.map((item) => (
                      <option key={item.generation_id} value={item.generation_id}>
                        {formatDateTime(item.created_at)} | {formatAutoGenGenerationTitle(item) ?? formatAutoGenModeLabel(item.mode)} | {item.count}
                      </option>
                    ))}
                  </select>
                </label>
              </div>
              {false ? (
                <>
              {/* generated questions editor */}
              <div className="autoruns-generated-questions">
                <div className="autoruns-generated-questions-head">
                  <strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
                  <button
                    type="button"
                    className="tab"
                    onClick={() => setEditableGeneratedQuestions([...(selectedAutogenGeneration?.questions ?? [])])}
                    disabled={!selectedAutogenGeneration}
                  >
                    Восстановить
                  </button>
                </div>
                {editableGeneratedQuestions.length === 0 ? (
                  <p className="muted">
                    {isSavedUserSessionsMode
                      ? "Список вопросов пуст. Сначала сохраните живую пользовательскую сессию."
                      : "Список вопросов пуст. Сгенерируйте пачку или восстановите из выбранной генерации."}
                  </p>
                ) : (
                  <div className="autoruns-generated-questions-list">
                    {editableGeneratedQuestions.map((question, index) => (
                      <div key={`${index}-${question.slice(0, 24)}`} className="autoruns-generated-question-item">
                        <span>{index + 1}. {question}</span>
                        <button
                          type="button"
                          className="autoruns-remove-question-btn"
                          onClick={() => {
                            if (isSavedUserSessionsMode) {
                              requestDeleteSavedSessionQuestion(index);
                              return;
                            }
                            setEditableGeneratedQuestions((prev) => prev.filter((_, itemIndex) => itemIndex !== index));
                          }}
                          title="Удалить вопрос из запуска"
                          aria-label="Удалить вопрос из запуска"
                        >
                          ×
                        </button>
                      </div>
                    ))}
                  </div>
                )}
              </div>
              <p className="muted">
                {isSavedUserSessionsMode
                  ? "Запуск воспроизводит сохраненную пользовательскую сессию как один последовательный multi-turn сценарий assistant_stage1."
                  : "Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}
              </p>
                </>
              ) : (
                <>
                  <div className="autoruns-generated-questions" style={isSavedUserSessionsMode ? { display: "none" } : undefined}>
                    <div className="autoruns-generated-questions-head">
                      <strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
                    </div>
                    {editableGeneratedQuestions.length === 0 ? (
                      <p className="muted">
                        {isSavedUserSessionsMode
                          ? "Список вопросов пуст. Сначала сохраните живую пользовательскую сессию."
                          : "Список вопросов пуст. Сгенерируйте пачку или добавьте вопрос вручную."}
                      </p>
                    ) : (
                      <div className="autoruns-generated-questions-list">
                        {editableGeneratedQuestions.map((question, index) => (
                          <div
                            key={`${index}-${question.slice(0, 24)}`}
                            className={[
                              "autoruns-generated-question-item",
                              dragOverQuestionIndex === index ? "drag-over" : "",
                              draggingQuestionIndex === index ? "dragging" : "",
                              editingQuestionIndex === index ? "editing" : ""
                            ].filter(Boolean).join(" ")}
                            onDragOver={(event) => handleQuestionDragOver(event, index)}
                            onDrop={(event) => void handleQuestionDrop(event, index)}
                          >
                            <button
                              type="button"
                              className="autoruns-question-grip-btn"
                              draggable={!generatedQuestionsBusy && editingQuestionIndex !== index}
                              disabled={generatedQuestionsBusy || editingQuestionIndex === index}
                              onDragStart={(event) => handleQuestionDragStart(event, index)}
                              onDragEnd={handleQuestionDragEnd}
                              title="Перетащить вопрос"
                              aria-label={`Перетащить вопрос ${index + 1}`}
                            >
                              <QuestionGripIcon />
                            </button>
                            {editingQuestionIndex === index ? (
                              <>
                                <input
                                  ref={questionEditorRef}
                                  className="autoruns-generated-question-input"
                                  value={editingQuestionDraft}
                                  onChange={(event) => setEditingQuestionDraft(event.target.value)}
                                  onBlur={handleQuestionEditorBlur}
                                  onKeyDown={handleQuestionEditorKeyDown}
                                  placeholder="Текст вопроса"
                                  disabled={generatedQuestionsBusy}
                                />
                                <button
                                  type="button"
                                  className="autoruns-remove-question-btn"
                                  onMouseDown={(event) => event.preventDefault()}
                                  onClick={() => void handleDeleteGeneratedQuestion(index)}
                                  title="Удалить вопрос"
                                  aria-label={`Удалить вопрос ${index + 1}`}
                                  disabled={generatedQuestionsBusy}
                                >
                                  ×
                                </button>
                              </>
                            ) : (
                              <button
                                type="button"
                                className="autoruns-generated-question-text"
                                onDoubleClick={() => startQuestionEdit(index)}
                                title="Двойной клик для редактирования"
                              >
                                {index + 1}. {question}
                              </button>
                            )}
                          </div>
                        ))}
                      </div>
                    )}
                    <button
                      type="button"
                      className="autoruns-add-question-btn"
                      onClick={() => void handleAddGeneratedQuestion()}
                      disabled={!selectedAutogenGeneration || generatedQuestionsBusy}
                    >
                      +
                    </button>
                  </div>
                  {!isSavedUserSessionsMode ? (
                    <p className="muted">Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету.</p>
                  ) : null}
                </>
              )}
                </>
              ) : null}

              <div className="autoruns-group-heading">
                <h4>{isSavedUserSessionsMode ? "Сохраненные пользовательские сессии" : "История автогенераций"}</h4>
                <button
                  type="button"
                  className="autoruns-group-toggle"
                  onClick={() => setSavedSessionsGroupExpanded((prev) => !prev)}
                  aria-label={
                    savedSessionsGroupExpanded
                      ? isSavedUserSessionsMode
                        ? "Скрыть группу сохраненных пользовательских сессий"
                        : "Скрыть группу истории автогенераций"
                      : isSavedUserSessionsMode
                        ? "Показать группу сохраненных пользовательских сессий"
                        : "Показать группу истории автогенераций"
                  }
                  title={savedSessionsGroupExpanded ? "Скрыть группу" : "Показать группу"}
                >
                  <GroupChevronIcon expanded={savedSessionsGroupExpanded} />
                </button>
              </div>
              {savedSessionsGroupExpanded ? (
              <div className="autoruns-autogen-list">
                {autogenHistoryBusy ? (
                  <p className="muted">
                    {isSavedUserSessionsMode ? "Загружаю сохраненные пользовательские сессии..." : "Загружаю историю автогенераций..."}
                  </p>
                ) : null}
                {!autogenHistoryBusy && visibleAutoGenHistory.length === 0 ? (
                  <p className="muted">
                    {isSavedUserSessionsMode ? "Сохраненные пользовательские сессии пока пусты." : "История автогенераций пока пустая."}
                  </p>
                ) : null}
                {visibleAutoGenHistory.slice(0, 30).map((item) => {
                  const isRunningThisGeneration = autogenRunBusy && runningAutogenGenerationId === item.generation_id;
                  const isAnotherGenerationRunning = autogenRunBusy && runningAutogenGenerationId !== item.generation_id;
                  return (
                  <article
                    key={item.generation_id}
                    className={[
                      "autoruns-autogen-item",
                      selectedAutogenGenerationId === item.generation_id ? "selected" : "",
                      expandedSavedSessionGenerationId === item.generation_id ? "expanded" : "",
                      isSavedUserSessionsMode ? "saved-session" : ""
                    ].filter(Boolean).join(" ")}
                    onClick={isSavedUserSessionsMode ? undefined : () => setSelectedAutogenGenerationId(item.generation_id)}
                  >
                    {isSavedUserSessionsMode ? (
                      <div className="autoruns-saved-session-topbar">
                        <button
                          type="button"
                          className="autoruns-saved-session-icon-btn"
                          disabled={autogenStopBusy || isAnotherGenerationRunning}
                          onClick={(event) => {
                            event.preventDefault();
                            event.stopPropagation();
                            if (isRunningThisGeneration) {
                              void stopAutogenCampaign();
                              return;
                            }
                            setSelectedAutogenGenerationId(item.generation_id);
                            void runAutogenCampaign(
                              item,
                              selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions
                            );
                          }}
                          title={isRunningThisGeneration ? "Остановить прогон" : "Запустить прогон"}
                          aria-label={`${isRunningThisGeneration ? "Остановить прогон" : "Запустить прогон"} для ${formatAutoGenGenerationTitle(item)}`}
                        >
                          {isRunningThisGeneration ? <CardStopIcon /> : <CardLaunchIcon />}
                        </button>
                        <button
                          type="button"
                          className="autoruns-autogen-delete-btn"
                          onClick={(event) => {
                            event.preventDefault();
                            event.stopPropagation();
                            openAutoGenDeleteModal(item);
                          }}
                          title="Удалить сохраненный набор"
                          aria-label={`Удалить набор ${item.generation_id}`}
                        >
                          ×
                        </button>
                      </div>
                    ) : null}
                    <header>
                      <strong>{formatAutoGenGenerationTitle(item)}</strong>
                      <div className="autoruns-autogen-card-actions">
                        <span>{formatDateTime(item.created_at)}</span>
                        <button
                          type="button"
                          className="autoruns-autogen-delete-btn"
                          onClick={(event) => {
                            event.preventDefault();
                            event.stopPropagation();
                            openAutoGenDeleteModal(item);
                          }}
                          title="Удалить сохраненный набор"
                          aria-label={`Удалить набор ${item.generation_id}`}
                        >
                          ×
                        </button>
                      </div>
                    </header>
                    <div className="autoruns-run-meta autoruns-run-id-row">
                      <span>{item.generation_id}</span>
                      <span
                        role="button"
                        tabIndex={0}
                        className="autoruns-copy-run-id-btn"
                        onClick={(event) => void copyIdentifierToClipboard(event, item.generation_id, "set id")}
                        onKeyDown={(event) => {
                          if (event.key === "Enter" || event.key === " ") {
                            event.preventDefault();
                            void copyIdentifierToClipboard(event, item.generation_id, "set id");
                          }
                        }}
                        title="Скопировать id набора"
                        aria-label={`Скопировать id набора ${item.generation_id}`}
                      >
                        <CopyOutlineIcon />
                      </span>
                    </div>
                    <div className="autoruns-run-meta">
                      режим={formatAutoGenModeLabel(item.mode)}
                    </div>
                    <div className="autoruns-run-meta">
                      тип={isAgentSemanticGeneration(item) ? "АГЕНТНЫЙ ПРОГОН" : "АВТОПРОГОН"}
                    </div>
                    {isSavedUserSessionsMode ? (
                      <>
                        <div className="autoruns-saved-session-footer">
                          <button
                            type="button"
                            className="autoruns-saved-session-icon-btn"
                            onClick={(event) => {
                              event.preventDefault();
                              event.stopPropagation();
                              toggleSavedSessionQuestions(item.generation_id);
                            }}
                            title={
                              expandedSavedSessionGenerationId === item.generation_id ? "Скрыть вопросы" : "Показать вопросы"
                            }
                            aria-label={
                              expandedSavedSessionGenerationId === item.generation_id ? "Скрыть вопросы" : "Показать вопросы"
                            }
                          >
                            <CardChevronIcon expanded={expandedSavedSessionGenerationId === item.generation_id} />
                          </button>
                        </div>
                        <div
                          className={
                            expandedSavedSessionGenerationId === item.generation_id
                              ? "autoruns-saved-session-questions expanded"
                              : "autoruns-saved-session-questions"
                          }
                        >
                          <div className="autoruns-generated-questions autoruns-generated-questions-embedded">
                            <div className="autoruns-generated-questions-head">
                              <strong>
                                Вопросы к запуску:{" "}
                                {selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions.length : item.questions.length}
                              </strong>
                            </div>
                            {(selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions).length === 0 ? (
                              <p className="muted">Список вопросов пуст.</p>
                            ) : (
                              <div className="autoruns-generated-questions-list">
                                {(selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions).map(
                                  (question, index) => (
                                    <div
                                      key={`${item.generation_id}-${index}-${question.slice(0, 24)}`}
                                      className={[
                                        "autoruns-generated-question-item",
                                        dragOverQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "drag-over" : "",
                                        draggingQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "dragging" : "",
                                        editingQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "editing" : ""
                                      ].filter(Boolean).join(" ")}
                                      onDragOver={(event) =>
                                        selectedAutogenGenerationId === item.generation_id ? handleQuestionDragOver(event, index) : undefined
                                      }
                                      onDrop={(event) =>
                                        selectedAutogenGenerationId === item.generation_id ? void handleQuestionDrop(event, index) : undefined
                                      }
                                    >
                                      <button
                                        type="button"
                                        className="autoruns-question-grip-btn"
                                        draggable={
                                          selectedAutogenGenerationId === item.generation_id &&
                                          !generatedQuestionsBusy &&
                                          editingQuestionIndex !== index
                                        }
                                        disabled={
                                          selectedAutogenGenerationId !== item.generation_id ||
                                          generatedQuestionsBusy ||
                                          editingQuestionIndex === index
                                        }
                                        onDragStart={(event) => {
                                          setSelectedAutogenGenerationId(item.generation_id);
                                          handleQuestionDragStart(event, index);
                                        }}
                                        onDragEnd={handleQuestionDragEnd}
                                        title="Перетащить вопрос"
                                        aria-label={`Перетащить вопрос ${index + 1}`}
                                      >
                                        <QuestionGripIcon />
                                      </button>
                                      {selectedAutogenGenerationId === item.generation_id && editingQuestionIndex === index ? (
                                        <>
                                          <input
                                            ref={questionEditorRef}
                                            className="autoruns-generated-question-input"
                                            value={editingQuestionDraft}
                                            onChange={(event) => setEditingQuestionDraft(event.target.value)}
                                            onBlur={handleQuestionEditorBlur}
                                            onKeyDown={handleQuestionEditorKeyDown}
                                            placeholder="Текст вопроса"
                                            disabled={generatedQuestionsBusy}
                                          />
                                          <button
                                            type="button"
                                            className="autoruns-remove-question-btn"
                                            onMouseDown={(event) => event.preventDefault()}
                                            onClick={() => void handleDeleteGeneratedQuestion(index)}
                                            title="Удалить вопрос"
                                            aria-label={`Удалить вопрос ${index + 1}`}
                                            disabled={generatedQuestionsBusy}
                                          >
                                            ×
                                          </button>
                                        </>
                                      ) : (
                                        <button
                                          type="button"
                                          className="autoruns-generated-question-text"
                                          onDoubleClick={() => {
                                            setSelectedAutogenGenerationId(item.generation_id);
                                            startQuestionEdit(index);
                                          }}
                                          title="Двойной клик для редактирования"
                                        >
                                          {index + 1}. {question}
                                        </button>
                                      )}
                                    </div>
                                  )
                                )}
                              </div>
                            )}
                            <button
                              type="button"
                              className="autoruns-add-question-btn"
                              onClick={() => {
                                setSelectedAutogenGenerationId(item.generation_id);
                                void handleAddGeneratedQuestion();
                              }}
                              disabled={selectedAutogenGenerationId !== item.generation_id || generatedQuestionsBusy}
                            >
                              +
                            </button>
                          </div>
                        </div>
                      </>
                    ) : null}
                  </article>
                  );
                })}
              </div>
              ) : null}

              <details className="autoruns-prompt-details">
                <summary>Копия активного промпта (только чтение)</summary>
                <label>
                  Системный
                  <textarea readOnly value={prompts.systemPrompt} />
                </label>
                <label>
                  Разработчика
                  <textarea readOnly value={prompts.developerPrompt} />
                </label>
                <label>
                  Доменный
                  <textarea readOnly value={prompts.domainPrompt} />
                </label>
                <label>
                  Заметки по схеме
                  <textarea readOnly value={prompts.schemaNotes} />
                </label>
                <label>
                  Примеры few-shot
                  <textarea readOnly value={prompts.fewShotExamples} />
                </label>
              </details>

          {errorText ? <p className="error-text">{errorText}</p> : null}
        </section>
        ) : null}

        <section className="autoruns-col">
          <div className="autoruns-col-header">
            <h3>Выдача прогонов</h3>
          </div>
          <div className="autoruns-stats-grid">
            <div>
              <span>Всего</span>
              <strong>{(history?.stats.runs_total ?? 0) + (activeAsyncJob ? 1 : 0)}</strong>
            </div>
            <div>
              <span>Средний score</span>
              <strong>{formatScore(history?.stats.avg_score_index ?? null)}</strong>
            </div>
            <div>
              <span>Тренд</span>
              <strong>{history ? trendLabel(history.stats.trend) : "нет данных"}</strong>
            </div>
            <div>
              <span>Блокеры</span>
              <strong>{history?.stats.blocking_runs ?? 0}</strong>
            </div>
          </div>
          <div className="autoruns-run-list">
            {runSelectItems.map((run) => (
              <button
                key={run.run_id}
                type="button"
                className={selectedRunId === run.run_id ? "autoruns-run-item selected" : "autoruns-run-item"}
                onClick={() => void loadRunDetail(run.run_id)}
              >
                <div className="autoruns-run-head">
                  <strong>{formatDateTime(run.run_timestamp)}</strong>
                  <span>{formatShortTarget(run.eval_target)}</span>
                </div>
                <div className="autoruns-run-meta autoruns-run-id-row">
                  <span>{run.run_id}</span>
                  <span
                    role="button"
                    tabIndex={0}
                    className="autoruns-copy-run-id-btn"
                    onClick={(event) => void copyIdentifierToClipboard(event, run.run_id, "run id")}
                    onKeyDown={(event) => {
                      if (event.key === "Enter" || event.key === " ") {
                        event.preventDefault();
                        void copyIdentifierToClipboard(event, run.run_id, "run id");
                      }
                    }}
                    title="Скопировать run id"
                    aria-label={`Скопировать run id ${run.run_id}`}
                  >
                    <CopyOutlineIcon />
                  </span>
                </div>
                <div className="autoruns-run-meta">
                  режим={run.mode ?? "нет данных"} | mock={String(run.use_mock)}
                </div>
                <div className="autoruns-run-meta">analysis_date={run.analysis_date ?? "current_state"}</div>
                {run.llm_provider || run.model ? (
                  <div className="autoruns-run-meta">
                    llm={run.llm_provider ?? "нет данных"} | модель={run.model ?? "нет данных"}
                  </div>
                ) : null}
                <div className="autoruns-run-meta">промпт={run.prompt_version ?? "нет данных"}</div>
                <div className="autoruns-run-foot">
                  <span>оценка: {formatScore(run.score_index)}</span>
                  <span>
                    закрыто/открыто: {run.closed_cases}/{run.open_cases}
                  </span>
                </div>
                <div className="autoruns-run-foot">
                  <span>блокеры: {run.blocking_failures}</span>
                  <span>качество: {run.quality_failures}</span>
                </div>
              </button>
            ))}
            {runSelectItems.length === 0 ? <p className="muted">За выбранный диапазон прогонов нет.</p> : null}
          </div>
        </section>
        <section className="autoruns-col">
          <div className="autoruns-col-header">
            <h3>Диалог прогона</h3>
            <div className="autoruns-dialog-toolbar">
            <label>
              Прогон
              <select
                value={selectedRunId}
                onChange={(event) => {
                  const nextRunId = event.target.value;
                  void loadRunDetail(nextRunId);
                }}
              >
                {runSelectItems.map((item) => (
                  <option key={item.run_id} value={item.run_id}>
                    {formatDateTime(item.run_timestamp)} | {item.run_id}
                  </option>
                ))}
              </select>
            </label>
            <label>
              Кейс
              <select
                value={selectedCaseId}
                onChange={(event) => {
                  const nextCaseId = event.target.value;
                  setSelectedCaseId(nextCaseId);
                  if (selectedRunId && nextCaseId) {
                    void loadCaseDialog(selectedRunId, nextCaseId);
                  }
                }}
              >
                {(runDetail?.cases.length ?? 0) > 0 ? <option value={ALL_CASES_ID}>ВСЕ кейсы подряд</option> : null}
                {(runDetail?.cases ?? []).map((item) => (
                  <option key={item.case_id} value={item.case_id}>
                    {item.case_id} | {item.status}
                  </option>
                ))}
              </select>
            </label>
            </div>
          </div>

          <div className="autoruns-case-list">
            {(runDetail?.cases.length ?? 0) > 0 ? (
              <button
                key={ALL_CASES_ID}
                type="button"
                className={selectedCaseId === ALL_CASES_ID ? "autoruns-case-item selected" : "autoruns-case-item"}
                onClick={() => {
                  setSelectedCaseId(ALL_CASES_ID);
                  if (selectedRunId) {
                    void loadCaseDialog(selectedRunId, ALL_CASES_ID);
                  }
                }}
              >
                <span>ВСЕ кейсы подряд</span>
                <span>{runDetail?.cases.length}</span>
              </button>
            ) : null}
            {(runDetail?.cases ?? []).map((item) => (
              <button
                key={item.case_id}
                type="button"
                className={selectedCaseId === item.case_id ? "autoruns-case-item selected" : "autoruns-case-item"}
                onClick={() => {
                  setSelectedCaseId(item.case_id);
                  if (selectedRunId) {
                    void loadCaseDialog(selectedRunId, item.case_id);
                  }
                }}
              >
                <span>{item.case_id}</span>
                <span>
                  {item.status}
                  {item.commented_count > 0 ? ` | комм=${item.commented_count}` : ""}
                </span>
              </button>
            ))}
          </div>

          <div className="autoruns-dialog-view">
            {dialogBusy || detailBusy ? <p className="muted">Загружаю диалог...</p> : null}
            {!dialogBusy && !detailBusy && (dialog?.messages.length ?? 0) === 0 ? (
              <p className="muted">Диалог для этого прогона не найден.</p>
            ) : null}
            {(dialog?.messages ?? []).map((item, index) => {
              const role = item.role === "assistant" ? "assistant" : "user";
              return (
                <article key={item.message_id ?? `${role}-${index}`} className={`autoruns-msg ${role}`}>
                  <header>
                    <strong>{role === "assistant" ? "Система" : "Модель/вопрос"}</strong>
                    <div className="autoruns-msg-head-actions">
                      {item.case_id ? <span className="autoruns-msg-case-tag">{item.case_id}</span> : null}
                      {formatDialogStepTag(item) ? (
                        <span className="autoruns-msg-case-tag">{formatDialogStepTag(item)}</span>
                      ) : null}
                      <span>{item.created_at ? formatDateTime(item.created_at) : "нет данных"}</span>
                      {role === "assistant" && !isLiveRunId(selectedRunId) ? (
                        <>
                          <button
                            type="button"
                            className={item.commented ? "autoruns-comment-icon commented" : "autoruns-comment-icon"}
                            onClick={() => openCommentModal(item)}
                            title="\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b"
                            aria-label="\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b"
                          >
                            <CommentBubbleIcon commented={item.commented} />
                          </button>
                          {item.annotation ? (
                            <button
                              type="button"
                              className={item.annotation.resolved ? "autoruns-resolve-toggle resolved" : "autoruns-resolve-toggle"}
                              onClick={() => void toggleAnnotationResolved(item.annotation!, !item.annotation!.resolved)}
                              disabled={annotationResolutionBusyId === item.annotation.annotation_id}
                              title={
                                item.annotation.resolved
                                  ? "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u043d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                                  : "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                              }
                              aria-label={
                                item.annotation.resolved
                                  ? "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u043d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                                  : "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                              }
                            >
                              <CommentResolvedIcon resolved={item.annotation.resolved} />
                            </button>
                          ) : null}
                        </>
                      ) : null}
                    </div>
                  </header>
                  <p>{item.text}</p>
                  {role === "assistant" && item.annotation ? (
                    <div className="autoruns-msg-annotation">
                      <strong>{renderRatingDots(item.annotation.rating)}</strong>
                      <span>{item.annotation.comment}</span>
                      <span className="muted">
                        {item.annotation.manual_case_decision}
                        {item.annotation.annotation_author ? ` | ${item.annotation.annotation_author}` : ""}
                      </span>
                    </div>
                  ) : null}
                  {(item.trace_id || item.reply_type) && (
                    <footer>
                      {item.trace_id ? <span>trace={item.trace_id}</span> : null}
                      {item.reply_type ? <span>reply_type={item.reply_type}</span> : null}
                    </footer>
                  )}
                </article>
              );
            })}
          </div>
        </section>

        {showAssistantMode ? (
          <div className="autoruns-col autoruns-assistant-live-col">
            <AssistantPanel
              sessionId={assistantLiveSessionId}
              conversation={assistantLiveConversation}
              inputValue={assistantLiveInput}
              onInputChange={setAssistantLiveInput}
              selectedContextChip={assistantLiveSelectedChip}
              onSelectContextChip={setAssistantLiveSelectedChip}
              onClearContextChip={() => setAssistantLiveSelectedChip(null)}
              useMock={assistantLiveUseMock}
              onUseMockChange={setAssistantLiveUseMock}
              onSend={sendAssistantLiveMessage}
              onClear={resetAssistantLiveSession}
              onSaveSession={openAssistantLiveSaveModal}
              busy={assistantLiveBusy}
              saveBusy={assistantLiveSaveModal.saving}
              saveDisabled={!assistantLiveSessionId.trim() || assistantLiveConversation.length === 0 || assistantLiveBusy}
              statusText={assistantLiveStatus}
              errorMessage={assistantLiveError}
              showSaveAction
              showCommentAction
              onCommentAssistantMessage={openAssistantLiveCommentModal}
              isAssistantMessageCommented={isAssistantLiveMessageCommented}
              canCommentAssistantMessage={canCommentAssistantLiveMessage}
            />
          </div>
        ) : null}

        {showProgressMode ? (
          <section className="autoruns-col">
            <div className="autoruns-col-header">
              <h3>Прогресс / регресс</h3>
            </div>
            <div className="autoruns-stats-grid">
              <div>
                <span>Последний score</span>
                <strong>{formatScore(history?.stats.latest_score_index ?? null)}</strong>
              </div>
              <div>
                <span>Предыдущий</span>
                <strong>{formatScore(history?.stats.previous_score_index ?? null)}</strong>
              </div>
              <div>
                <span>Тренд</span>
                <strong>{history ? trendLabel(history.stats.trend) : "нет данных"}</strong>
              </div>
              <div>
                <span>Пробелы качества</span>
                <strong>{history?.stats.quality_gap_runs ?? 0}</strong>
              </div>
            </div>
            <h4>Покрытие доменов (история)</h4>
            {renderCoverageRows(history?.stats.domain_coverage ?? [])}
            <h4 style={{ marginTop: 14 }}>Покрытие доменов (выбранный прогон)</h4>
            {renderCoverageRows(runDetail?.coverage.domain_coverage ?? [])}
            <h4 style={{ marginTop: 14 }}>Очереди фиксов пост-анализа</h4>
            {postAnalysisBusy ? <p className="muted">Собираю пост-анализ...</p> : null}
            {!postAnalysisBusy ? (
              <div className="autoruns-stats-grid">
                {Object.entries(postAnalysis?.post_analysis.stats.by_queue ?? {}).map(([queue, total]) => (
                  <div key={queue}>
                    <span>{queue}</span>
                    <strong>{total}</strong>
                  </div>
                ))}
              </div>
            ) : null}
            <div className="autoruns-autogen-list">
              {(postAnalysis?.post_analysis.recommended_regression_candidates ?? []).slice(0, 12).map((item) => (
                <article key={item.annotation_id} className="autoruns-autogen-item">
                  <header>
                    <strong>{item.manual_case_decision}</strong>
                    <span>{item.rating}/5</span>
                  </header>
                  <div className="autoruns-run-meta">
                    {item.domain ?? "неизвестно"} / {item.query_class ?? "неизвестно"}
                  </div>
                  <p>{item.comment}</p>
                </article>
              ))}
              {!postAnalysisBusy && (postAnalysis?.post_analysis.recommended_regression_candidates.length ?? 0) === 0 ? (
                <p className="muted">Рекомендованных кандидатов пока нет.</p>
              ) : null}
            </div>
          </section>
        ) : null}

        {showCommentsMode ? (
          <section className="autoruns-col">
            <div className="autoruns-col-header">
              <h3>Комментарии</h3>
            </div>
              <h4>Размеченные ответы</h4>
              <div className="autoruns-comment-filter-row">
                <label>
                  Фильтр решений
                  <select
                    value={annotationDecisionFilter}
                    onChange={(event) => setAnnotationDecisionFilter(event.target.value as ManualCaseDecision | "all")}
                  >
                    <option value="all">все</option>
                    {(availableManualDecisions.length > 0
                      ? availableManualDecisions
                      : ((manualDecisionSchema?.enum as ManualCaseDecision[] | undefined) ?? [])
                    ).map((decision) => (
                      <option key={decision} value={decision}>
                        {String(((manualDecisionSchema?.labels as Record<string, unknown> | undefined)?.[decision] ?? decision))}
                      </option>
                    ))}
                  </select>
                </label>
                <button
                  type="button"
                  className="tab autoruns-resolved-filter-toggle"
                  onClick={() => setHideResolvedAnnotations((prev) => !prev)}
                >
                  {hideResolvedAnnotations
                    ? "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435"
                    : "\u0421\u043a\u0440\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435"}
                </button>
              </div>
              <div className="autoruns-stats-grid">
                <div>
                  <span>Комментариев</span>
                  <strong>{unifiedVisibleAnnotations.length}</strong>
                </div>
                <div>
                  <span>Средний рейтинг</span>
                  <strong>{annotationsAverageRating === null ? "нет данных" : `${annotationsAverageRating.toFixed(2)} / 5`}</strong>
                </div>
                <div>
                  <span>Последний</span>
                  <strong>
                    {unifiedVisibleAnnotations.length > 0 ? formatDateTime(unifiedVisibleAnnotations[0].updated_at) : "нет данных"}
                  </strong>
                </div>
                <div>
                  <span>Статус</span>
                  <strong>{annotationsBusy ? "обновляю" : "готово"}</strong>
                </div>
              </div>

              <div className="button-row">
                <button type="button" disabled={annotationsBusy} onClick={() => void loadAnnotations()}>
                  {annotationsBusy ? "Обновляю..." : "Обновить список"}
                </button>
                <button type="button" className="tab" disabled={postAnalysisBusy} onClick={() => void loadPostAnalysis()}>
                  {postAnalysisBusy ? "Идет пост-анализ..." : "Обновить пост-анализ"}
                </button>
              </div>

              <div className="autoruns-comments-list">
                {annotationsBusy ? <p className="muted">Загружаю комментарии...</p> : null}
                {!annotationsBusy && unifiedVisibleAnnotations.length === 0 ? (
                  <p className="muted">
                    {annotations.length === 0 && assistantLiveAnnotations.length === 0
                      ? "Пока нет откомментированных ответов."
                      : "\u041d\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043a\u0435\u0439\u0441\u043e\u0432 \u043f\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u0444\u0438\u043b\u044c\u0442\u0440\u0443."}
                  </p>
                ) : null}
                {unifiedVisibleAnnotations.map((item) => {
                  if (item.source === "assistant_live") {
                    const annotation = item.assistant;
                    return (
                      <article key={item.key} className="autoruns-comment-item">
                        <div className="autoruns-comment-head">
                          <strong>{renderRatingDots(annotation.rating)}</strong>
                          <div className="autoruns-comment-head-actions">
                            <span>{formatDateTime(annotation.updated_at)}</span>
                          </div>
                        </div>
                        <div className="autoruns-run-meta">live-session: {annotation.session_id}</div>
                        <div className="autoruns-run-meta">msg={annotation.message_index}</div>
                        <div className="autoruns-run-meta">
                          source=assistant_live
                          {annotation.annotation_author ? ` | author=${annotation.annotation_author}` : ""}
                        </div>
                        {annotation.context.question_text ? <p>Q: {annotation.context.question_text}</p> : null}
                        {annotation.context.answer_text ? <p>A: {annotation.context.answer_text}</p> : null}
                        <p>{annotation.comment}</p>
                      </article>
                    );
                  }
                  const annotation = item.autorun;
                  return (
                    <article
                      key={item.key}
                      className={selectedAnnotationId === annotation.annotation_id ? "autoruns-comment-item selected" : "autoruns-comment-item"}
                      onClick={() => void openAnnotationContext(annotation)}
                      role="button"
                      tabIndex={0}
                      onKeyDown={(event) => {
                        if (event.key === "Enter" || event.key === " ") {
                          event.preventDefault();
                          void openAnnotationContext(annotation);
                        }
                      }}
                    >
                      <div className="autoruns-comment-head">
                        <strong>{renderRatingDots(annotation.rating)}</strong>
                        <div className="autoruns-comment-head-actions">
                          <span>{formatDateTime(annotation.updated_at)}</span>
                          <button
                            type="button"
                            className={annotation.resolved ? "autoruns-resolve-toggle resolved" : "autoruns-resolve-toggle"}
                            onClick={(event) => {
                              event.preventDefault();
                              event.stopPropagation();
                              void toggleAnnotationResolved(annotation, !annotation.resolved);
                            }}
                            disabled={annotationResolutionBusyId === annotation.annotation_id}
                            title={
                              annotation.resolved
                                ? "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u043d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                                : "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                            }
                            aria-label={
                              annotation.resolved
                                ? "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u043d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                                : "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0435\u0439\u0441 \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439"
                            }
                          >
                            <CommentResolvedIcon resolved={annotation.resolved} />
                          </button>
                        </div>
                      </div>
                      <div className="autoruns-run-meta">{annotation.run_id}</div>
                      <div className="autoruns-run-meta">
                        case={annotation.case_id} | msg={annotation.message_index}
                      </div>
                      <div className="autoruns-run-meta">
                        decision={annotation.manual_case_decision}
                        {annotation.annotation_author ? ` | author=${annotation.annotation_author}` : ""}
                      </div>
                      {annotation.resolved_at ? (
                        <div className="autoruns-run-meta">
                          {"\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e"}: {formatDateTime(annotation.resolved_at)}
                          {annotation.resolved_by ? ` | by=${annotation.resolved_by}` : ""}
                        </div>
                      ) : null}
                      {annotation.context.question_text ? <p>Q: {annotation.context.question_text}</p> : null}
                      {annotation.context.answer_text ? <p>A: {annotation.context.answer_text}</p> : null}
                      <p>{annotation.comment}</p>
                    </article>
                  );
                })}
              </div>

              {selectedAnnotation ? (
                <>
                  <h4>Тех-контекст брака</h4>
                  <div className="autoruns-meta-list">
                    <div>
                      <span>trace:</span>
                      <strong>{selectedAnnotation.technical_context.trace_id ?? "нет данных"}</strong>
                    </div>
                    <div>
                      <span>reply_type:</span>
                      <strong>{selectedAnnotation.technical_context.reply_type ?? "нет данных"}</strong>
                    </div>
                    <div>
                      <span>domain:</span>
                      <strong>{selectedAnnotation.technical_context.domain ?? "нет данных"}</strong>
                    </div>
                    <div>
                      <span>query_class:</span>
                      <strong>{selectedAnnotation.technical_context.query_class ?? "нет данных"}</strong>
                    </div>
                  </div>
                  <h4>JSON разбор</h4>
                  <JsonView
                    value={{
                      annotation_id: selectedAnnotation.annotation_id,
                      run_id: selectedAnnotation.run_id,
                      case_id: selectedAnnotation.case_id,
                      message_index: selectedAnnotation.message_index,
                      rating: selectedAnnotation.rating,
                      comment: selectedAnnotation.comment,
                      manual_case_decision: selectedAnnotation.manual_case_decision,
                      annotation_author: selectedAnnotation.annotation_author,
                      resolved: selectedAnnotation.resolved,
                      resolved_at: selectedAnnotation.resolved_at,
                      resolved_by: selectedAnnotation.resolved_by,
                      context: selectedAnnotation.context,
                      technical_context: selectedAnnotation.technical_context,
                      case_summary: selectedAnnotation.case_summary
                        ? {
                            case_id: selectedAnnotation.case_summary.case_id,
                            domain: selectedAnnotation.case_summary.domain,
                            query_class: selectedAnnotation.case_summary.query_class,
                            checks: selectedAnnotation.case_summary.checks,
                            metric_subscores: selectedAnnotation.case_summary.metric_subscores
                          }
                        : null
                    }}
                  />
                </>
              ) : null}

          </section>
        ) : null}
      </div>

      {assistantLiveSaveModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeAssistantLiveSaveModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Сохранить ручную сессию</h3>
            <p className="muted">Технический чат будет сохранен в автопрогоны как пользовательская multi-turn сессия.</p>

            <label>
              Название
              <input
                value={assistantLiveSaveModal.title}
                onChange={(event) => setAssistantLiveSaveModal((prev) => ({ ...prev, title: event.target.value }))}
                placeholder="Например: НДС и склад на март 2020"
                disabled={assistantLiveSaveModal.saving}
              />
            </label>

            {assistantLiveSaveModal.error ? <p className="error-text">{assistantLiveSaveModal.error}</p> : null}

            <div className="button-row">
              <button type="button" onClick={() => void submitAssistantLiveSaveModal()} disabled={assistantLiveSaveModal.saving}>
                {assistantLiveSaveModal.saving ? "Сохраняю..." : "Сохранить"}
              </button>
              <button type="button" className="tab" onClick={() => closeAssistantLiveSaveModal()} disabled={assistantLiveSaveModal.saving}>
                Отмена
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {savedSessionQuestionDeleteModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeSavedSessionQuestionDeleteModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Удалить вопрос</h3>
            <p className="muted">Действительно удалить вопрос из сохраненной пользовательской сессии?</p>
            <p className="autoruns-comment-quote">{savedSessionQuestionDeleteModal.questionText}</p>

            {savedSessionQuestionDeleteModal.error ? <p className="error-text">{savedSessionQuestionDeleteModal.error}</p> : null}

            <div className="button-row">
              <button type="button" onClick={() => void submitSavedSessionQuestionDelete()} disabled={savedSessionQuestionDeleteModal.saving}>
                {savedSessionQuestionDeleteModal.saving ? "Удаляю..." : "Да"}
              </button>
              <button
                type="button"
                className="tab"
                onClick={() => closeSavedSessionQuestionDeleteModal()}
                disabled={savedSessionQuestionDeleteModal.saving}
              >
                Нет
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {autoGenDeleteModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeAutoGenDeleteModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Удалить сохраненный набор</h3>
            <p className="muted">Будет удалена карточка истории и связанный файл кейс-сета на бэке.</p>
            <p className="autoruns-comment-quote">{autoGenDeleteModal.title}</p>

            {autoGenDeleteModal.error ? <p className="error-text">{autoGenDeleteModal.error}</p> : null}

            <div className="button-row">
              <button type="button" onClick={() => void submitAutoGenDeleteModal()} disabled={autoGenDeleteModal.saving}>
                {autoGenDeleteModal.saving ? "Удаляю..." : "Да"}
              </button>
              <button type="button" className="tab" onClick={() => closeAutoGenDeleteModal()} disabled={autoGenDeleteModal.saving}>
                Нет
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {assistantLiveCommentModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeAssistantLiveCommentModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Комментарий к ответу ассистента</h3>
            <p className="muted">Комментарий будет добавлен в общий список комментариев справа с меткой `assistant_live`.</p>

            {assistantLiveCommentModalQuestion ? (
              <details className="autoruns-prompt-details" open>
                <summary>Вопрос пользователя</summary>
                <p className="autoruns-comment-quote">{assistantLiveCommentModalQuestion.text}</p>
              </details>
            ) : null}
            {assistantLiveCommentModalMessage ? (
              <details className="autoruns-prompt-details" open>
                <summary>Ответ ассистента</summary>
                <p className="autoruns-comment-quote">{assistantLiveCommentModalMessage.text}</p>
              </details>
            ) : null}

            <div className="autoruns-rating-row" role="group" aria-label="Рейтинг ответа ассистента">
              {[1, 2, 3, 4, 5].map((value) => (
                <button
                  key={value}
                  type="button"
                  className={assistantLiveCommentModal.rating >= value ? "autoruns-rating-dot active" : "autoruns-rating-dot"}
                  onClick={() => setAssistantLiveCommentModal((prev) => ({ ...prev, rating: value }))}
                  disabled={assistantLiveCommentModal.saving}
                  aria-label={`Оценка ${value}`}
                >
                  {assistantLiveCommentModal.rating >= value ? "●" : "○"}
                </button>
              ))}
            </div>

            <div className="autoruns-form-grid">
              <label>
                Автор комментария
                <input
                  value={assistantLiveCommentModal.annotationAuthor}
                  onChange={(event) => setAssistantLiveCommentModal((prev) => ({ ...prev, annotationAuthor: event.target.value }))}
                  placeholder="manual_reviewer"
                  disabled={assistantLiveCommentModal.saving}
                />
              </label>
            </div>

            <label>
              Комментарий
              <textarea
                value={assistantLiveCommentModal.comment}
                onChange={(event) => setAssistantLiveCommentModal((prev) => ({ ...prev, comment: event.target.value }))}
                placeholder="Что именно не так в ответе и что нужно исправить."
                rows={4}
                disabled={assistantLiveCommentModal.saving}
              />
            </label>

            {assistantLiveCommentModal.error ? <p className="error-text">{assistantLiveCommentModal.error}</p> : null}

            <div className="button-row">
              <button type="button" onClick={() => void submitAssistantLiveCommentModal()} disabled={assistantLiveCommentModal.saving}>
                {assistantLiveCommentModal.saving ? "Сохраняю..." : "Готово"}
              </button>
              <button
                type="button"
                className="tab"
                onClick={() => closeAssistantLiveCommentModal()}
                disabled={assistantLiveCommentModal.saving}
              >
                Отмена
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {commentModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeCommentModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Комментарий к ответу системы</h3>
            <p className="muted">Оцените ответ по 5-балльной шкале и добавьте комментарий по браку.</p>

            {modalMessage ? (
              <>
                <details className="autoruns-prompt-details" open>
                  <summary>{"\u0412\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f"}</summary>
                  <p className="autoruns-comment-quote">
                    {modalQuestion?.text ?? "\u0412\u043e\u043f\u0440\u043e\u0441 \u0432 \u0434\u0438\u0430\u043b\u043e\u0433\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d."}
                  </p>
                </details>
                <details className="autoruns-prompt-details" open>
                  <summary>{"\u041e\u0442\u0432\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b"}</summary>
                  <p className="autoruns-comment-quote">{modalMessage.text}</p>
                </details>
              </>
            ) : null}

            <div className="autoruns-rating-row" role="group" aria-label="Рейтинг ответа">
              {[1, 2, 3, 4, 5].map((value) => (
                <button
                  key={value}
                  type="button"
                  className={commentModal.rating >= value ? "autoruns-rating-dot active" : "autoruns-rating-dot"}
                  onClick={() => setCommentModal((prev) => ({ ...prev, rating: value }))}
                  disabled={commentModal.saving}
                  aria-label={`Оценка ${value}`}
                >
                  {commentModal.rating >= value ? "●" : "○"}
                </button>
              ))}
            </div>

            <div className="autoruns-form-grid">
              <label>
                Решение по кейсу
                <select
                  value={commentModal.manualCaseDecision}
                  onChange={(event) =>
                    setCommentModal((prev) => ({ ...prev, manualCaseDecision: event.target.value as ManualCaseDecision }))
                  }
                  disabled={commentModal.saving}
                >
                  {(availableManualDecisions.length > 0
                    ? availableManualDecisions
                    : ((manualDecisionSchema?.enum as ManualCaseDecision[] | undefined) ?? [DEFAULT_MANUAL_DECISION])
                  ).map((decision) => (
                    <option key={decision} value={decision}>
                      {String(((manualDecisionSchema?.labels as Record<string, unknown> | undefined)?.[decision] ?? decision))}
                    </option>
                  ))}
                </select>
              </label>
              <label>
                Автор комментария
                <input
                  value={commentModal.annotationAuthor}
                  onChange={(event) => setCommentModal((prev) => ({ ...prev, annotationAuthor: event.target.value }))}
                  placeholder="manual_reviewer"
                  disabled={commentModal.saving}
                />
              </label>
            </div>

            <label>
              Комментарий
              <textarea
                value={commentModal.comment}
                onChange={(event) => setCommentModal((prev) => ({ ...prev, comment: event.target.value }))}
                placeholder="Почему ответ бракованный, что именно пошло не так, какие технические детали проверить."
                rows={4}
                disabled={commentModal.saving}
              />
            </label>

            {commentModal.error ? <p className="error-text">{commentModal.error}</p> : null}

            <div className="button-row">
              <button type="button" onClick={() => void submitCommentModal()} disabled={commentModal.saving}>
                {commentModal.saving ? "Сохраняю..." : "Готово"}
              </button>
              <button type="button" className="tab" onClick={() => closeCommentModal()} disabled={commentModal.saving}>
                Отмена
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </PanelFrame>
  );
}


