/**
 * Pure derivation function that extracts rich per-task detail from
 * the execution event stream and status snapshot.
 *
 * No React dependencies — independently testable and usable outside
 * the inspector component.
 *
 * @since T05 (Runtime Inspector)
 */
import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
import { kindToDisplayName } from "../kind-metadata";
import { taskKindToString } from "../workflow-graph-conversions";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const BIGINT_ZERO = BigInt(0);
// ---------------------------------------------------------------------------
// Main derivation
// ---------------------------------------------------------------------------
/**
 * Derives rich per-task detail by joining the event stream with the
 * status snapshot.
 *
 * Returns `null` when no data is available for the given task name
 * (no events AND no snapshot entry).
 */
export function deriveTaskDetail(taskName, events, taskSnapshot, derivedState) {
    const taskEvents = events.filter((e) => e.taskName === taskName);
    if (taskEvents.length === 0 && !taskSnapshot && !derivedState) {
        return null;
    }
    const buckets = bucketEvents(taskEvents);
    const summary = buildSummary(taskSnapshot, derivedState, buckets);
    const input = buildIO(taskSnapshot?.input, buckets.inputSummary, taskSnapshot?.artifactIds ?? []);
    const output = buildIO(taskSnapshot?.output, buckets.outputSummary, []);
    const snapshotMeta = taskSnapshot?.metadata
        ? taskSnapshot.metadata
        : undefined;
    const error = buildError(buckets, snapshotMeta);
    const retries = buildRetryHistory(buckets);
    const taskKind = derivedState?.taskKind ??
        buckets.taskKind ??
        WorkflowTaskKind.workflow_task_kind_unspecified;
    const agentCall = buildAgentCall(buckets, taskKind, snapshotMeta);
    const approval = buildApproval(buckets, taskSnapshot?.output);
    const status = derivedState?.status ?? "pending";
    const displayName = kindToDisplayName(taskKindToString(taskKind));
    return {
        taskName,
        taskKind,
        displayName,
        status,
        summary,
        input,
        output,
        error,
        retries,
        agentCall,
        approval,
        eventLog: taskEvents,
    };
}
function bucketEvents(taskEvents) {
    const buckets = {
        taskKind: undefined,
        starts: [],
        completions: [],
        failures: [],
        retryings: [],
        skipped: null,
        agentStarted: null,
        agentProgress: null,
        agentCompleted: null,
        approvalRequested: null,
        approvalResolved: null,
        inputSummary: null,
        outputSummary: null,
    };
    for (const evt of taskEvents) {
        const p = evt.payload;
        switch (p.case) {
            case "taskStarted":
                buckets.taskKind ??= p.value.taskKind;
                buckets.starts.push({
                    attemptNumber: p.value.attemptNumber,
                    occurredAt: evt.occurredAt,
                    inputSummary: p.value.inputSummary,
                });
                if (p.value.inputSummary) {
                    buckets.inputSummary = p.value.inputSummary;
                }
                break;
            case "taskCompleted":
                buckets.taskKind ??= p.value.taskKind;
                buckets.completions.push({
                    durationMs: Number(p.value.durationMs),
                    costMicros: p.value.costMicros,
                    tokensUsed: p.value.tokensUsed,
                    outputSummary: p.value.outputSummary,
                });
                if (p.value.outputSummary) {
                    buckets.outputSummary = p.value.outputSummary;
                }
                break;
            case "taskFailed":
                buckets.taskKind ??= p.value.taskKind;
                buckets.failures.push({
                    attemptNumber: p.value.attemptNumber,
                    maxAttempts: p.value.maxAttempts,
                    willRetry: p.value.willRetry,
                    error: p.value.error,
                    durationMs: Number(p.value.durationMs),
                });
                break;
            case "taskSkipped":
                buckets.taskKind ??= p.value.taskKind;
                buckets.skipped = { reason: p.value.reason };
                break;
            case "taskRetrying":
                buckets.retryings.push({
                    failedAttempt: p.value.failedAttempt,
                    nextAttempt: p.value.nextAttempt,
                    delayMs: Number(p.value.delayMs),
                });
                break;
            case "agentCallStarted":
                buckets.agentStarted = {
                    childExecutionId: p.value.childExecutionId,
                    agentSlug: p.value.agentSlug,
                    messageSummary: p.value.messageSummary,
                };
                break;
            case "agentCallProgress":
                buckets.agentProgress = {
                    childExecutionId: p.value.childExecutionId,
                    agentPhase: p.value.agentPhase,
                    currentToolName: p.value.currentToolName,
                    tokensConsumed: p.value.tokensConsumed,
                    messagesCount: p.value.messagesCount,
                    toolCallsCount: p.value.toolCallsCount,
                };
                break;
            case "agentCallCompleted":
                buckets.agentCompleted = {
                    durationMs: Number(p.value.durationMs),
                    tokensConsumed: p.value.tokensConsumed,
                    costMicros: p.value.costMicros,
                    error: p.value.error,
                    agentPhase: p.value.agentPhase,
                };
                break;
            case "approvalRequested":
                buckets.approvalRequested = {
                    prompt: p.value.prompt,
                    approvers: [...p.value.approvers],
                    timeoutSeconds: p.value.timeoutSeconds,
                    outcomes: (p.value.outcomes ?? []).map((o) => ({ name: o.name, label: o.label })),
                    formSchema: p.value.formSchema ? p.value.formSchema : null,
                };
                break;
            case "approvalResolved":
                buckets.approvalResolved = {
                    action: p.value.action,
                    resolvedBy: p.value.resolvedBy,
                    comment: p.value.comment,
                    waitDurationMs: Number(p.value.waitDurationMs),
                };
                break;
        }
    }
    return buckets;
}
// ---------------------------------------------------------------------------
// Section builders
// ---------------------------------------------------------------------------
function buildSummary(snapshot, derived, buckets) {
    const startedAt = snapshot?.startedAt || buckets.starts[0]?.occurredAt || null;
    const completedAt = snapshot?.completedAt || null;
    const durationMs = derived?.durationMs ?? 0;
    const costMicros = snapshot?.costMicros ?? derived?.costMicros ?? BIGINT_ZERO;
    const inputTokens = snapshot?.inputTokens ?? BIGINT_ZERO;
    const outputTokens = snapshot?.outputTokens ?? BIGINT_ZERO;
    const totalTokens = inputTokens + outputTokens > BIGINT_ZERO
        ? inputTokens + outputTokens
        : derived?.tokensUsed ?? BIGINT_ZERO;
    const attemptNumber = derived?.attemptNumber ?? buckets.starts.length;
    return {
        startedAt,
        completedAt,
        durationMs,
        costMicros,
        inputTokens,
        outputTokens,
        totalTokens,
        attemptNumber,
    };
}
function buildIO(snapshotData, eventSummary, artifactIds) {
    if (snapshotData && Object.keys(snapshotData).length > 0) {
        return {
            data: snapshotData,
            summary: eventSummary,
            artifactIds,
            source: "snapshot",
        };
    }
    if (eventSummary && Object.keys(eventSummary).length > 0) {
        return {
            data: eventSummary,
            summary: eventSummary,
            artifactIds,
            source: "event-summary",
        };
    }
    return null;
}
function buildError(buckets, snapshotMetadata) {
    if (buckets.failures.length === 0)
        return null;
    const latest = buckets.failures[buckets.failures.length - 1];
    const category = typeof snapshotMetadata?.error_category === "string"
        ? snapshotMetadata.error_category
        : null;
    const detail = typeof snapshotMetadata?.error_detail === "string"
        ? snapshotMetadata.error_detail
        : null;
    return {
        message: latest.error,
        attemptNumber: latest.attemptNumber,
        maxAttempts: latest.maxAttempts,
        willRetry: latest.willRetry,
        durationMs: latest.durationMs,
        category,
        detail,
    };
}
function buildRetryHistory(buckets) {
    if (buckets.starts.length <= 1 && buckets.failures.length === 0)
        return null;
    const attempts = [];
    for (let i = 0; i < buckets.starts.length; i++) {
        const start = buckets.starts[i];
        const failure = buckets.failures.find((f) => f.attemptNumber === start.attemptNumber);
        const completion = i === buckets.starts.length - 1 && buckets.completions.length > 0
            ? buckets.completions[buckets.completions.length - 1]
            : null;
        const retry = buckets.retryings.find((r) => r.failedAttempt === start.attemptNumber);
        attempts.push({
            attemptNumber: start.attemptNumber,
            status: failure ? "failed" : completion ? "completed" : "failed",
            durationMs: failure ? failure.durationMs : completion ? completion.durationMs : 0,
            error: failure ? failure.error : "",
            delayBeforeMs: retry ? retry.delayMs : 0,
            startedAt: start.occurredAt,
        });
    }
    if (attempts.length <= 1 && buckets.failures.length === 0)
        return null;
    const currentAttempt = buckets.starts.length > 0
        ? buckets.starts[buckets.starts.length - 1].attemptNumber
        : 1;
    return { attempts, currentAttempt };
}
function buildAgentCall(buckets, taskKind, snapshotMeta) {
    if (buckets.agentStarted) {
        const childExecutionId = buckets.agentStarted.childExecutionId ||
            buckets.agentProgress?.childExecutionId ||
            "";
        return {
            childExecutionId,
            agentSlug: buckets.agentStarted.agentSlug,
            agentPhase: String(buckets.agentCompleted?.agentPhase ?? buckets.agentProgress?.agentPhase ?? ""),
            messagesCount: buckets.agentProgress?.messagesCount ?? 0,
            toolCallsCount: buckets.agentProgress?.toolCallsCount ?? 0,
            tokensConsumed: buckets.agentCompleted?.tokensConsumed ?? buckets.agentProgress?.tokensConsumed ?? BIGINT_ZERO,
            costMicros: buckets.agentCompleted?.costMicros ?? BIGINT_ZERO,
            error: buckets.agentCompleted?.error ?? "",
            currentToolName: buckets.agentProgress?.currentToolName ?? "",
        };
    }
    if (taskKind === WorkflowTaskKind.agent_call && snapshotMeta) {
        const aexId = snapshotMeta.agent_execution_id;
        if (typeof aexId === "string" && aexId) {
            return {
                childExecutionId: aexId,
                agentSlug: typeof snapshotMeta.agent_slug === "string" ? snapshotMeta.agent_slug : "",
                agentPhase: "",
                messagesCount: 0,
                toolCallsCount: typeof snapshotMeta.tool_call_count === "number" ? snapshotMeta.tool_call_count : 0,
                tokensConsumed: BIGINT_ZERO,
                costMicros: BIGINT_ZERO,
                error: "",
                currentToolName: "",
            };
        }
    }
    return null;
}
function buildApproval(buckets, taskOutput) {
    if (!buckets.approvalRequested)
        return null;
    const req = buckets.approvalRequested;
    const res = buckets.approvalResolved;
    // The human_input task's output IS the canonical decision record: the
    // runner persists the reviewer's full response ({ outcome, reviewer,
    // responded_at, comment, form_data, auto_resolved }) as the task output,
    // a google.protobuf.Struct, so every field survives. Prefer it, and fall
    // back to the lightweight approval_resolved event for fields the output
    // snapshot has not captured yet (the brief window after a decision is
    // signalled but before the status snapshot refreshes).
    const outputOutcome = readSnapshotString(taskOutput, "outcome");
    const hasDecision = res !== null || outputOutcome !== "";
    if (!hasDecision) {
        return {
            prompt: req.prompt,
            approvers: req.approvers,
            outcomes: req.outcomes,
            formSchema: req.formSchema,
            timeoutSeconds: req.timeoutSeconds,
            decision: null,
        };
    }
    return {
        prompt: req.prompt,
        approvers: req.approvers,
        outcomes: req.outcomes,
        formSchema: req.formSchema,
        timeoutSeconds: req.timeoutSeconds,
        decision: {
            outcome: outputOutcome,
            reviewer: readSnapshotString(taskOutput, "reviewer") || (res?.resolvedBy ?? ""),
            respondedAt: readSnapshotString(taskOutput, "responded_at") || null,
            comment: readSnapshotString(taskOutput, "comment") || (res?.comment ?? ""),
            formData: readSnapshotObject(taskOutput, "form_data"),
            waitDurationMs: res?.waitDurationMs ?? 0,
            autoResolved: taskOutput?.["auto_resolved"] === true,
        },
    };
}
/** Reads a string field from a task-output Struct, or `""` when absent. */
function readSnapshotString(obj, key) {
    const value = obj?.[key];
    return typeof value === "string" ? value : "";
}
/** Reads a nested object field from a task-output Struct, or `null`. */
function readSnapshotObject(obj, key) {
    const value = obj?.[key];
    return value !== null && typeof value === "object" && !Array.isArray(value)
        ? value
        : null;
}
//# sourceMappingURL=derive-task-detail.js.map