/**
 * Bound audit-emit capability.
 *
 * `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
 * subscriber chain, and the optional `AuditLogConfig`. Built by the
 * consumer's `audit_factory` callback on `CreateAppBackendOptions` —
 * `create_app_backend` invokes the factory once with its constructed
 * `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
 * for `deps.audit.emit(ctx, input)` and never see the pool — handlers
 * cannot accidentally emit an audit event against the request's
 * transactional `db` (which would be rolled back with the parent on a
 * handler throw).
 *
 * Four methods cover every fan-out shape the auth domain needs:
 *
 * - `emit(ctx, input)` — fire-and-forget pool write. Pushes the in-flight
 *   promise onto `ctx.pending_effects` for post-response flushing. Errors are
 *   logged, never thrown. Returns `void` so callers don't pile up `void`
 *   keywords or accidentally `await` something whose handle is already in
 *   `pending_effects`.
 * - `emit_role_grant_target(ctx, auth, input)` — wrapper that lifts the
 *   `actor_id` / `account_id` / `ip` boilerplate every role-grant-shape audit
 *   site repeated. Delegates to `emit`.
 * - `emit_pool(input)` — awaitable pool write for code paths without a
 *   `pending_effects` queue (cleanup sweeps, ad-hoc maintenance scripts).
 *   Same write-then-notify semantics as `emit`, just synchronous-with-await.
 * - `notify(event)` — fan out an already-written audit row (e.g. rows
 *   returned by `query_accept_offer` that were inserted in-transaction by
 *   the query layer). Runs every listener on the chain; per-listener throws
 *   are isolated.
 *
 * The chain is a documented mutable seam — `create_app_server` appends
 * additional listeners after the backend is built (the factory-managed
 * audit-log SSE, per-endpoint WS auth guards and logout closers, any
 * `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
 * runs. Consumers can also append listeners directly on the emitter
 * they return from `audit_factory` for setups that don't pass through
 * `create_app_server`.
 *
 * @module
 */
import { query_audit_log } from './audit_log_queries.js';
import { builtin_audit_log_config, } from './audit_log_schema.js';
/**
 * Build a bound `AuditEmitter`. Typical caller is the consumer's
 * `audit_factory` callback on `CreateAppBackendOptions` —
 * `create_app_backend` invokes that callback with its constructed
 * `{db, log}` and lands the result on `AppDeps.audit`.
 *
 * @param options - pool, logger, optional initial subscriber, optional config
 * @returns the bound emitter; closes over the pool + config + listener chain
 */
export const create_audit_emitter = (options) => {
    const { db, log, audit_log_config = builtin_audit_log_config, emit_decorator } = options;
    const on_event_chain = [];
    if (options.on_audit_event)
        on_event_chain.push(options.on_audit_event);
    const notify = (event) => {
        for (const listener of on_event_chain) {
            try {
                listener(event);
            }
            catch (err) {
                log.error('Audit log listener failed:', err);
            }
        }
    };
    const emit_pool = async (input) => {
        try {
            const event = await query_audit_log({ db }, input, audit_log_config);
            notify(event);
        }
        catch (err) {
            log.error('Audit log write failed:', err);
        }
    };
    const base_emit = (ctx, input) => {
        ctx.pending_effects.push(emit_pool(input));
    };
    // The decorated `emit` is what `emit_role_grant_target` captures below
    // and what gets exposed on the returned object — both call shapes
    // route through any `emit_decorator` the caller supplied. Production
    // passes no decorator, so this collapses to `base_emit`.
    const emit = emit_decorator ? emit_decorator(base_emit) : base_emit;
    const emit_role_grant_target = (ctx, auth, input) => {
        emit(ctx, {
            event_type: input.event_type,
            actor_id: auth.actor.id,
            account_id: auth.account.id,
            outcome: input.outcome,
            target_account_id: input.target_account_id,
            target_actor_id: input.target_actor_id,
            ip: ctx.client_ip,
            metadata: input.metadata,
        });
    };
    // Freeze the slot layout so consumers cannot hot-patch `emit` /
    // `emit_role_grant_target` / `emit_pool` / `notify` after construction.
    // The previous test helper `patch_audit_emit_capture` did exactly this
    // and only happened to work because the four slots were writable —
    // `emit_role_grant_target` calls the closed-over inner `emit`, not
    // `this.emit`, so the patch silently bypassed role-grant-shape emits.
    // Tests that need instrumentation pass `emit_decorator` so the wrap
    // is captured by the closure before the freeze (see
    // `create_emit_ordering_audit_factory`). `on_event_chain` is a
    // frozen reference but its array contents stay mutable —
    // `create_app_server` appends to it post-assembly, by design.
    return Object.freeze({ emit, emit_role_grant_target, emit_pool, notify, on_event_chain });
};
