"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildConnection = buildConnection;
exports.collectWorkers = collectWorkers;
exports.startWorkers = startWorkers;
const hotmesh_1 = require("@hotmeshio/hotmesh");
const db_1 = require("../lib/db");
const interceptor_1 = require("../services/interceptor");
const registry_1 = require("../services/workers/registry");
const logger_1 = require("../lib/logger");
const telemetry_1 = require("../lib/telemetry");
const events_1 = require("../lib/events");
const maintenance_1 = require("../services/maintenance");
const cron_1 = require("../services/cron");
const mcp_1 = require("../services/mcp");
const yamlWorkflowWorkers = __importStar(require("../services/yaml-workflow/workers"));
const migrate_1 = require("../lib/db/migrate");
/**
 * Create a named no-op workflow function for readonly/observer workers.
 * The function name is used by `registerWorker` for discovery.
 */
function createNoOpWorkflow(name) {
    const container = {
        [name](..._args) {
            /* readonly no-op */
        },
    };
    return container[name];
}
/**
 * Build the connection descriptor used by HotMesh / Durable.
 */
function buildConnection() {
    return (0, db_1.getConnection)();
}
/**
 * Collect all workers: system, optional examples, and user-provided.
 */
async function collectWorkers(startConfig) {
    const { getSystemWorkers, builtinMcpServerFactories } = await Promise.resolve().then(() => __importStar(require('../system')));
    // Normalize user workers: string workflows become named no-ops (readonly only)
    const userWorkers = (startConfig.workers ?? []).map((w) => {
        if (typeof w.workflow === 'string') {
            if (!w.connection?.readonly) {
                throw new Error(`Worker "${w.workflow}" on queue "${w.taskQueue}": ` +
                    'string workflow names require connection.readonly = true');
            }
            return { ...w, workflow: createNoOpWorkflow(w.workflow) };
        }
        return w;
    });
    const workers = [
        ...getSystemWorkers(),
        ...userWorkers,
    ];
    logger_1.loggerRegistry.info('[long-tail] system workflows loaded');
    if (startConfig.examples) {
        const { exampleWorkers } = await Promise.resolve().then(() => __importStar(require('../examples')));
        workers.push(...exampleWorkers);
        logger_1.loggerRegistry.info('[long-tail] example workflows loaded');
    }
    return { workers, builtinMcpServerFactories };
}
/**
 * Run database migrations, start all workers, connect adapters,
 * register MCP server factories, and seed data.
 */
async function startWorkers(startConfig, workers, builtinMcpServerFactories) {
    // Run migrations
    logger_1.loggerRegistry.info('[long-tail] running migrations...');
    await (0, migrate_1.migrate)();
    // Seed admin user from config (idempotent, runs before workers)
    let adminUserId;
    if (startConfig.seed?.admin) {
        const { seedAdmin } = await Promise.resolve().then(() => __importStar(require('../services/user/seed-admin')));
        adminUserId = await seedAdmin(startConfig.seed.admin).catch((err) => {
            logger_1.loggerRegistry.warn(`[long-tail] seed admin error: ${err.message}`);
            return undefined;
        });
    }
    const connection = buildConnection();
    // Readonly mode: all user-provided workers are observers — skip crons, triggers, and agent seeding.
    // System workers (mcpQuery, etc.) are always added by collectWorkers, so check the original config.
    const userWorkers = startConfig.workers ?? [];
    const isReadonly = userWorkers.length > 0 && userWorkers.every((w) => w.connection?.readonly);
    if (workers.length) {
        // Connect telemetry before HotMesh starts
        if (telemetry_1.telemetryRegistry.hasAdapter) {
            await telemetry_1.telemetryRegistry.connect();
        }
        // Register LT interceptors
        await (0, interceptor_1.registerLT)(connection, {
            defaultRole: startConfig.interceptor?.defaultRole ?? 'reviewer',
        });
        // Start each worker
        for (const w of workers) {
            if (w.connection?.readonly) {
                // Readonly workers register for discovery only — they must not
                // consume messages from the stream (that is the real worker's job).
                (0, registry_1.registerWorker)(w.workflow.name, w.taskQueue);
                logger_1.loggerRegistry.info(`[long-tail] readonly worker registered: ${w.taskQueue}::${w.workflow.name}`);
                continue;
            }
            const label = `${w.taskQueue}::${w.workflow.name}`;
            const worker = await hotmesh_1.Durable.Worker.create({
                connection,
                taskQueue: w.taskQueue,
                workflow: w.workflow,
                guid: `${label}-${hotmesh_1.Durable.guid()}`,
            });
            await worker.run();
            (0, registry_1.registerWorker)(w.workflow.name, w.taskQueue);
        }
        logger_1.loggerRegistry.info(`[long-tail] workers started on queues: ${workers.map((w) => w.taskQueue).join(', ')}`);
        // Seed workflow configs (insert-if-absent — DB is source of truth)
        const workersWithConfig = workers.filter((w) => w.config);
        if (workersWithConfig.length) {
            const { seedWorkflowConfig } = await Promise.resolve().then(() => __importStar(require('../services/config/write')));
            const { ltConfig } = await Promise.resolve().then(() => __importStar(require('../modules/ltconfig')));
            for (const w of workersWithConfig) {
                const workflowType = w.workflow.name;
                const c = w.config;
                try {
                    const inserted = await seedWorkflowConfig({
                        workflow_type: workflowType,
                        task_queue: w.taskQueue,
                        invocable: c.invocable ?? false,
                        default_role: c.defaultRole ?? 'reviewer',
                        description: c.description ?? null,
                        roles: c.roles ?? [],
                        invocation_roles: c.invocationRoles ?? [],
                        consumes: c.consumes ?? [],
                        tool_tags: c.toolTags ?? [],
                        envelope_schema: c.envelopeSchema ?? null,
                        resolver_schema: c.resolverSchema ?? null,
                        cron_schedule: c.cronSchedule ?? null,
                        execute_as: c.executeAs ?? null,
                    });
                    if (inserted)
                        logger_1.loggerRegistry.info(`[long-tail] config seeded: ${workflowType}`);
                }
                catch (err) {
                    logger_1.loggerRegistry.warn(`[long-tail] config seed failed for ${workflowType}: ${err.message}`);
                }
            }
            ltConfig.invalidate();
        }
        // Start maintenance cron (skip in readonly/API mode)
        if (maintenance_1.maintenanceRegistry.hasConfig && !isReadonly) {
            await maintenance_1.maintenanceRegistry.connect();
            logger_1.loggerRegistry.info('[long-tail] maintenance cron started');
        }
        // Start workflow cron schedules (skip in readonly/API mode)
        if (!isReadonly) {
            await cron_1.cronRegistry.connect();
        }
        // Connect MCP adapter
        if (mcp_1.mcpRegistry.hasAdapter) {
            await mcp_1.mcpRegistry.connect();
            logger_1.loggerRegistry.info('[long-tail] MCP adapter connected');
        }
        // Register MCP server factories: built-in (from system/) + user-provided
        // Both system and user factories can carry inline config for DB seeding.
        const { registerBuiltinServer } = await Promise.resolve().then(() => __importStar(require('../services/mcp/client')));
        const { seedMcpServer, cleanStaleBuiltinServers } = await Promise.resolve().then(() => __importStar(require('../services/mcp/db')));
        const userFactories = startConfig.mcp?.serverFactories ?? {};
        // Resolve user factories — plain function or { factory, config }
        const resolvedUserFactories = {};
        for (const [name, entry] of Object.entries(userFactories)) {
            if (typeof entry === 'function') {
                resolvedUserFactories[name] = { factory: entry };
            }
            else {
                resolvedUserFactories[name] = entry;
            }
        }
        // Merge system (always have config) + user factories
        const allFactories = {
            ...builtinMcpServerFactories,
            ...resolvedUserFactories,
        };
        // 1. Register all factories (runtime — always applied)
        for (const [name, entry] of Object.entries(allFactories)) {
            registerBuiltinServer(name, entry.factory);
        }
        logger_1.loggerRegistry.info(`[long-tail] ${Object.keys(allFactories).length} MCP server factories registered`);
        // 2. Seed MCP server configs (insert-if-absent + drift log)
        for (const [name, entry] of Object.entries(allFactories)) {
            if (entry.config) {
                try {
                    const inserted = await seedMcpServer({ name, ...entry.config });
                    if (inserted)
                        logger_1.loggerRegistry.info(`[long-tail] MCP server seeded: ${name}`);
                }
                catch (err) {
                    logger_1.loggerRegistry.warn(`[long-tail] MCP server seed failed for ${name}: ${err.message}`);
                }
            }
        }
        // 3. Clean stale builtin servers no longer in factory list
        await cleanStaleBuiltinServers(Object.keys(allFactories));
        // Register workers for active YAML (deterministic) workflows
        await yamlWorkflowWorkers.registerAllActiveWorkers();
    }
    // Seed topic catalog (system topics + user-declared topics)
    const { seedSystemTopics, seedConfigTopics } = await Promise.resolve().then(() => __importStar(require('../services/topics/system-topics')));
    await seedSystemTopics();
    if (startConfig.topics?.length)
        await seedConfigTopics(startConfig.topics);
    // Seed agents (from startConfig + example system agents when enabled)
    const systemAgents = startConfig.examples
        ? (await Promise.resolve().then(() => __importStar(require('../system')))).getSystemAgents()
        : [];
    const allAgentConfigs = [...(startConfig.agents ?? []), ...systemAgents];
    if (allAgentConfigs.length > 0) {
        const { seedAgent } = await Promise.resolve().then(() => __importStar(require('../services/agent')));
        const { seedSubscription } = await Promise.resolve().then(() => __importStar(require('../services/agent/subscriptions')));
        for (const agentConfig of allAgentConfigs) {
            try {
                // Map flat schedules into behaviors.schedules for DB storage
                const behaviors = {};
                if (agentConfig.schedules?.length) {
                    behaviors.schedules = agentConfig.schedules;
                    behaviors.cron = agentConfig.schedules[0].cron;
                }
                const inserted = await seedAgent({
                    id: agentConfig.name,
                    description: agentConfig.description,
                    goals: agentConfig.goals,
                    rules: agentConfig.rules,
                    status: (agentConfig.status ?? 'active'),
                    knowledge_domain: agentConfig.knowledge_domain,
                    behaviors,
                    workflow_type: agentConfig.schedules?.[0]?.workflow_type,
                });
                if (inserted)
                    logger_1.loggerRegistry.info(`[long-tail] agent seeded: ${agentConfig.name}`);
                // Seed subscriptions for this agent — id IS the name
                if (agentConfig.subscriptions?.length) {
                    for (const sub of agentConfig.subscriptions) {
                        try {
                            const subInserted = await seedSubscription(agentConfig.name, sub);
                            if (subInserted)
                                logger_1.loggerRegistry.info(`[long-tail] subscription seeded: ${agentConfig.name}/${sub.topic}`);
                        }
                        catch (subErr) {
                            logger_1.loggerRegistry.warn(`[long-tail] subscription seed failed: ${agentConfig.name}/${sub.topic}: ${subErr.message}`);
                        }
                    }
                }
            }
            catch (err) {
                logger_1.loggerRegistry.warn(`[long-tail] agent seed failed for ${agentConfig.name}: ${err.message}`);
            }
        }
    }
    // Register the in-process callback adapter for agent event triggers.
    // Reuse existing instance if already registered (e.g., from SDK createClient).
    const { CallbackEventAdapter } = await Promise.resolve().then(() => __importStar(require('../lib/events/callback')));
    const { agentTriggerRegistry } = await Promise.resolve().then(() => __importStar(require('../services/agent/trigger-registry')));
    let callbackAdapter = events_1.eventRegistry.getAdapter(CallbackEventAdapter);
    if (!callbackAdapter) {
        callbackAdapter = new CallbackEventAdapter();
        events_1.eventRegistry.register(callbackAdapter);
    }
    // Bridge cross-container events to the local callback adapter.
    // Any transport adapter (NATS, SNS, GCP Pub/Sub, etc.) that implements
    // setCallbackBridge() will subscribe to the bus and forward events
    // from other containers so agent triggers fire locally.
    events_1.eventRegistry.bridgeCallbackAdapter(callbackAdapter);
    // Connect event adapters (outside workers guard so API-only containers
    // still connect to NATS and can publish/receive events)
    if (events_1.eventRegistry.hasAdapters) {
        await events_1.eventRegistry.connect();
        logger_1.loggerRegistry.info('[long-tail] event adapters connected');
    }
    // Arm agent event subscriptions and crons (skip in readonly/API mode)
    if (!isReadonly) {
        try {
            await agentTriggerRegistry.connect(callbackAdapter);
        }
        catch (err) {
            logger_1.loggerRegistry.warn(`[long-tail] agent trigger registry: ${err.message}`);
        }
        try {
            await cron_1.cronRegistry.connectAgentCrons();
        }
        catch (err) {
            logger_1.loggerRegistry.warn(`[long-tail] agent cron schedules: ${err.message}`);
        }
    }
    else {
        logger_1.loggerRegistry.info('[long-tail] readonly mode — agent triggers and crons skipped');
    }
    // Ensure system bot account exists for cron/system-initiated workflows
    const { ensureSystemBot } = await Promise.resolve().then(() => __importStar(require('../services/iam/bots')));
    await ensureSystemBot().catch((err) => logger_1.loggerRegistry.warn(`[long-tail] system bot seed error: ${err.message}`));
    // Seed example data when enabled
    if (startConfig.examples) {
        const { seedExamples } = await Promise.resolve().then(() => __importStar(require('../examples')));
        const seedClient = new hotmesh_1.Durable.Client({ connection });
        setTimeout(() => {
            seedExamples(seedClient).catch((err) => logger_1.loggerRegistry.warn(`[long-tail] seed error: ${err.message}`));
        }, 2000);
    }
    return { adminUserId };
}
