import { parseJson, writeError } from "./registry.js";
export function registerTraceRoutes(routes, deps) {
    /**
     * GET /api/v1/traces
     *   ?limit=50         (max 500)
     *   &offset=0
     *   &sessionId=<id>   (optional filter)
     *   &ownerAgentKind=<kind>   (optional namespace filter)
     *   &ownerProfileId=<id>     (optional namespace filter)
     *   &q=<substring>    (optional case-insensitive summary/text filter)
     *
     * Returns: { traces: TraceDTO[], limit, offset, nextOffset? }
     *
     * Used by the Memories viewer as its primary "list" endpoint so
     * users can see their memory entries even when semantic retrieval
     * would miss them (fresh install, empty query, embedder offline).
     */
    routes.set("GET /api/v1/traces", async (ctx) => {
        const params = ctx.url.searchParams;
        const parsedLimit = Number(params.get("limit"));
        const parsedOffset = Number(params.get("offset"));
        const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
        const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
        const sessionId = params.get("sessionId") || undefined;
        const namespace = parseNamespace(params.get("namespace"));
        const ownerAgentKind = params.get("ownerAgentKind") || namespace?.ownerAgentKind || undefined;
        const ownerProfileId = params.get("ownerProfileId") || namespace?.ownerProfileId || undefined;
        const q = params.get("q") || undefined;
        // When `groupByTurn=true`, pagination treats each (episodeId, turnId)
        // pair as one "memory" — matching the viewer's grouped display where
        // a user query + its tool steps + final reply collapse into one card.
        const groupByTurn = params.get("groupByTurn") === "true";
        const includeTotal = params.get("includeTotal") !== "false";
        const listLimit = includeTotal ? limit : limit + 1;
        const rawTraces = await deps.core.listTraces({
            limit: listLimit,
            offset,
            sessionId: sessionId,
            ownerAgentKind,
            ownerProfileId,
            q,
            groupByTurn,
        });
        const { traces, hasMore } = trimTracePage(rawTraces, limit, groupByTurn);
        const total = includeTotal
            ? await deps.core.countTraces({
                sessionId: sessionId,
                ownerAgentKind,
                ownerProfileId,
                q,
                groupByTurn,
            })
            : undefined;
        // When grouping, `traces.length === limit` is no longer a reliable
        // "has more" signal (a single turn can yield many traces). Use the
        // total count instead to detect a next page.
        const nextOffset = includeTotal
            ? groupByTurn
                ? offset + limit < (total ?? 0) ? offset + limit : undefined
                : traces.length === limit ? offset + limit : undefined
            : hasMore ? offset + limit : undefined;
        return {
            traces,
            limit,
            offset,
            total,
            nextOffset,
        };
    });
    routes.setPattern("GET /api/v1/traces/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const trace = await deps.core.getTrace(id);
        if (!trace) {
            writeError(ctx, 404, "not_found", `trace not found: ${id}`);
            return;
        }
        return trace;
    });
    /**
     * PATCH /api/v1/traces/:id — viewer's edit modal. Mutable fields:
     * summary, userText, agentText, tags. Returns the updated DTO.
     */
    routes.setPattern("PATCH /api/v1/traces/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const updated = await deps.core.updateTrace(id, body);
        if (!updated) {
            writeError(ctx, 404, "not_found", `trace not found: ${id}`);
            return;
        }
        return updated;
    });
    /**
     * DELETE /api/v1/traces/:id — hard delete. Idempotent: returns
     * `{ deleted: false }` when the id is unknown.
     */
    routes.setPattern("DELETE /api/v1/traces/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        return await deps.core.deleteTrace(id);
    });
    /**
     * POST /api/v1/traces/delete — bulk delete.
     *   body: { ids: string[] }
     * Used by the viewer's "批量删除" bar.
     */
    routes.set("POST /api/v1/traces/delete", async (ctx) => {
        const body = parseJson(ctx);
        const ids = Array.isArray(body.ids)
            ? body.ids.filter((v) => typeof v === "string" && v.length > 0)
            : [];
        if (ids.length === 0) {
            writeError(ctx, 400, "invalid_argument", "ids[] is required");
            return;
        }
        return await deps.core.deleteTraces(ids);
    });
    /**
     * POST /api/v1/traces/:id/share — set or clear the share state.
     *   body: {
     *     scope: 'private' | 'public' | 'hub' | null,
     *     target?: string,
     *     anonymize?: boolean  // (reserved; currently advisory only)
     *   }
     */
    routes.setPattern("POST /api/v1/traces/:id/share", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const scope = body.scope === undefined ? "public" : body.scope;
        const updated = await deps.core.shareTrace(id, {
            scope: scope ?? null,
            target: body.target ?? null,
            sharedAt: scope ? Date.now() : null,
        });
        if (!updated) {
            writeError(ctx, 404, "not_found", `trace not found: ${id}`);
            return;
        }
        return updated;
    });
    routes.setPattern("GET /api/v1/episodes/:id/timeline", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const traces = await deps.core.timeline({ episodeId: id });
        return { episodeId: id, traces };
    });
}
function trimTracePage(traces, limit, groupByTurn) {
    if (!groupByTurn) {
        return {
            traces: traces.slice(0, limit),
            hasMore: traces.length > limit,
        };
    }
    const turnOrder = new Map();
    const kept = [];
    for (const trace of traces) {
        const key = `${trace.episodeId ?? "_"}:${trace.turnId}`;
        let index = turnOrder.get(key);
        if (index === undefined) {
            index = turnOrder.size;
            turnOrder.set(key, index);
        }
        if (index < limit)
            kept.push(trace);
    }
    return {
        traces: kept,
        hasMore: turnOrder.size > limit,
    };
}
function parseNamespace(value) {
    if (!value)
        return null;
    const [ownerAgentKind, ownerProfileId] = value.split("/", 2).map((part) => part.trim());
    if (!ownerAgentKind || !ownerProfileId)
        return null;
    return { ownerAgentKind, ownerProfileId };
}
//# sourceMappingURL=trace.js.map