// The public session/run API. createSession() builds the run context, kicks off the loop
// asynchronously, and exposes the run id, config fingerprint, a result Promise, and a
// cancel() that aborts the single per-run AbortController (ADR-0004 D4, D9).
import { HARNESS_VERSION } from "@oscharko-dev/keiko-contracts";
import { systemClock } from "@oscharko-dev/keiko-model-gateway/internal/resilience";
import { newCounters } from "./context.js";
import { Emitter } from "./emitter.js";
import { HARNESS_CODES, toFailure } from "./errors.js";
import { defaultFingerprinter, defaultIdSource } from "./fingerprint.js";
import { runLoop } from "./loop.js";
import { MemoryEventSink } from "./sinks.js";
import { resolveTaskPlan } from "./tasks/policy.js";
import { DEFAULT_LIMITS, } from "./types.js";
// HARNESS_VERSION lives in @oscharko-dev/keiko-contracts and is re-exported here as
// part of the harness session surface.
export { HARNESS_VERSION };
function resolveLimits(config) {
    return { ...DEFAULT_LIMITS, ...config.limits };
}
function resolveDryRun(config) {
    return config.dryRun ?? true;
}
function buildResult(ctx, outcome, sink, identity) {
    return {
        runId: identity.runId,
        fingerprint: identity.fingerprint,
        outcome,
        taskType: ctx.taskType,
        ...(ctx.report === undefined ? {} : { report: ctx.report }),
        ...(ctx.patchDiff === undefined ? {} : { patchDiff: ctx.patchDiff }),
        ...(ctx.failure === undefined ? {} : { failure: ctx.failure }),
        startedAt: ctx.startedAt,
        finishedAt: ctx.clock.now(),
        events: sink.events(),
    };
}
function buildContext(task, config, deps, signal, runId, fingerprint) {
    const clock = deps.clock ?? systemClock;
    const memory = new MemoryEventSink();
    const plan = resolveTaskPlan(task);
    const ctx = {
        model: deps.model,
        tools: deps.tools,
        emitter: new Emitter([memory, deps.sink], clock, runId, fingerprint),
        clock,
        signal,
        limits: resolveLimits(config),
        modelId: config.model,
        taskType: task.taskType,
        plan,
        startedAt: clock.now(),
        counters: newCounters(),
        messages: [...plan.messages],
        lastResponse: undefined,
        patchDiff: undefined,
        report: undefined,
        failure: undefined,
        cancelReason: undefined,
        cancelledAtState: undefined,
    };
    return { ctx, memory };
}
function armWallTimeDeadline(ctx, controller, clock) {
    let cleared = false;
    const deadlineController = new AbortController();
    void clock
        .sleep(ctx.limits.maxWallTimeMs, deadlineController.signal)
        .then(() => {
        if (cleared || controller.signal.aborted) {
            return;
        }
        ctx.failure = toFailure(HARNESS_CODES.LIMIT_WALL_TIME, "wall-time budget exhausted");
        ctx.cancelReason = "maxWallTimeMs exceeded";
        controller.abort("maxWallTimeMs exceeded");
    })
        .catch(() => undefined);
    return () => {
        cleared = true;
        deadlineController.abort("run finished");
    };
}
export function createSession(task, config, deps) {
    const limits = resolveLimits(config);
    const dryRun = resolveDryRun(config);
    const runId = (deps.idSource ?? defaultIdSource).newRunId();
    const fingerprint = (deps.fingerprinter ?? defaultFingerprinter).compute({
        taskType: task.taskType,
        taskInput: task,
        limits,
        modelId: config.model,
        workingDirectory: config.workingDirectory,
        dryRun,
        harnessVersion: HARNESS_VERSION,
    });
    const controller = new AbortController();
    const { ctx, memory } = buildContext(task, config, deps, controller.signal, runId, fingerprint);
    const clearDeadline = armWallTimeDeadline(ctx, controller, ctx.clock);
    ctx.emitter.emit({
        type: "run:started",
        taskType: task.taskType,
        modelId: config.model,
        limits,
    });
    // Defer the loop to a microtask so a cancel() issued synchronously after createSession is
    // observed at the loop's first abort check, before any model or tool call is made.
    const result = Promise.resolve()
        .then(() => runLoop(ctx))
        .finally(clearDeadline)
        .then((outcome) => buildResult(ctx, outcome, memory, { runId, fingerprint }));
    return {
        runId,
        fingerprint,
        result,
        cancel: (reason) => {
            ctx.cancelReason = reason;
            controller.abort(reason);
        },
    };
}
