/**
 * Pure derivation functions for the execution waterfall timeline.
 *
 * Converts an append-only event list into positioned waterfall entries
 * (horizontal bars on a time axis) and a scale specification for the
 * time axis labels/ticks.
 *
 * No React dependency — independently importable and testable (DD-003).
 *
 * @since T07 (Execution Waterfall Timeline)
 */
const BIGINT_ZERO = BigInt(0);
// ---------------------------------------------------------------------------
// deriveWaterfallEntries
// ---------------------------------------------------------------------------
/**
 * Derives positioned waterfall entries from an ordered event list.
 *
 * Walks the event list once (O(n)) to build entries with absolute
 * time positions relative to execution start. Handles retries as
 * stacked attempt segments and agent calls as nested child spans.
 *
 * @param events - Events ordered by sequence_number ascending.
 * @param executionStartIso - ISO 8601 timestamp of execution start
 *   (from `execution_started.occurred_at`). When empty/absent, falls
 *   back to the first event's `occurred_at`.
 */
export function deriveWaterfallEntries(events, executionStartIso) {
    if (events.length === 0)
        return [];
    const execStartEpoch = parseIsoMs(executionStartIso || events[0].occurredAt);
    const tasks = new Map();
    for (const evt of events) {
        const taskName = evt.taskName;
        const evtMs = parseIsoMs(evt.occurredAt) - execStartEpoch;
        const p = evt.payload;
        switch (p.case) {
            case "taskStarted": {
                const existing = tasks.get(taskName);
                if (existing) {
                    // Retry: a new attempt on an existing task
                    existing.status = "running";
                    existing.currentAttemptStart = evtMs;
                    existing.currentAttemptNumber = p.value.attemptNumber;
                    existing.endMs = null;
                    existing.taskKind = p.value.taskKind;
                }
                else {
                    tasks.set(taskName, {
                        taskName,
                        taskKind: p.value.taskKind,
                        status: "running",
                        startMs: evtMs,
                        endMs: null,
                        durationMs: 0,
                        costMicros: BIGINT_ZERO,
                        tokensUsed: BIGINT_ZERO,
                        attempts: [],
                        children: [],
                        approvalWaitMs: null,
                        currentAttemptStart: evtMs,
                        currentAttemptNumber: p.value.attemptNumber,
                    });
                }
                break;
            }
            case "taskCompleted": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.status = "completed";
                    acc.endMs = evtMs;
                    acc.durationMs = Number(p.value.durationMs);
                    acc.costMicros = p.value.costMicros;
                    acc.tokensUsed = p.value.tokensUsed;
                    acc.attempts.push({
                        attemptNumber: acc.currentAttemptNumber,
                        startMs: acc.currentAttemptStart,
                        endMs: evtMs,
                        status: "completed",
                        backoffMs: acc.currentAttemptStart > acc.startMs && acc.attempts.length > 0
                            ? acc.currentAttemptStart - (acc.attempts[acc.attempts.length - 1].endMs ?? acc.currentAttemptStart)
                            : 0,
                    });
                }
                break;
            }
            case "taskFailed": {
                const acc = tasks.get(taskName);
                if (acc) {
                    const failStatus = p.value.willRetry ? "retrying" : "failed";
                    acc.status = failStatus;
                    acc.endMs = p.value.willRetry ? null : evtMs;
                    acc.durationMs = Number(p.value.durationMs);
                    acc.attempts.push({
                        attemptNumber: p.value.attemptNumber,
                        startMs: acc.currentAttemptStart,
                        endMs: evtMs,
                        status: "failed",
                        backoffMs: acc.currentAttemptStart > acc.startMs && acc.attempts.length > 0
                            ? acc.currentAttemptStart - (acc.attempts[acc.attempts.length - 1].endMs ?? acc.currentAttemptStart)
                            : 0,
                    });
                    if (!p.value.willRetry) {
                        acc.costMicros = BIGINT_ZERO;
                        acc.tokensUsed = BIGINT_ZERO;
                    }
                }
                break;
            }
            case "taskSkipped": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.status = "skipped";
                    acc.endMs = evtMs;
                }
                else {
                    tasks.set(taskName, {
                        taskName,
                        taskKind: p.value.taskKind,
                        status: "skipped",
                        startMs: evtMs,
                        endMs: evtMs,
                        durationMs: 0,
                        costMicros: BIGINT_ZERO,
                        tokensUsed: BIGINT_ZERO,
                        attempts: [],
                        children: [],
                        approvalWaitMs: null,
                        currentAttemptStart: evtMs,
                        currentAttemptNumber: 0,
                    });
                }
                break;
            }
            case "taskRetrying": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.status = "retrying";
                }
                break;
            }
            case "approvalRequested": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.status = "waiting_approval";
                }
                break;
            }
            case "approvalResolved": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.status = "running";
                    acc.approvalWaitMs = Number(p.value.waitDurationMs);
                }
                break;
            }
            case "agentCallStarted": {
                const acc = tasks.get(taskName);
                if (acc) {
                    acc.children.push({
                        label: p.value.agentSlug || "agent",
                        startMs: evtMs,
                        endMs: null,
                        spanType: "agent_call",
                    });
                }
                break;
            }
            case "agentCallCompleted": {
                const acc = tasks.get(taskName);
                if (acc) {
                    const lastChild = acc.children[acc.children.length - 1];
                    if (lastChild && lastChild.spanType === "agent_call" && lastChild.endMs === null) {
                        // Mutate the last child span to close it
                        acc.children[acc.children.length - 1] = {
                            ...lastChild,
                            endMs: evtMs,
                        };
                    }
                    acc.costMicros = p.value.costMicros;
                    acc.tokensUsed = p.value.tokensConsumed;
                }
                break;
            }
        }
    }
    // Convert accumulators to immutable entries, ordered by startMs
    const entries = [];
    for (const acc of tasks.values()) {
        entries.push({
            taskName: acc.taskName,
            taskKind: acc.taskKind,
            status: acc.status,
            startMs: acc.startMs,
            endMs: acc.endMs,
            durationMs: acc.durationMs,
            costMicros: acc.costMicros,
            tokensUsed: acc.tokensUsed,
            attempts: acc.attempts,
            children: acc.children,
            approvalWaitMs: acc.approvalWaitMs,
        });
    }
    entries.sort((a, b) => a.startMs - b.startMs);
    return entries;
}
// ---------------------------------------------------------------------------
// deriveWaterfallScale
// ---------------------------------------------------------------------------
/**
 * Nice tick intervals for the time axis. Each value is a duration in ms.
 * The algorithm picks the smallest interval that produces <= MAX_TICKS ticks.
 */
const NICE_INTERVALS = [
    10, 20, 50, 100, 200, 500,
    1_000, 2_000, 5_000, 10_000, 15_000, 30_000,
    60_000, 120_000, 300_000, 600_000, 900_000, 1_800_000,
    3_600_000, 7_200_000, 14_400_000, 28_800_000, 86_400_000,
];
const MAX_TICKS = 12;
/**
 * Computes a time axis scale for the waterfall.
 *
 * @param entries - Waterfall entries (used to find the max extent).
 * @param executionDurationMs - Total execution duration. When the execution
 *   is still running, pass `Date.now() - executionStartEpoch`.
 */
export function deriveWaterfallScale(entries, executionDurationMs) {
    // Find the maximum time extent across all entries
    let maxMs = executionDurationMs;
    for (const entry of entries) {
        const entryEnd = entry.endMs ?? executionDurationMs;
        if (entryEnd > maxMs)
            maxMs = entryEnd;
    }
    if (maxMs <= 0)
        maxMs = 1000; // minimum 1 second range
    // Pick the smallest nice interval that yields <= MAX_TICKS ticks
    let interval = NICE_INTERVALS[NICE_INTERVALS.length - 1];
    for (const candidate of NICE_INTERVALS) {
        if (Math.ceil(maxMs / candidate) <= MAX_TICKS) {
            interval = candidate;
            break;
        }
    }
    const ticks = [];
    for (let t = 0; t <= maxMs; t += interval) {
        ticks.push(t);
    }
    // Ensure we don't exceed the total range by more than one tick
    if (ticks.length > 0 && ticks[ticks.length - 1] < maxMs) {
        ticks.push(ticks[ticks.length - 1] + interval);
    }
    // For very dense ticks, label every other one
    const labelEveryN = ticks.length > 8 ? 2 : 1;
    const totalMs = ticks.length > 0 ? ticks[ticks.length - 1] : maxMs;
    return { totalMs, ticks, labelEveryN };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function parseIsoMs(iso) {
    if (!iso)
        return 0;
    const d = new Date(iso);
    return Number.isNaN(d.getTime()) ? 0 : d.getTime();
}
//# sourceMappingURL=derive-waterfall-entries.js.map