import type { PolygonRunState } from "./runArchive";

export type PolygonProviderRole =
  | "world"
  | "physics"
  | "state"
  | "controller"
  | "transport"
  | "sensor"
  | "traffic";

export interface PolygonProviderDescriptor {
  providerId: string;
  roles: PolygonProviderRole[];
  capabilities: string[];
}

export interface PolygonProviderProfile {
  profileId: string;
  providers: PolygonProviderDescriptor[];
  clock: {
    providerId: string;
    domain: string;
    unit: "nanoseconds";
    mode: "simulation";
  };
  controlProfiles: string[];
  canonicalFrames: {
    world: "map_enu";
    body: "base_link_flu";
  };
}

export interface PolygonWorkerStatus {
  workerId: string;
  available: boolean;
  controlAvailable: boolean;
  activeRunId: string | null;
  runState: PolygonRunState | null;
  activeProviderIds: string[];
  providerProfile: PolygonProviderProfile | null;
  isolation: {
    network: string;
    processIdentity: string;
    artifactPolicy: "d-only";
  };
}

export interface PolygonVehicleState {
  runId: string;
  sequence: number;
  observedAtUtc: string;
  hostMonotonicNs: number;
  simTimeNs: number;
  position: { x: number; y: number; z: number };
  orientation: { x: number; y: number; z: number; w: number };
  sourceProvider: string;
  sourceTopic: string;
  sourceSignal: "ground-truth";
  sourceQuality: "diagnostic";
}

export interface PolygonCommandAcceptance {
  runId: string;
  commandId: string;
  sequence: number;
  issuedAtSimNs: number;
  validUntilSimNs: number;
  speedMps: number;
  steeringNormalized: number;
  deliveryProvider: string;
  controlProfile: string;
  accepted: true;
  controllerReady: boolean;
  ttlExpiredCount: number;
  diagnostics: Record<string, boolean>;
}

export class PolygonWorkerContractError extends Error {}

export class PolygonWorkerApiError extends Error {
  constructor(message: string, readonly status: number | null = null) {
    super(message);
  }
}

type PolygonWorkerFetch = (
  input: RequestInfo | URL,
  init?: RequestInit,
) => Promise<Response>;

const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SAFE_CAPABILITY = /^[a-z0-9][a-z0-9._/-]{0,127}$/;
const SAFE_CLOCK_DOMAIN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const PROVIDER_ROLES = new Set<PolygonProviderRole>([
  "world",
  "physics",
  "state",
  "controller",
  "transport",
  "sensor",
  "traffic",
]);
const RUN_STATES = new Set<PolygonRunState>([
  "admitted",
  "starting",
  "running",
  "paused",
  "stopping",
  "completed",
  "failed",
  "aborted",
]);
const STATUS_KEYS = new Set([
  "schema_version",
  "worker_id",
  "transport",
  "mode",
  "available",
  "control_available",
  "active_run_id",
  "run_state",
  "active_provider_ids",
  "provider_profile",
  "isolation",
  "authority",
]);
const PROVIDER_PROFILE_KEYS = new Set([
  "schema_version",
  "profile_id",
  "providers",
  "clock",
  "control_profiles",
  "canonical_frames",
]);
const PROVIDER_DESCRIPTOR_KEYS = new Set(["provider_id", "roles", "capabilities"]);
const CLOCK_KEYS = new Set(["provider_id", "domain", "unit", "mode"]);
const CANONICAL_FRAME_KEYS = new Set(["world", "body"]);
const ISOLATION_KEYS = new Set(["network", "process_identity", "artifact_policy"]);
const AUTHORITY_KEYS = new Set([
  "scope",
  "actuator_authority",
  "direct_actuator_setpoints_allowed",
]);
const VEHICLE_KEYS = new Set([
  "schema_version",
  "run_id",
  "sequence",
  "observed_at_utc",
  "host_monotonic_ns",
  "sim_time_ns",
  "frame_id",
  "child_frame_id",
  "pose",
  "source",
  "safety",
]);
const POSE_KEYS = new Set(["position_m", "orientation_xyzw"]);
const POSITION_KEYS = new Set(["x", "y", "z"]);
const ORIENTATION_KEYS = new Set(["x", "y", "z", "w"]);
const SOURCE_KEYS = new Set(["provider", "topic", "signal", "quality"]);
const SAFETY_KEYS = new Set([
  "scope",
  "actuator_authority",
  "navigation_or_safety_accepted",
]);
const COMMAND_KEYS = new Set([
  "schema_version",
  "run_id",
  "command_id",
  "sequence",
  "issued_at_sim_ns",
  "valid_until_sim_ns",
  "speed_mps",
  "steering_normalized",
  "authority_scope",
  "delivery",
]);
const COMMAND_DELIVERY_KEYS = new Set([
  "provider_id",
  "control_profile",
  "accepted",
  "controller_ready",
  "ttl_expired_count",
  "diagnostics",
]);

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function record(value: unknown, label: string): Record<string, unknown> {
  if (!isRecord(value)) {
    throw new PolygonWorkerContractError(`${label} должен быть объектом.`);
  }
  return value;
}

function exactKeys(
  value: Record<string, unknown>,
  expected: ReadonlySet<string>,
  label: string,
) {
  const keys = Object.keys(value);
  if (keys.length !== expected.size || keys.some((key) => !expected.has(key))) {
    throw new PolygonWorkerContractError(`${label} содержит неизвестные или отсутствующие поля.`);
  }
}

function stringValue(value: unknown, label: string, maximum = 512): string {
  if (typeof value !== "string") {
    throw new PolygonWorkerContractError(`${label} должен быть строкой.`);
  }
  const normalized = value.trim();
  if (!normalized || normalized.length > maximum) {
    throw new PolygonWorkerContractError(`${label} имеет недопустимую длину.`);
  }
  return normalized;
}

function safeId(value: unknown, label: string): string {
  const identifier = stringValue(value, label, 128);
  if (!SAFE_ID.test(identifier)) {
    throw new PolygonWorkerContractError(`${label} содержит небезопасный идентификатор.`);
  }
  return identifier;
}

function booleanValue(value: unknown, label: string): boolean {
  if (typeof value !== "boolean") {
    throw new PolygonWorkerContractError(`${label} должен быть boolean.`);
  }
  return value;
}

function integerValue(value: unknown, label: string): number {
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
    throw new PolygonWorkerContractError(`${label} должен быть неотрицательным целым.`);
  }
  return value;
}

function finiteValue(value: unknown, label: string): number {
  if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) >= 1e9) {
    throw new PolygonWorkerContractError(`${label} должен быть конечным числом.`);
  }
  return value;
}

function uniqueStrings(
  value: unknown,
  label: string,
  validator: (item: unknown, itemLabel: string) => string,
): string[] {
  if (!Array.isArray(value) || value.length === 0 || value.length > 32) {
    throw new PolygonWorkerContractError(`${label} должен быть ограниченным массивом.`);
  }
  const result = value.map((item, index) => validator(item, `${label}[${index}]`));
  if (new Set(result).size !== result.length) {
    throw new PolygonWorkerContractError(`${label} содержит повторения.`);
  }
  return result;
}

function decodeProviderProfile(payload: unknown): PolygonProviderProfile {
  const value = record(payload, "provider_profile");
  exactKeys(value, PROVIDER_PROFILE_KEYS, "provider_profile");
  if (value.schema_version !== "missioncore.simulation-provider-profile/v1") {
    throw new PolygonWorkerContractError("provider_profile имеет неизвестную схему.");
  }
  if (!Array.isArray(value.providers) || value.providers.length === 0 || value.providers.length > 32) {
    throw new PolygonWorkerContractError("provider_profile.providers имеет недопустимый размер.");
  }
  const providers = value.providers.map((item, index): PolygonProviderDescriptor => {
    const provider = record(item, `providers[${index}]`);
    exactKeys(provider, PROVIDER_DESCRIPTOR_KEYS, `providers[${index}]`);
    const roles = uniqueStrings(
      provider.roles,
      `providers[${index}].roles`,
      (role, label) => {
        const result = stringValue(role, label, 32);
        if (!PROVIDER_ROLES.has(result as PolygonProviderRole)) {
          throw new PolygonWorkerContractError(`${label} содержит неизвестную роль.`);
        }
        return result;
      },
    ) as PolygonProviderRole[];
    const capabilities = uniqueStrings(
      provider.capabilities,
      `providers[${index}].capabilities`,
      (capability, label) => {
        const result = stringValue(capability, label, 128);
        if (!SAFE_CAPABILITY.test(result)) {
          throw new PolygonWorkerContractError(`${label} содержит небезопасную возможность.`);
        }
        return result;
      },
    );
    return {
      providerId: safeId(provider.provider_id, `providers[${index}].provider_id`),
      roles,
      capabilities,
    };
  });
  if (new Set(providers.map((provider) => provider.providerId)).size !== providers.length) {
    throw new PolygonWorkerContractError("provider_profile содержит повторяющиеся provider_id.");
  }
  const clockValue = record(value.clock, "provider_profile.clock");
  exactKeys(clockValue, CLOCK_KEYS, "provider_profile.clock");
  const clockProviderId = safeId(clockValue.provider_id, "clock.provider_id");
  const clockDomain = stringValue(clockValue.domain, "clock.domain", 128);
  if (
    !SAFE_CLOCK_DOMAIN.test(clockDomain) ||
    clockValue.unit !== "nanoseconds" ||
    clockValue.mode !== "simulation"
  ) {
    throw new PolygonWorkerContractError("provider_profile.clock нарушает v1 контракт.");
  }
  const clockProvider = providers.find((provider) => provider.providerId === clockProviderId);
  if (!clockProvider?.capabilities.includes("clock.simulation")) {
    throw new PolygonWorkerContractError("Источник simulation clock не объявлен провайдером.");
  }
  if (!providers.some((provider) =>
    provider.roles.includes("state") &&
    provider.capabilities.includes("state.vehicle-pose"))) {
    throw new PolygonWorkerContractError("provider_profile не предоставляет VehicleState.");
  }
  const controlProfiles = uniqueStrings(
    value.control_profiles,
    "provider_profile.control_profiles",
    (profile, label) => {
      const result = stringValue(profile, label, 64);
      if (!["rover-speed-steering/v1", "rover-speed-yaw-rate/v1"].includes(result)) {
        throw new PolygonWorkerContractError(`${label} содержит неизвестный профиль.`);
      }
      return result;
    },
  );
  const controllerCapabilities = new Set(
    providers
      .filter((provider) => provider.roles.includes("controller"))
      .flatMap((provider) => provider.capabilities),
  );
  if (controlProfiles.some((profile) => !controllerCapabilities.has(`command.${profile}`))) {
    throw new PolygonWorkerContractError("Контроллер не поддерживает заявленный профиль команд.");
  }
  const frames = record(value.canonical_frames, "canonical_frames");
  exactKeys(frames, CANONICAL_FRAME_KEYS, "canonical_frames");
  if (frames.world !== "map_enu" || frames.body !== "base_link_flu") {
    throw new PolygonWorkerContractError("provider_profile не нормализован в ENU/FLU.");
  }
  return {
    profileId: safeId(value.profile_id, "provider_profile.profile_id"),
    providers,
    clock: {
      providerId: clockProviderId,
      domain: clockDomain,
      unit: "nanoseconds",
      mode: "simulation",
    },
    controlProfiles,
    canonicalFrames: {
      world: "map_enu",
      body: "base_link_flu",
    },
  };
}

export function decodePolygonWorkerStatus(payload: unknown): PolygonWorkerStatus {
  const value = record(payload, "Статус Simulation Worker");
  exactKeys(value, STATUS_KEYS, "Статус Simulation Worker");
  if (
    value.schema_version !== "missioncore.simulation-worker-status/v2" ||
    value.transport !== "unix" ||
    value.mode !== "simulation"
  ) {
    throw new PolygonWorkerContractError("Статус Simulation Worker имеет неизвестную схему.");
  }
  const isolation = record(value.isolation, "isolation");
  exactKeys(isolation, ISOLATION_KEYS, "isolation");
  if (isolation.artifact_policy !== "d-only") {
    throw new PolygonWorkerContractError("Simulation Worker нарушает D-only политику.");
  }
  const authority = record(value.authority, "authority");
  exactKeys(authority, AUTHORITY_KEYS, "authority");
  if (
    authority.scope !== "virtual-only" ||
    authority.actuator_authority !== false ||
    authority.direct_actuator_setpoints_allowed !== false
  ) {
    throw new PolygonWorkerContractError("Simulation Worker вышел за virtual-only границу.");
  }
  if (!Array.isArray(value.active_provider_ids) || value.active_provider_ids.length > 32) {
    throw new PolygonWorkerContractError("active_provider_ids должен быть ограниченным массивом.");
  }
  const activeProviderIds = value.active_provider_ids.map((item, index) =>
    safeId(item, `active_provider_ids[${index}]`));
  if (new Set(activeProviderIds).size !== activeProviderIds.length) {
    throw new PolygonWorkerContractError("active_provider_ids содержит повторения.");
  }
  const available = booleanValue(value.available, "available");
  const controlAvailable = booleanValue(value.control_available, "control_available");
  const providerProfile = value.provider_profile === null
    ? null
    : decodeProviderProfile(value.provider_profile);
  if ((available && providerProfile === null) || (!available && controlAvailable)) {
    throw new PolygonWorkerContractError("Доступность worker и provider_profile противоречат.");
  }
  const activeRunId = value.active_run_id === null
    ? null
    : safeId(value.active_run_id, "active_run_id");
  const runStateValue = value.run_state === null
    ? null
    : stringValue(value.run_state, "run_state", 32);
  if (
    (runStateValue !== null && !RUN_STATES.has(runStateValue as PolygonRunState)) ||
    (activeRunId === null) !== (runStateValue === null)
  ) {
    throw new PolygonWorkerContractError("Активный прогон и его состояние противоречат друг другу.");
  }
  return {
    workerId: safeId(value.worker_id, "worker_id"),
    available,
    controlAvailable,
    activeRunId,
    runState: runStateValue as PolygonRunState | null,
    activeProviderIds,
    providerProfile,
    isolation: {
      network: stringValue(isolation.network, "isolation.network", 64),
      processIdentity: safeId(isolation.process_identity, "isolation.process_identity"),
      artifactPolicy: "d-only",
    },
  };
}

export function decodePolygonVehicleState(payload: unknown): PolygonVehicleState {
  const value = record(payload, "VehicleState");
  exactKeys(value, VEHICLE_KEYS, "VehicleState");
  if (
    value.schema_version !== "missioncore.vehicle-state/v1" ||
    value.frame_id !== "map_enu" ||
    value.child_frame_id !== "base_link_flu"
  ) {
    throw new PolygonWorkerContractError("VehicleState имеет неизвестную схему координат.");
  }
  const pose = record(value.pose, "pose");
  exactKeys(pose, POSE_KEYS, "pose");
  const position = record(pose.position_m, "position_m");
  exactKeys(position, POSITION_KEYS, "position_m");
  const orientation = record(pose.orientation_xyzw, "orientation_xyzw");
  exactKeys(orientation, ORIENTATION_KEYS, "orientation_xyzw");
  const source = record(value.source, "source");
  exactKeys(source, SOURCE_KEYS, "source");
  if (
    source.signal !== "ground-truth" ||
    source.quality !== "diagnostic"
  ) {
    throw new PolygonWorkerContractError("Live-сигнал не маркирован как diagnostic ground truth.");
  }
  const safety = record(value.safety, "safety");
  exactKeys(safety, SAFETY_KEYS, "safety");
  if (
    safety.scope !== "virtual-only" ||
    safety.actuator_authority !== false ||
    safety.navigation_or_safety_accepted !== false
  ) {
    throw new PolygonWorkerContractError("VehicleState нарушает virtual-only границу.");
  }
  const observedAtUtc = stringValue(value.observed_at_utc, "observed_at_utc", 64);
  if (!Number.isFinite(Date.parse(observedAtUtc))) {
    throw new PolygonWorkerContractError("observed_at_utc должен быть ISO-датой.");
  }
  return {
    runId: safeId(value.run_id, "run_id"),
    sequence: integerValue(value.sequence, "sequence"),
    observedAtUtc,
    hostMonotonicNs: integerValue(value.host_monotonic_ns, "host_monotonic_ns"),
    simTimeNs: integerValue(value.sim_time_ns, "sim_time_ns"),
    position: {
      x: finiteValue(position.x, "position.x"),
      y: finiteValue(position.y, "position.y"),
      z: finiteValue(position.z, "position.z"),
    },
    orientation: {
      x: finiteValue(orientation.x, "orientation.x"),
      y: finiteValue(orientation.y, "orientation.y"),
      z: finiteValue(orientation.z, "orientation.z"),
      w: finiteValue(orientation.w, "orientation.w"),
    },
    sourceProvider: safeId(source.provider, "source.provider"),
    sourceTopic: stringValue(source.topic, "source.topic", 512),
    sourceSignal: "ground-truth",
    sourceQuality: "diagnostic",
  };
}

export function decodePolygonCommandAcceptance(payload: unknown): PolygonCommandAcceptance {
  const value = record(payload, "Подтверждение команды");
  exactKeys(value, COMMAND_KEYS, "Подтверждение команды");
  if (
    value.schema_version !== "missioncore.command-acceptance/v2" ||
    value.authority_scope !== "virtual-only"
  ) {
    throw new PolygonWorkerContractError("Команда вышла за virtual-only контракт.");
  }
  const delivery = record(value.delivery, "delivery");
  exactKeys(delivery, COMMAND_DELIVERY_KEYS, "delivery");
  if (delivery.control_profile !== "rover-speed-steering/v1" || delivery.accepted !== true) {
    throw new PolygonWorkerContractError("Команда подтверждена несовместимым adapter.");
  }
  const diagnosticsValue = record(delivery.diagnostics, "delivery.diagnostics");
  if (Object.keys(diagnosticsValue).length > 16) {
    throw new PolygonWorkerContractError("delivery.diagnostics превышает допустимый размер.");
  }
  const diagnostics = Object.fromEntries(
    Object.entries(diagnosticsValue).map(([key, item]) => [
      safeId(key, "delivery.diagnostics key"),
      booleanValue(item, `delivery.diagnostics.${key}`),
    ]),
  );
  const sequence = integerValue(value.sequence, "sequence");
  const issuedAtSimNs = integerValue(value.issued_at_sim_ns, "issued_at_sim_ns");
  const validUntilSimNs = integerValue(value.valid_until_sim_ns, "valid_until_sim_ns");
  const speedMps = finiteValue(value.speed_mps, "speed_mps");
  const steeringNormalized = finiteValue(
    value.steering_normalized,
    "steering_normalized",
  );
  if (
    sequence < 1 ||
    validUntilSimNs <= issuedAtSimNs ||
    validUntilSimNs - issuedAtSimNs > 250_000_000 ||
    speedMps < -1.5 ||
    speedMps > 1.5 ||
    steeringNormalized < -1 ||
    steeringNormalized > 1
  ) {
    throw new PolygonWorkerContractError("Команда нарушает допустимый S1 envelope.");
  }
  return {
    runId: safeId(value.run_id, "run_id"),
    commandId: safeId(value.command_id, "command_id"),
    sequence,
    issuedAtSimNs,
    validUntilSimNs,
    speedMps,
    steeringNormalized,
    deliveryProvider: safeId(delivery.provider_id, "delivery.provider_id"),
    controlProfile: "rover-speed-steering/v1",
    accepted: true,
    controllerReady: booleanValue(delivery.controller_ready, "delivery.controller_ready"),
    ttlExpiredCount: integerValue(
      delivery.ttl_expired_count,
      "delivery.ttl_expired_count",
    ),
    diagnostics,
  };
}

async function responseBody(response: Response): Promise<unknown> {
  const contentType = response.headers.get("content-type") ?? "";
  if (!contentType.toLowerCase().startsWith("application/json")) {
    throw new PolygonWorkerContractError("Polygon Worker API вернул не JSON.");
  }
  try {
    return await response.json();
  } catch {
    throw new PolygonWorkerContractError("Polygon Worker API вернул повреждённый JSON.");
  }
}

function apiError(body: unknown, fallback: string, status: number): PolygonWorkerApiError {
  if (isRecord(body) && typeof body.detail === "string") {
    const detail = body.detail.trim();
    if (detail && detail.length <= 1_000) return new PolygonWorkerApiError(detail, status);
  }
  return new PolygonWorkerApiError(`${fallback} HTTP ${status}.`, status);
}

async function requestJson(
  url: string,
  init: RequestInit,
  fallback: string,
  fetcher: PolygonWorkerFetch,
): Promise<unknown> {
  let response: Response;
  try {
    response = await fetcher(url, init);
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") throw error;
    throw new PolygonWorkerApiError(fallback);
  }
  const body = await responseBody(response);
  if (!response.ok) throw apiError(body, fallback, response.status);
  return body;
}

export async function fetchPolygonWorkerStatus({
  signal,
  fetcher = globalThis.fetch,
}: {
  signal?: AbortSignal;
  fetcher?: PolygonWorkerFetch;
} = {}): Promise<PolygonWorkerStatus> {
  return decodePolygonWorkerStatus(await requestJson(
    "/api/v1/polygon/worker",
    { method: "GET", headers: { Accept: "application/json" }, signal },
    "Не удалось получить статус Simulation Worker.",
    fetcher,
  ));
}

export async function fetchPolygonVehicleState({
  signal,
  fetcher = globalThis.fetch,
}: {
  signal?: AbortSignal;
  fetcher?: PolygonWorkerFetch;
} = {}): Promise<PolygonVehicleState> {
  return decodePolygonVehicleState(await requestJson(
    "/api/v1/polygon/worker/live",
    { method: "GET", headers: { Accept: "application/json" }, signal },
    "Не удалось получить live-состояние ровера.",
    fetcher,
  ));
}

export async function startPolygonWorker({
  idempotencyKey,
  signal,
  fetcher = globalThis.fetch,
}: {
  idempotencyKey: string;
  signal?: AbortSignal;
  fetcher?: PolygonWorkerFetch;
}): Promise<PolygonWorkerStatus> {
  return decodePolygonWorkerStatus(await requestJson(
    "/api/v1/polygon/worker/runs",
    {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify({ scenario_id: "stock-rover-ackermann" }),
      signal,
    },
    "Не удалось запустить Simulation Worker.",
    fetcher,
  ));
}

export async function stopPolygonWorker(
  runId: string,
  {
    idempotencyKey,
    signal,
    fetcher = globalThis.fetch,
  }: {
    idempotencyKey: string;
    signal?: AbortSignal;
    fetcher?: PolygonWorkerFetch;
  },
): Promise<PolygonWorkerStatus> {
  if (!SAFE_ID.test(runId)) {
    throw new PolygonWorkerContractError("Некорректный идентификатор активного прогона.");
  }
  return decodePolygonWorkerStatus(await requestJson(
    `/api/v1/polygon/worker/runs/${encodeURIComponent(runId)}/stop`,
    {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: "{}",
      signal,
    },
    "Не удалось остановить Simulation Worker.",
    fetcher,
  ));
}

export async function sendPolygonRoverCommand(
  runId: string,
  {
    speedMps,
    steeringNormalized,
    idempotencyKey,
    signal,
    fetcher = globalThis.fetch,
  }: {
    speedMps: number;
    steeringNormalized: number;
    idempotencyKey: string;
    signal?: AbortSignal;
    fetcher?: PolygonWorkerFetch;
  },
): Promise<PolygonCommandAcceptance> {
  if (!SAFE_ID.test(runId)) {
    throw new PolygonWorkerContractError("Некорректный идентификатор активного прогона.");
  }
  if (
    !Number.isFinite(speedMps) ||
    !Number.isFinite(steeringNormalized) ||
    speedMps < -1.5 ||
    speedMps > 1.5 ||
    steeringNormalized < -1 ||
    steeringNormalized > 1
  ) {
    throw new PolygonWorkerContractError("Команда вышла за допустимый S1 envelope.");
  }
  return decodePolygonCommandAcceptance(await requestJson(
    `/api/v1/polygon/worker/runs/${encodeURIComponent(runId)}/commands`,
    {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify({
        speed_mps: speedMps,
        steering_normalized: steeringNormalized,
      }),
      signal,
    },
    "Simulation Worker не подтвердил команду ровера.",
    fetcher,
  ));
}
