/**
 * GitHub event ingest with afterWrite race-safe idempotency.
 *
 * Mirrors src/connectors/slack/ingest.ts. The dedupe key is sha256(eventName +
 * ':' + rawBody) (codex P0 #3) — derived from the signed body, not from the
 * unsigned X-GitHub-Delivery header, so a replay attacker cannot bypass
 * idempotency by rotating the delivery UUID.
 *
 * Race semantics (codex P1 #6):
 *   - Fast path: hasSeenKey pre-check returns 'duplicate' for the common case.
 *   - Slow path: hasSeenKey passes (no row yet). Two workers may race into
 *     remember() concurrently. Inside the writeEntry SAVEPOINT, INSERT OR
 *     IGNORE on github_event_log either inserts (changes=1, commit) or
 *     collides (changes=0, throw DuplicateIdempotencyError -> SAVEPOINT
 *     rolls back this worker's memory row). Exactly one memory exists per
 *     idempotency_key.
 *
 * The Slack precedent's race test was insufficient — it tested the fast path,
 * not the SAVEPOINT collision. The `__testInjectBeforeLog` hook below lets
 * tests pre-populate github_event_log inside the SAVEPOINT to actually
 * exercise the changes=0 -> rollback path.
 */
import { remember } from '../../api.js';
import { openHippoDb, closeHippoDb } from '../../db.js';
import { hasSeenKey, lookupMemoryByKey, DuplicateIdempotencyError } from './idempotency.js';
import { computeIdempotencyKey } from './signature.js';
import { issueEventToRememberOpts, issueCommentEventToRememberOpts, pullRequestEventToRememberOpts, prReviewCommentEventToRememberOpts, } from './transform.js';
function transformEvent(event) {
    switch (event.eventName) {
        case 'issues':
            return issueEventToRememberOpts(event.payload);
        case 'issue_comment':
            return issueCommentEventToRememberOpts(event.payload);
        case 'pull_request':
            return pullRequestEventToRememberOpts(event.payload);
        case 'pull_request_review_comment':
            return prReviewCommentEventToRememberOpts(event.payload);
    }
}
/**
 * v1.3.1: extract the source-normalized identifier the idempotency key needs.
 * Backfill and webhook both produce IngestEvent objects describing the same
 * source revision, so deriving the key from these fields collapses both paths
 * onto the same dedupe row. Mirrors the artifactRef strings in transform.ts.
 */
function eventArtifactRef(event) {
    const repo = event.payload.repository?.full_name ?? 'unknown/unknown';
    switch (event.eventName) {
        case 'issues':
            return `github://${repo}/issue/${event.payload.issue.number}`;
        case 'issue_comment':
            return `github://${repo}/issue/${event.payload.issue.number}/comment/${event.payload.comment.id}`;
        case 'pull_request':
            return `github://${repo}/pull/${event.payload.pull_request.number}`;
        case 'pull_request_review_comment':
            return `github://${repo}/pull/${event.payload.pull_request.number}/review_comment/${event.payload.comment.id}`;
    }
}
function eventUpdatedAt(event) {
    switch (event.eventName) {
        case 'issues':
            return event.payload.issue.updated_at ?? null;
        case 'issue_comment':
            return event.payload.comment.updated_at ?? null;
        case 'pull_request':
            return event.payload.pull_request.updated_at ?? null;
        case 'pull_request_review_comment':
            return event.payload.comment.updated_at ?? null;
    }
}
export function ingestEvent(ctx, input) {
    const idempotencyKey = computeIdempotencyKey(eventArtifactRef(input.event), eventUpdatedAt(input.event));
    // Fast path: pre-check. Avoids running the transform / opening a write tx
    // for the common already-seen case (GitHub auto-retries with the same body).
    const db = openHippoDb(ctx.hippoRoot);
    try {
        if (hasSeenKey(db, idempotencyKey)) {
            return { status: 'duplicate', memoryId: lookupMemoryByKey(db, idempotencyKey) };
        }
    }
    finally {
        closeHippoDb(db);
    }
    const opts = transformEvent(input.event);
    if (!opts) {
        // Empty body: no memory to write, but mark seen so a retry of the same
        // empty event returns 'duplicate' (not 'skipped' again — that would
        // re-run the transform on every retry).
        const db2 = openHippoDb(ctx.hippoRoot);
        try {
            db2
                .prepare(`INSERT OR IGNORE INTO github_event_log (idempotency_key, delivery_id, event_name, ingested_at, memory_id) VALUES (?, ?, ?, ?, ?)`)
                .run(idempotencyKey, input.deliveryId, input.event.eventName, new Date().toISOString(), null);
        }
        finally {
            closeHippoDb(db2);
        }
        return { status: 'skipped', memoryId: null };
    }
    // Atomic write via afterWrite. Inside writeEntry's SAVEPOINT:
    //   1. memories row INSERT lands.
    //   2. (test-only) __testInjectBeforeLog can race-inject a colliding key.
    //   3. INSERT OR IGNORE on github_event_log; if a concurrent worker (or
    //      the test injection) beat us, changes=0 -> throw -> SAVEPOINT rolls
    //      back our memory row. Other worker's commit stands.
    try {
        // v1.12.0: drop the legacy `|| 'connector:github'` fallback (see slack/ingest.ts:73 for rationale).
        const result = remember(ctx, {
            ...opts,
            afterWrite: (innerDb, memoryId) => {
                if (input.__testInjectBeforeLog) {
                    input.__testInjectBeforeLog(innerDb, idempotencyKey);
                }
                const inserted = innerDb
                    .prepare(`INSERT OR IGNORE INTO github_event_log (idempotency_key, delivery_id, event_name, ingested_at, memory_id) VALUES (?, ?, ?, ?, ?)`)
                    .run(idempotencyKey, input.deliveryId, input.event.eventName, new Date().toISOString(), memoryId);
                if (Number(inserted.changes ?? 0) === 0) {
                    throw new DuplicateIdempotencyError(idempotencyKey);
                }
            },
        });
        return { status: 'ingested', memoryId: result.id };
    }
    catch (e) {
        if (e instanceof DuplicateIdempotencyError) {
            // Other worker's row is committed. Return its memory_id so callers
            // behave identically to the fast-path 'duplicate' branch.
            const db3 = openHippoDb(ctx.hippoRoot);
            try {
                return { status: 'skipped_duplicate', memoryId: lookupMemoryByKey(db3, idempotencyKey) };
            }
            finally {
                closeHippoDb(db3);
            }
        }
        throw e;
    }
}
//# sourceMappingURL=ingest.js.map