import { useEffect, useMemo, useRef, useState } from "react";
import { apiClient } from "./api/client";
import { AssistantSamPanel } from "./components/AssistantSamPanel";
import { AutoRunsHistoryPanel } from "./components/AutoRunsHistoryPanel";
import { AssistantPanel } from "./components/AssistantPanel";
import { ConnectionPanel } from "./components/ConnectionPanel";
import { HistoryPanel } from "./components/HistoryPanel";
import { MetricsPanel } from "./components/MetricsPanel";
import { OutputPanel } from "./components/OutputPanel";
import { PanelFrame } from "./components/PanelFrame";
import { PromptPanel } from "./components/PromptPanel";
import { QueryPanel } from "./components/QueryPanel";
import { RuntimePanel } from "./components/RuntimePanel";
import { DEFAULT_CONNECTION, DEFAULT_PROMPTS, DEFAULT_QUERY } from "./state/defaults";
import { designConfig } from "../../../designconfig";
import type {
  AssistantConversationItem,
  AssistantAnnotationRecord,
  AssistantSelectionChip,
  ConnectionState,
  HistoryItem,
  NormalizeResultState,
  PromptState,
  QueryState,
  RuntimeRun,
  TabKey,
  UiMode
} from "./state/types";

const SESSION_CONFIG_KEY = "ndc_normalizer_session_config_v1";
const AUTORUNS_LAYOUT_CONFIG_KEY = "ndc_autoruns_layout_config_v1";
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];
const DEFAULT_UI_MODE: UiMode = "assistant";
const AUTOLOAD_PROMPT_VERSION = "normalizer_v2_0_2";
const ASSISTANT_PROMPT_VERSION = "address_query_runtime_v1";
const TAB_KEYS: TabKey[] = ["normalized", "fragments", "scope", "flags", "route", "raw", "validation", "logs"];
const DEFAULT_ASSISTANT_ANNOTATION_AUTHOR = "manual_reviewer";

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

function withTs(message: string): string {
  return `[${new Date().toLocaleTimeString("ru-RU")}] ${message}`;
}

function diffPrompts(current: PromptState, previous: PromptState | null): string {
  if (!previous) return "Previous preset is not selected.";
  const fields: Array<keyof PromptState> = ["systemPrompt", "developerPrompt", "domainPrompt", "schemaNotes", "fewShotExamples"];
  const changed = fields
    .filter((field) => current[field] !== previous[field])
    .map((field) => `${field}: ${Math.abs(current[field].length - previous[field].length)} chars delta`);
  if (changed.length === 0) return "No changes against previous preset.";
  return `Changed fields: ${changed.length}. ${changed.join(" | ")}`;
}

function buildAssistantFollowupMessage(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}`;
}

export default function App() {
  const [connection, setConnection] = useState<ConnectionState>(DEFAULT_CONNECTION);
  const [prompts, setPrompts] = useState<PromptState>(DEFAULT_PROMPTS);
  const [query, setQuery] = useState<QueryState>(DEFAULT_QUERY);
  const [result, setResult] = useState<NormalizeResultState | null>(null);
  const [historyItems, setHistoryItems] = useState<HistoryItem[]>([]);
  const [appLogs, setAppLogs] = useState<string[]>([]);
  const [activeTab, setActiveTab] = useState<TabKey>("normalized");
  const [busy, setBusy] = useState(false);
  const [modelsBusy, setModelsBusy] = useState(false);
  const [modelOptions, setModelOptions] = useState<string[]>([]);
  const [connectionStatus, setConnectionStatus] = useState("");
  const [presetList, setPresetList] = useState<
    Array<{
      id: string;
      name: string;
      prompt_version: string;
      systemPrompt: string;
      developerPrompt: string;
      domainPrompt: string;
      schemaNotes?: string;
      fewShotExamples?: string;
    }>
  >([]);
  const [selectedPresetId, setSelectedPresetId] = useState("");
  const [presetName, setPresetName] = useState("NDC custom preset");
  const [previousPreset, setPreviousPreset] = useState<PromptState | null>(null);
  const [diffSummary, setDiffSummary] = useState("");
  const [useMock, setUseMock] = useState(false);
  const [runs, setRuns] = useState<RuntimeRun[]>([]);
  const [selectedRunId, setSelectedRunId] = useState("");
  const [runTrace, setRunTrace] = useState<unknown[]>([]);
  const [evalBusy, setEvalBusy] = useState(false);
  const [evalReport, setEvalReport] = useState<unknown>(null);
  const [lastError, setLastError] = useState("");

  const [uiMode, setUiMode] = useState<UiMode>(DEFAULT_UI_MODE);
  const [showAutorunsAssistantMode, setShowAutorunsAssistantMode] = useState(true);
  const [showAutorunsDecompositionMode, setShowAutorunsDecompositionMode] = useState(true);
  const [showAutorunsProgressMode, setShowAutorunsProgressMode] = useState(true);
  const [showAutorunsCommentsMode, setShowAutorunsCommentsMode] = useState(true);
  const [showAssistantConnectionMode, setShowAssistantConnectionMode] = useState(true);
  const [showAssistantPromptMode, setShowAssistantPromptMode] = useState(true);
  const [showAssistantChatMode, setShowAssistantChatMode] = useState(true);
  const [showAssistantCommentsMode, setShowAssistantCommentsMode] = useState(true);
  const [showAssistantSamMode, setShowAssistantSamMode] = useState(true);
  const [showDecompositionConnectionMode, setShowDecompositionConnectionMode] = useState(true);
  const [showDecompositionPromptMode, setShowDecompositionPromptMode] = useState(true);
  const [showDecompositionQueryMode, setShowDecompositionQueryMode] = useState(true);
  const [showDecompositionOutputMode, setShowDecompositionOutputMode] = useState(true);
  const [showDecompositionMetricsMode, setShowDecompositionMetricsMode] = useState(true);
  const [showDecompositionHistoryMode, setShowDecompositionHistoryMode] = useState(true);
  const [showDecompositionRuntimeMode, setShowDecompositionRuntimeMode] = useState(true);
  const [assistantSessionId, setAssistantSessionId] = useState("");
  const [assistantConversation, setAssistantConversation] = useState<AssistantConversationItem[]>([]);
  const [assistantInput, setAssistantInput] = useState("");
  const [assistantSelectedChip, setAssistantSelectedChip] = useState<AssistantSelectionChip | null>(null);
  const [assistantBusy, setAssistantBusy] = useState(false);
  const [assistantStatus, setAssistantStatus] = useState("");
  const [assistantError, setAssistantError] = useState("");
  const [assistantAnnotations, setAssistantAnnotations] = useState<AssistantAnnotationRecord[]>([]);
  const [assistantAnnotationsBusy, setAssistantAnnotationsBusy] = useState(false);
  const [assistantCommentModal, setAssistantCommentModal] = useState<AssistantCommentModalState>({
    open: false,
    messageIndex: -1,
    rating: 3,
    comment: "",
    annotationAuthor: DEFAULT_ASSISTANT_ANNOTATION_AUTHOR,
    saving: false,
    error: ""
  });
  const presetAutoloadDoneRef = useRef(false);
  const skipPresetAutoloadRef = useRef(false);
  const sharedConnectionSyncReadyRef = useRef(false);

  useEffect(() => {
    const root = document.documentElement;
    const { colors } = designConfig;
    root.style.setProperty("--rgb-background", colors.backgroundRgb);
    root.style.setProperty("--rgb-surface-main", colors.mainSurfaceRgb);
    root.style.setProperty("--rgb-surface-horizontal", colors.horizontalSurfaceRgb);
    root.style.setProperty("--rgb-surface-focus", colors.focusSurfaceRgb);
    root.style.setProperty("--rgb-assistant-chip", colors.assistantChipRgb);
    root.style.setProperty("--rgb-assistant-chip-hover", colors.assistantChipHoverRgb);
    root.style.setProperty("--rgb-assistant-chip-selected", colors.assistantChipSelectedRgb);
    root.style.setProperty("--rgb-assistant-chip-selected-text", colors.assistantChipSelectedTextRgb);
    root.style.setProperty("--rgb-active", colors.activeRgb);
    root.style.setProperty("--rgb-active-text", colors.activeTextRgb);
    root.style.setProperty("--rgb-text-main", colors.textMainRgb);
    root.style.setProperty("--rgb-text-muted", colors.textMutedRgb);
    root.style.setProperty("--rgb-danger", colors.dangerRgb);
    root.style.setProperty("--rgb-scrollbar-track", colors.scrollbarTrackRgb);
    root.style.setProperty("--rgb-scrollbar-thumb", colors.scrollbarThumbRgb);
    root.style.setProperty("--rgb-scrollbar-thumb-hover", colors.scrollbarThumbHoverRgb);
    root.style.setProperty("--mode-column-width", `${designConfig.layout.modeColumnWidthPx}px`);
    root.style.setProperty("--mode-toggle-width", `${designConfig.layout.modeToggleWidthPx}px`);
  }, []);

  const log = (message: string) => {
    setAppLogs((prev) => [withTs(message), ...prev].slice(0, 300));
  };

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

  useEffect(() => {
    const bootstrapSharedConnection = async () => {
      const cached = localStorage.getItem(SESSION_CONFIG_KEY);
      if (cached) {
        try {
          const parsed = JSON.parse(cached) as Partial<ConnectionState>;
          setConnection((prev) => ({
            ...prev,
            llmProvider: parsed.llmProvider === "local" ? "local" : "openai",
            model: parsed.model ?? prev.model,
            baseUrl: parsed.baseUrl ?? prev.baseUrl,
            temperature: parsed.temperature ?? prev.temperature,
            maxOutputTokens: parsed.maxOutputTokens ?? prev.maxOutputTokens
          }));
        } catch {
          // ignore broken local cache
        }
      }

      try {
        const payload = await apiClient.loadSharedConnectionConfig();
        if (payload.connection && payload.connection.llmProvider === "local") {
          setConnection((prev) => ({
            ...prev,
            llmProvider: "local",
            model: payload.connection?.model ?? prev.model,
            baseUrl: payload.connection?.baseUrl ?? prev.baseUrl,
            temperature: payload.connection?.temperature ?? prev.temperature,
            maxOutputTokens: payload.connection?.maxOutputTokens ?? prev.maxOutputTokens
          }));
          log(`Shared local LLM config loaded: ${payload.connection.model}`);
        }
      } catch (error) {
        log(`Shared local config load error: ${error instanceof Error ? error.message : String(error)}`);
      } finally {
        sharedConnectionSyncReadyRef.current = true;
      }
    };

    void bootstrapSharedConnection();

    const cachedAutorunsLayout = localStorage.getItem(AUTORUNS_LAYOUT_CONFIG_KEY);
    if (cachedAutorunsLayout) {
      try {
        const parsed = JSON.parse(cachedAutorunsLayout) as {
          uiMode?: UiMode;
          activeTab?: TabKey;
          showAutorunsAssistantMode?: boolean;
          showAutorunsDecompositionMode?: boolean;
          showAutorunsProgressMode?: boolean;
          showAutorunsCommentsMode?: boolean;
          showAssistantConnectionMode?: boolean;
          showAssistantPromptMode?: boolean;
          showAssistantChatMode?: boolean;
          showAssistantCommentsMode?: boolean;
          showAssistantSamMode?: boolean;
          showDecompositionConnectionMode?: boolean;
          showDecompositionPromptMode?: boolean;
          showDecompositionQueryMode?: boolean;
          showDecompositionOutputMode?: boolean;
          showDecompositionMetricsMode?: boolean;
          showDecompositionHistoryMode?: boolean;
          showDecompositionRuntimeMode?: boolean;
          prompts?: PromptState;
        };
        if (parsed.uiMode === "assistant" || parsed.uiMode === "decomposition" || parsed.uiMode === "autoruns") {
          setUiMode(parsed.uiMode);
        }
        if (parsed.activeTab && TAB_KEYS.includes(parsed.activeTab)) {
          setActiveTab(parsed.activeTab);
        }
        if (typeof parsed.showAutorunsAssistantMode === "boolean") {
          setShowAutorunsAssistantMode(parsed.showAutorunsAssistantMode);
        }
        if (typeof parsed.showAutorunsDecompositionMode === "boolean") {
          setShowAutorunsDecompositionMode(parsed.showAutorunsDecompositionMode);
        }
        if (typeof parsed.showAutorunsProgressMode === "boolean") {
          setShowAutorunsProgressMode(parsed.showAutorunsProgressMode);
        }
        if (typeof parsed.showAutorunsCommentsMode === "boolean") {
          setShowAutorunsCommentsMode(parsed.showAutorunsCommentsMode);
        }
        if (typeof parsed.showAssistantConnectionMode === "boolean") {
          setShowAssistantConnectionMode(parsed.showAssistantConnectionMode);
        }
        if (typeof parsed.showAssistantPromptMode === "boolean") {
          setShowAssistantPromptMode(parsed.showAssistantPromptMode);
        }
        if (typeof parsed.showAssistantChatMode === "boolean") {
          setShowAssistantChatMode(parsed.showAssistantChatMode);
        }
        if (typeof parsed.showAssistantCommentsMode === "boolean") {
          setShowAssistantCommentsMode(parsed.showAssistantCommentsMode);
        }
        if (typeof parsed.showAssistantSamMode === "boolean") {
          setShowAssistantSamMode(parsed.showAssistantSamMode);
        }
        if (typeof parsed.showDecompositionConnectionMode === "boolean") {
          setShowDecompositionConnectionMode(parsed.showDecompositionConnectionMode);
        }
        if (typeof parsed.showDecompositionPromptMode === "boolean") {
          setShowDecompositionPromptMode(parsed.showDecompositionPromptMode);
        }
        if (typeof parsed.showDecompositionQueryMode === "boolean") {
          setShowDecompositionQueryMode(parsed.showDecompositionQueryMode);
        }
        if (typeof parsed.showDecompositionOutputMode === "boolean") {
          setShowDecompositionOutputMode(parsed.showDecompositionOutputMode);
        }
        if (typeof parsed.showDecompositionMetricsMode === "boolean") {
          setShowDecompositionMetricsMode(parsed.showDecompositionMetricsMode);
        }
        if (typeof parsed.showDecompositionHistoryMode === "boolean") {
          setShowDecompositionHistoryMode(parsed.showDecompositionHistoryMode);
        }
        if (typeof parsed.showDecompositionRuntimeMode === "boolean") {
          setShowDecompositionRuntimeMode(parsed.showDecompositionRuntimeMode);
        }
        if (parsed.prompts) {
          setPrompts((prev) => ({
            ...prev,
            ...parsed.prompts
          }));
          skipPresetAutoloadRef.current = true;
        }
      } catch {
        // ignore broken local cache
      }
    }

    void refreshHistory();
    void refreshPresets();
    void refreshRuns();
  }, []);

  useEffect(() => {
    if (!sharedConnectionSyncReadyRef.current) {
      return;
    }
    if (connection.llmProvider !== "local") {
      return;
    }
    const timer = window.setTimeout(() => {
      void apiClient
        .saveSharedConnectionConfig(connection)
        .catch((error) =>
          log(`Shared local config sync error: ${error instanceof Error ? error.message : String(error)}`)
        );
    }, 250);
    return () => window.clearTimeout(timer);
  }, [connection.baseUrl, connection.llmProvider, connection.maxOutputTokens, connection.model, connection.temperature]);

  async function refreshHistory() {
    try {
      const payload = await apiClient.loadHistory();
      setHistoryItems(payload.items ?? []);
    } catch (error) {
      log(`History load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function refreshPresets() {
    try {
      const payload = await apiClient.loadPresets();
      const presets = payload.presets ?? [];
      setPresetList(presets);
      if (skipPresetAutoloadRef.current) {
        presetAutoloadDoneRef.current = true;
        return;
      }
      if (presetAutoloadDoneRef.current) {
        return;
      }
      const presetForAutoload =
        presets.find((item) => item.prompt_version === AUTOLOAD_PROMPT_VERSION) ??
        presets.find((item) => item.id === "default-normalizer-v2_0_2");
      if (!presetForAutoload) {
        presetAutoloadDoneRef.current = true;
        log(`Preset autoload skipped: ${AUTOLOAD_PROMPT_VERSION} not found.`);
        return;
      }
      setSelectedPresetId(presetForAutoload.id);
      setPreviousPreset(prompts);
      setPrompts({
        systemPrompt: presetForAutoload.systemPrompt,
        developerPrompt: presetForAutoload.developerPrompt,
        domainPrompt: presetForAutoload.domainPrompt,
        schemaNotes: presetForAutoload.schemaNotes ?? "",
        fewShotExamples: presetForAutoload.fewShotExamples ?? ""
      });
      presetAutoloadDoneRef.current = true;
      log(`Preset autoloaded: ${presetForAutoload.name} (${presetForAutoload.prompt_version}).`);
    } catch (error) {
      log(`Presets load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function refreshRuns() {
    try {
      const payload = await apiClient.listRuns();
      setRuns(payload.items ?? []);
    } catch (error) {
      log(`Runs load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  function saveLocalConfig() {
    localStorage.setItem(
      SESSION_CONFIG_KEY,
      JSON.stringify({
        model: connection.model,
        llmProvider: connection.llmProvider,
        baseUrl: connection.baseUrl,
        temperature: connection.temperature,
        maxOutputTokens: connection.maxOutputTokens
      })
    );
    if (connection.llmProvider === "local") {
      void apiClient
        .saveSharedConnectionConfig(connection)
        .then(() => {
          log("Local config saved and synced to shared agent config (without API key).");
        })
        .catch((error) => {
          log(`Local config saved, but shared sync failed: ${error instanceof Error ? error.message : String(error)}`);
        });
      return;
    }
    log("Local config saved (without API key).");
  }

  function saveAutorunsLayout() {
    localStorage.setItem(
      AUTORUNS_LAYOUT_CONFIG_KEY,
      JSON.stringify({
        uiMode,
        activeTab,
        showAutorunsAssistantMode,
        showAutorunsDecompositionMode,
        showAutorunsProgressMode,
        showAutorunsCommentsMode,
        showAssistantConnectionMode,
        showAssistantPromptMode,
        showAssistantChatMode,
        showAssistantCommentsMode,
        showAssistantSamMode,
        showDecompositionConnectionMode,
        showDecompositionPromptMode,
        showDecompositionQueryMode,
        showDecompositionOutputMode,
        showDecompositionMetricsMode,
        showDecompositionHistoryMode,
        showDecompositionRuntimeMode,
        prompts
      })
    );
    window.dispatchEvent(new CustomEvent(AUTORUNS_SAVE_EVENT));
    log("UI layout and prompts saved.");
  }

  async function testConnection() {
    setBusy(true);
    setLastError("");
    try {
      const payload = await apiClient.testConnection(connection);
      if (payload.provider === "local") {
        if (payload.model_found === true) {
          setConnectionStatus(`LOCAL OK - ${payload.model}`);
          log(`Local model is available: ${payload.model} (catalog size=${payload.models_count ?? "n/a"}).`);
        } else if (payload.model_found === false) {
          setConnectionStatus(`LOCAL OK, model not loaded - ${payload.model}`);
          log(
            `Local server is reachable, but model '${payload.model}' is not in loaded catalog. ` +
              `Use 'Load model list' and select one of loaded models.`
          );
        } else {
          setConnectionStatus(`LOCAL OK (model list unavailable) - ${payload.model}`);
          log("Local server is reachable, but model catalog could not be verified.");
        }
      } else {
        setConnectionStatus(`OPENAI OK - ${payload.model}`);
        log(`OpenAI connection ok: ${payload.model}`);
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setConnectionStatus("Connection error");
      setLastError(`Test connection: ${message}`);
      log(`Test connection error: ${message}`);
    } finally {
      setBusy(false);
    }
  }

  async function reloadModels() {
    setModelsBusy(true);
    try {
      const payload = await apiClient.listModels(connection);
      const models = payload.models ?? [];
      setModelOptions(models);
      if (models.length > 0) {
        setConnection((prev) => {
          if (prev.model && models.includes(prev.model)) {
            return prev;
          }
          return { ...prev, model: models[0] };
        });
      }
      log(`Model catalog loaded (${connection.llmProvider}): ${models.length} items.`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      log(`Load model list error: ${message}`);
    } finally {
      setModelsBusy(false);
    }
  }

  useEffect(() => {
    setModelOptions([]);
  }, [connection.llmProvider, connection.baseUrl]);

  async function normalize(saveAsCase: boolean) {
    setBusy(true);
    setLastError("");
    try {
      const payload = await apiClient.normalize({
        connection,
        prompts,
        promptVersion: "normalizer_v2_0_2",
        query: {
          userQuestion: query.userQuestion,
          periodHint: query.periodHint,
          businessContext: query.businessContext,
          expectedRoute: query.expectedRoute
        },
        saveAsTestCase: saveAsCase,
        useMock
      });
      setResult(payload);
      setActiveTab("normalized");
      log(`Normalize done: trace=${payload.trace_id}, validation=${payload.validation.passed ? "passed" : "failed"}`);
      void refreshHistory();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setLastError(`Normalize: ${message}`);
      log(`Normalize error: ${message}`);
    } finally {
      setBusy(false);
    }
  }

  function loadSelectedPreset() {
    const selected = presetList.find((item) => item.id === selectedPresetId);
    if (!selected) {
      log("Preset is not selected.");
      return;
    }
    setPreviousPreset(prompts);
    setPrompts({
      systemPrompt: selected.systemPrompt,
      developerPrompt: selected.developerPrompt,
      domainPrompt: selected.domainPrompt,
      schemaNotes: selected.schemaNotes ?? "",
      fewShotExamples: selected.fewShotExamples ?? ""
    });
    log(`Preset loaded: ${selected.name}`);
  }

  async function savePreset() {
    try {
      await apiClient.savePreset({
        name: presetName || "NDC preset",
        prompt_version: "normalizer_v2_0_2",
        systemPrompt: prompts.systemPrompt,
        developerPrompt: prompts.developerPrompt,
        domainPrompt: prompts.domainPrompt,
        schemaNotes: prompts.schemaNotes,
        fewShotExamples: prompts.fewShotExamples
      });
      log("Preset saved.");
      await refreshPresets();
    } catch (error) {
      log(`Preset save error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  function resetDefaults() {
    setPrompts(DEFAULT_PROMPTS);
    log("Prompt panel reset to defaults.");
  }

  function diffWithPrevious() {
    const summary = diffPrompts(prompts, previousPreset);
    setDiffSummary(summary);
    log(summary);
  }

  function applyBatchFormat() {
    const formatted = query.batchQuestionsRaw
      .split(";")
      .map((item) => item.trim())
      .filter(Boolean)
      .join("\n\n");
    if (!formatted) {
      return;
    }
    setQuery((prev) => ({
      ...prev,
      batchQuestionsRaw: formatted
    }));
    log("Batch field formatted: `;` converted to blank-line separators.");
  }

  async function openTrace(traceId: string) {
    try {
      const payload = await apiClient.loadTrace(traceId);
      const trace = payload.trace as Record<string, unknown>;
      const normalized = (trace.parsed_normalized_json ?? null) as NormalizeResultState["normalized"];
      setResult({
        trace_id: String(trace.trace_id ?? traceId),
        ok: Boolean((trace.validation_result as { passed?: boolean } | undefined)?.passed),
        normalized,
        route_hint_summary:
          (trace.route_hint_summary as NormalizeResultState["route_hint_summary"] | undefined) ??
          (normalized
            ? {
                route_hint: (normalized as { route_hint?: string }).route_hint ?? null,
                confidence: (normalized as { confidence?: { route_hint?: string } }).confidence?.route_hint ?? null
              }
            : null),
        raw_model_output: trace.raw_model_response ?? {},
        validation: (trace.validation_result as NormalizeResultState["validation"]) ?? { passed: false, errors: ["validation not found"] },
        usage: (trace.usage as NormalizeResultState["usage"]) ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
        latency_ms: Number(trace.latency_ms ?? 0),
        prompt_version: String(trace.prompt_version ?? "unknown"),
        schema_version: String(trace.schema_version ?? "unknown")
      });
      setActiveTab("raw");
      setLastError("");
      log(`Trace opened: ${traceId}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setLastError(`Trace: ${message}`);
      log(`Trace open error ${traceId}: ${message}`);
    }
  }

  async function startRun() {
    try {
      const payload = await apiClient.startRun();
      setSelectedRunId(payload.run.runId);
      log(`Run started: ${payload.run.runId}`);
      log("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button.");
      await refreshRuns();
    } catch (error) {
      log(`Run start error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function finishRun() {
    if (!selectedRunId) return;
    try {
      await apiClient.finishRun(selectedRunId);
      log(`Run finished: ${selectedRunId}`);
      await refreshRuns();
    } catch (error) {
      log(`Run finish error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function runEval() {
    setEvalBusy(true);
    setLastError("");
    try {
      log("Starting eval in v2 contour.");
      const rawQuestions = query.batchQuestionsRaw.trim() || query.userQuestion.trim();
      if (!rawQuestions) {
        throw new Error("Fill batch field or Raw user question first.");
      }
      const payload = await apiClient.runEval({
        connection,
        prompts,
        promptVersion: "normalizer_v2_0_2",
        mode: "single-pass-strict",
        rawQuestions,
        useMock
      });
      setEvalReport(payload.report);
      log("Eval v2.0.2 run finished.");
      const report = payload.report as { metrics?: Record<string, unknown>; run_id?: string };
      if (report.run_id) {
        log(`Eval run id: ${report.run_id}`);
      }
      if (report.metrics) {
        const metrics = report.metrics;
        log(
          `Eval metrics v2.0.2: schema=${metrics.schema_validation_pass_rate ?? "n/a"}%, route_accuracy=${metrics.route_resolution_accuracy ?? "n/a"}%, no_route_precision=${metrics.no_route_precision ?? "n/a"}%, state_consistency=${metrics.execution_state_consistency_rate ?? "n/a"}%`
        );
      }
      await refreshHistory();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      if (message.includes("Legacy eval runner supports normalized_query_v1 only")) {
        setEvalReport({
          status: "plan_only",
          prompt_version: "normalizer_v2",
          reason: "backend eval runner is still legacy-v1 only",
          plan_file: "reports/v2_pilot_eval_plan.md",
          next_steps: [
            "run cheap mock sanity for schema/fragment/scope",
            "run small real batch (10-15 messages, temperature=0)",
            "run challenge-30 replay with v2 metrics"
          ]
        });
        log("Backend is legacy-only for eval right now. Showing v2 pilot plan.");
      } else {
        setLastError(`Eval: ${message}`);
        log(`Eval run error: ${message}`);
      }
    } finally {
      setEvalBusy(false);
    }
  }

  async function copyEvalReport() {
    try {
      const text = JSON.stringify(evalReport ?? {}, null, 2);
      await navigator.clipboard.writeText(text);
      log("Eval report copied to clipboard.");
    } catch (error) {
      log(`Eval report copy error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  const assistantAnnotationsByMessageId = useMemo(() => {
    const map = new Map<string, AssistantAnnotationRecord>();
    for (const item of assistantAnnotations) {
      if (item.message_id) {
        map.set(item.message_id, item);
      }
    }
    return map;
  }, [assistantAnnotations]);

  const assistantCommentModalMessage =
    assistantCommentModal.messageIndex >= 0 ? assistantConversation[assistantCommentModal.messageIndex] ?? null : null;

  const assistantCommentModalQuestion = useMemo(() => {
    if (assistantCommentModal.messageIndex < 0) return null;
    for (let index = assistantCommentModal.messageIndex - 1; index >= 0; index -= 1) {
      const candidate = assistantConversation[index];
      if (candidate?.role === "user") {
        return candidate;
      }
    }
    return null;
  }, [assistantCommentModal.messageIndex, assistantConversation]);

  async function loadAssistantAnnotationsForSession(sessionId: string): Promise<void> {
    if (!sessionId.trim()) {
      setAssistantAnnotations([]);
      return;
    }
    setAssistantAnnotationsBusy(true);
    try {
      const payload = await apiClient.loadAssistantAnnotations({
        session_id: sessionId,
        limit: 400
      });
      setAssistantAnnotations(payload.items ?? []);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      log(`Assistant annotations load error: ${message}`);
    } finally {
      setAssistantAnnotationsBusy(false);
    }
  }

  function closeAssistantCommentModal(options?: { force?: boolean }) {
    setAssistantCommentModal((prev) => {
      if (prev.saving && !options?.force) {
        return prev;
      }
      return {
        open: false,
        messageIndex: -1,
        rating: 3,
        comment: "",
        annotationAuthor: DEFAULT_ASSISTANT_ANNOTATION_AUTHOR,
        saving: false,
        error: ""
      };
    });
  }

  function openAssistantCommentModal(item: AssistantConversationItem, index: number): void {
    if (item.role !== "assistant") return;
    const sessionIdFromState = assistantSessionId.trim();
    const sessionIdFromItem = String(item.session_id ?? "").trim();
    const resolvedSessionId = sessionIdFromState || sessionIdFromItem;
    if (!resolvedSessionId) {
      setAssistantError("Сначала получите ответ ассистента в активной сессии.");
      return;
    }
    if (!sessionIdFromState && sessionIdFromItem) {
      setAssistantSessionId(sessionIdFromItem);
    }
    const existing = assistantAnnotationsByMessageId.get(item.message_id) ?? null;
    setAssistantCommentModal({
      open: true,
      messageIndex: index,
      rating: existing?.rating ?? 3,
      comment: existing?.comment ?? "",
      annotationAuthor: existing?.annotation_author ?? DEFAULT_ASSISTANT_ANNOTATION_AUTHOR,
      saving: false,
      error: ""
    });
  }

  function canCommentAssistantMessage(item: AssistantConversationItem): boolean {
    return item.role === "assistant";
  }

  function isAssistantMessageCommented(item: AssistantConversationItem): boolean {
    return item.role === "assistant" && assistantAnnotationsByMessageId.has(item.message_id);
  }

  async function submitAssistantCommentModal(): Promise<void> {
    if (!assistantSessionId.trim()) {
      setAssistantCommentModal((prev) => ({ ...prev, error: "Сессия ассистента не найдена." }));
      return;
    }
    if (assistantCommentModal.messageIndex < 0) {
      return;
    }
    if (!assistantCommentModal.comment.trim()) {
      setAssistantCommentModal((prev) => ({ ...prev, error: "Добавьте комментарий." }));
      return;
    }

    setAssistantCommentModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const payload = await apiClient.saveAssistantAnnotation({
        session_id: assistantSessionId,
        message_index: assistantCommentModal.messageIndex,
        rating: assistantCommentModal.rating,
        comment: assistantCommentModal.comment.trim(),
        annotation_author: assistantCommentModal.annotationAuthor.trim() || undefined
      });
      setAssistantAnnotations((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));
      });
      closeAssistantCommentModal({ force: true });
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantCommentModal((prev) => ({ ...prev, saving: false, error: message }));
    }
  }

  function resetAssistantSession() {
    setAssistantSessionId("");
    setAssistantConversation([]);
    setAssistantInput("");
    setAssistantSelectedChip(null);
    setAssistantStatus("");
    setAssistantError("");
    setAssistantAnnotations([]);
    closeAssistantCommentModal({ force: true });
    log("Assistant session reset.");
  }

  async function sendAssistantMessage() {
    const userMessage = buildAssistantFollowupMessage(assistantInput, assistantSelectedChip);
    if (!userMessage) {
      return;
    }

    setAssistantBusy(true);
    setAssistantError("");
    setAssistantInput("");
      setAssistantConversation((prev) => [
      ...prev,
      {
        message_id: `local-${Date.now()}`,
        session_id: assistantSessionId || "pending",
        role: "user",
        text: userMessage,
        reply_type: null,
        created_at: new Date().toISOString(),
        trace_id: null,
        debug: null
      }
    ]);

    const stopTicker = startAssistantStatusTicker();
    try {
      const response = await apiClient.sendAssistantMessage({
        connection,
        prompts,
        userMessage,
        sessionId: assistantSessionId || undefined,
        promptVersion: ASSISTANT_PROMPT_VERSION,
        useMock
      });
      setAssistantSessionId(response.session_id);
      setAssistantConversation(response.conversation);
      setAssistantStatus("Ответ готов");
      await loadAssistantAnnotationsForSession(response.session_id);
      log(`Assistant reply received: trace=${response.debug.trace_id}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantError(message);
      setAssistantStatus("Ошибка ассистента");
      log(`Assistant error: ${message}`);
    } finally {
      stopTicker();
      setAssistantBusy(false);
    }
  }

  useEffect(() => {
    if (!assistantSessionId.trim()) {
      setAssistantAnnotations([]);
      return;
    }
    void loadAssistantAnnotationsForSession(assistantSessionId);
  }, [assistantSessionId]);

  useEffect(() => {
    if (!selectedRunId) {
      setRunTrace([]);
      return;
    }
    void apiClient
      .runTrace(selectedRunId)
      .then((payload) => setRunTrace(payload.items))
      .catch((error) => log(`Run trace error: ${error instanceof Error ? error.message : String(error)}`));
  }, [selectedRunId]);

  return (
    <main
      className={`app-root ${
        uiMode === "assistant" || uiMode === "decomposition" || uiMode === "autoruns" ? "app-root-autoruns" : ""
      }`}
    >
      <header className="app-topbar">
        <div className="mode-switch-row">
          <button type="button" className={uiMode === "assistant" ? "tab active" : "tab"} onClick={() => setUiMode("assistant")}>
            Ассистент
          </button>
          <button type="button" className={uiMode === "decomposition" ? "tab active" : "tab"} onClick={() => setUiMode("decomposition")}>
            Декомпозиция
          </button>
          <button type="button" className={uiMode === "autoruns" ? "tab active" : "tab"} onClick={() => setUiMode("autoruns")}>
            История автопрогонов
          </button>
          <button type="button" className="tab" onClick={saveAutorunsLayout}>
            Сохранить
          </button>
        </div>

        {uiMode === "assistant" ? (
          <div className="mode-switch-row mode-switch-row-right">
            <button
              type="button"
              className={showAssistantConnectionMode ? "tab active" : "tab"}
              onClick={() => setShowAssistantConnectionMode((prev) => !prev)}
            >
              LLM Connector
            </button>
            <button
              type="button"
              className={showAssistantPromptMode ? "tab active" : "tab"}
              onClick={() => setShowAssistantPromptMode((prev) => !prev)}
            >
              Prompt Manager
            </button>
            <button type="button" className={showAssistantChatMode ? "tab active" : "tab"} onClick={() => setShowAssistantChatMode((prev) => !prev)}>
              Режим ассистента
            </button>
            <button
              type="button"
              className={showAssistantCommentsMode ? "tab active" : "tab"}
              onClick={() => setShowAssistantCommentsMode((prev) => !prev)}
            >
              Комментарии ассистента
            </button>
            <button type="button" className={showAssistantSamMode ? "tab active" : "tab"} onClick={() => setShowAssistantSamMode((prev) => !prev)}>
              SAM
            </button>
          </div>
        ) : uiMode === "decomposition" ? (
          <div className="mode-switch-row mode-switch-row-right">
            <button
              type="button"
              className={showDecompositionConnectionMode ? "tab active" : "tab"}
              onClick={() => setShowDecompositionConnectionMode((prev) => !prev)}
            >
              LLM
            </button>
            <button
              type="button"
              className={showDecompositionPromptMode ? "tab active" : "tab"}
              onClick={() => setShowDecompositionPromptMode((prev) => !prev)}
            >
              Prompt
            </button>
            <button type="button" className={showDecompositionQueryMode ? "tab active" : "tab"} onClick={() => setShowDecompositionQueryMode((prev) => !prev)}>
              Запрос
            </button>
            <button type="button" className={showDecompositionOutputMode ? "tab active" : "tab"} onClick={() => setShowDecompositionOutputMode((prev) => !prev)}>
              Выход
            </button>
            <button
              type="button"
              className={showDecompositionMetricsMode ? "tab active" : "tab"}
              onClick={() => setShowDecompositionMetricsMode((prev) => !prev)}
            >
              Метрики
            </button>
            <button
              type="button"
              className={showDecompositionHistoryMode ? "tab active" : "tab"}
              onClick={() => setShowDecompositionHistoryMode((prev) => !prev)}
            >
              История
            </button>
            <button
              type="button"
              className={showDecompositionRuntimeMode ? "tab active" : "tab"}
              onClick={() => setShowDecompositionRuntimeMode((prev) => !prev)}
            >
              NDC Run Monitor
            </button>
          </div>
        ) : uiMode === "autoruns" ? (
          <div className="mode-switch-row mode-switch-row-right">
            <button
              type="button"
              className={showAutorunsAssistantMode ? "tab active" : "tab"}
              onClick={() => setShowAutorunsAssistantMode((prev) => !prev)}
            >
              Режим ассистента
            </button>
            <button
              type="button"
              className={showAutorunsDecompositionMode ? "tab active" : "tab"}
              onClick={() => setShowAutorunsDecompositionMode((prev) => !prev)}
            >
              Режим декомпозиции
            </button>
            <button
              type="button"
              className={showAutorunsProgressMode ? "tab active" : "tab"}
              onClick={() => setShowAutorunsProgressMode((prev) => !prev)}
            >
              Прогресс/регресс
            </button>
            <button
              type="button"
              className={showAutorunsCommentsMode ? "tab active" : "tab"}
              onClick={() => setShowAutorunsCommentsMode((prev) => !prev)}
            >
              Комментарии
            </button>
          </div>
        ) : null}
      </header>

      {uiMode === "assistant" ? (
        <div className="layout-grid layout-grid-mode-columns">
          <div className="mode-columns">
            {showAssistantConnectionMode ? (
              <div className="mode-col">
                <ConnectionPanel
                  value={connection}
                  modelOptions={modelOptions}
                  modelsBusy={modelsBusy}
                  onChange={setConnection}
                  onReloadModels={reloadModels}
                  onSaveLocalConfig={saveLocalConfig}
                  onTestConnection={testConnection}
                  lastStatus={connectionStatus}
                  busy={busy || assistantBusy}
                />
              </div>
            ) : null}

            {showAssistantPromptMode ? (
              <div className="mode-col mode-col-wide">
                <PromptPanel
                  value={prompts}
                  onChange={setPrompts}
                  presets={presetList}
                  selectedPresetId={selectedPresetId}
                  onSelectPreset={setSelectedPresetId}
                  onLoadPreset={loadSelectedPreset}
                  onSavePreset={savePreset}
                  onResetDefaults={resetDefaults}
                  onDiffPrevious={diffWithPrevious}
                  presetName={presetName}
                  onPresetNameChange={setPresetName}
                  diffSummary={diffSummary}
                />
              </div>
            ) : null}

            {showAssistantChatMode ? (
              <div className="mode-col mode-col-xwide">
                <AssistantPanel
                  sessionId={assistantSessionId}
                  conversation={assistantConversation}
                  inputValue={assistantInput}
                  onInputChange={setAssistantInput}
                  selectedContextChip={assistantSelectedChip}
                  onSelectContextChip={setAssistantSelectedChip}
                  onClearContextChip={() => setAssistantSelectedChip(null)}
                  useMock={useMock}
                  onUseMockChange={setUseMock}
                  onSend={sendAssistantMessage}
                  onClear={resetAssistantSession}
                  busy={assistantBusy}
                  statusText={assistantStatus}
                  errorMessage={assistantError}
                  showCommentAction
                  onCommentAssistantMessage={openAssistantCommentModal}
                  isAssistantMessageCommented={isAssistantMessageCommented}
                  canCommentAssistantMessage={canCommentAssistantMessage}
                />
              </div>
            ) : null}

            {showAssistantCommentsMode ? (
              <div className="mode-col">
                <PanelFrame className="assistant-comments-frame" title="Комментарии ассистента">
                  <div className="assistant-comments-shell">
                    <div className="assistant-comments-toolbar">
                      <span className="muted">
                        {assistantSessionId ? `session: ${assistantSessionId}` : "Сессия не запущена"}
                      </span>
                      <button
                        type="button"
                        className="tab"
                        onClick={() => void loadAssistantAnnotationsForSession(assistantSessionId)}
                        disabled={!assistantSessionId || assistantAnnotationsBusy}
                      >
                        {assistantAnnotationsBusy ? "Обновляю..." : "Обновить"}
                      </button>
                    </div>
                    <div className="assistant-comments-list">
                      {!assistantSessionId ? <p className="muted">Появится после первого ответа ассистента.</p> : null}
                      {assistantSessionId && assistantAnnotations.length === 0 && !assistantAnnotationsBusy ? (
                        <p className="muted">Комментариев по этой сессии пока нет.</p>
                      ) : null}
                      {assistantAnnotations.map((item) => (
                        <article key={item.annotation_id} className="assistant-comment-item">
                          <div className="assistant-comment-head">
                            <strong>{`${"●".repeat(Math.max(1, Math.min(5, Math.round(item.rating))))}${"○".repeat(Math.max(0, 5 - Math.round(item.rating)))}`}</strong>
                            <span>{new Date(item.updated_at).toLocaleString("ru-RU")}</span>
                          </div>
                          {item.context.question_text ? <p>Q: {item.context.question_text}</p> : null}
                          {item.context.answer_text ? <p>A: {item.context.answer_text}</p> : null}
                          <p>{item.comment}</p>
                          <div className="assistant-comment-meta">
                            {item.context.trace_id ? <span>{`trace=${item.context.trace_id}`}</span> : null}
                            {item.context.reply_type ? <span>{`reply_type=${item.context.reply_type}`}</span> : null}
                          </div>
                        </article>
                      ))}
                    </div>
                  </div>
                </PanelFrame>
              </div>
            ) : null}

            {showAssistantSamMode ? (
              <div className="mode-col">
                <AssistantSamPanel
                  sessionId={assistantSessionId}
                  conversation={assistantConversation}
                  statusText={assistantStatus}
                  errorMessage={assistantError}
                  useMock={useMock}
                  appLogs={appLogs}
                />
              </div>
            ) : null}

            {!showAssistantConnectionMode &&
            !showAssistantPromptMode &&
            !showAssistantChatMode &&
            !showAssistantCommentsMode &&
            !showAssistantSamMode ? (
              <div className="mode-columns-empty">Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке.</div>
            ) : null}
          </div>
        </div>
      ) : uiMode === "decomposition" ? (
        <div className="layout-grid layout-grid-mode-columns">
          <div className="mode-columns">
            {showDecompositionConnectionMode ? (
              <div className="mode-col">
                <ConnectionPanel
                  value={connection}
                  modelOptions={modelOptions}
                  modelsBusy={modelsBusy}
                  onChange={setConnection}
                  onReloadModels={reloadModels}
                  onSaveLocalConfig={saveLocalConfig}
                  onTestConnection={testConnection}
                  lastStatus={connectionStatus}
                  busy={busy}
                />
              </div>
            ) : null}

            {showDecompositionPromptMode ? (
              <div className="mode-col mode-col-wide">
                <PromptPanel
                  value={prompts}
                  onChange={setPrompts}
                  presets={presetList}
                  selectedPresetId={selectedPresetId}
                  onSelectPreset={setSelectedPresetId}
                  onLoadPreset={loadSelectedPreset}
                  onSavePreset={savePreset}
                  onResetDefaults={resetDefaults}
                  onDiffPrevious={diffWithPrevious}
                  presetName={presetName}
                  onPresetNameChange={setPresetName}
                  diffSummary={diffSummary}
                />
              </div>
            ) : null}

            {showDecompositionQueryMode ? (
              <div className="mode-col">
                <QueryPanel
                  value={query}
                  onChange={setQuery}
                  onApplyBatchFormat={applyBatchFormat}
                  onNormalize={normalize}
                  busy={busy}
                  useMock={useMock}
                  onUseMockChange={setUseMock}
                  errorMessage={lastError}
                />
              </div>
            ) : null}

            {showDecompositionOutputMode ? (
              <div className="mode-col mode-col-xwide">
                <OutputPanel tab={activeTab} onTabChange={setActiveTab} result={result} appLogs={appLogs} />
              </div>
            ) : null}

            {showDecompositionMetricsMode ? (
              <div className="mode-col">
                <MetricsPanel result={result} />
              </div>
            ) : null}

            {showDecompositionHistoryMode ? (
              <div className="mode-col">
                <HistoryPanel items={historyItems} onRefresh={refreshHistory} onOpenTrace={openTrace} />
              </div>
            ) : null}

            {showDecompositionRuntimeMode ? (
              <div className="mode-col mode-col-xwide">
                <RuntimePanel
                  runs={runs}
                  selectedRunId={selectedRunId}
                  onSelectRun={setSelectedRunId}
                  onStartRun={startRun}
                  onFinishRun={finishRun}
                  onRefreshRuns={refreshRuns}
                  onRunEval={runEval}
                  onCopyEvalReport={copyEvalReport}
                  evalBusy={evalBusy}
                  traceItems={runTrace}
                  evalReport={evalReport}
                />
              </div>
            ) : null}

            {!showDecompositionConnectionMode &&
            !showDecompositionPromptMode &&
            !showDecompositionQueryMode &&
            !showDecompositionOutputMode &&
            !showDecompositionMetricsMode &&
            !showDecompositionHistoryMode &&
            !showDecompositionRuntimeMode ? (
              <div className="mode-columns-empty">Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке.</div>
            ) : null}
          </div>
        </div>
      ) : (
        <div className="layout-grid layout-grid-autoruns">
          <AutoRunsHistoryPanel
            connection={connection}
            prompts={prompts}
            assistantPromptVersion={ASSISTANT_PROMPT_VERSION}
            decompositionPromptVersion={AUTOLOAD_PROMPT_VERSION}
            showAssistantMode={showAutorunsAssistantMode}
            showDecompositionMode={showAutorunsDecompositionMode}
            showProgressMode={showAutorunsProgressMode}
            showCommentsMode={showAutorunsCommentsMode}
            onLog={log}
          />
        </div>
      )}

      {assistantCommentModal.open ? (
        <div
          className="autoruns-comment-modal-backdrop"
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              closeAssistantCommentModal();
            }
          }}
        >
          <div className="autoruns-comment-modal">
            <h3>Комментарий к ответу ассистента</h3>
            <p className="muted">Эта разметка хранится отдельно от комментариев автопрогонов.</p>

            {assistantCommentModalQuestion ? (
              <details className="autoruns-prompt-details" open>
                <summary>Вопрос пользователя</summary>
                <p className="autoruns-comment-quote">{assistantCommentModalQuestion.text}</p>
              </details>
            ) : null}
            {assistantCommentModalMessage ? (
              <details className="autoruns-prompt-details" open>
                <summary>Ответ ассистента</summary>
                <p className="autoruns-comment-quote">{assistantCommentModalMessage.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={assistantCommentModal.rating >= value ? "autoruns-rating-dot active" : "autoruns-rating-dot"}
                  onClick={() => setAssistantCommentModal((prev) => ({ ...prev, rating: value }))}
                  disabled={assistantCommentModal.saving}
                  aria-label={`Оценка ${value}`}
                >
                  {assistantCommentModal.rating >= value ? "●" : "○"}
                </button>
              ))}
            </div>

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

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

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

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