import { useEffect, useRef, useState } from "react";
import { apiClient } from "./api/client";
import { AutoRunsHistoryPanel } from "./components/AutoRunsHistoryPanel";
import { ConnectionPanel } from "./components/ConnectionPanel";
import { HistoryPanel } from "./components/HistoryPanel";
import { MetricsPanel } from "./components/MetricsPanel";
import { OutputPanel } from "./components/OutputPanel";
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 {
  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 DEFAULT_UI_MODE: UiMode = "autoruns";
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"];

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(" | ")}`;
}

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 [showAutorunsSettingsMode, setShowAutorunsSettingsMode] = useState(true);
  const [showAutorunsAutoRunsMode, setShowAutorunsAutoRunsMode] = useState(true);
  const [showAutorunsAssistantMode, setShowAutorunsAssistantMode] = useState(true);
  const [showAutorunsDecompositionMode, setShowAutorunsDecompositionMode] = useState(true);
  const [showAutorunsProgressMode, setShowAutorunsProgressMode] = useState(true);
  const [showAutorunsCommentsMode, setShowAutorunsCommentsMode] = 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 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));
  };

  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 | "assistant";
          activeTab?: TabKey;
          showAutorunsSettingsMode?: boolean;
          showAutorunsAutoRunsMode?: boolean;
          showAutorunsAssistantMode?: boolean;
          showAutorunsDecompositionMode?: boolean;
          showAutorunsProgressMode?: boolean;
          showAutorunsCommentsMode?: boolean;
          showDecompositionConnectionMode?: boolean;
          showDecompositionPromptMode?: boolean;
          showDecompositionQueryMode?: boolean;
          showDecompositionOutputMode?: boolean;
          showDecompositionMetricsMode?: boolean;
          showDecompositionHistoryMode?: boolean;
          showDecompositionRuntimeMode?: boolean;
          prompts?: PromptState;
        };
        if (parsed.uiMode === "assistant" || parsed.uiMode === "autoruns" || parsed.uiMode === "decomposition") {
          setUiMode("autoruns");
        }
        if (parsed.activeTab && TAB_KEYS.includes(parsed.activeTab)) {
          setActiveTab(parsed.activeTab);
        }
        if (typeof parsed.showAutorunsSettingsMode === "boolean") {
          setShowAutorunsSettingsMode(parsed.showAutorunsSettingsMode);
        }
        if (typeof parsed.showAutorunsAutoRunsMode === "boolean") {
          setShowAutorunsAutoRunsMode(parsed.showAutorunsAutoRunsMode);
        }
        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.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,
        showAutorunsSettingsMode,
        showAutorunsAutoRunsMode,
        showAutorunsAssistantMode,
        showAutorunsDecompositionMode,
        showAutorunsProgressMode,
        showAutorunsCommentsMode,
        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)}`);
    }
  }

  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 app-root-autoruns">
      <header className="app-topbar">
        <div className="mode-switch-row">
          <button type="button" className="tab active" onClick={() => setUiMode("autoruns")}>
            Управление ассистентом
          </button>
          <button type="button" className="tab" onClick={saveAutorunsLayout}>
            Сохранить
          </button>
        </div>

        <div className="mode-switch-row mode-switch-row-right">
          <button
            type="button"
            className={showAutorunsSettingsMode ? "tab active" : "tab"}
            onClick={() => setShowAutorunsSettingsMode((prev) => !prev)}
          >
            Настройки
          </button>
          <button
            type="button"
            className={showAutorunsAutoRunsMode ? "tab active" : "tab"}
            onClick={() => setShowAutorunsAutoRunsMode((prev) => !prev)}
          >
            Автопрогоны
          </button>
          <button
            type="button"
            className={showAutorunsAssistantMode ? "tab active" : "tab"}
            onClick={() => setShowAutorunsAssistantMode((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>
      </header>

      <div className="layout-grid layout-grid-autoruns">
        <AutoRunsHistoryPanel
          connection={connection}
          modelOptions={modelOptions}
          modelsBusy={modelsBusy}
          connectionStatus={connectionStatus}
          connectionBusy={busy}
          onConnectionChange={setConnection}
          onReloadModels={reloadModels}
          onSaveLocalConfig={saveLocalConfig}
          onTestConnection={testConnection}
          prompts={prompts}
          onPromptsChange={setPrompts}
          promptPresets={presetList}
          selectedPresetId={selectedPresetId}
          onSelectPreset={setSelectedPresetId}
          onLoadPreset={loadSelectedPreset}
          onSavePreset={savePreset}
          onResetDefaults={resetDefaults}
          onDiffPrevious={diffWithPrevious}
          presetName={presetName}
          onPresetNameChange={setPresetName}
          diffSummary={diffSummary}
          assistantPromptVersion={ASSISTANT_PROMPT_VERSION}
          decompositionPromptVersion={AUTOLOAD_PROMPT_VERSION}
          showSettingsMode={showAutorunsSettingsMode}
          showAutoRunsMode={showAutorunsAutoRunsMode}
          showAssistantMode={showAutorunsAssistantMode}
          showProgressMode={showAutorunsProgressMode}
          showCommentsMode={showAutorunsCommentsMode}
          onLog={log}
        />
      </div>
    </main>
  );
}
