import {
  onAfterCommit
} from "./chunk-4NRHKTBW.js";
import {
  getSessionRow
} from "./chunk-SEGKFEG5.js";
import {
  listEventsByTrace
} from "./chunk-LH3NEWMT.js";
import {
  getAgent
} from "./chunk-LGPJPAHU.js";
import {
  getConfig
} from "./chunk-2MAQ6JFL.js";

// src/observability/otlp.ts
import { createHash } from "crypto";
var STATUS_CODE_OK = 1;
var STATUS_CODE_ERROR = 2;
function toHex(input, bytes) {
  return createHash("sha256").update(input).digest("hex").slice(0, bytes * 2);
}
function traceIdHex(traceId) {
  return toHex(traceId, 16);
}
function spanIdHex(spanId) {
  return toHex(spanId, 8);
}
function strAttr(key, value) {
  if (value == null) return null;
  return { key, value: { stringValue: value } };
}
function intAttr(key, value) {
  if (value == null) return null;
  return { key, value: { intValue: String(Math.round(value)) } };
}
function numAttr(key, value) {
  if (value == null) return null;
  return { key, value: { doubleValue: value } };
}
function boolAttr(key, value) {
  if (value == null) return null;
  return { key, value: { boolValue: value } };
}
function compact(kvs) {
  return kvs.filter((k) => k != null);
}
function msToNano(ms) {
  return `${BigInt(ms) * 1000000n}`;
}
function parsePayload(row) {
  try {
    return JSON.parse(row.payload_json);
  } catch {
    return {};
  }
}
function backendSystem(row) {
  try {
    const sess = getSessionRow(row.session_id);
    if (!sess) return "agentstep";
    const agent = getAgent(sess.agent_id, sess.agent_version);
    if (!agent) return "agentstep";
    switch (agent.engine) {
      case "claude":
      case "anthropic":
        return "anthropic";
      case "codex":
        return "openai";
      case "gemini":
        return "gemini";
      case "factory":
        return "factory";
      case "opencode":
        return "opencode";
      default:
        return agent.engine;
    }
  } catch {
    return "agentstep";
  }
}
function traceToOtlpSpans(events, traceId) {
  const spans = [];
  const open = /* @__PURE__ */ new Map();
  let defaultSystem = "agentstep";
  if (events.length > 0) {
    defaultSystem = backendSystem(events[0]);
  }
  for (const e of events) {
    if (!e.span_id) continue;
    const payload = parsePayload(e);
    if (e.type === "span.model_request_start") {
      const model = typeof payload.model === "string" ? payload.model : null;
      open.set(e.span_id, {
        spanId: e.span_id,
        parentSpanId: e.parent_span_id ?? null,
        name: model ? `turn ${model}` : "turn",
        startMs: e.received_at,
        attributes: compact([
          strAttr("gen_ai.system", defaultSystem),
          strAttr("gen_ai.operation.name", "chat"),
          strAttr("gen_ai.request.model", model),
          strAttr("agentstep.session_id", e.session_id),
          strAttr("agentstep.event.type", e.type)
        ])
      });
      continue;
    }
    if (e.type === "span.model_request_end") {
      const openSpan = open.get(e.span_id);
      if (!openSpan) {
        continue;
      }
      open.delete(e.span_id);
      const usage = payload.model_usage ?? null;
      const status = typeof payload.status === "string" ? payload.status : "ok";
      const statusCode = status === "ok" ? STATUS_CODE_OK : STATUS_CODE_ERROR;
      spans.push({
        traceId: traceIdHex(traceId),
        spanId: spanIdHex(e.span_id),
        parentSpanId: openSpan.parentSpanId ? spanIdHex(openSpan.parentSpanId) : void 0,
        name: openSpan.name,
        kind: 1,
        // INTERNAL
        startTimeUnixNano: msToNano(openSpan.startMs),
        endTimeUnixNano: msToNano(e.received_at),
        attributes: compact([
          ...openSpan.attributes,
          intAttr("gen_ai.usage.input_tokens", usage?.input_tokens),
          intAttr("gen_ai.usage.output_tokens", usage?.output_tokens),
          intAttr("gen_ai.usage.cache_read_input_tokens", usage?.cache_read_input_tokens),
          intAttr("gen_ai.usage.cache_creation_input_tokens", usage?.cache_creation_input_tokens),
          numAttr("agentstep.cost_usd", usage?.cost_usd),
          strAttr("agentstep.span.status", status)
        ]),
        status: {
          code: statusCode,
          message: statusCode === STATUS_CODE_ERROR ? status : void 0
        }
      });
      continue;
    }
    if (e.type === "span.tool_call_start") {
      const name = typeof payload.name === "string" ? payload.name : "tool";
      const toolClass = typeof payload.tool_class === "string" ? payload.tool_class : null;
      open.set(e.span_id, {
        spanId: e.span_id,
        parentSpanId: e.parent_span_id ?? null,
        name: `tool ${name}`,
        startMs: e.received_at,
        attributes: compact([
          strAttr("gen_ai.system", defaultSystem),
          strAttr("gen_ai.operation.name", "tool"),
          strAttr("agentstep.tool.name", name),
          strAttr("agentstep.tool.class", toolClass),
          strAttr("agentstep.session_id", e.session_id),
          strAttr(
            "agentstep.tool.tool_use_id",
            typeof payload.tool_use_id === "string" ? payload.tool_use_id : null
          )
        ])
      });
      continue;
    }
    if (e.type === "span.tool_call_end") {
      const openSpan = open.get(e.span_id);
      if (!openSpan) continue;
      open.delete(e.span_id);
      const status = typeof payload.status === "string" ? payload.status : "ok";
      const statusCode = status === "ok" ? STATUS_CODE_OK : STATUS_CODE_ERROR;
      const durationMs = typeof payload.duration_ms === "number" ? payload.duration_ms : null;
      spans.push({
        traceId: traceIdHex(traceId),
        spanId: spanIdHex(e.span_id),
        parentSpanId: openSpan.parentSpanId ? spanIdHex(openSpan.parentSpanId) : void 0,
        name: openSpan.name,
        kind: 1,
        startTimeUnixNano: msToNano(openSpan.startMs),
        endTimeUnixNano: msToNano(e.received_at),
        attributes: compact([
          ...openSpan.attributes,
          numAttr("agentstep.tool.duration_ms", durationMs),
          boolAttr(
            "agentstep.tool.is_error",
            typeof payload.is_error === "boolean" ? payload.is_error : status !== "ok"
          )
        ]),
        status: {
          code: statusCode,
          message: statusCode === STATUS_CODE_ERROR ? status : void 0
        }
      });
      continue;
    }
    if (e.type === "span.outcome_evaluation_start" || e.type === "span.outcome_evaluation_end") {
      if (e.type === "span.outcome_evaluation_start") {
        open.set(e.span_id, {
          spanId: e.span_id,
          parentSpanId: e.parent_span_id ?? null,
          name: "outcome_evaluation",
          startMs: e.received_at,
          attributes: compact([
            strAttr("gen_ai.system", defaultSystem),
            strAttr("gen_ai.operation.name", "grader"),
            strAttr("agentstep.session_id", e.session_id),
            intAttr("agentstep.grader.iteration", typeof payload.iteration === "number" ? payload.iteration : null)
          ])
        });
      } else {
        const openSpan = open.get(e.span_id);
        if (!openSpan) continue;
        open.delete(e.span_id);
        spans.push({
          traceId: traceIdHex(traceId),
          spanId: spanIdHex(e.span_id),
          parentSpanId: openSpan.parentSpanId ? spanIdHex(openSpan.parentSpanId) : void 0,
          name: openSpan.name,
          kind: 1,
          startTimeUnixNano: msToNano(openSpan.startMs),
          endTimeUnixNano: msToNano(e.received_at),
          attributes: compact([
            ...openSpan.attributes,
            strAttr("agentstep.grader.result", typeof payload.result === "string" ? payload.result : null)
          ]),
          status: { code: STATUS_CODE_OK }
        });
      }
      continue;
    }
  }
  if (open.size > 0) {
    const lastMs = events[events.length - 1]?.received_at ?? Date.now();
    for (const openSpan of open.values()) {
      spans.push({
        traceId: traceIdHex(traceId),
        spanId: spanIdHex(openSpan.spanId),
        parentSpanId: openSpan.parentSpanId ? spanIdHex(openSpan.parentSpanId) : void 0,
        name: `${openSpan.name} (unclosed)`,
        kind: 1,
        startTimeUnixNano: msToNano(openSpan.startMs),
        endTimeUnixNano: msToNano(lastMs),
        attributes: compact([
          ...openSpan.attributes,
          strAttr("agentstep.span.status", "unclosed")
        ]),
        status: { code: STATUS_CODE_ERROR, message: "unclosed span" }
      });
    }
  }
  return spans;
}
function buildOtlpPayload(events, traceId) {
  const spans = traceToOtlpSpans(events, traceId);
  return {
    resourceSpans: [
      {
        resource: {
          attributes: compact([
            strAttr("service.name", "agentstep-gateway"),
            strAttr("service.version", process.env.GATEWAY_VERSION ?? "unknown")
          ])
        },
        scopeSpans: [
          {
            scope: { name: "@agentstep/agent-sdk", version: "0.2.10" },
            spans
          }
        ]
      }
    ]
  };
}
async function exportTrace(traceId) {
  const cfg = getConfig();
  if (!cfg.otlpEndpoint) {
    return { ok: false, error: "no OTLP endpoint configured", spanCount: 0, traceId };
  }
  const events = listEventsByTrace(traceId);
  if (events.length === 0) {
    return { ok: false, error: "trace is empty", spanCount: 0, traceId };
  }
  const payload = buildOtlpPayload(events, traceId);
  const spanCount = payload.resourceSpans[0].scopeSpans[0].spans.length;
  if (spanCount === 0) {
    return { ok: false, error: "no spans to export", spanCount: 0, traceId };
  }
  const url = cfg.otlpEndpoint.endsWith("/v1/traces") ? cfg.otlpEndpoint : `${cfg.otlpEndpoint.replace(/\/+$/, "")}/v1/traces`;
  try {
    const res = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...cfg.otlpAuthorization ? { Authorization: cfg.otlpAuthorization } : {}
      },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(1e4)
    });
    if (!res.ok) {
      const body = await res.text().catch(() => "");
      return {
        ok: false,
        status: res.status,
        error: `OTLP export failed: ${res.status} ${body.slice(0, 200)}`,
        spanCount,
        traceId
      };
    }
    return { ok: true, status: res.status, spanCount, traceId };
  } catch (err) {
    return {
      ok: false,
      error: err instanceof Error ? err.message : String(err),
      spanCount,
      traceId
    };
  }
}
var installed = false;
function installOtlpExporter() {
  if (installed) return;
  installed = true;
  onAfterCommit((sessionId, row) => {
    if (row.type !== "span.model_request_end") return;
    if (row.parent_span_id != null) return;
    if (!row.trace_id) return;
    const sess = getSessionRow(sessionId);
    if (sess?.parent_session_id) return;
    void exportTrace(row.trace_id).then((result) => {
      if (!result.ok) {
        console.warn(`[otlp] export failed for ${row.trace_id}: ${result.error}`);
      } else if (process.env.DEBUG_OTLP) {
        console.log(
          `[otlp] exported trace ${row.trace_id}: ${result.spanCount} spans \u2192 ${result.status}`
        );
      }
    });
  });
}

export {
  traceToOtlpSpans,
  buildOtlpPayload,
  exportTrace,
  installOtlpExporter
};
