// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * WU-2643 (INIT-057 Phase 4): narrow conductor emission for
 * `orchestration:next_safe_actions_updated`.
 *
 * The phone-home screen needs sub-5-second refresh whenever a WU's
 * next_safe_actions array changes. Re-pushing the full status document on
 * every tick is wasteful, so the narrow kind carries only
 * `{initiative, next_safe_action_count}` (SDK shape from WU-2637, narrower
 * than the legacy OrchestrationStatusDocument).
 *
 * The debouncer hashes the canonical actions array and suppresses repeat
 * emissions when the hash has not changed since the last push. This keeps
 * emission volume bounded to genuine state transitions rather than polling
 * ticks.
 *
 * @module kernel-event-sync/narrow-emissions
 */
import { createHash } from 'node:crypto';
import { KERNEL_EVENT_KINDS } from '@lumenflow/control-plane-sdk';
import { buildKernelEventV2 } from './emitters.js';
const ACTIONS_HASH_ALGORITHM = 'sha256';
const ACTIONS_HASH_ENCODING = 'hex';
const ACTIONS_HASH_PREFIX_LENGTH = 32;
/**
 * Build the narrow SDK-typed event. Delegates event_id / schema_version /
 * timestamp stamping to the shared `buildKernelEventV2` factory so invariants
 * stay centralised (WU-2638).
 */
export function buildNextSafeActionsUpdatedEvent(input) {
    const options = {};
    if (input.timestamp !== undefined) {
        options.timestamp = input.timestamp;
    }
    if (input.idempotencyKey !== undefined) {
        options.idempotencyKey = input.idempotencyKey;
    }
    const event = buildKernelEventV2({
        kind: KERNEL_EVENT_KINDS.ORCHESTRATION_NEXT_SAFE_ACTIONS_UPDATED,
        initiative: input.initiative,
        next_safe_action_count: input.actions.length,
    }, options);
    // Narrow the union back to the concrete variant for callers that index
    // kind-specific fields directly.
    return event;
}
/**
 * Canonicalise the actions array and hash it. Stable under repeated calls
 * for the same inputs so the debouncer can compare against the last-pushed
 * hash without a reference-equality dance.
 *
 * The hash is short (32 hex chars) because we only need change-detection,
 * not cryptographic commitment.
 */
export function hashNextSafeActions(actions) {
    // JSON.stringify with a sorted key order would break for heterogeneous
    // action shapes; instead rely on each action already being a shallow
    // object whose keys come from the SDK contract. Stringifying the whole
    // array preserves ordering so a reorder counts as a change — which is
    // the desired phone-home behaviour.
    const payload = JSON.stringify(actions);
    return createHash(ACTIONS_HASH_ALGORITHM)
        .update(payload)
        .digest(ACTIONS_HASH_ENCODING)
        .slice(0, ACTIONS_HASH_PREFIX_LENGTH);
}
/**
 * Create a debouncer bound to a sink. Hash state lives in a closure Map keyed
 * by initiative id so different initiatives never suppress each other's
 * emissions.
 */
export function createNextSafeActionsDebouncer(options) {
    const lastHashByInitiative = new Map();
    return {
        push(input) {
            const hash = hashNextSafeActions(input.actions);
            const previous = lastHashByInitiative.get(input.initiative);
            if (previous === hash) {
                return { emitted: false, hash };
            }
            lastHashByInitiative.set(input.initiative, hash);
            const event = buildNextSafeActionsUpdatedEvent(input);
            options.sink(event);
            return { emitted: true, hash };
        },
    };
}
//# sourceMappingURL=narrow-emissions.js.map