import { randomUUID } from "node:crypto";
import { normalizeOpenCodeEvent } from "./RuntimeEventMapper.js";
import { formatRuntimeTimelineRecord } from "./runtimeTimelineContract.js";
export class RuntimeEventIngestor {
    runtime;
    localStore;
    audit;
    permissionHandler;
    #controller;
    #worker;
    #state = "idle";
    #lastError;
    constructor(runtime, localStore, audit, permissionHandler) {
        this.runtime = runtime;
        this.localStore = localStore;
        this.audit = audit;
        this.permissionHandler = permissionHandler;
    }
    ensureStarted() {
        if (this.#worker || this.#state === "running")
            return;
        const controller = new AbortController();
        this.#controller = controller;
        this.#state = "running";
        this.#lastError = undefined;
        this.#worker = this.run(controller);
    }
    async stop() {
        this.#controller?.abort();
        await this.#worker;
    }
    status() {
        return {
            state: this.#state,
            ...(this.#lastError ? { lastError: this.#lastError } : {}),
        };
    }
    async run(controller) {
        try {
            for await (const rawEvent of this.runtime.subscribeEvents(controller.signal)) {
                await this.record(rawEvent);
            }
            if (controller.signal.aborted) {
                this.#state = "idle";
            }
        }
        catch (error) {
            if (controller.signal.aborted) {
                this.#state = "idle";
                return;
            }
            const message = error instanceof Error ? error.message : String(error);
            this.#state = "failed";
            this.#lastError = message;
            this.audit.append({
                type: "runtime.event_ingestor_failed",
                severity: "warning",
                message: "Runtime event ingestion failed.",
                metadata: { error: message },
            });
        }
        finally {
            if (this.#controller === controller)
                this.#controller = undefined;
            if (this.#worker)
                this.#worker = undefined;
        }
    }
    async record(rawEvent) {
        const preliminary = normalizeOpenCodeEvent(rawEvent);
        const run = preliminary.runtimeSessionId
            ? this.localStore.getRunByRuntimeSessionId(preliminary.runtimeSessionId)
            : undefined;
        const event = normalizeOpenCodeEvent(rawEvent, run ? { runId: run.id } : {});
        const stored = this.localStore.appendRuntimeEvent({
            id: event.id,
            source: event.source,
            type: event.type,
            occurredAt: event.at,
            title: event.title,
            description: event.description,
            tone: event.tone,
            metadata: event.metadata,
            ...(event.runId ? { runId: event.runId } : {}),
            ...(event.runtimeSessionId
                ? { runtimeSessionId: event.runtimeSessionId }
                : {}),
        });
        this.localStore.appendOutboxEvent({
            id: `evt_${randomUUID()}`,
            type: "runtime.event_recorded",
            aggregateId: stored.runId ?? stored.id,
            payload: {
                event: formatRuntimeTimelineRecord(stored),
            },
        });
        if (this.permissionHandler) {
            try {
                await this.permissionHandler.handleRuntimeEvent(rawEvent, stored);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                this.audit.append({
                    type: "permission.event_handling_failed",
                    severity: "warning",
                    message: "Permission event handling failed.",
                    metadata: { runtimeEventId: stored.id, error: message },
                });
            }
        }
    }
}
