import type { GameClientBridgeJsonObject, GameClientBridgeSnapshotResponse } from "../api/types"; import type { ScumCompanionHealthView, ScumFlagView, ScumOperationsSnapshotView, ScumPlayerView, ScumSessionView, ScumSnapshotCollection, ScumSquadView, ScumVehicleView } from "../contracts/scumOperations"; import { safeDiagnosticText } from "../utils/safeDiagnosticText"; const maxVisibleItems = 50; export function projectScumOperationsSnapshots(snapshots: GameClientBridgeSnapshotResponse[]): ScumOperationsSnapshotView { return { health: projectHealth(latestPayload(snapshots, "companion.health")), sessions: projectCollection(latestSnapshot(snapshots, "online.sessions"), "sessions", projectSession), players: projectCollection(latestSnapshot(snapshots, "players"), "players", projectPlayer), squads: projectCollection(latestSnapshot(snapshots, "squads"), "squads", projectSquad), vehicles: projectCollection(latestSnapshot(snapshots, "vehicles"), "vehicles", projectVehicle), flags: projectCollection(latestSnapshot(snapshots, "flags"), "flags", projectFlag) }; } function latestSnapshot(snapshots: GameClientBridgeSnapshotResponse[], type: string): GameClientBridgeSnapshotResponse | undefined { return snapshots .filter((snapshot) => snapshot.type === type) .sort((left, right) => right.observedAt.localeCompare(left.observedAt) || right.sequence - left.sequence)[0]; } function latestPayload(snapshots: GameClientBridgeSnapshotResponse[], type: string): GameClientBridgeJsonObject | undefined { return latestSnapshot(snapshots, type)?.payload; } function projectHealth(payload: GameClientBridgeJsonObject | undefined): ScumCompanionHealthView | undefined { if (!payload) return undefined; const status = readText(payload.status, 20); return { status: status === "online" || status === "degraded" || status === "offline" ? status : "unknown", version: readOptionalText(payload.version, 40), observedAt: readOptionalText(payload.observedAt, 64), latencyMs: readOptionalNumber(payload.latencyMs, 0, 30000), capabilities: readArray(payload.capabilities).map((value) => readText(value, 80)).filter(Boolean).slice(0, 16) }; } function projectCollection( snapshot: GameClientBridgeSnapshotResponse | undefined, key: string, project: (value: unknown) => T | null ): ScumSnapshotCollection { const values = snapshot ? readArray(snapshot.payload[key]) : []; return { observedAt: snapshot?.observedAt, total: values.length, items: values.slice(0, maxVisibleItems).map(project).filter((value): value is T => value !== null) }; } function projectSession(value: unknown): ScumSessionView | null { const record = readObject(value); const sessionId = readText(record?.sessionId, 120); const playerName = readText(record?.playerName, 80); return sessionId && playerName ? { sessionId, playerName, startedAt: readOptionalText(record?.startedAt, 64) } : null; } function projectPlayer(value: unknown): ScumPlayerView | null { const record = readObject(value); const playerId = readText(record?.playerId, 96); const playerName = readText(record?.playerName, 80); const status = readText(record?.status, 20); return playerId && playerName && status ? { playerId, playerName, status, squadId: readOptionalText(record?.squadId, 96), pingMs: readOptionalNumber(record?.pingMs, 0, 10000), lastSeenAt: readOptionalText(record?.lastSeenAt, 64) } : null; } function projectSquad(value: unknown): ScumSquadView | null { const record = readObject(value); const squadId = readText(record?.squadId, 96); const name = readText(record?.name, 80); const memberCount = readOptionalNumber(record?.memberCount, 0, 64); return squadId && name && memberCount !== undefined ? { squadId, name, memberCount, leaderPlayerId: readOptionalText(record?.leaderPlayerId, 96), lastActiveAt: readOptionalText(record?.lastActiveAt, 64) } : null; } function projectVehicle(value: unknown): ScumVehicleView | null { const record = readObject(value); const vehicleId = readText(record?.vehicleId, 96); const vehicleType = readText(record?.vehicleType, 80); const status = readText(record?.status, 20); return vehicleId && vehicleType && status ? { vehicleId, vehicleType, status, ownerPlayerId: readOptionalText(record?.ownerPlayerId, 96), squadId: readOptionalText(record?.squadId, 96), fuelPercent: readOptionalNumber(record?.fuelPercent, 0, 100), healthPercent: readOptionalNumber(record?.healthPercent, 0, 100), lastSeenAt: readOptionalText(record?.lastSeenAt, 64) } : null; } function projectFlag(value: unknown): ScumFlagView | null { const record = readObject(value); const flagId = readText(record?.flagId, 96); const status = readText(record?.status, 20); return flagId && status ? { flagId, status, ownerPlayerId: readOptionalText(record?.ownerPlayerId, 96), squadId: readOptionalText(record?.squadId, 96), radiusMeters: readOptionalNumber(record?.radiusMeters, 0, 5000), lastUpdatedAt: readOptionalText(record?.lastUpdatedAt, 64) } : null; } function readObject(value: unknown): Record | undefined { return value && typeof value === "object" && !Array.isArray(value) ? value as Record : undefined; } function readArray(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function readText(value: unknown, maxLength: number): string { if (typeof value !== "string") return ""; return (safeDiagnosticText(value.slice(0, maxLength), "") ?? "").trim(); } function readOptionalText(value: unknown, maxLength: number): string | undefined { return readText(value, maxLength) || undefined; } function readOptionalNumber(value: unknown, minimum: number, maximum: number): number | undefined { return typeof value === "number" && Number.isFinite(value) && value >= minimum && value <= maximum ? value : undefined; }