import { isFtsAvailable } from './db.js';
import { appendAuditEvent } from './audit.js';
import { markSummaryDirtyInTx } from './store.js';
/**
 * The only legitimate path to remove a `kind='raw'` row from `memories`.
 *
 * Snapshots the full row into `raw_archive`, flips `kind` to `'archived'` so the
 * append-only trigger lets the delete through, then deletes the row. All in one
 * SAVEPOINT so it can be nested inside an outer transaction (e.g. batchWriteAndDelete).
 *
 * Throws if the row does not exist or is not `kind='raw'`.
 */
export function archiveRawMemory(db, id, opts) {
    const row = db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id);
    if (!row)
        throw new Error(`memory not found: ${id}`);
    if (row.kind !== 'raw') {
        throw new Error(`memory ${id} is not raw (kind=${String(row.kind)})`);
    }
    // SAVEPOINT (not BEGIN) so this works whether or not we're already inside a
    // transaction. SQLite refuses BEGIN within a transaction; SAVEPOINT nests safely.
    db.exec('SAVEPOINT archive_raw');
    try {
        // GDPR Path A (v0.39): raw_archive stores ONLY metadata, not the original
        // memory content. The audit_log row appended below carries op='archive_raw'
        // for the compliance audit trail. True right-to-be-forgotten — the original
        // content is unrecoverable from raw_archive after this point.
        const archivedAt = new Date().toISOString();
        const redactedPayload = JSON.stringify({
            redacted: true,
            archived_at: archivedAt,
            tenant_id: row.tenant_id ?? 'default',
            kind: row.kind,
            reason: opts.reason,
        });
        db.prepare(`INSERT INTO raw_archive (memory_id, archived_at, reason, archived_by, payload_json) VALUES (?, ?, ?, ?, ?)`).run(id, archivedAt, opts.reason, opts.who, redactedPayload);
        // Flip kind to 'archived' so the BEFORE DELETE trigger no longer fires, then delete.
        db.prepare(`UPDATE memories SET kind = 'archived' WHERE id = ?`).run(id);
        db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
        // FTS5 is a virtual table — no FK CASCADE applies. Purge the FTS row so the
        // archived content is not searchable after archive. Without this the original
        // raw text remains in memories_fts until the next DB-open backfill, defeating
        // GDPR right-to-be-forgotten.
        if (isFtsAvailable(db)) {
            try {
                db.prepare(`DELETE FROM memories_fts WHERE id = ?`).run(id);
            }
            catch {
                // Best effort only. The DELETE on memories already succeeded; FTS will
                // self-heal on next DB open via backfillFtsIndex.
            }
        }
        // A5 audit: emit archive_raw event inside the SAVEPOINT so the audit row is
        // committed atomically with the row deletion. Use the row's own tenant_id
        // (fetched above as part of SELECT *), not the env. Archives must be
        // attributed to the tenant that owns the row, not whatever HIPPO_TENANT
        // happens to be set to in the calling shell.
        try {
            appendAuditEvent(db, {
                tenantId: String(row.tenant_id ?? 'default'),
                actor: opts.who || 'cli',
                op: 'archive_raw',
                targetId: id,
                metadata: { reason: opts.reason },
            });
        }
        catch {
            // Audit must not crash the archive. Failures here mean the audit table
            // is unwritable; the archive itself has already succeeded.
        }
        // v0.30 / E2 — DAG live-coupling: archive of a child under a level-2
        // summary marks parent dirty. Inside the SAVEPOINT so the dirty-mark
        // commits atomically with the archive. row.dag_parent_id was fetched
        // via SELECT * at L28 (schema v28 includes it).
        if (row.dag_parent_id) {
            markSummaryDirtyInTx(db, String(row.dag_parent_id), String(row.tenant_id ?? 'default'), opts.who || 'cli');
        }
        // afterArchive hook (v0.39 commit 3): connector-level idempotency markers
        // (e.g. slack_event_log) must commit atomically with the archive itself.
        // Throwing here rolls back the entire SAVEPOINT — both the archive and any
        // hook side effects. The hook runs INSIDE the SAVEPOINT so its writes
        // share the archive's transactional fate.
        if (opts.afterArchive) {
            opts.afterArchive(db, id);
        }
        db.exec('RELEASE SAVEPOINT archive_raw');
    }
    catch (e) {
        db.exec('ROLLBACK TO SAVEPOINT archive_raw');
        db.exec('RELEASE SAVEPOINT archive_raw');
        throw e;
    }
}
//# sourceMappingURL=raw-archive.js.map