"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEngine = getEngine;
exports.listApps = listApps;
exports.rollCall = rollCall;
exports.applyThrottle = applyThrottle;
exports.getStreamStats = getStreamStats;
exports.getStreamMessages = getStreamMessages;
exports.subscribeMesh = subscribeMesh;
exports.isBridgeActive = isBridgeActive;
const hotmesh_1 = require("@hotmeshio/hotmesh");
const db_1 = require("../../lib/db");
const sql_1 = require("./sql");
const stream_messages_sql_1 = require("./stream-messages-sql");
const quorum_bridge_1 = require("./quorum-bridge");
// ─── Engine cache ───────────────────────────────────────────────────────────
/** Cached read-only HotMesh engines keyed by appId */
const engines = new Map();
/**
 * Get or create a read-only HotMesh engine for control plane operations.
 * The engine taps into the Postgres quorum channel for the given appId,
 * enabling rollCall and throttle without running any workflows.
 */
async function getEngine(appId) {
    const cached = engines.get(appId);
    if (cached)
        return cached;
    const engine = await hotmesh_1.HotMesh.init({
        appId,
        guid: `controlplane::${appId}-${hotmesh_1.HotMesh.guid()}`,
        engine: {
            connection: (0, db_1.getConnection)(),
        },
    });
    engines.set(appId, engine);
    return engine;
}
// ─── Application discovery ──────────────────────────────────────────────────
/**
 * List all active HotMesh application IDs from the `hmsh_applications` table.
 */
async function listApps() {
    const pool = (0, db_1.getPool)();
    const { rows } = await pool.query(sql_1.LIST_APPS);
    return rows.map((r) => ({
        appId: r.app_id,
        version: r.version,
    }));
}
// ─── Roll call ──────────────────────────────────────────────────────────────
/**
 * Execute a roll call against the mesh for a given appId.
 * Broadcasts a ping and collects pong responses from all engines and workers.
 */
async function rollCall(appId, delay) {
    const engine = await getEngine(appId);
    return engine.rollCall(delay);
}
// ─── Throttle ───────────────────────────────────────────────────────────────
/**
 * Broadcast a throttle command to the mesh.
 *
 * @param appId — target application
 * @param options.throttle — ms delay per message (-1 = pause, 0 = resume)
 * @param options.topic — target a specific worker topic
 * @param options.guid — target a specific engine/worker by GUID
 */
async function applyThrottle(appId, options) {
    const engine = await getEngine(appId);
    return engine.throttle(options);
}
// ─── Stream statistics ──────────────────────────────────────────────────────
/** Valid interval values (whitelist to prevent injection) */
const VALID_INTERVALS = {
    '15m': '15 minutes',
    '30m': '30 minutes',
    '1h': '1 hour',
    '1d': '1 day',
    '7d': '7 days',
};
/**
 * Get stream processing statistics for a given appId schema.
 *
 * @param schema — the Postgres schema (appId, e.g. "durable")
 * @param duration — time range key (15m, 30m, 1h, 1d, 7d)
 * @param streamName — optional: filter to a specific stream (task queue topic)
 */
async function getStreamStats(schema, duration = '1h', streamName) {
    const interval = VALID_INTERVALS[duration];
    if (!interval)
        throw new Error(`Invalid duration: ${duration}`);
    const pool = (0, db_1.getPool)();
    const stream = streamName || null;
    const [pendingRes, processedRes, byStreamRes] = await Promise.all([
        pool.query((0, sql_1.COUNT_PENDING)(schema), [stream]),
        pool.query((0, sql_1.COUNT_PROCESSED_SINCE)(schema), [interval, stream]),
        pool.query((0, sql_1.VOLUME_BY_STREAM)(schema), [interval, stream]),
    ]);
    return {
        pending: pendingRes.rows[0]?.count ?? 0,
        processed: processedRes.rows[0]?.count ?? 0,
        byStream: byStreamRes.rows,
    };
}
// ─── Stream message browsing ────────────────────────────────────────────
/**
 * Browse stream messages across engine_streams and worker_streams tables
 * with pagination, filtering, and sorting.
 *
 * @param schema — the Postgres schema (namespace, e.g. "durable")
 * @param params — pagination, filter, and sort options
 */
async function getStreamMessages(schema, params) {
    const limit = Math.min(Math.max(params.limit ?? 25, 1), 100);
    const offset = Math.max(params.offset ?? 0, 0);
    const sortColumn = stream_messages_sql_1.VALID_SORT_COLUMNS[params.sort_by ?? 'created_at'];
    if (!sortColumn)
        throw new Error(`Invalid sort_by: ${params.sort_by}`);
    const sortOrder = (params.order ?? 'desc').toLowerCase();
    if (!stream_messages_sql_1.VALID_SORT_ORDERS.has(sortOrder))
        throw new Error(`Invalid order: ${params.order}`);
    const { source } = params;
    const streamName = params.stream_name ? `%${params.stream_name}%` : null;
    const status = params.status ?? null;
    const msgType = params.msg_type ?? null;
    const topic = params.topic ?? null;
    const workflowName = params.workflow_name ?? null;
    const jid = params.jid ?? null;
    const aid = params.aid ?? null;
    const pool = (0, db_1.getPool)();
    const queryParams = [streamName, status, msgType, topic, workflowName, jid, aid, limit, offset];
    const countParams = [streamName, status, msgType, topic, workflowName, jid, aid];
    const [messagesRes, countRes] = await Promise.all([
        pool.query((0, stream_messages_sql_1.LIST_STREAM_MESSAGES)(schema, sortColumn, sortOrder, source), queryParams),
        pool.query((0, stream_messages_sql_1.COUNT_STREAM_MESSAGES)(schema, source), countParams),
    ]);
    return {
        messages: messagesRes.rows,
        total: countRes.rows[0]?.count ?? 0,
    };
}
// ─── Quorum bridge ──────────────────────────────────────────────────────────
/** Track which appIds have active quorum bridges */
const activeBridges = new Set();
/**
 * Start the quorum→NATS bridge for an appId.
 * Subscribes to the HotMesh quorum channel and republishes
 * messages to NATS on `lt.mesh.*` topics.
 */
async function subscribeMesh(appId) {
    if (activeBridges.has(appId))
        return;
    const engine = await getEngine(appId);
    await (0, quorum_bridge_1.startQuorumBridge)(engine, appId);
    activeBridges.add(appId);
}
/**
 * Check if a quorum bridge is active for an appId.
 */
function isBridgeActive(appId) {
    return activeBridges.has(appId);
}
