import assert from "node:assert/strict";
import { after, before, test } from "node:test";

import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer } from "vite";

let server;
let xgridsK1Manifest;
let xgridsK1ObservationSources;
let openObservationSource;
let shouldRestartObservationSource;
let consumeCameraLeaseRetry;
let resetCameraLeaseRetryBudget;
let initialObservationWindowRect;
let ObservationTimeline;
let shouldCaptureWorkspacePointer;
let normalizeTimelineRange;
let timelineOffsetSeconds;
let formatTimelineDuration;
let normalizeAccumulationSeconds;
let formatAccumulationDuration;
let resolveRerunSourceUrl;
let resolveRecordedBlueprintUrl;
let fetchRecordedBlueprintRrd;
let resolveRecordedPerceptionUrl;
let fetchRecordedPerceptionRrd;
let resolveRecordedPointColorsUrl;
let fetchRecordedPointColorsRrd;
let recordedPointColorKey;
let isRecordedPlaybackFullyBuffered;
let recordedObservationSources;
let selectRecordedMediaEpoch;
let recordedMediaLocalTime;

before(async () => {
  server = await createServer({
    appType: "custom",
    logLevel: "silent",
    server: { middlewareMode: true },
  });
  ({ xgridsK1Manifest } = await server.ssrLoadModule(
    "@xgrids-k1/frontend/manifest.ts",
  ));
  ({ xgridsK1ObservationSources } = await server.ssrLoadModule(
    "@xgrids-k1/frontend/observationSources.ts",
  ));
  ({ openObservationSource, shouldRestartObservationSource } = await server.ssrLoadModule(
    "/src/core/observation/layoutPolicy.ts",
  ));
  ({ consumeCameraLeaseRetry, resetCameraLeaseRetryBudget } = await server.ssrLoadModule(
    "/src/components/MseFmp4WebSocketPlayer.tsx",
  ));
  ({ initialObservationWindowRect, shouldCaptureWorkspacePointer } = await server.ssrLoadModule(
    "/src/components/FloatingObservationWindow.tsx",
  ));
  ({
    ObservationTimeline,
    normalizeTimelineRange,
    timelineOffsetSeconds,
    formatTimelineDuration,
    normalizeAccumulationSeconds,
    formatAccumulationDuration,
  } =
    await server.ssrLoadModule("/src/components/ObservationTimeline.tsx"));
  ({
    resolveRerunSourceUrl,
    resolveRecordedBlueprintUrl,
    fetchRecordedBlueprintRrd,
    resolveRecordedPerceptionUrl,
    fetchRecordedPerceptionRrd,
    resolveRecordedPointColorsUrl,
    fetchRecordedPointColorsRrd,
    recordedPointColorKey,
    isRecordedPlaybackFullyBuffered,
  } = await server.ssrLoadModule(
    "/src/components/RerunViewport.tsx",
  ));
  ({ recordedObservationSources } = await server.ssrLoadModule(
    "/src/core/observation/recordedObservationSources.ts",
  ));
  ({ selectRecordedMediaEpoch, recordedMediaLocalTime } = await server.ssrLoadModule(
    "/src/components/RecordedFmp4Player.tsx",
  ));
});

test("observation camera windows tile from the bottom-right above the live timeline", () => {
  const bounds = { width: 1280, height: 720 };
  const left = initialObservationWindowRect(0, 2, bounds);
  const right = initialObservationWindowRect(1, 2, bounds);

  assert.equal(left.y, right.y);
  assert.ok(left.x + left.width < right.x);
  assert.equal(right.x + right.width, bounds.width - 18);
  assert.ok(right.y + right.height <= bounds.height - 64);
});

test("observation camera windows stack without overlap when the viewport is narrow", () => {
  const bounds = { width: 420, height: 700 };
  const lower = initialObservationWindowRect(0, 2, bounds);
  const upper = initialObservationWindowRect(1, 2, bounds);

  assert.equal(lower.x, upper.x);
  assert.ok(upper.y + upper.height < lower.y);
  for (const rect of [lower, upper]) {
    assert.ok(rect.x >= 0 && rect.y >= 0);
    assert.ok(rect.x + rect.width <= bounds.width);
    assert.ok(rect.y + rect.height <= bounds.height - 64);
  }
});

test("observation camera tiling remains in bounds on a constrained viewport", () => {
  const bounds = { width: 260, height: 280 };
  const rects = Array.from({ length: 2 }, (_, index) => (
    initialObservationWindowRect(index, 2, bounds)
  ));

  for (const rect of rects) {
    assert.ok(rect.width > 0 && rect.height > 0);
    assert.ok(rect.x >= 0 && rect.y >= 0);
    assert.ok(rect.x + rect.width <= bounds.width);
    assert.ok(rect.y + rect.height <= bounds.height - 64);
  }
  assert.ok(rects[1].y + rects[1].height <= rects[0].y);
});

test("floating observation interaction captures resize and movable header pointers", () => {
  const target = (matches) => ({ closest: (selector) => matches.includes(selector) });
  assert.equal(
    shouldCaptureWorkspacePointer(0, target([".nodedc-workspace-window__resize"])),
    true,
  );
  assert.equal(
    shouldCaptureWorkspacePointer(0, target([".nodedc-workspace-window__head"])),
    true,
  );
  assert.equal(
    shouldCaptureWorkspacePointer(
      0,
      target([".nodedc-workspace-window__head", "button, input, select, textarea, a"]),
    ),
    false,
  );
  assert.equal(
    shouldCaptureWorkspacePointer(2, target([".nodedc-workspace-window__resize"])),
    false,
  );
});

test("recorded observation timeline clamps relative seek time without epoch precision in the UI", () => {
  const range = normalizeTimelineRange({
    min: 0,
    max: 12_500_000_000,
  });
  assert.ok(range);
  assert.equal(timelineOffsetSeconds(range, range.min + 2_250_000_000), 2.25);
  assert.equal(timelineOffsetSeconds(range, range.min - 1), 0);
  assert.equal(timelineOffsetSeconds(range, range.max + 1), 12.5);
  assert.equal(formatTimelineDuration(62.125), "01:02.125");
});

test("recorded observation timeline rejects empty and non-finite ranges", () => {
  assert.equal(normalizeTimelineRange(null), null);
  assert.equal(normalizeTimelineRange({ min: 10, max: 10 }), null);
  assert.equal(normalizeTimelineRange({ min: Number.NaN, max: 10 }), null);
});

test("accumulation control normalizes UI values and distinguishes a single frame", () => {
  assert.equal(normalizeAccumulationSeconds(-3), 0);
  assert.equal(normalizeAccumulationSeconds(12.6), 13);
  assert.equal(normalizeAccumulationSeconds(999), 120);
  assert.equal(normalizeAccumulationSeconds(Number.NaN), 0);
  assert.equal(formatAccumulationDuration(0), "Кадр");
  assert.equal(formatAccumulationDuration(12), "12 с");
});

test("spatial timeline renders synchronized accumulation and playback controls", () => {
  const markup = renderToStaticMarkup(createElement(ObservationTimeline, {
    active: true,
    sourceCount: 2,
    mode: "recorded",
    seekable: true,
    rangeNs: { min: 0, max: 20_000_000_000 },
    currentNs: 5_000_000_000,
    accumulationSeconds: 12,
    onAccumulationChange: () => undefined,
    onAccumulationCommit: () => undefined,
    onSeek: () => undefined,
  }));

  assert.match(markup, /data-accumulation="true"/);
  assert.match(markup, /Накопление/);
  assert.match(markup, /aria-label="Окно накопления облака точек"/);
  assert.match(markup, /aria-valuetext="12 с"/);
  assert.match(markup, /aria-label="Позиция воспроизведения"/);
  assert.equal((markup.match(/type="range"/g) ?? []).length, 2);
});

test("Rerun expands only root-relative session recordings onto the current origin", () => {
  assert.equal(
    resolveRerunSourceUrl(
      " /api/v1/observation-sessions/session-1/recording.rrd ",
      "http://127.0.0.1:5174",
    ),
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/recording.rrd",
  );
  assert.equal(
    resolveRerunSourceUrl("rerun+http://127.0.0.1:9877/proxy", "http://127.0.0.1:5174"),
    "rerun+http://127.0.0.1:9877/proxy",
  );
  assert.equal(
    resolveRerunSourceUrl("//different-authority.invalid/session.rrd", "http://127.0.0.1:5174"),
    "//different-authority.invalid/session.rrd",
  );
});

test("recorded blueprint endpoint is derived only from canonical same-origin RRD sources", () => {
  assert.equal(
    resolveRecordedBlueprintUrl(
      "/api/v1/observation-sessions/session-1/recording.rrd",
      "http://127.0.0.1:5174",
    ),
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
  );
  assert.equal(
    resolveRecordedBlueprintUrl(
      "https://outside.invalid/api/v1/observation-sessions/session-1/recording.rrd",
      "http://127.0.0.1:5174",
    ),
    null,
  );
  assert.equal(
    resolveRecordedBlueprintUrl(
      "/api/v1/observation-sessions/../recording.rrd",
      "http://127.0.0.1:5174",
    ),
    null,
  );
});

test("recorded replay becomes ready only after the complete declared timeline is buffered", () => {
  assert.equal(isRecordedPlaybackFullyBuffered(null, 20), false);
  assert.equal(
    isRecordedPlaybackFullyBuffered({ min: 0, max: 19_500_000_000 }, 20),
    false,
  );
  assert.equal(
    isRecordedPlaybackFullyBuffered({ min: 0, max: 19_999_500_000 }, 20),
    true,
  );
  assert.equal(
    isRecordedPlaybackFullyBuffered({ min: 0, max: 1 }, undefined),
    true,
  );
  assert.equal(
    isRecordedPlaybackFullyBuffered({ min: 0, max: 20_000_000_000 }, Number.NaN),
    false,
  );
});

test("recorded blueprint fetch is bounded, strict and sends only display settings", async () => {
  const calls = [];
  const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
  const result = await fetchRecordedBlueprintRrd(
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
    {
      accumulationSeconds: 24,
      showGrid: false,
      showPoints: true,
      showTrajectory: false,
      pointSize: 4.5,
      colorMode: "height",
      palette: "custom",
      customColor: "#35d7c1",
    },
    { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
    {
      origin: "http://127.0.0.1:5174",
      blueprintSessionId: "a".repeat(32),
      activeView: "perception3d",
      viewResetGeneration: 1,
      followTrajectory: true,
      perceptionLayers: {
        enabled: true,
        detections2d: true,
        segmentation: false,
        cuboids3d: true,
      },
      fetcher: async (input, init) => {
        calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
        return new Response(payload, {
          status: 200,
          headers: { "Content-Type": "application/vnd.rerun.rrd" },
        });
      },
    },
  );
  assert.deepEqual([...result], [...payload]);
  assert.equal(calls[0].init.method, "POST");
  assert.equal(calls[0].init.credentials, "same-origin");
  assert.deepEqual(calls[0].body, {
    application_id: "nodedc_mission_core_recorded",
    recording_id: "recording-001",
    blueprint_session_id: "a".repeat(32),
    accumulation_seconds: 24,
    show_grid: false,
    show_points: true,
    show_trajectory: false,
    point_size: 4.5,
    color_mode: "height",
    palette: "custom",
    custom_color: "#35d7c1",
    active_view: "perception3d",
    view_reset_generation: 1,
    follow_trajectory: true,
    unified_perception: true,
    show_detections_2d: true,
    show_segmentation: false,
    show_cuboids_3d: true,
  });

  await fetchRecordedBlueprintRrd(
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
    {
      accumulationSeconds: 0,
      showGrid: true,
      showPoints: true,
      showTrajectory: true,
      pointSize: 2.5,
      colorMode: "intensity",
      palette: "turbo",
      customColor: "#ffffff",
    },
    { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
    {
      origin: "http://127.0.0.1:5174",
      blueprintSessionId: "b".repeat(32),
      perceptionLayers: {
        enabled: true,
        detections2d: false,
        segmentation: false,
        cuboids3d: true,
      },
      fetcher: async (input, init) => {
        calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
        return new Response(payload, {
          status: 200,
          headers: { "Content-Type": "application/vnd.rerun.rrd" },
        });
      },
    },
  );
  assert.equal(calls[1].body.unified_perception, false);
  assert.equal(calls[1].body.show_cuboids_3d, true);

  await assert.rejects(
    fetchRecordedBlueprintRrd(
      "https://outside.invalid/api/v1/observation-sessions/session-1/blueprint.rrd",
      {
        accumulationSeconds: 24,
        showGrid: false,
        showPoints: true,
        showTrajectory: false,
        pointSize: 4.5,
        colorMode: "height",
        palette: "custom",
        customColor: "#35d7c1",
      },
      { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
      {
        origin: "http://127.0.0.1:5174",
        blueprintSessionId: "a".repeat(32),
        fetcher: async () => new Response(payload),
      },
    ),
    /Unsafe recorded blueprint request/,
  );
});

test("recorded point colors use one strict same-origin component overlay", async () => {
  const endpoint = resolveRecordedPointColorsUrl(
    "/api/v1/observation-sessions/session-1/recording.rrd",
    "http://127.0.0.1:5174",
  );
  assert.equal(
    endpoint,
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/point-colors.rrd",
  );
  assert.equal(
    resolveRecordedPointColorsUrl(
      "https://outside.invalid/api/v1/observation-sessions/session-1/recording.rrd",
      "http://127.0.0.1:5174",
    ),
    null,
  );
  assert.equal(
    recordedPointColorKey({
      colorMode: "intensity",
      palette: "turbo",
      customColor: "#112233",
    }),
    "intensity|turbo|-",
  );
  assert.equal(
    recordedPointColorKey({
      colorMode: "class",
      palette: "turbo",
      customColor: "#112233",
    }),
    "class|turbo|#112233",
  );

  const calls = [];
  const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
  const result = await fetchRecordedPointColorsRrd(
    endpoint,
    {
      colorMode: "distance",
      palette: "viridis",
      customColor: "#35d7c1",
    },
    { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
    {
      origin: "http://127.0.0.1:5174",
      fetcher: async (input, init) => {
        calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
        return new Response(payload, {
          status: 200,
          headers: {
            "Content-Type": "application/vnd.rerun.rrd",
            "Content-Length": String(payload.byteLength),
          },
        });
      },
    },
  );
  assert.deepEqual([...result], [...payload]);
  assert.deepEqual(calls[0].body, {
    application_id: "nodedc_mission_core_recorded",
    recording_id: "recording-001",
    color_mode: "distance",
    palette: "viridis",
    custom_color: "#35d7c1",
  });
});

test("recorded perception fetch admits one complete same-origin RRD or no layer", async () => {
  const endpoint = resolveRecordedPerceptionUrl(
    "/api/v1/observation-sessions/session-1/recording.rrd",
    "http://127.0.0.1:5174",
  );
  assert.equal(
    endpoint,
    "http://127.0.0.1:5174/api/v1/observation-sessions/session-1/perception.rrd",
  );
  const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
  const progress = [];
  const result = await fetchRecordedPerceptionRrd(
    endpoint,
    { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
    {
      origin: "http://127.0.0.1:5174",
      onProgress: (receivedBytes, totalBytes) => {
        progress.push([receivedBytes, totalBytes]);
      },
      fetcher: async (_input, init) => {
        assert.deepEqual(JSON.parse(String(init.body)), {
          application_id: "nodedc_mission_core_recorded",
          recording_id: "recording-001",
        });
        return new Response(payload, {
          status: 200,
          headers: {
            "Content-Type": "application/vnd.rerun.rrd",
            "Content-Length": String(payload.byteLength),
          },
        });
      },
    },
  );
  assert.deepEqual([...result], [...payload]);
  assert.deepEqual(progress[0], [0, payload.byteLength]);
  assert.deepEqual(progress.at(-1), [payload.byteLength, payload.byteLength]);

  const absent = await fetchRecordedPerceptionRrd(
    endpoint,
    { applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
    {
      origin: "http://127.0.0.1:5174",
      fetcher: async () => new Response(null, { status: 204 }),
    },
  );
  assert.equal(absent, null);
});

test("recorded replay creates an isolated source catalog without live device bindings", () => {
  const sources = recordedObservationSources({
    kind: "rerun-recording",
    sessionId: "session-1",
    sourceUrl: "/api/v1/observation-sessions/session-1/recording.rrd",
    viewerSourceUrl: `/api/v1/observation-sessions/session-1/recording.rrd?generation=${"a".repeat(64)}`,
    mediaType: "application/vnd.rerun.rrd",
    timeline: "session_time",
    timelineStartSeconds: 0,
    timelineEndSeconds: 20,
    seekable: true,
    byteLength: 123,
    sha256: "a".repeat(64),
    playback: { speed: 1, loop: false },
    mediaSources: [{
      id: "recorded.camera.abc123",
      label: "Записанная камера 1",
      modality: "video",
      manifestUrl: "/api/v1/observation-sessions/session-1/media/recorded-video-abc123/manifest",
      manifestGenerationSha256: "c".repeat(64),
      byteLength: 1_024,
      mediaType: "video/mp4",
      timelineStartSeconds: 0.25,
      timelineEndSeconds: 20,
      seekable: true,
      synchronization: "host-arrival-best-effort",
    }],
  });
  assert.deepEqual(sources.map(({ modality }) => modality), ["point-cloud", "video"]);
  assert.equal(sources[1].delivery.kind, "recorded-fmp4-manifest");
  assert.equal(sources[1].delivery.manifestGenerationSha256, "c".repeat(64));
  assert.deepEqual(sources[1].binding, {});
  assert.equal(sources.some(({ provider }) => provider.pluginId.includes("xgrids")), false);
  assert.equal(JSON.stringify(sources).includes("192.168"), false);
});

test("recorded camera epoch selection and shared-clock offset are deterministic", () => {
  const epochs = [
    { ordinal: 1, timelineStartSeconds: 0.25, timelineEndSeconds: 9 },
    { ordinal: 2, timelineStartSeconds: 10, timelineEndSeconds: 20 },
  ];
  assert.equal(selectRecordedMediaEpoch(epochs, 0), null);
  assert.equal(selectRecordedMediaEpoch(epochs, 0.25).ordinal, 1);
  assert.equal(selectRecordedMediaEpoch(epochs, 9), epochs[0]);
  assert.equal(selectRecordedMediaEpoch(epochs, 9.9), null);
  assert.equal(selectRecordedMediaEpoch(epochs, 10).ordinal, 2);
  assert.equal(recordedMediaLocalTime(10, 12.5), 2.5);
  assert.equal(recordedMediaLocalTime(10, 8), 0);
  assert.equal(recordedMediaLocalTime(10, 30, 4), 4);
});

after(async () => {
  await server?.close();
});

function model() {
  const activeModel = xgridsK1Manifest.spec.models[0];
  assert.ok(activeModel, "XGRIDS plugin must declare at least one model");
  return activeModel;
}

function cameraRow(sourceId, label) {
  return {
    stream_id: `camera.preview.${sourceId.split(".").at(-1)}`,
    source_id: sourceId,
    semantic_channel_id: "camera.preview.live",
    label,
    sensor_kind: "camera",
    modality: "encoded-video",
    availability: "available",
    endpoint_label: "MSE · fMP4",
    activation: {
      group_id: "camera.preview.decoder",
      max_active: 1,
      selected: false,
      controllable: true,
    },
    delivery: null,
  };
}

function declaredState(cameraRows = [
  cameraRow("sensor.camera.left", "K1 · камера слева"),
  cameraRow("sensor.camera.right", "K1 · камера справа"),
]) {
  const activeModel = model();
  return {
    phase: "connected",
    source_mode: "idle",
    k1_ip: "192.168.7.10",
    foxglove_ws_url: "ws://192.168.7.10:8765",
    foxglove_viewer_url: "http://192.168.7.10:8765/vendor-viewer",
    compatibility: {
      profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
      camera_preview: "rtsp://192.168.7.10:8554/vendor-preview",
    },
    device_ref: {
      device_id: "device-k1-001",
      model_id: activeModel.id,
      identity_stability: "stable",
      identity_basis: "hardware-identifier",
    },
    device_session: {
      device_session_id: "device-session-001",
      device_id: "device-k1-001",
      compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
      connectivity: "connected",
    },
    sensor_catalog: {
      schema_version: "missioncore.sensor-catalog/v1alpha2",
      revision: "test-profile",
      streams: [
        { stream_id: "spatial.point-cloud.live", modality: "point-cloud", availability: "observed" },
        ...cameraRows,
      ],
    },
    camera_preview: {
      phase: "idle",
      revision: 1,
      generation: 0,
      active_source_id: null,
      delivery: null,
    },
  };
}

function pointCloudStreamingState() {
  return {
    ...declaredState(),
    phase: "streaming",
    source_mode: "live",
    rerun_grpc_url: "rerun+http://127.0.0.1:9877/proxy",
    acquisition: {
      acquisition_id: "acquisition-001",
      device_id: "device-k1-001",
      device_session_id: "device-session-001",
      compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
      control_mode: "operator-manual",
      requested_streams: ["spatial.point-cloud.live"],
      target_host: "127.0.0.1",
      duration_seconds: 0,
      evidence_policy: "required",
      state: "acquiring",
      state_revision: 3,
    },
  };
}

function cameraStreamingState(sourceId) {
  const state = pointCloudStreamingState();
  const delivery = {
    id: `preview-generation-7:${sourceId}`,
    kind: "mse-fmp4-websocket",
    url: "/api/v1/device-plugins/xgrids-k1/camera-preview/ws?generation=7",
    media_type: 'video/mp4; codecs="avc1.640028"',
  };
  state.sensor_catalog = {
    ...state.sensor_catalog,
    streams: state.sensor_catalog.streams.map((stream) =>
      stream.source_id === sourceId
        ? {
            ...stream,
            availability: "streaming",
            activation: { ...stream.activation, selected: true },
            delivery,
          }
        : stream),
  };
  state.camera_preview = {
    phase: "streaming",
    revision: 4,
    generation: 7,
    active_source_id: sourceId,
    delivery,
  };
  return state;
}

function collectUrlLikeStrings(value, found = []) {
  if (typeof value === "string") {
    if (value.includes("://")) found.push(value);
    return found;
  }
  if (Array.isArray(value)) {
    for (const item of value) collectUrlLikeStrings(item, found);
    return found;
  }
  if (value && typeof value === "object") {
    for (const item of Object.values(value)) collectUrlLikeStrings(item, found);
  }
  return found;
}

test("K1 maps zero, one or N catalog cameras without model-specific source ids", () => {
  const zero = xgridsK1ObservationSources(declaredState([]), model());
  const one = xgridsK1ObservationSources(
    declaredState([cameraRow("rig.front", "Передняя камера")]),
    model(),
  );
  const many = xgridsK1ObservationSources(declaredState(), model());

  assert.deepEqual(zero.map(({ modality }) => modality), ["point-cloud"]);
  assert.deepEqual(one.map(({ sourceId }) => sourceId), ["sensor.lidar.primary", "rig.front"]);
  assert.deepEqual(many.map(({ sourceId }) => sourceId), [
    "sensor.lidar.primary",
    "sensor.camera.left",
    "sensor.camera.right",
  ]);
  assert.equal(new Set(many.map(({ id }) => id)).size, many.length);
  assert.ok(many.every(({ provider }) => provider.pluginId && provider.modelId));
  assert.ok(many.every(({ binding }) => binding.deviceId === "device-k1-001"));
});

test("descriptor ids remain stable while point cloud and selected camera start streaming", () => {
  const declared = xgridsK1ObservationSources(declaredState(), model());
  const streaming = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.left"),
    model(),
  );

  assert.deepEqual(streaming.map(({ id }) => id), declared.map(({ id }) => id));
  assert.equal(declared[0].availability, "available");
  assert.equal(streaming[0].availability, "streaming");
  assert.equal(streaming[0].previewUrl, "rerun+http://127.0.0.1:9877/proxy");
});

test("only the authoritative selected camera receives browser delivery", () => {
  const sources = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.left"),
    model(),
  );
  const cameras = sources.filter(({ modality }) => modality === "video");
  const [left, right] = cameras;

  assert.equal(cameras.length, 2);
  assert.equal(left.activation.selected, true);
  assert.equal(left.activation.maxActive, 1);
  assert.equal(left.availability, "streaming");
  assert.equal(left.delivery.kind, "mse-fmp4-websocket");
  assert.equal(left.transport, "websocket");
  assert.equal(right.activation.selected, false);
  assert.equal(right.availability, "available");
  assert.equal(right.delivery, null);
  assert.equal(left.activation.groupId, right.activation.groupId);
  assert.match(left.activation.groupId, /device-session-001/);
});

test("switching left to right keeps ids stable and never exposes both deliveries", () => {
  const left = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.left"),
    model(),
  ).filter(({ modality }) => modality === "video");
  const right = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.right"),
    model(),
  ).filter(({ modality }) => modality === "video");

  assert.deepEqual(right.map(({ id }) => id), left.map(({ id }) => id));
  assert.deepEqual(left.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
    "sensor.camera.left",
  ]);
  assert.deepEqual(right.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
    "sensor.camera.right",
  ]);
});

test("camera descriptors never leak vendor RTSP endpoints or device IP addresses", () => {
  const sources = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.left"),
    model(),
  );
  const cameras = sources.filter(({ modality }) => modality === "video");

  assert.ok(cameras.every(({ previewUrl }) => previewUrl === null));
  assert.deepEqual(collectUrlLikeStrings(sources), [
    "rerun+http://127.0.0.1:9877/proxy",
  ]);
  const serialized = JSON.stringify(cameras);
  assert.doesNotMatch(serialized, /rtsp:\/\//i);
  assert.doesNotMatch(serialized, /192\.168\.7\.10/);
  assert.doesNotMatch(serialized, /vendor-(?:preview|viewer)/);
});

test("unattested, duplicate and unsafe camera entries fail closed", () => {
  const duplicate = cameraRow("sensor.camera.left", "Duplicate");
  const state = cameraStreamingState("sensor.camera.left");
  state.compatibility.profile_id = null;
  state.device_session.compatibility_profile_id = null;
  state.sensor_catalog.streams.push(duplicate);
  state.camera_preview.delivery = {
    ...state.camera_preview.delivery,
    url: "ws://192.168.7.10:9000/leak",
  };

  const cameras = xgridsK1ObservationSources(state, model()).filter(
    ({ modality }) => modality === "video",
  );
  assert.deepEqual(cameras.map(({ sourceId }) => sourceId), ["sensor.camera.right"]);
  assert.equal(cameras[0].availability, "unverified");
  assert.equal(cameras[0].activation.controllable, false);
  assert.equal(cameras[0].delivery, null);
});

test("camera delivery rejects literal and encoded endpoint or credential leaks", () => {
  const unsafeUrls = [
    "/api/preview?upstream=rtsp://camera.local/live",
    "/api/preview?upstream=rtsp%3A%2F%2Fcamera.local%2Flive",
    "/api/preview?upstream=rtsp%253A%252F%252Fcamera.local%252Flive",
    "/api/preview?endpoint=192.0.2.52:8554",
    "/api/preview?endpoint=192%2E168%2E68%2E52",
    "/api/preview?password=not-for-the-browser",
    "/api/preview?%70%61%73%73%77%6f%72%64=not-for-the-browser",
    "/api/preview/camera:secret@device",
    "/%2f%2fevil.example/preview",
  ];

  for (const url of unsafeUrls) {
    const state = cameraStreamingState("sensor.camera.left");
    state.camera_preview.delivery = { ...state.camera_preview.delivery, url };
    state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
      stream.source_id === "sensor.camera.left"
        ? { ...stream, delivery: { ...stream.delivery, url } }
        : stream,
    );
    const left = xgridsK1ObservationSources(state, model()).find(
      ({ sourceId }) => sourceId === "sensor.camera.left",
    );
    assert.ok(left, `left camera descriptor missing for ${url}`);
    assert.equal(left.delivery, null, `unsafe delivery escaped for ${url}`);
  }
});

test("manual camera reconnect restores an exhausted lease retry budget", () => {
  let budget = resetCameraLeaseRetryBudget("delivery-7");
  for (const expectedDelay of [400, 1_000, 2_000]) {
    const retry = consumeCameraLeaseRetry(budget, "delivery-7");
    assert.equal(retry.delay, expectedDelay);
    budget = retry.budget;
  }
  assert.equal(consumeCameraLeaseRetry(budget, "delivery-7").delay, null);

  budget = resetCameraLeaseRetryBudget("delivery-7");
  const retryAfterManualReset = consumeCameraLeaseRetry(budget, "delivery-7");
  assert.equal(retryAfterManualReset.delay, 400);
  assert.equal(retryAfterManualReset.budget.count, 1);
});

test("layout policy evicts only exclusive camera peers", () => {
  const sources = xgridsK1ObservationSources(
    cameraStreamingState("sensor.camera.left"),
    model(),
  );
  const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
  const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
  const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
  assert.ok(left && right && pointCloud);

  const change = openObservationSource(
    [pointCloud.id, left.id],
    right.id,
    sources,
  );
  assert.deepEqual(change.visibleIds, [pointCloud.id, right.id]);
  assert.deepEqual(change.removedIds, [left.id]);
});

test("selected camera without delivery is explicitly restartable", () => {
  const state = cameraStreamingState("sensor.camera.left");
  state.camera_preview = {
    ...state.camera_preview,
    phase: "error",
    delivery: null,
  };
  state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
    stream.source_id === "sensor.camera.left"
      ? { ...stream, availability: "error", delivery: null }
      : stream,
  );
  const sources = xgridsK1ObservationSources(state, model());
  const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
  const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
  assert.ok(left && right);

  assert.equal(left.activation.selected, true);
  assert.equal(left.delivery, null);
  assert.equal(left.availability, "error");
  assert.equal(shouldRestartObservationSource(left), true);
  assert.equal(shouldRestartObservationSource(right), false);
});
