// screens-simulator.jsx — Virtual Demo Stand
//
// Wires to the simulation service via Keeper.api:
//   • simulatorDevices()                          → GET /simulator/devices
//   • simulatorScenariosList()                    → GET /simulator/scenarios
//   • simulatorScenarioCreate(payload)            → POST /simulator/scenarios
//   • simulatorScenarioStart(id)                  → POST /simulator/scenarios/{id}/start
//   • simulatorScenarioTick(id)                   → POST /simulator/scenarios/{id}/tick
//   • simulatorScenarioTelemetry(id)              → GET /simulator/scenarios/{id}/telemetry
//   • simulatorScenarioStop(id)                   → POST /simulator/scenarios/{id}/stop
//
// Falls back silently to window.DATA.devices when the backend is offline,
// mapping the demo device records to the minimal shape the screen needs.

/* ─── CP-16 Virtual Demo Stand ────────────────────────────── */
function SimulatorStand() {
  // ── Scenario options ─────────────────────────────────────────────────────
  const SCENARIOS = [
    { id: "normal",           label: "Норма",            icon: "ri-checkbox-circle-line",  color: "#26972B" },
    { id: "heatwave",         label: "Тепловой удар",    icon: "ri-temp-hot-line",         color: "#E5342B" },
    { id: "irrigation_leak",  label: "Утечка полива",    icon: "ri-drop-line",             color: "#FF7A00" },
    { id: "low_pollination",  label: "Низкое опыление",  icon: "ri-bug-line",              color: "#a04200" },
  ];
  const EVENT_INJECTIONS = [
    {
      id: "pest_outbreak",
      label: "Очаг вредителей",
      icon: "ri-bug-2-line",
      scenario_id: "low_pollination",
      summary: "Сценарий backend: low_pollination",
      default_duration_sec: 360,
    },
    {
      id: "weather_change",
      label: "Резкая смена погоды",
      icon: "ri-thunderstorms-line",
      scenario_id: "heatwave",
      summary: "Сценарий backend: heatwave",
      default_duration_sec: 300,
    },
    {
      id: "equipment_failure",
      label: "Сбой оборудования",
      icon: "ri-alarm-warning-line",
      scenario_id: "irrigation_leak",
      summary: "Сценарий backend: irrigation_leak",
      default_duration_sec: 420,
    },
  ];

  // ── Component state ──────────────────────────────────────────────────────
  const [devices, setDevices]           = React.useState([]);       // VirtualDeviceSpec[]
  const [loading, setLoading]           = React.useState(true);     // initial devices fetch
  const [error, setError]               = React.useState(null);     // fetch error message
  const [usingFallback, setUsingFallback] = React.useState(false);  // true = offline demo data

  const [selectedDevice, setSelectedDevice] = React.useState(null); // device id string
  const [scenario, setScenario]         = React.useState("normal");

  const [sample, setSample]             = React.useState(null);     // last telemetry sample
  const [sampleLoading, setSampleLoading] = React.useState(false);
  const [sampleError, setSampleError]   = React.useState(null);

  const [scenarioStatus, setScenarioStatus] = React.useState(null); // "sending"|"ok"|"error"
  const [scenarioRunState, setScenarioRunState] = React.useState("idle"); // "idle"|"running"|"stopping"
  const [activeScenarioId, setActiveScenarioId] = React.useState(null);
  const [activeInjectionId, setActiveInjectionId] = React.useState(null);
  const [remainingSec, setRemainingSec] = React.useState(null);
  const [duration, setDuration]         = React.useState(300);      // seconds
  const autoTickTimerRef = React.useRef(null);
  const countdownTimerRef = React.useRef(null);
  const statusResetTimerRef = React.useRef(null);
  const tickInFlightRef = React.useRef(false);
  const activeScenarioIdRef = React.useRef(null);
  const runDeadlineRef = React.useRef(null);

  // ── Fetch virtual device catalogue on mount ──────────────────────────────
  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);

    Keeper.api.simulatorDevices()
      .then((data) => {
        if (cancelled) return;
        // API returns array of VirtualDeviceSpec objects; normalise to at least
        // { id, name, type } so the UI always has something to display.
        const list = Array.isArray(data) ? data : (data.devices || []);
        setDevices(list);
        setSelectedDevice(list.length > 0 ? (list[0].device_id || list[0].id) : null);
        setUsingFallback(false);
      })
      .catch(() => {
        if (cancelled) return;
        // Backend offline — fall back to window.DATA.devices, mapping to minimal shape.
        const fallback = (window.DATA && window.DATA.devices || []).map(d => ({
          device_id: d.id,
          name:      d.name,
          type:      d.type,
          zone:      d.zone,
          icon:      d.icon,
          _demo:     true,
        }));
        setDevices(fallback);
        setSelectedDevice(fallback.length > 0 ? fallback[0].device_id : null);
        setUsingFallback(true);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => { cancelled = true; };
  }, []);

  function toVirtualDeviceSpec(device) {
    return {
      device_id: device.device_id || device.id || selectedDevice,
      name: device.name || "Virtual Device",
      greenhouse_id: device.greenhouse_id || "gh-default",
      zone_id: device.zone_id || device.zone || "zone-1",
      firmware_version: device.firmware_version || "1.0.2",
      battery_pct: typeof device.battery_pct === "number" ? device.battery_pct : 90,
      charging: typeof device.charging === "boolean" ? device.charging : false,
      mesh_hop_count: typeof device.mesh_hop_count === "number" ? device.mesh_hop_count : 1,
      gate_mode: device.gate_mode || "AUTO",
      sample_interval_sec: typeof device.sample_interval_sec === "number" ? device.sample_interval_sec : 60,
    };
  }

  function pickTelemetryByDevice(events, deviceId) {
    if (!Array.isArray(events) || events.length === 0) return null;
    return events.find((event) => event.device_id === deviceId) || events[0] || null;
  }

  function clearStatusResetTimer() {
    if (statusResetTimerRef.current) {
      clearTimeout(statusResetTimerRef.current);
      statusResetTimerRef.current = null;
    }
  }

  function scheduleStatusReset(timeoutMs) {
    clearStatusResetTimer();
    statusResetTimerRef.current = setTimeout(() => {
      setScenarioStatus(null);
      statusResetTimerRef.current = null;
    }, timeoutMs);
  }

  function clearAutoTickLoop() {
    if (autoTickTimerRef.current) {
      clearInterval(autoTickTimerRef.current);
      autoTickTimerRef.current = null;
    }
    if (countdownTimerRef.current) {
      clearInterval(countdownTimerRef.current);
      countdownTimerRef.current = null;
    }
    runDeadlineRef.current = null;
    tickInFlightRef.current = false;
    setRemainingSec(null);
  }

  async function tickAndLoadScenarioSample(scenarioId, deviceId, scen) {
    await Keeper.api.simulatorScenarioTick(scenarioId);
    const telemetry = await Keeper.api.simulatorScenarioTelemetry(scenarioId);
    const data = pickTelemetryByDevice(telemetry, deviceId);
    if (data) {
      setSample(data);
      setSampleError(null);
      return;
    }
    const synth = buildSyntheticSample(deviceId, scen);
    setSample(synth);
    setSampleError("Телеметрия сценария пока недоступна — показаны синтетические данные");
  }

  async function stopActiveScenario(reason = "manual") {
    const runningId = activeScenarioIdRef.current;
    clearAutoTickLoop();
    if (!runningId) {
      setScenarioRunState("idle");
      return;
    }
    setScenarioRunState("stopping");
    try {
      await Keeper.api.simulatorScenarioStop(runningId);
    } catch (_) {
      // Ignore stop failures during cleanup/switch to keep UI responsive.
    } finally {
      if (activeScenarioIdRef.current === runningId) {
        activeScenarioIdRef.current = null;
        setActiveScenarioId(null);
      }
      setScenarioRunState("idle");
      if (reason === "auto-complete") {
        setScenarioStatus("ok");
        scheduleStatusReset(2500);
      }
    }
  }

  function startAutoTickLoop(scenarioId, deviceId, scen, tickIntervalSec, runDurationSec) {
    clearAutoTickLoop();
    const deadlineMs = Date.now() + (runDurationSec * 1000);
    runDeadlineRef.current = deadlineMs;
    setRemainingSec(runDurationSec);

    const updateCountdown = () => {
      if (!runDeadlineRef.current) return;
      const left = Math.max(0, Math.ceil((runDeadlineRef.current - Date.now()) / 1000));
      setRemainingSec(left);
      if (left <= 0 && activeScenarioIdRef.current === scenarioId) {
        void stopActiveScenario("auto-complete");
      }
    };

    countdownTimerRef.current = setInterval(updateCountdown, 1000);
    updateCountdown();

    autoTickTimerRef.current = setInterval(async () => {
      if (tickInFlightRef.current || activeScenarioIdRef.current !== scenarioId) return;
      if (runDeadlineRef.current && Date.now() >= runDeadlineRef.current) {
        void stopActiveScenario("auto-complete");
        return;
      }
      tickInFlightRef.current = true;
      try {
        await tickAndLoadScenarioSample(scenarioId, deviceId, scen);
      } catch (_) {
        const synth = buildSyntheticSample(deviceId, scen);
        setSample(synth);
        setSampleError("Сервис недоступен — показаны синтетические данные");
      } finally {
        tickInFlightRef.current = false;
      }
    }, Math.max(1, tickIntervalSec) * 1000);
  }

  // ── Sample telemetry for the currently selected device + scenario ─────────
  async function fetchSample() {
    if (!selectedDevice) return;
    setSampleLoading(true);
    setSampleError(null);

    if (!activeScenarioIdRef.current) {
      const synth = buildSyntheticSample(selectedDevice, scenario);
      setSample(synth);
      setSampleError("Сценарий не активирован — показаны синтетические данные");
      setSampleLoading(false);
      return;
    }

    try {
      await tickAndLoadScenarioSample(activeScenarioIdRef.current, selectedDevice, scenario);
    } catch (_) {
      // Generate a local synthetic sample so the demo still looks alive
      // even when the simulator service is unreachable.
      const synth = buildSyntheticSample(selectedDevice, scenario);
      setSample(synth);
      setSampleError("Сервис недоступен — показаны синтетические данные");
    } finally {
      setSampleLoading(false);
    }
  }

  // ── Push a scenario activation to the simulation service ─────────────────
  async function activateScenario(nextScenario = scenario) {
    if (!selectedDevice) return;
    const scenarioName = nextScenario || "normal";
    setScenarioStatus("sending");
    clearStatusResetTimer();
    try {
      const selected = devices.find((device) => (device.device_id || device.id) === selectedDevice);
      if (!selected) throw new Error("selected device not found");

      if (activeScenarioIdRef.current) {
        await stopActiveScenario("restart");
      }

      const existing = await Keeper.api.simulatorScenariosList();
      const runningForDevice = Array.isArray(existing)
        ? existing.find((item) => {
            if (item.status !== "running" || !Array.isArray(item.devices)) return false;
            return item.devices.some((d) => (d.device_id || d.id) === selectedDevice);
          })
        : null;

      if (runningForDevice && runningForDevice.scenario_id) {
        await Keeper.api.simulatorScenarioStop(runningForDevice.scenario_id);
      }

      const created = await Keeper.api.simulatorScenarioCreate({
        name: scenarioName,
        devices: [toVirtualDeviceSpec(selected)],
        tick_interval_sec: Math.max(1, selected.sample_interval_sec || 60),
        meta: {
          requested_duration_sec: String(duration),
        },
      });
      const scenarioId = created && created.scenario_id;
      if (!scenarioId) throw new Error("missing scenario_id");

      await Keeper.api.simulatorScenarioStart(scenarioId);
      await tickAndLoadScenarioSample(scenarioId, selectedDevice, scenarioName);

      setScenario(scenarioName);
      activeScenarioIdRef.current = scenarioId;
      setActiveScenarioId(scenarioId);
      setScenarioRunState("running");
      startAutoTickLoop(
        scenarioId,
        selectedDevice,
        scenarioName,
        Math.max(1, selected.sample_interval_sec || 60),
        duration,
      );
      setScenarioStatus("ok");
      scheduleStatusReset(3000);
    } catch (_) {
      setScenarioRunState("idle");
      setScenarioStatus("error");
      scheduleStatusReset(4000);
    }
  }

  async function handleDeviceSelect(nextDeviceId) {
    if (nextDeviceId === selectedDevice) return;
    if (activeScenarioIdRef.current) {
      await stopActiveScenario("switch");
    }
    setSelectedDevice(nextDeviceId);
    setSample(null);
    setSampleError(null);
    setScenarioStatus(null);
  }

  async function handleScenarioSelect(nextScenario) {
    if (nextScenario === scenario) return;
    if (activeScenarioIdRef.current) {
      await stopActiveScenario("switch");
    }
    setScenario(nextScenario);
    setActiveInjectionId(null);
    setSample(null);
    setSampleError(null);
    setScenarioStatus(null);
  }

  async function injectEvent(eventId) {
    const config = EVENT_INJECTIONS.find((item) => item.id === eventId);
    if (!config) return;
    setActiveInjectionId(config.id);
    setDuration(config.default_duration_sec);
    await activateScenario(config.scenario_id);
  }

  // Stop timers and active scenario when leaving the screen.
  React.useEffect(() => {
    return () => {
      clearStatusResetTimer();
      clearAutoTickLoop();
      if (activeScenarioIdRef.current) {
        Keeper.api.simulatorScenarioStop(activeScenarioIdRef.current).catch(() => {});
      }
    };
  }, []);

  // ── Helpers ───────────────────────────────────────────────────────────────
  // Build a plausible synthetic TelemetryEvent when the backend is offline.
  // Shape mirrors TelemetryEvent sub-objects from keeper_contracts/telemetry.py
  // so the display logic works identically for live and synthetic samples.
  function buildSyntheticSample(deviceId, scen) {
    const base = {
      device_id: deviceId,
      scenario:  scen,
      timestamp: new Date().toISOString(),
    };
    if (scen === "heatwave") {
      return {
        ...base,
        microclimate: { temperature_c: 38.4, humidity_pct: 28.1, co2_ppm: 920.0, insolation_lux: 22000.0 },
        irrigation:   { substrate_moisture_pct: 55.0, drip_flow_lh: 1.4, substrate_ec: 3.1, feed_ec: 2.8, drain_ec: 3.6 },
        pollination:  { departures: 98, returns: 91, activity_index: 0.93 },
        power:        { battery_pct: 61, charging: false },
        connectivity: { online: true, signal_dbm: -72, mesh_hop_count: 2 },
      };
    }
    if (scen === "irrigation_leak") {
      return {
        ...base,
        microclimate: { temperature_c: 23.1, humidity_pct: 91.4, co2_ppm: 760.0, insolation_lux: 16800.0 },
        irrigation:   { substrate_moisture_pct: 47.0, drip_flow_lh: 2.9, substrate_ec: 2.6, feed_ec: 2.6, drain_ec: 3.6 },
        pollination:  { departures: 135, returns: 131, activity_index: 0.97 },
        power:        { battery_pct: 54, charging: false },
        connectivity: { online: true, signal_dbm: -68, mesh_hop_count: 1 },
      };
    }
    if (scen === "low_pollination") {
      return {
        ...base,
        microclimate: { temperature_c: 22.8, humidity_pct: 65.0, co2_ppm: 780.0, insolation_lux: 17500.0 },
        irrigation:   { substrate_moisture_pct: 62.0, drip_flow_lh: 1.2, substrate_ec: 2.4, feed_ec: 2.1, drain_ec: 2.0 },
        pollination:  { departures: 52, returns: 38, activity_index: 0.73 },
        power:        { battery_pct: 73, charging: false },
        connectivity: { online: true, signal_dbm: -65, mesh_hop_count: 1 },
      };
    }
    // normal
    return {
      ...base,
      microclimate: { temperature_c: 22.4, humidity_pct: 68.2, co2_ppm: 812.0, insolation_lux: 18400.0 },
      irrigation:   { substrate_moisture_pct: 61.0, drip_flow_lh: 1.2, substrate_ec: 2.4, feed_ec: 2.1, drain_ec: 2.0 },
      pollination:  { departures: 142, returns: 138, activity_index: 0.97 },
      power:        { battery_pct: 84, charging: false },
      connectivity: { online: true, signal_dbm: -62, mesh_hop_count: 1 },
    };
  }

  // Format a raw value for display (round numbers, replace "." with "," for RU locale)
  function fmt(v) {
    if (v == null) return "—";
    if (typeof v === "number") return v.toFixed(1).replace(".", ",");
    return String(v);
  }

  function fmtDurationShort(sec) {
    if (!sec || sec <= 0) return "—";
    if (sec < 60) return `${sec} c`;
    if (sec < 3600) return `${Math.round(sec / 60)} мин`;
    return "1 ч";
  }

  const activeScenarioMeta = SCENARIOS.find(s => s.id === scenario) || SCENARIOS[0];
  const activeDevice = devices.find(d => (d.device_id || d.id) === selectedDevice);
  const isWalkthroughDeviceDone = Boolean(selectedDevice);
  const isWalkthroughConfigDone = Boolean(activeScenarioId) || scenario !== "normal" || duration !== 300 || Boolean(activeInjectionId);
  const isWalkthroughRunDone = Boolean(activeScenarioId);
  const isWalkthroughSampleDone = Boolean(sample);
  const walkthroughSteps = [
    {
      id: "device",
      icon: "ri-cpu-line",
      title: "Выбрать устройство",
      details: "Назначьте виртуальный сенсор для сценария",
      done: isWalkthroughDeviceDone,
    },
    {
      id: "configure",
      icon: "ri-sliders-line",
      title: "Настроить сценарий",
      details: "Выберите режим, длительность или инъекцию события",
      done: isWalkthroughConfigDone,
    },
    {
      id: "run",
      icon: "ri-play-circle-line",
      title: "Запустить прогон",
      details: "Отправьте сценарий и дождитесь статуса «Выполняется»",
      done: isWalkthroughRunDone,
    },
    {
      id: "review",
      icon: "ri-bar-chart-2-line",
      title: "Разобрать телеметрию",
      details: "Проверьте KPI и объясните реакцию системы",
      done: isWalkthroughSampleDone,
    },
  ];
  const walkthroughDoneCount = walkthroughSteps.filter((step) => step.done).length;
  const walkthroughProgressPct = Math.round((walkthroughDoneCount / walkthroughSteps.length) * 100);
  const walkthroughCurrentIndex = walkthroughSteps.findIndex((step) => !step.done);
  const walkthroughFocusIndex = walkthroughCurrentIndex === -1 ? walkthroughSteps.length - 1 : walkthroughCurrentIndex;
  const walkthroughFocusStepId = walkthroughSteps[walkthroughFocusIndex].id;
  const walkthroughStatusLabel = walkthroughDoneCount === walkthroughSteps.length
    ? "Готово"
    : `Шаг ${walkthroughFocusIndex + 1} из ${walkthroughSteps.length}`;
  const walkthroughStepStates = walkthroughSteps.map((step, index) => {
    const state = step.done
      ? "done"
      : index === walkthroughFocusIndex
      ? "current"
      : "pending";
    return Object.assign({}, step, { state });
  });
  const walkthroughHint = (() => {
    if (!isWalkthroughDeviceDone) {
      return {
        icon: "ri-mouse-line",
        title: "Выберите любое устройство из каталога",
        message: "Для демо-тренинга начинаем с фиксации устройства в левой колонке.",
        actionLabel: "",
        actionKind: "none",
      };
    }
    if (!isWalkthroughConfigDone) {
      return {
        icon: "ri-lightbulb-line",
        title: "Подсказка: используйте готовый training-профиль",
        message: "Для sales-показа удобно включить «Тепловой удар» и длительность 10 минут.",
        actionLabel: "Применить training-профиль",
        actionKind: "preset",
      };
    }
    if (!isWalkthroughRunDone) {
      return {
        icon: "ri-play-line",
        title: "Сценарий готов к запуску",
        message: "Нажмите запуск, чтобы получить живой поток телеметрии для демонстрации.",
        actionLabel: "Запустить сейчас",
        actionKind: "run",
      };
    }
    if (!isWalkthroughSampleDone) {
      return {
        icon: "ri-radar-line",
        title: "Сценарий выполняется",
        message: "Снимите свежий sample, чтобы показать KPI, JSON payload и динамику.",
        actionLabel: "Загрузить sample",
        actionKind: "sample",
      };
    }
    return {
      icon: "ri-checkbox-circle-line",
      title: "Walkthrough завершён",
      message: "Можно повторить с другим событием или остановить текущий прогон.",
      actionLabel: activeScenarioId ? "Остановить сценарий" : "",
      actionKind: activeScenarioId ? "stop" : "none",
    };
  })();

  async function handleWalkthroughAction() {
    if (walkthroughHint.actionKind === "preset") {
      await handleScenarioSelect("heatwave");
      setDuration(600);
      setActiveInjectionId(null);
      return;
    }
    if (walkthroughHint.actionKind === "run") {
      await activateScenario();
      return;
    }
    if (walkthroughHint.actionKind === "sample") {
      await fetchSample();
      return;
    }
    if (walkthroughHint.actionKind === "stop") {
      await stopActiveScenario("manual");
    }
  }

  // ── Render ────────────────────────────────────────────────────────────────
  return (
    <div className="page" data-screen-label="16 Виртуальный стенд">

      {/* ── Page header ─────────────────────────────────────────────────── */}
      <div className="page-head">
        <div>
          <div className="page-title">Виртуальный стенд</div>
          <div className="page-sub">
            Симуляция телеметрии устройств · пробуйте сценарии без реального оборудования
          </div>
        </div>
        <div className="actions">
          {usingFallback && (
            <span className="status status--warn" style={{ fontSize: 12 }}>
              <span className="dot"></span>Симулятор офлайн · демо-данные
            </span>
          )}
          <button
            className="btn btn--secondary"
            onClick={fetchSample}
            disabled={!selectedDevice || sampleLoading}
          >
            <i className={sampleLoading ? "ri-loader-4-line" : "ri-refresh-line"}></i>
            {sampleLoading ? "Загрузка…" : "Обновить"}
          </button>
          {activeScenarioId ? (
            <button
              className="btn btn--danger"
              onClick={() => { void stopActiveScenario("manual"); }}
              disabled={scenarioRunState === "stopping"}
            >
              <i className={scenarioRunState === "stopping" ? "ri-loader-4-line" : "ri-stop-circle-line"}></i>
              {scenarioRunState === "stopping" ? "Остановка…" : "Остановить сценарий"}
            </button>
          ) : (
            <button
              className={
                "btn " +
                (scenarioStatus === "ok"
                  ? "btn--primary"
                  : scenarioStatus === "error"
                  ? "btn--danger"
                  : "btn--primary") +
                (walkthroughFocusStepId === "run" && !activeScenarioId ? " walkthrough-pulse" : "")
              }
              onClick={activateScenario}
              disabled={!selectedDevice || scenarioStatus === "sending"}
            >
              <i className={
                scenarioStatus === "sending" ? "ri-loader-4-line" :
                scenarioStatus === "ok"      ? "ri-checkbox-circle-line" :
                scenarioStatus === "error"   ? "ri-error-warning-line" :
                "ri-play-line"
              }></i>
              {scenarioStatus === "sending" ? "Отправка..." :
               scenarioStatus === "ok"      ? "Запущено" :
               scenarioStatus === "error"   ? "Не удалось запустить" :
               "Запустить сценарий"}
            </button>
          )}
        </div>
      </div>

      {/* ── Loading skeleton ─────────────────────────────────────────────── */}
      {loading && (
        <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "24px 0", color: "var(--fg-muted)", fontSize: 14 }}>
          <i className="ri-loader-4-line" style={{ fontSize: 18 }}></i>
          Загрузка каталога виртуальных устройств…
        </div>
      )}

      {!loading && (
        <>
          <div className="card walkthrough-card">
            <div className="walkthrough-card__head">
              <div>
                <h3 className="card-title" style={{ marginBottom: 4 }}>
                  Guided Walkthrough · Sales / Training
                </h3>
                <div className="walkthrough-card__sub">
                  Пошаговый режим помогает быстро провести демо на стенде
                </div>
              </div>
              <span className={"status " + (walkthroughDoneCount === walkthroughSteps.length ? "status--ok" : "status--info")}>
                <span className="dot"></span>{walkthroughStatusLabel}
              </span>
            </div>
            <div className="walkthrough-progress">
              <i style={{ width: `${walkthroughProgressPct}%` }}></i>
            </div>
            <div className="walkthrough-layout">
              <ol className="walkthrough-steps">
                {walkthroughStepStates.map((step, index) => (
                  <li key={step.id} className={"walkthrough-step is-" + step.state}>
                    <div className="walkthrough-step__marker">
                      {step.state === "done" ? <i className="ri-check-line"></i> : (index + 1)}
                    </div>
                    <div className="walkthrough-step__body">
                      <div className="walkthrough-step__title">
                        <i className={step.icon}></i>{step.title}
                      </div>
                      <div className="walkthrough-step__meta">{step.details}</div>
                    </div>
                    <span className="walkthrough-step__state">
                      {step.state === "done" ? "готово" : step.state === "current" ? "сейчас" : "далее"}
                    </span>
                  </li>
                ))}
              </ol>
              <div className="walkthrough-hint">
                <div className="walkthrough-hint__icon">
                  <i className={walkthroughHint.icon}></i>
                </div>
                <div>
                  <div className="walkthrough-hint__title">{walkthroughHint.title}</div>
                  <div className="walkthrough-hint__text">{walkthroughHint.message}</div>
                  {walkthroughHint.actionKind !== "none" && (
                    <button
                      className="btn btn--secondary btn--sm"
                      onClick={() => { void handleWalkthroughAction(); }}
                      disabled={scenarioStatus === "sending" || sampleLoading || scenarioRunState === "stopping"}
                      style={{ marginTop: 10 }}
                    >
                      <i className="ri-corner-down-right-line"></i>
                      {walkthroughHint.actionLabel}
                    </button>
                  )}
                </div>
              </div>
            </div>
          </div>

          <div className="cmd-grid" style={{ alignItems: "flex-start" }}>

            {/* ── Left panel: device selector + scenario picker ─────────────── */}
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>

            {/* Device catalogue */}
            <div className={"card walkthrough-target " + (walkthroughFocusStepId === "device" ? "is-current" : "")} style={{ padding: 0, overflow: "hidden" }}>
              <div style={{
                padding: "12px 16px",
                borderBottom: "1px solid var(--border)",
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center"
              }}>
                <h3 className="card-title" style={{ margin: 0 }}>
                  Виртуальные устройства
                </h3>
                <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>
                  {devices.length} шт.
                </span>
              </div>
              {devices.length === 0 && (
                <div style={{ padding: "20px 16px", color: "var(--fg-muted)", fontSize: 13 }}>
                  Устройства не найдены
                </div>
              )}
              {devices.map(d => {
                const devId = d.device_id || d.id;
                const isSelected = devId === selectedDevice;
                return (
                  <div
                    key={devId}
                    className={"tele-item" + (isSelected ? " on" : "")}
                    onClick={() => { void handleDeviceSelect(devId); }}
                    style={{ cursor: "pointer" }}
                  >
                    <i className={d.icon || "ri-cpu-line"} style={{ fontSize: 14, color: "var(--fg-muted)" }}></i>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <b style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", display: "block" }}>
                        {d.name || devId}
                      </b>
                      <span className="id">
                        {d.type || d.device_type || "—"}
                        {d.zone ? " · " + d.zone : ""}
                      </span>
                    </div>
                    {d._demo && (
                      <span style={{ fontSize: 10, color: "var(--fg-muted)", padding: "2px 6px", border: "1px solid var(--border)", borderRadius: 4 }}>
                        demo
                      </span>
                    )}
                  </div>
                );
              })}
            </div>

            {/* Scenario picker */}
            <div className={"card walkthrough-target " + (walkthroughFocusStepId === "configure" ? "is-current" : "")}>
              <h3 className="card-title" style={{ marginBottom: 12 }}>Сценарий</h3>
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {SCENARIOS.map(s => (
                  <button
                    key={s.id}
                    className={"btn btn--sm " + (scenario === s.id ? "btn--primary" : "btn--secondary")}
                    style={{
                      justifyContent: "flex-start",
                      gap: 8,
                      ...(scenario === s.id ? { background: s.color, borderColor: s.color } : {}),
                    }}
                    onClick={() => { void handleScenarioSelect(s.id); }}
                  >
                    <i className={s.icon}></i>
                    {s.label}
                  </button>
                ))}
              </div>

              {/* Duration stepper */}
              <div style={{ marginTop: 14, borderTop: "1px solid var(--border)", paddingTop: 14 }}>
                <div style={{ fontSize: 12, color: "var(--fg-muted)", marginBottom: 8 }}>Длительность</div>
                <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
                  {[60, 300, 600, 3600].map(sec => (
                    <button
                      key={sec}
                      className={"btn btn--sm " + (duration === sec ? "btn--primary" : "btn--secondary")}
                      disabled={Boolean(activeScenarioId)}
                      onClick={() => setDuration(sec)}
                    >
                      {sec < 60 ? sec + "с" : sec < 3600 ? (sec / 60) + "м" : "1ч"}
                    </button>
                  ))}
                </div>
              </div>
            </div>

            {/* Event injection controls */}
            <div className="card">
              <h3 className="card-title" style={{ marginBottom: 12 }}>Инъекция событий</h3>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {EVENT_INJECTIONS.map((eventConfig) => (
                  <button
                    key={eventConfig.id}
                    className={"btn btn--sm " + (activeInjectionId === eventConfig.id ? "btn--primary" : "btn--secondary")}
                    style={{ justifyContent: "flex-start", gap: 8 }}
                    onClick={() => { void injectEvent(eventConfig.id); }}
                    disabled={!selectedDevice || scenarioStatus === "sending"}
                    title={eventConfig.summary}
                  >
                    <i className={eventConfig.icon}></i>
                    {eventConfig.label}
                  </button>
                ))}
              </div>
              <div style={{ fontSize: 11, color: "var(--fg-muted)", marginTop: 10, lineHeight: 1.45 }}>
                События мапятся на backend-сценарии: pest outbreak → low_pollination,
                weather change → heatwave, equipment failure → irrigation_leak.
              </div>
            </div>
          </div>

          {/* ── Right panel: telemetry sample ─────────────────────────────── */}
          <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>

            {/* Device + scenario context header */}
            <div className={"card walkthrough-target " + (walkthroughFocusStepId === "review" ? "is-current" : "")} style={{ padding: "12px 16px" }}>
              <div style={{ display: "flex", gap: 12, alignItems: "center" }}>
                <div style={{
                  width: 36, height: 36, borderRadius: 8,
                  background: activeScenarioMeta.color + "20",
                  color: activeScenarioMeta.color,
                  display: "flex", alignItems: "center", justifyContent: "center", fontSize: 18
                }}>
                  <i className={activeScenarioMeta.icon}></i>
                </div>
                <div style={{ flex: 1 }}>
                  <div style={{ font: "600 14px Inter", color: "var(--fg-strong)" }}>
                    {activeDevice ? (activeDevice.name || selectedDevice) : "Устройство не выбрано"}
                  </div>
                  <div className="id-mono" style={{ fontSize: 11 }}>
                    {selectedDevice} · сценарий: {activeScenarioMeta.label}
                  </div>
                  {activeScenarioId && (
                    <div style={{ fontSize: 11, color: "var(--fg-muted)", marginTop: 4 }}>
                      Сессия: {activeScenarioId} · осталось: {remainingSec != null ? `${remainingSec} c` : "—"}
                    </div>
                  )}
                </div>
                <button
                  className={"btn btn--secondary btn--sm " + (walkthroughFocusStepId === "review" ? "walkthrough-pulse" : "")}
                  onClick={fetchSample}
                  disabled={!selectedDevice || sampleLoading}
                >
                  <i className={sampleLoading ? "ri-loader-4-line" : "ri-refresh-line"}></i>
                  Пробник
                </button>
              </div>
            </div>

            {/* Sample error banner */}
            {sampleError && (
              <div style={{
                padding: "10px 14px",
                background: "#FFF3CD",
                border: "1px solid #FFB300",
                borderRadius: 8,
                fontSize: 12,
                color: "#7A5000",
                display: "flex",
                gap: 8,
                alignItems: "flex-start"
              }}>
                <i className="ri-information-line" style={{ fontSize: 14, marginTop: 1, flexShrink: 0 }}></i>
                {sampleError}
              </div>
            )}

            {/* No sample yet placeholder */}
            {!sample && !sampleLoading && (
              <div className="card" style={{ padding: 32, textAlign: "center", color: "var(--fg-muted)" }}>
                <i className="ri-pulse-line" style={{ fontSize: 40, display: "block", marginBottom: 12 }}></i>
                <div style={{ fontSize: 14, marginBottom: 6 }}>Нет данных</div>
                <div style={{ fontSize: 12 }}>Нажмите «Обновить» или «Пробник», чтобы получить телеметрию</div>
              </div>
            )}

            {/* Telemetry sample cards */}
            {sample && (
              <>
                {/* ── Microclimate sub-object ─────────────────────────────── */}
                <div style={{ fontSize: 11, fontWeight: 600, color: "var(--fg-muted)", textTransform: "uppercase", letterSpacing: ".05em", padding: "4px 2px" }}>
                  Микроклимат
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 10, marginBottom: 10 }}>
                  {[
                    { path: ["microclimate","temperature_c"],  label: "Температура",   unit: "°C",   icon: "ri-temp-hot-line",      color: "#FF7A00" },
                    { path: ["microclimate","humidity_pct"],   label: "Влажность",     unit: "%",    icon: "ri-drop-line",          color: "#007BFB" },
                    { path: ["microclimate","co2_ppm"],        label: "CO₂",           unit: "ppm",  icon: "ri-bubble-chart-line",  color: "#7FB539" },
                    { path: ["microclimate","insolation_lux"], label: "Освещённость",  unit: "лк",   icon: "ri-sun-line",           color: "#E8B84A" },
                  ].map(({ path, label, unit, icon, color }) => {
                    const val = sample[path[0]] ? sample[path[0]][path[1]] : sample[path[1]];
                    return (
                      <div key={path.join(".")} className="kpi" style={{ cursor: "default" }}>
                        <div className="kpi-lbl">{label}<i className={icon} style={{ color }}></i></div>
                        <div className="kpi-val">{fmt(val)}<span className="kpi-unit">{unit}</span></div>
                      </div>
                    );
                  })}
                </div>

                {/* ── Irrigation sub-object ───────────────────────────────── */}
                <div style={{ fontSize: 11, fontWeight: 600, color: "var(--fg-muted)", textTransform: "uppercase", letterSpacing: ".05em", padding: "4px 2px" }}>
                  Полив и питание
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 10 }}>
                  {[
                    { path: ["irrigation","substrate_ec"],          label: "EC субстрата", unit: "mS/cm", icon: "ri-flask-line",        color: "#FFB300" },
                    { path: ["irrigation","drain_ec"],              label: "EC дренажа",   unit: "mS/cm", icon: "ri-flask-line",        color: "#FF7A00" },
                    { path: ["irrigation","drip_flow_lh"],          label: "Расход полива",unit: "л/ч",   icon: "ri-water-flash-line",  color: "#007BFB" },
                    { path: ["irrigation","substrate_moisture_pct"],label: "Влажность субстрата",unit:"%",icon: "ri-drop-line",        color: "#26972B" },
                    { path: ["irrigation","feed_ec"],               label: "EC раствора",  unit: "mS/cm", icon: "ri-test-tube-line",    color: "#7FB539" },
                    { path: ["power","battery_pct"],                label: "Батарея",      unit: "%",     icon: "ri-battery-line",      color: "#a04200" },
                  ].map(({ path, label, unit, icon, color }) => {
                    const val = sample[path[0]] ? sample[path[0]][path[1]] : sample[path[1]];
                    return (
                      <div key={path.join(".")} className="kpi" style={{ cursor: "default" }}>
                        <div className="kpi-lbl">{label}<i className={icon} style={{ color }}></i></div>
                        <div className="kpi-val">{fmt(val)}<span className="kpi-unit">{unit}</span></div>
                      </div>
                    );
                  })}
                </div>

                {/* Raw JSON payload for transparency */}
                <div className="card" style={{ padding: 0, overflow: "hidden" }}>
                  <div style={{
                    padding: "10px 14px",
                    borderBottom: "1px solid var(--border)",
                    display: "flex",
                    justifyContent: "space-between",
                    alignItems: "center",
                    fontSize: 12,
                    color: "var(--fg-muted)",
                    fontWeight: 600,
                  }}>
                    <span>Сырые данные · TelemetryEvent</span>
                    {sample.timestamp && (
                      <span style={{ fontFamily: "ui-monospace,Menlo,monospace", fontWeight: 400 }}>
                        {new Date(sample.timestamp).toLocaleTimeString("ru-RU")}
                      </span>
                    )}
                  </div>
                  <pre style={{
                    margin: 0,
                    padding: "12px 14px",
                    fontSize: 11,
                    lineHeight: 1.6,
                    fontFamily: "ui-monospace,Menlo,monospace",
                    color: "var(--fg)",
                    background: "#1D1C1D",
                    overflowX: "auto",
                    maxHeight: 240,
                    overflowY: "auto",
                  }}>
                    <code style={{ color: "#FAC20B" }}>
                      {JSON.stringify(sample, null, 2)}
                    </code>
                  </pre>
                </div>
              </>
            )}

            {/* Scenario lifecycle status */}
            {(scenarioStatus || activeScenarioId) && (
              <div className={"card " + (walkthroughFocusStepId === "run" ? "walkthrough-target is-current" : "")} style={{
                padding: "12px 14px",
                display: "flex",
                gap: 10,
                alignItems: "center",
                fontSize: 13,
                background: activeScenarioId ? "#E8F2FF" : scenarioStatus === "ok" ? "#E6F7E6" : scenarioStatus === "error" ? "#FCE2E0" : "var(--card)",
                border: "1px solid " + (activeScenarioId ? "#007BFB" : scenarioStatus === "ok" ? "#26972B" : scenarioStatus === "error" ? "#E5342B" : "var(--border)"),
              }}>
                <i className={
                  activeScenarioId ? "ri-play-circle-line" :
                  scenarioStatus === "sending" ? "ri-loader-4-line" :
                  scenarioStatus === "ok"      ? "ri-checkbox-circle-line" :
                  "ri-error-warning-line"
                } style={{
                  fontSize: 18,
                  color: activeScenarioId ? "#007BFB" : scenarioStatus === "ok" ? "#26972B" : scenarioStatus === "error" ? "#E5342B" : "var(--fg-muted)"
                }}></i>
                <div>
                  <div style={{ fontWeight: 600 }}>
                    {activeScenarioId ? "Сценарий выполняется (автотик)" :
                     scenarioStatus === "sending" ? "Отправка сценария…" :
                     scenarioStatus === "ok"      ? "Сценарий запущен" :
                     "Ошибка активации сценария"}
                  </div>
                  <div style={{ fontSize: 11, color: "var(--fg-muted)", marginTop: 2 }}>
                    {selectedDevice} · {activeScenarioMeta.label} · {activeScenarioId ? `до завершения ${remainingSec != null ? `${remainingSec} c` : "—"}` : fmtDurationShort(duration)}
                  </div>
                </div>
              </div>
            )}
          </div>
        </div>
        </>
      )}
    </div>
  );
}

window.SimulatorStand = SimulatorStand;
