/**
 * `trace_rollups`, one row per trace (run × task × model × trial) (D0091).
 *
 * The per-execution agent-behavior summary: how many turns and tool calls,
 * which docs URLs were opened, which search terms were issued, how many files
 * were written, and the token/latency/cost envelope. Answers "which docs get
 * opened, how much back-and-forth, how expensive" without unpacking every tool
 * call.
 *
 * Grain is one `traces` entry (`EvalTrace`). A run with no traces yields zero
 * rows, absence is normal (trace capture shipped late June).
 *
 * Derives from the canonical `EvalTrace` core type (D0045).
 *
 * @see packages/core/src/types/trace.ts, EvalTrace, TraceEvent
 * @see docs/design-docs/bigquery-analytics-fact-loader.md
 */
import { z } from "zod";
import { defineFactTable, } from "../descriptor.js";
import { axisString, LOADED_AT_COLUMN, nonNegIntOrNull, numOrNull, runFactBase, runFactBaseShape, RUN_FACT_BASE_COLUMNS, strOrNull, } from "./common.js";
import { trialNumber } from "./trace-common.js";
const rowSchema = z.object({
    ...runFactBaseShape,
    mode: z.string(),
    task: z.string(),
    model_id: z.string(),
    trial: z.number(),
    trace_id: z.string(),
    turn_count: z.number(),
    tool_call_count: z.number(),
    urls_visited: z.array(z.string()),
    search_terms: z.array(z.string()),
    files_written_count: z.number(),
    total_tokens: z.number().nullable(),
    wall_clock_ms: z.number().nullable(),
    cost: z.number().nullable(),
});
const schema = [
    ...RUN_FACT_BASE_COLUMNS,
    { name: "mode", type: "STRING" },
    { name: "task", type: "STRING" },
    { name: "model_id", type: "STRING" },
    { name: "trial", type: "INT64", mode: "REQUIRED" },
    { name: "trace_id", type: "STRING", description: "Trace id." },
    {
        name: "turn_count",
        type: "INT64",
        mode: "REQUIRED",
        description: "LLM request cycles.",
    },
    { name: "tool_call_count", type: "INT64", mode: "REQUIRED" },
    {
        name: "urls_visited",
        type: "STRING",
        mode: "REPEATED",
        description: "Docs/other URLs opened.",
    },
    { name: "search_terms", type: "STRING", mode: "REPEATED" },
    { name: "files_written_count", type: "INT64", mode: "REQUIRED" },
    { name: "total_tokens", type: "INT64" },
    {
        name: "wall_clock_ms",
        type: "INT64",
        description: "Execution wall-clock (ms).",
    },
    { name: "cost", type: "FLOAT64", description: "Estimated cost (USD)." },
    LOADED_AT_COLUMN,
];
// ---------------------------------------------------------------------------
// Extractor
// ---------------------------------------------------------------------------
/** Filter an unknown array down to its string elements (empty when not an array). */
function stringArray(value) {
    if (!Array.isArray(value))
        return [];
    return value.filter((v) => typeof v === "string");
}
/** Count `llm_request` events (the turn signal), tolerant of a missing log. */
function turnCount(events) {
    if (!Array.isArray(events))
        return 0;
    let n = 0;
    for (const e of events) {
        if (e != null &&
            typeof e === "object" &&
            "type" in e &&
            e.type === "llm_request") {
            n += 1;
        }
    }
    return n;
}
function extract(ctx) {
    return ctx.artifacts
        .entries("traces")
        .map((entry) => {
        const trace = entry.payload;
        const toolCallCount = Array.isArray(trace.toolCalls)
            ? trace.toolCalls.length
            : 0;
        const filesWrittenCount = Array.isArray(trace.filesWritten)
            ? trace.filesWritten.length
            : 0;
        return {
            ...runFactBase(ctx, entry.sourceUri),
            mode: axisString(entry.association.mode),
            task: axisString(entry.association.task),
            model_id: axisString(entry.association.model),
            trial: trialNumber(entry.association.trial),
            trace_id: strOrNull(trace.traceId) ?? "",
            turn_count: turnCount(trace.events),
            tool_call_count: toolCallCount,
            urls_visited: stringArray(trace.urlsVisited),
            search_terms: stringArray(trace.searchTerms),
            files_written_count: filesWrittenCount,
            total_tokens: nonNegIntOrNull(trace.tokensUsed?.totalTokens),
            wall_clock_ms: nonNegIntOrNull(trace.durationMs),
            cost: numOrNull(trace.costEstimate),
        };
    });
}
export const traceRollups = defineFactTable({
    table: "trace_rollups",
    dataset: "ailf_facts",
    sourceArtifacts: ["traces"],
    schema,
    rowSchema,
    partitionField: "completed_at",
    clusterFields: ["mode", "model_id"],
    mergeKey: ["run_id", "mode", "task", "model_id", "trial"],
    extract,
});
export { rowSchema as traceRollupFactRowSchema };
