// Ordering rules remain plugin-private because revisions are vendor runtime state.
import type { XgridsCameraPreviewState, XgridsK1State } from "./api";

function monotonicInteger(value: number | null | undefined): number | null {
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
}

function deviceSessionScope(state: XgridsK1State): string | null {
  const sessionId = state.device_session?.device_session_id;
  return typeof sessionId === "string" && sessionId.trim() ? sessionId : null;
}

function cameraSnapshotIsAtLeastAsNew(
  current: XgridsCameraPreviewState,
  incoming: XgridsCameraPreviewState,
): boolean {
  const currentRevision = monotonicInteger(current.revision);
  const incomingRevision = monotonicInteger(incoming.revision);

  if (currentRevision !== null || incomingRevision !== null) {
    if (incomingRevision === null) return false;
    if (currentRevision === null) return true;
    if (incomingRevision !== currentRevision) return incomingRevision > currentRevision;
  }

  const currentGeneration = monotonicInteger(current.generation);
  const incomingGeneration = monotonicInteger(incoming.generation);
  if (currentGeneration === null) return true;
  if (incomingGeneration === null) return false;
  return incomingGeneration >= currentGeneration;
}

/**
 * Select an authoritative runtime snapshot without allowing asynchronous REST,
 * mutation, or event responses to roll camera preview state backwards.
 *
 * Revision is the primary ordering key. In particular, a stop snapshot with a
 * newer revision and a null generation is valid and must replace the active
 * generation. Generation is only a tie-breaker when revisions are equal or
 * absent.
 */
export function selectMonotonicXgridsState(
  current: XgridsK1State | null,
  incoming: XgridsK1State,
): XgridsK1State {
  if (current && deviceSessionScope(current) !== deviceSessionScope(incoming)) {
    return incoming;
  }
  if (!current?.camera_preview) return incoming;
  if (!incoming.camera_preview) return current;
  // Backend snapshots are atomic. Reject the whole stale response instead of
  // combining camera/catalog/metrics fields captured at different moments.
  return cameraSnapshotIsAtLeastAsNew(current.camera_preview, incoming.camera_preview)
    ? incoming
    : current;
}
