import { createEventBus } from '../internal/event-bus.js';
import { createScopeModule } from '../scope/index.js';
import { ReasonCode } from '../types/index.js';
import { asNimiError, createNimiError } from './errors.js';
import { RuntimeMethodIds } from './method-ids.js';
import { PHASE2_AUDIT_METHOD_IDS, PHASE2_MODULE_KEYS, RUNTIME_METHOD_LOOKUP, SDK_RUNTIME_MAJOR_VERSION, normalizeText, nowIso, resolveHealthStatus, toIsoFromTimestamp, } from './helpers.js';
import { createCorePassthroughClients, createHealthEventStreams, createAiModule, createAppAuthClient, createAppClient, createMediaModule, createRawModule, createRuntimeEventsModule, createScopeClient, emitAuthTokenIssuedEvent, emitAuthTokenRevokedEvent, } from './runtime-modules.js';
import { attachRuntimeAgentSurface, createRuntimeAgentAnchorsModule, createRuntimeAgentTurnsModule, } from './runtime-agent-surface.js';
import { createRuntimeAvatarDebugModule, } from './runtime-avatar-debug.js';
import { createRuntimeCompanionParticipationModule, } from './runtime-companion-participation.js';
import { createRuntimeArtifactsModule, } from './runtime-artifacts.js';
import { createRuntimeAppLifecycleModule, } from './runtime-app-lifecycle.js';
import { createRuntimeProtectedScopeHelper } from './protected-access.js';
import { ensureRuntimeClientForCall, invokeWithRuntimeRetry, resolveReadyTimeout, resolveRuntimeCallOptions, resolveRuntimeStreamOptions, waitForRuntimeReady, } from './runtime-infra.js';
import { assertRuntimeMethodAvailable, runtimeAiRequestRequiresSubject, checkRuntimeVersionCompatibility, resolveOptionalRuntimeSubjectUserId, resolveRuntimeSubjectUserId, wrapModeDStream, } from './runtime-guards.js';
import { runtimeRawCall } from './runtime-raw-call.js';
import { closeRuntime, connectRuntime, readyRuntime } from './runtime-lifecycle.js';
import { FallbackPolicy } from './generated/runtime/v1/ai.js';
import { runtimeGenerateConvenience, runtimeStreamConvenience, } from './runtime-convenience.js';
function hasOwn(value, key) {
    return Object.prototype.hasOwnProperty.call(value, key);
}
function isNodeRuntime() {
    return typeof process !== 'undefined' && Boolean(process?.versions?.node);
}
function readNodeEnv(name) {
    if (!isNodeRuntime()) {
        return '';
    }
    return normalizeText(process.env?.[name]);
}
export class Runtime {
    appId;
    auth;
    appAuth;
    account;
    ai;
    artifacts;
    media;
    workflow;
    model;
    local;
    connector;
    knowledge;
    memory;
    agent;
    avatarDebug;
    companionParticipation;
    app;
    appLifecycle;
    audit;
    healthEvents;
    providerHealthEvents;
    scope;
    events;
    unsafeRaw;
    transport;
    #client = null;
    #connectPromise = null;
    #state = {
        status: 'idle',
    };
    #retryTransitionEpoch = 0;
    #runtimeVersion = null;
    #versionCompatibility = {
        state: 'unknown',
        compatible: true,
        checked: false,
        sdkRuntimeMajor: SDK_RUNTIME_MAJOR_VERSION,
        runtimeVersion: null,
        runtimeMajor: null,
        reason: 'metadata_missing',
    };
    #versionChecked = false;
    #options;
    #scopeModule;
    #eventBus = createEventBus();
    #ctx;
    constructor(options = {}) {
        const appIdInput = hasOwn(options, 'appId')
            ? options.appId
            : readNodeEnv('NIMI_APP_ID') || 'nimi.app';
        const normalizedAppId = normalizeText(appIdInput);
        if (!normalizedAppId) {
            throw createNimiError({
                message: 'appId is required',
                reasonCode: ReasonCode.SDK_APP_ID_REQUIRED,
                actionHint: 'set_app_id',
                source: 'sdk',
            });
        }
        this.appId = normalizedAppId;
        const transportInput = options.transport || (isNodeRuntime()
            ? {
                type: 'node-grpc',
                endpoint: readNodeEnv('NIMI_RUNTIME_ENDPOINT') || '127.0.0.1:46371',
            }
            : undefined);
        if (!transportInput) {
            throw createNimiError({
                message: 'transport is required outside Node.js. App-level consumers should use createPlatformClient(); direct Runtime construction only auto-configures transport in Node.js. Otherwise pass transport explicitly (for example node-grpc or tauri-ipc).',
                reasonCode: ReasonCode.SDK_TRANSPORT_INVALID,
                actionHint: 'set_transport',
                source: 'sdk',
            });
        }
        this.transport = transportInput;
        const transportWithObserver = {
            ...transportInput,
            _responseMetadataObserver: (metadata) => {
                const version = metadata['x-nimi-runtime-version'];
                if (version && !this.#runtimeVersion) {
                    this.#runtimeVersion = version;
                    this.#emitTelemetry('runtime.version.detected', { version });
                    this.#checkVersionCompatibility(version);
                }
            },
        };
        this.#options = {
            ...options,
            appId: this.appId,
            transport: transportWithObserver,
            connection: {
                waitForReadyTimeoutMs: options.connection?.waitForReadyTimeoutMs,
            },
        };
        this.#scopeModule = createScopeModule({ appId: this.appId });
        this.#ctx = {
            appId: this.appId,
            options: this.#options,
            invoke: (op) => this.#invoke(op),
            invokeWithClient: (op) => this.#invokeWithClient(op),
            resolveRuntimeCallOptions: (input) => resolveRuntimeCallOptions(this.#options, input),
            resolveRuntimeStreamOptions: (input) => resolveRuntimeStreamOptions(this.#options, input),
            resolveSubjectUserId: (explicit) => resolveRuntimeSubjectUserId({
                explicit,
                subjectContext: this.#options.subjectContext,
            }),
            resolveOptionalSubjectUserId: (explicit) => resolveOptionalRuntimeSubjectUserId({
                explicit,
                subjectContext: this.#options.subjectContext,
            }),
            normalizeScenarioHead: async ({ head, metadata }) => {
                const subjectUserId = runtimeAiRequestRequiresSubject({
                    request: { head },
                    metadata,
                })
                    ? await resolveRuntimeSubjectUserId({
                        explicit: head.subjectUserId,
                        subjectContext: this.#options.subjectContext,
                    })
                    : await resolveOptionalRuntimeSubjectUserId({
                        explicit: head.subjectUserId,
                        subjectContext: this.#options.subjectContext,
                    });
                return {
                    ...head,
                    subjectUserId: subjectUserId || '',
                    fallback: head.fallback ?? FallbackPolicy.DENY,
                };
            },
            emitTelemetry: (name, data) => this.#emitTelemetry(name, data),
        };
        this.events = createRuntimeEventsModule(this.#eventBus);
        const passthrough = createCorePassthroughClients({
            assertMethodAvailable: (moduleKey, methodKey) => this.#assertMethodAvailable(moduleKey, methodKey),
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
        });
        this.auth = passthrough.auth;
        this.account = passthrough.account;
        this.workflow = passthrough.workflow;
        this.model = passthrough.model;
        this.local = passthrough.local;
        this.connector = passthrough.connector;
        this.knowledge = passthrough.knowledge;
        this.memory = passthrough.memory;
        this.audit = passthrough.audit;
        const healthStreams = createHealthEventStreams({
            audit: this.audit,
            wrapModeDStream: (source) => this.#wrapModeDStream(source),
        });
        this.healthEvents = healthStreams.healthEvents;
        this.providerHealthEvents = healthStreams.providerHealthEvents;
        this.app = createAppClient({
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
            wrapModeDStream: (source) => this.#wrapModeDStream(source),
        });
        this.appAuth = createAppAuthClient({
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
            resolvePublishedCatalogVersion: (requested) => this.#scopeModule.resolvePublishedCatalogVersion(requested),
            emitTelemetry: (name, data) => this.#emitTelemetry(name, data),
            authEvents: {
                emitTokenIssued: (tokenId) => emitAuthTokenIssuedEvent(this.#eventBus, tokenId),
                emitTokenRevoked: (tokenId) => emitAuthTokenRevokedEvent(this.#eventBus, tokenId),
            },
        });
        const protectedScopeHelper = createRuntimeProtectedScopeHelper({
            runtime: {
                appId: this.appId,
                transport: this.transport,
                auth: this.auth,
                appAuth: this.appAuth,
            },
            getSubjectUserId: () => this.#ctx.resolveSubjectUserId(undefined),
        });
        this.agent = attachRuntimeAgentSurface(passthrough.agent, {
            anchors: createRuntimeAgentAnchorsModule({
                appId: this.appId,
                agent: passthrough.agent,
                protectedAccess: protectedScopeHelper,
                resolveSubjectUserId: (explicit) => this.#ctx.resolveSubjectUserId(explicit),
            }),
            turns: createRuntimeAgentTurnsModule({
                appId: this.appId,
                agent: passthrough.agent,
                app: this.app,
                protectedAccess: protectedScopeHelper,
                resolveSubjectUserId: (explicit) => this.#ctx.resolveSubjectUserId(explicit),
            }),
        });
        this.avatarDebug = createRuntimeAvatarDebugModule({
            appId: this.appId,
            agent: passthrough.agent,
            protectedAccess: protectedScopeHelper,
            resolveSubjectUserId: (explicit) => this.#ctx.resolveSubjectUserId(explicit),
        });
        this.companionParticipation = createRuntimeCompanionParticipationModule({
            appId: this.appId,
            agent: passthrough.agent,
            protectedAccess: protectedScopeHelper,
            resolveSubjectUserId: (explicit) => this.#ctx.resolveSubjectUserId(explicit),
        });
        this.scope = createScopeClient({
            invoke: (operation) => this.#invoke(operation),
            scopeModule: this.#scopeModule,
        });
        const ctx = this.#ctx;
        this.ai = createAiModule({
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
            ctx,
        });
        this.artifacts = createRuntimeArtifactsModule({ ctx });
        this.appLifecycle = createRuntimeAppLifecycleModule({ ctx });
        this.media = createMediaModule(ctx);
        const rawCall = (methodId, inputValue, optionsValue) => runtimeRawCall({
            methodId,
            request: inputValue,
            options: optionsValue,
            methodLookup: RUNTIME_METHOD_LOOKUP,
            assertMethodAvailable: (moduleKey, methodKey) => this.#assertMethodAvailable(moduleKey, methodKey),
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
            createMethodNotAllowlistedError: (missingMethodId) => createNimiError({
                message: `runtime method is not allowlisted: ${missingMethodId}`,
                reasonCode: ReasonCode.SDK_RUNTIME_CODEC_MISSING,
                actionHint: 'use_runtime_method_ids',
                source: 'sdk',
            }),
            createMethodNotImplementedError: (moduleKey, methodKey) => createNimiError({
                message: `${moduleKey}.${methodKey} is not implemented`,
                reasonCode: ReasonCode.SDK_RUNTIME_CODEC_MISSING,
                actionHint: 'check_runtime_method_mapping',
                source: 'sdk',
            }),
        });
        const unsafeRaw = createRawModule({
            rawCall,
            invokeWithClient: (operation) => this.#invokeWithClient(operation),
        });
        this.unsafeRaw = unsafeRaw;
    }
    async connect() {
        await connectRuntime({
            appId: this.appId,
            options: this.#options,
            getState: () => this.#state,
            getConnectPromise: () => this.#connectPromise,
            setState: (state) => {
                this.#state = state;
            },
            setConnectPromise: (promise) => {
                this.#connectPromise = promise;
            },
            setClient: (client) => {
                this.#client = client;
            },
            emitConnected: (at) => {
                this.#eventBus.emit('runtime.connected', { at });
            },
            emitTelemetry: (name, data) => this.#emitTelemetry(name, data),
        });
    }
    async ready(input) {
        const timeoutMs = this.#resolveReadyTimeout(input?.timeoutMs);
        await readyRuntime({
            timeoutMs,
            waitForReady: (effectiveTimeoutMs) => this.#waitForReady(effectiveTimeoutMs),
            health: () => this.health(),
            markReady: (at) => {
                this.#state = {
                    ...this.#state,
                    status: 'ready',
                    lastReadyAt: at,
                };
            },
        });
    }
    async close() {
        await closeRuntime({
            getState: () => this.#state,
            getConnectPromise: () => this.#connectPromise,
            getClient: () => this.#client,
            setState: (state) => {
                this.#state = state;
            },
            setConnectPromise: (promise) => {
                this.#connectPromise = promise;
            },
            setClient: (client) => {
                this.#client = client;
            },
            emitDisconnected: (at) => {
                this.#eventBus.emit('runtime.disconnected', { at });
            },
            emitTelemetry: (name, data) => this.#emitTelemetry(name, data),
        });
    }
    state() {
        return { ...this.#state };
    }
    runtimeVersion() {
        return this.#runtimeVersion;
    }
    versionCompatibility() {
        return { ...this.#versionCompatibility };
    }
    async health() {
        const response = await this.#invokeWithClient(async (client) => client.audit.getRuntimeHealth({}));
        return {
            status: resolveHealthStatus(response.status),
            reason: normalizeText(response.reason) || undefined,
            queueDepth: response.queueDepth,
            activeWorkflows: response.activeWorkflows,
            activeInferenceJobs: response.activeInferenceJobs,
            cpuMilli: normalizeText(response.cpuMilli) || undefined,
            memoryBytes: normalizeText(response.memoryBytes) || undefined,
            vramBytes: normalizeText(response.vramBytes) || undefined,
            sampledAt: toIsoFromTimestamp(response.sampledAt),
        };
    }
    async generate(input) {
        return runtimeGenerateConvenience(this, input);
    }
    async stream(input) {
        return runtimeStreamConvenience(this, input);
    }
    call(method, input, options) {
        const methodId = typeof method === 'string' ? method : method.methodId;
        return this.unsafeRaw.call(methodId, input, options);
    }
    // ── Private infrastructure methods ──────────────────────────────────
    #resolveReadyTimeout(timeoutMs) {
        return resolveReadyTimeout(this.#options, timeoutMs);
    }
    async #waitForReady(timeoutMs) {
        await waitForRuntimeReady({
            stateStatus: this.#state.status,
            connectPromise: this.#connectPromise,
            connect: () => this.connect(),
            timeoutMs,
        });
    }
    async #ensureClientForCall() {
        return ensureRuntimeClientForCall({
            options: this.#options,
            stateStatus: this.#state.status,
            client: this.#client,
            waitForReady: (timeoutMs) => this.#waitForReady(timeoutMs),
            getClient: () => this.#client,
        });
    }
    async #invokeWithClient(operation) {
        return this.#invoke(async () => {
            const client = await this.#ensureClientForCall();
            return operation(client);
        });
    }
    async #invoke(operation) {
        let retryEpoch = null;
        return invokeWithRuntimeRetry({
            operation,
            options: this.#options,
            normalizeError: (error) => asNimiError(error, {
                reasonCode: ReasonCode.RUNTIME_CALL_FAILED,
                actionHint: 'retry_or_check_runtime_status',
                source: 'runtime',
            }),
            onRecovered: (attempt) => {
                if (this.#state.status === 'closing' || this.#state.status === 'closed') {
                    return;
                }
                if (retryEpoch === null || retryEpoch !== this.#retryTransitionEpoch) {
                    return;
                }
                if (this.#state.status !== 'ready') {
                    const at = nowIso();
                    this.#state = {
                        ...this.#state,
                        status: 'ready',
                        lastReadyAt: at,
                    };
                    this.#eventBus.emit('runtime.connected', { at });
                    this.#emitTelemetry('runtime.connected', {
                        at,
                        reason: 'auto_retry_recovered',
                        attempt,
                    });
                }
            },
            onRetry: (normalized, attempt, backoffMs, maxAttempts) => {
                if (this.#state.status === 'closing' || this.#state.status === 'closed') {
                    return;
                }
                retryEpoch = ++this.#retryTransitionEpoch;
                const at = nowIso();
                this.#client = null;
                const wasReady = this.#state.status === 'ready';
                this.#state = {
                    ...this.#state,
                    status: 'idle',
                };
                if (wasReady) {
                    this.#eventBus.emit('runtime.disconnected', {
                        at,
                        reasonCode: normalized.reasonCode,
                    });
                }
                this.#emitTelemetry('runtime.disconnected', {
                    at,
                    reasonCode: normalized.reasonCode,
                    attempt,
                });
                this.#emitTelemetry('runtime.retry', {
                    attempt,
                    maxAttempts,
                    backoffMs,
                    reasonCode: normalized.reasonCode,
                });
            },
            onTerminalError: (normalized) => {
                if (normalized.reasonCode === ReasonCode.OPERATION_ABORTED
                    && (this.#state.status === 'closing' || this.#state.status === 'closed')) {
                    return;
                }
                this.#eventBus.emit('error', {
                    error: normalized,
                    at: nowIso(),
                });
                this.#emitTelemetry('runtime.error', {
                    reasonCode: normalized.reasonCode,
                    actionHint: normalized.actionHint,
                    traceId: normalized.traceId,
                });
            },
        });
    }
    #emitTelemetry(name, data) {
        if (!this.#options.telemetry?.enabled || typeof this.#options.telemetry.onEvent !== 'function') {
            return;
        }
        this.#options.telemetry.onEvent({
            name,
            at: nowIso(),
            data,
        });
    }
    #checkVersionCompatibility(version) {
        const status = checkRuntimeVersionCompatibility({
            version,
            versionChecked: this.#versionChecked,
            sdkRuntimeMajor: SDK_RUNTIME_MAJOR_VERSION,
            emitTelemetry: (name, data) => this.#emitTelemetry(name, data),
            emitError: (error) => this.#eventBus.emit('error', { error, at: nowIso() }),
            setStatus: (nextStatus) => {
                this.#versionCompatibility = nextStatus;
            },
        });
        this.#versionChecked = status.compatible;
    }
    #assertMethodAvailable(moduleKey, methodKey) {
        assertRuntimeMethodAvailable({
            moduleKey,
            methodKey,
            runtimeVersion: this.#runtimeVersion,
            sdkRuntimeMajor: SDK_RUNTIME_MAJOR_VERSION,
            phase2ModuleKeys: PHASE2_MODULE_KEYS,
            phase2AuditMethodIds: PHASE2_AUDIT_METHOD_IDS,
            auditMethodIds: RuntimeMethodIds.audit,
        });
    }
    #wrapModeDStream(source) {
        return wrapModeDStream({
            source,
            onCancelled: () => {
                this.#eventBus.emit('runtime.disconnected', {
                    at: nowIso(),
                    reasonCode: ReasonCode.RUNTIME_GRPC_CANCELLED,
                });
                this.#emitTelemetry('runtime.mode-d.cancelled', {
                    at: nowIso(),
                });
            },
        });
    }
}
//# sourceMappingURL=runtime.js.map