import type { RuntimePhase, SourceMode as RuntimeSourceMode } from "@mission-core/plugin-sdk"; import type { AcquisitionState, XgridsApplicationControlPhase, XgridsAcquisition, XgridsK1State, XgridsOperation, } from "./api"; const TERMINAL_ACQUISITION_STATES = new Set([ "completed", "failed", "aborted", "interrupted", ]); const FAILED_OPERATION_STATUSES = new Set([ "failed", "cancelled", "timed_out", "interrupted", ]); export type LiveStartPlan = "prepare" | "resume-prepared" | "already-running" | "blocked"; export type ControlSessionEntryPlan = | "open" | "continue" | "failed" | "duplicate-open"; export function controlSessionEntryPlan( phase: XgridsApplicationControlPhase, openedByCurrentOperatorAction: boolean, canOpen: boolean, ): ControlSessionEntryPlan { if (phase === "failed") { return !openedByCurrentOperatorAction && canOpen ? "open" : "failed"; } if (["idle", "closed", "completed"].includes(phase)) { return openedByCurrentOperatorAction ? "duplicate-open" : "open"; } return "continue"; } export function isTerminalAcquisitionState( state: AcquisitionState | null | undefined, ): boolean { return state ? TERMINAL_ACQUISITION_STATES.has(state) : false; } export function shouldRenderSpatialControls( state: XgridsK1State | null | undefined, ): boolean { const acquisition = state?.acquisition; if (!acquisition || state?.source_mode === "replay") return false; return ( !isTerminalAcquisitionState(acquisition.state) || acquisition.cleanup_pending === true ); } export function recoverableAcquisition( state: XgridsK1State | null | undefined, ): XgridsAcquisition | null { const acquisition = state?.acquisition; return acquisition && !isTerminalAcquisitionState(acquisition.state) ? acquisition : null; } export function isConfirmedLiveState(state: XgridsK1State | null | undefined): boolean { return state?.source_mode === "live" && state.acquisition?.state === "acquiring"; } export function isSourceRuntimeBusy(state: XgridsK1State | null | undefined): boolean { return state?.source_mode === "live" || state?.source_mode === "replay"; } export function isVendorWriteCapable( state: XgridsK1State | null | undefined, ): boolean { return ( state?.compatibility?.vendor_writes_enabled === true && state.compatibility.permitted_mode === "active-control" ); } export function isSoftwareCommandedAcquisition( state: XgridsK1State | null | undefined, ): boolean { return isVendorWriteCapable(state) && state?.acquisition?.control_mode === "plugin-commanded"; } export function confirmedRuntimeSourceMode( state: XgridsK1State | null | undefined, ): RuntimeSourceMode { if (state?.source_mode === "replay") return "replay"; if (isConfirmedLiveState(state)) return "live"; return "idle"; } export function effectiveAcquisition( state: XgridsK1State | null | undefined, ): XgridsAcquisition | null { if (state?.source_mode === "replay") return null; return state?.acquisition ?? null; } export function liveStartPlan(state: XgridsK1State | null | undefined): LiveStartPlan { if (state?.source_mode === "replay") return "blocked"; const acquisition = recoverableAcquisition(state); if (!acquisition) return state?.source_mode === "live" ? "blocked" : "prepare"; if (acquisition.state === "prepared") return "resume-prepared"; if ( acquisition.state === "starting" || acquisition.state === "awaiting_external_start" || acquisition.state === "acquiring" ) { return "already-running"; } return "blocked"; } export function normalizeRuntimePhase( state: XgridsK1State | null | undefined, ): RuntimePhase { const phase = state?.phase; const acquisitionState = effectiveAcquisition(state)?.state; if (acquisitionState === "failed" || acquisitionState === "interrupted") return "error"; if (acquisitionState === "awaiting_external_start" || acquisitionState === "starting") { return "starting"; } if (acquisitionState === "acquiring") return "streaming"; if ( acquisitionState === "awaiting_external_stop" || acquisitionState === "stopping" || acquisitionState === "finalizing" ) { return "stopping"; } if (acquisitionState === "prepared") return "connected"; if (phase === "error") return "error"; if (phase === "connected") { const controlPhase = state?.application_control_session?.state; return controlPhase && !["idle", "connecting", "closed", "completed", "failed"].includes(controlPhase) ? "connected" : "configuring"; } if (phase === "starting_live") return "starting"; if (phase === "live") return "streaming"; if (phase === "replay") return "replaying"; if (phase === "stopping") return "stopping"; if (["scanning", "device_selected", "provisioning", "connecting"].includes(phase ?? "")) { return "configuring"; } return "idle"; } export function spatialSourceId( state: XgridsK1State | null | undefined, sourceUrl: string, ): string | null { if (!sourceUrl) return null; if (state?.source_mode === "replay") return `replay:${sourceUrl}`; return state?.acquisition?.acquisition_id ?? sourceUrl; } export function sourceStatusLabel(state: XgridsK1State | null | undefined): string { if (state?.source_mode === "replay") return "Повтор записи"; if (isConfirmedLiveState(state)) return "Реальное время · данные подтверждены"; if (state?.source_mode === "live") { if (state.acquisition?.state === "failed" || state.phase === "error") { return "Ошибка локального приёмника"; } return "Ожидание реальных данных"; } if (state?.acquisition?.state === "prepared") return "Приём подготовлен"; return "Ожидание"; } export function operationByIdempotencyKey( state: XgridsK1State | null | undefined, action: string, idempotencyKey: string | null | undefined, ): XgridsOperation | null { if (!idempotencyKey) return null; return ( [...(state?.operations ?? [])] .reverse() .find( (operation) => operation.action === action && operation.idempotency_key === idempotencyKey, ) ?? null ); } export function operationNeedsReconciliation( operation: XgridsOperation | null | undefined, ): boolean { if (!operation || !FAILED_OPERATION_STATUSES.has(operation.status)) return false; return operation.error?.safe_to_retry !== true; } function defaultUuid(): string { const cryptoApi = globalThis.crypto; if (!cryptoApi) { throw new Error("Web Crypto недоступен; безопасный идентификатор операции не создан."); } if (typeof cryptoApi.randomUUID === "function") return cryptoApi.randomUUID(); const bytes = new Uint8Array(16); cryptoApi.getRandomValues(bytes); bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; const hex = [...bytes].map((value) => value.toString(16).padStart(2, "0")); return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex .slice(6, 8) .join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`; } export function provisioningIntentKey( current: string | null, createUuid: () => string = defaultUuid, ): string { return current ?? `network-provision:${createUuid()}`; }