"use strict";
/**
 * dag-builder.ts
 * Build a call-graph style DAG:
 * - Nodes are unique entities (workflow/agent/tool/model)
 * - Edges represent calls/sequence between those entities
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildCallDagGraph = buildCallDagGraph;
function nsToMs(ns) {
    return typeof ns === 'number' && Number.isFinite(ns) ? ns / 1000000 : 0;
}
function safeStr(v, fallback) {
    const s = typeof v === 'string' ? v.trim() : '';
    return s ? s : fallback;
}
function extractRole(span) {
    const a = span?.attributes ?? {};
    const v = a['genai.agent.role'] ?? a['gen_ai.agent.role'] ?? a['agent.role'] ?? a['role'];
    return typeof v === 'string' && v.trim() ? v.trim() : undefined;
}
function extractModelName(span) {
    const a = span?.attributes ?? {};
    const v = a['genai.request.model'] ??
        a['gen_ai.request.model'] ??
        a['genai.response.model'] ??
        a['gen_ai.response.model'] ??
        a['model'];
    if (v === undefined || v === null)
        return undefined;
    const s = String(v).trim();
    if (!s || s.toLowerCase() === 'unknown')
        return undefined;
    return s;
}
function classifySpanAsEntity(span, opts) {
    const tool = opts.toolBySpanId.get(span.id);
    if (tool) {
        const name = safeStr(tool.name, 'tool');
        return { entityId: `tool:${name}`, kind: 'tool', label: name };
    }
    const model = opts.modelBySpanId.get(span.id);
    if (model) {
        const name = safeStr(model.modelName, 'model');
        return { entityId: `model:${name}`, kind: 'model', label: name, model: name };
    }
    const agent = opts.agentBySpanId.get(span.id);
    if (agent) {
        const name = safeStr(agent.name, 'agent');
        return { entityId: `agent:${name}`, kind: 'agent', label: name, role: agent.role };
    }
    // Fallback heuristics (avoid flooding graph with "other" nodes)
    const modelName = extractModelName(span);
    if (modelName) {
        return { entityId: `model:${modelName}`, kind: 'model', label: modelName, model: modelName };
    }
    return null;
}
function bumpEdge(edgeMap, e) {
    const key = `${e.type}:${e.from}->${e.to}`;
    const prev = edgeMap.get(key);
    if (prev) {
        prev.count = (prev.count ?? 1) + 1;
    }
    else {
        edgeMap.set(key, { ...e, count: 1 });
    }
}
function findNearestEntityAncestor(spanById, entityOfSpanId, startParentSpanId) {
    let cur = startParentSpanId;
    let guard = 0;
    while (cur && guard < 50) {
        const ent = entityOfSpanId.get(cur);
        if (ent)
            return ent;
        const s = spanById.get(cur);
        cur = s?.parentSpanId ?? null;
        guard++;
    }
    return undefined;
}
function buildCallDagGraph(spans, tools, agents, models) {
    const spanById = new Map(spans.map((s) => [s.id, s]));
    const toolBySpanId = new Map(tools.map((t) => [t.spanId, t]));
    const modelBySpanId = new Map(models.map((m) => [m.spanId, m]));
    const agentBySpanId = new Map(agents.map((a) => [a.id, a]));
    const workflowId = `workflow:${spans[0]?.traceId ?? 'trace'}`;
    const workflowLabel = safeStr(spans.find((s) => s.parentSpanId === null)?.name ?? spans[0]?.name, 'workflow');
    const nodes = new Map();
    nodes.set(workflowId, {
        id: workflowId,
        kind: 'workflow',
        label: workflowLabel,
        metrics: { durationMs: 0 },
    });
    const nodeDuration = new Map();
    const nodeTokens = new Map();
    const nodeCost = new Map();
    const nodeRole = new Map();
    const nodeModel = new Map();
    const nodeKind = new Map();
    const nodeLabel = new Map();
    const entityOfSpanId = new Map();
    for (const s of spans) {
        const ent = classifySpanAsEntity(s, { toolBySpanId, modelBySpanId, agentBySpanId });
        if (!ent)
            continue;
        entityOfSpanId.set(s.id, ent.entityId);
        nodeKind.set(ent.entityId, ent.kind);
        nodeLabel.set(ent.entityId, ent.label);
        const dur = nsToMs(s.duration);
        nodeDuration.set(ent.entityId, (nodeDuration.get(ent.entityId) ?? 0) + dur);
        if (ent.role)
            nodeRole.set(ent.entityId, ent.role);
        const roleFromSpan = extractRole(s);
        if (roleFromSpan)
            nodeRole.set(ent.entityId, roleFromSpan);
        if (ent.model)
            nodeModel.set(ent.entityId, ent.model);
        const modelFromSpan = extractModelName(s);
        if (modelFromSpan)
            nodeModel.set(ent.entityId, modelFromSpan);
        const tool = toolBySpanId.get(s.id);
        if (tool?.tokenUsage?.totalTokens) {
            nodeTokens.set(ent.entityId, (nodeTokens.get(ent.entityId) ?? 0) + tool.tokenUsage.totalTokens);
        }
        const model = modelBySpanId.get(s.id);
        if (model?.tokenUsage?.totalTokens) {
            nodeTokens.set(ent.entityId, (nodeTokens.get(ent.entityId) ?? 0) + model.tokenUsage.totalTokens);
        }
        if (typeof model?.cost === 'number' && Number.isFinite(model.cost)) {
            nodeCost.set(ent.entityId, (nodeCost.get(ent.entityId) ?? 0) + model.cost);
        }
    }
    // Materialize nodes
    for (const [id, kind] of nodeKind.entries()) {
        const label = nodeLabel.get(id) ?? id;
        const metrics = {
            durationMs: nodeDuration.get(id) ?? 0,
            tokensTotal: nodeTokens.get(id),
            costUsd: nodeCost.get(id),
        };
        nodes.set(id, {
            id,
            kind,
            label,
            role: nodeRole.get(id),
            model: nodeModel.get(id),
            metrics,
        });
    }
    // Aggregate call edges (parentSpanId relationships)
    const edgeMap = new Map();
    for (const s of spans) {
        const childEnt = entityOfSpanId.get(s.id);
        if (!childEnt)
            continue;
        const parentId = s.parentSpanId;
        const parentEnt = parentId
            ? entityOfSpanId.get(parentId) ?? findNearestEntityAncestor(spanById, entityOfSpanId, parentId)
            : undefined;
        const from = parentEnt ?? workflowId;
        const to = childEnt;
        if (from === to)
            continue;
        bumpEdge(edgeMap, { from, to, type: 'invokes' });
    }
    // Add sequence edges based on start time ordering (coarse but useful for navigation)
    const sorted = [...spans].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
    let prevEnt = null;
    for (const s of sorted) {
        const ent = entityOfSpanId.get(s.id) ?? null;
        if (!ent)
            continue;
        if (prevEnt && prevEnt !== ent) {
            bumpEdge(edgeMap, { from: prevEnt, to: ent, type: 'sequence' });
        }
        prevEnt = ent;
    }
    const graph = {
        nodes: [...nodes.values()],
        edges: [...edgeMap.values()],
    };
    return { graph, entityOfSpanId };
}
//# sourceMappingURL=dagBuilder.js.map