/**
 * Session-retrospective skip-rate visibility tool.
 *
 * Called by the admin agent at session start. Returns the ratio of recent
 * sessions that closed without calling `session-retrospective-mark-complete`
 * — sessions where the typed-edge pass, learnings pass, and SOUL.md pass
 * never ran. The admin agent surfaces the result to the operator if the
 * rate is non-zero so closing via Sidebar Archive / tab-close becomes a
 * visible cost rather than a silent one.
 *
 * Mechanism: count `:Conversation` nodes for this account in the recent
 * window; count those whose corresponding session log contains the
 * sentinel tool-use of `session-retrospective-mark-complete`. The ratio
 * is the skip rate. Pending nodes (prose with `updatedAt` later than the
 * most-recent completed retrospective) carry the deferred typed-edge work
 * that will land at the next /end.
 */
import { int } from "neo4j-driver";
export async function sessionRetrospectiveSkipRate(args, session, accountId) {
    const windowSize = Math.max(1, Math.min(args.windowSize ?? 10, 100));
    // Conversations carry `retrospectiveCompletedAt` when the sentinel tool
    // fired (set by the admin mark-complete tool through log-ingest +
    // platform/ui session writer). When the property is null/missing, the
    // retrospective was skipped.
    // The oldest-pending bound is the earliest updatedAt across prose nodes
    // for this account whose updatedAt is *after* the most-recent completed
    // retrospective — i.e. nodes the next session's pass will pick up.
    const cypher = `
    CALL {
      MATCH (c:Conversation {accountId: $accountId})
      RETURN c
      ORDER BY c.updatedAt DESC
      LIMIT $windowSize
    }
    WITH collect(c) AS recent
    WITH recent,
         [c IN recent WHERE c.retrospectiveCompletedAt IS NOT NULL] AS done,
         [c IN recent WHERE c.retrospectiveCompletedAt IS NOT NULL | c.retrospectiveCompletedAt]
           AS doneTimestamps
    WITH size(recent) AS windowSessions,
         size(done) AS completedRetrospectives,
         CASE WHEN size(doneTimestamps) > 0
              THEN apoc.coll.max(doneTimestamps)
              ELSE null END AS lastCompletedAt
    OPTIONAL MATCH (n)
    WHERE n.accountId = $accountId
      AND ANY(l IN labels(n) WHERE l IN $proseLabels)
      AND (lastCompletedAt IS NULL OR n.updatedAt > lastCompletedAt)
    WITH windowSessions, completedRetrospectives, min(n.updatedAt) AS oldestPending
    RETURN windowSessions, completedRetrospectives, oldestPending
  `;
    // APOC is bundled with the Maxy Neo4j install. If unavailable, fall back to
    // a pure-Cypher variant that uses reduce() for max.
    const fallbackCypher = `
    CALL {
      MATCH (c:Conversation {accountId: $accountId})
      RETURN c
      ORDER BY c.updatedAt DESC
      LIMIT $windowSize
    }
    WITH collect(c) AS recent
    WITH recent,
         [c IN recent WHERE c.retrospectiveCompletedAt IS NOT NULL] AS done,
         [c IN recent WHERE c.retrospectiveCompletedAt IS NOT NULL | c.retrospectiveCompletedAt]
           AS doneTimestamps
    WITH size(recent) AS windowSessions,
         size(done) AS completedRetrospectives,
         reduce(acc = null, ts IN doneTimestamps |
                CASE WHEN acc IS NULL OR ts > acc THEN ts ELSE acc END)
           AS lastCompletedAt
    OPTIONAL MATCH (n)
    WHERE n.accountId = $accountId
      AND ANY(l IN labels(n) WHERE l IN $proseLabels)
      AND (lastCompletedAt IS NULL OR n.updatedAt > lastCompletedAt)
    WITH windowSessions, completedRetrospectives, min(n.updatedAt) AS oldestPending
    RETURN windowSessions, completedRetrospectives, oldestPending
  `;
    const proseLabels = [
        "Message",
        "Page",
        "Meeting",
        "Email",
        "Idea",
        "Note",
        "Post",
        "Report",
    ];
    let records;
    try {
        const result = await session.run(cypher, {
            accountId,
            windowSize: int(windowSize),
            proseLabels,
        });
        records = result.records;
    }
    catch {
        const result = await session.run(fallbackCypher, {
            accountId,
            windowSize: int(windowSize),
            proseLabels,
        });
        records = result.records;
    }
    if (records.length === 0) {
        return {
            windowSessions: 0,
            completedRetrospectives: 0,
            skipRate: 0,
            oldestPendingSinceIso: null,
        };
    }
    const r = records[0];
    const windowSessions = Number(r.get("windowSessions") ?? 0);
    const completedRetrospectives = Number(r.get("completedRetrospectives") ?? 0);
    const oldestPending = r.get("oldestPending");
    const skipRate = windowSessions > 0
        ? Number(((windowSessions - completedRetrospectives) / windowSessions).toFixed(3))
        : 0;
    const accountShort = accountId.slice(0, 8);
    const oldestPendingIso = oldestPending == null ? null : String(oldestPending);
    console.log(`[session-retrospective-skip-rate] accountId=${accountShort}… window=${windowSessions} completed=${completedRetrospectives} skip-rate=${skipRate} oldest-pending=${oldestPendingIso ?? "none"}`);
    return {
        windowSessions,
        completedRetrospectives,
        skipRate,
        oldestPendingSinceIso: oldestPendingIso,
    };
}
//# sourceMappingURL=session-retrospective-skip-rate.js.map