import { KnowledgeFabricEngine } from '../engines/knowledge-fabric.js';
import { createSkillRuntime } from '../engines/skill-runtime.js';
import { nexusEventBus } from '../engines/event-bus.js';
import { createEngineHealthStatus } from '../engines/runtime-health.js';
import { SynapseConfig } from './config.js';
import { openSynapseDb, resolveSynapsePaths } from './db/client.js';
import { restoreSynapseFromLedger, insertFieldReport } from './field-reports/restorer.js';
import { startLedgerBatchCommitter } from './field-reports/ledger.js';
import { registerCompactionListener } from './compaction/standdown.js';
import { startWatchdogPatrol } from './watchdog/patrol.js';
import { executeMandatePipeline } from './mandate/pipeline.js';
import { requestApproval, resolveApproval, getPendingApprovals } from './approvals/gate.js';
import { getAllStrikeTeams, getStrikeTeam } from './missions/crud.js';
import { getAllOperatives, getOperative, insertOperative, updateOperativeMission, updateOperativeProfile, clearOperativeSuspension } from './operatives/crud.js';
import { getSharedLicenseManager } from '../licensing/index.js';
import { transitionOperative } from './operatives/state-machine.js';
import { queryEcho } from './echo/query.js';
import { runSortieByOperativeId } from './sorties/runner.js';
import { startSortieScheduler } from './sorties/scheduler.js';
function createProviders(options) {
    return {
        repoRoot: options.repoRoot,
        orchestrator: options.orchestrator,
        memory: options.memory,
        sessionDNA: options.sessionDNA,
        skillRuntime: createSkillRuntime(undefined, undefined, options.repoRoot),
        knowledgeFabric: new KnowledgeFabricEngine({ repoRoot: options.repoRoot, memory: options.memory }),
        coordination: options.coordination,
        claimWorkItem: options.coordination
            ? (workItemId, operativeId) => options.coordination.claimWorkItem(workItemId, operativeId)
            : undefined,
        completeWorkItem: options.coordination
            ? (workItemId, operativeId, status) => options.coordination.completeWorkItem(workItemId, operativeId, status)
            : undefined,
    };
}
function storeOperationalMemory(providers, content, tags, input) {
    providers.memory.store(content, 0.74, ['#shared', '#synapse', ...tags].filter(Boolean), undefined, 0, {
        scope: 'shared',
        source: input.source ?? 'runtime',
        provenance: {
            source: input.source ?? 'runtime',
            agentId: input.operativeId ?? undefined,
            lane: 'shared',
            summary: input.summary,
            containerTags: [
                ...(input.strikeTeamId ? [`#team:${input.strikeTeamId}`] : []),
                ...(input.worklistId ? [`#worklist:${input.worklistId}`] : []),
                ...(input.workItemId ? [`#workitem:${input.workItemId}`] : []),
                ...(input.operativeId ? [`#operative:${input.operativeId}`] : []),
            ],
        },
    });
}
function hireOperativeDirect(db, providers, input) {
    const budgetCapUsd = Number(input.budgetCapUsd ?? 10);
    if (!Number.isFinite(budgetCapUsd) || budgetCapUsd <= 0) {
        throw new Error('[Synapse] budgetCapUsd must be greater than 0 for direct hires.');
    }
    // ── License operative cap check (soft — warn but proceed) ────────────────
    try {
        const _lm = getSharedLicenseManager();
        const _opCount = getAllOperatives(db).length;
        const _capResult = _lm.checkCap('operatives', _opCount);
        if (_capResult?.exceeded) {
            nexusEventBus.emit('license.cap.reached', {
                type: 'operatives', current: _opCount,
                limit: _capResult.limit, tier: _capResult.tier,
                upgradeUrl: _capResult.upgradeUrl,
            });
        }
    }
    catch { /* cap check must never break operative hiring */ }
    const operative = insertOperative(db, {
        name: input.name,
        roleTitle: input.roleTitle ?? 'Operative',
        reportsToOperativeId: input.reportsToOperativeId ?? null,
        skillId: input.skillId ?? null,
        specialistId: input.specialistId ?? null,
        strikeTeamId: input.strikeTeamId ?? null,
        budgetCapUsd,
        sortieIntervalMs: input.sortieIntervalMs ?? undefined,
        origin: input.origin ?? 'manual',
    });
    nexusEventBus.emit('synapse.operative.hired', {
        operativeId: operative.id,
        name: operative.name,
        skillId: operative.skillId,
        specialistId: operative.specialistId,
        roleTitle: operative.roleTitle,
        reportsToOperativeId: operative.reportsToOperativeId,
        strikeTeamId: operative.strikeTeamId,
    });
    storeOperationalMemory(providers, `[Synapse:Hire] ${operative.name} hired as ${operative.roleTitle ?? 'Operative'}`, ['#hire'], {
        source: 'runtime',
        operativeId: operative.id,
        strikeTeamId: operative.strikeTeamId,
        summary: 'Synapse operative hired.',
    });
    return operative;
}
function maybeResolveApprovalHire(db, providers, approval) {
    if (!approval || approval.status !== 'approved' || approval.action !== 'hire-operative' || !approval.payload) {
        return;
    }
    hireOperativeDirect(db, providers, {
        name: typeof approval.payload.name === 'string' ? approval.payload.name : undefined,
        roleTitle: typeof approval.payload.roleTitle === 'string' ? approval.payload.roleTitle : null,
        reportsToOperativeId: typeof approval.payload.reportsToOperativeId === 'string' ? approval.payload.reportsToOperativeId : null,
        skillId: typeof approval.payload.skillId === 'string' ? approval.payload.skillId : null,
        specialistId: typeof approval.payload.specialistId === 'string' ? approval.payload.specialistId : null,
        strikeTeamId: typeof approval.payload.strikeTeamId === 'string' ? approval.payload.strikeTeamId : null,
        budgetCapUsd: typeof approval.payload.budgetCapUsd === 'number' ? approval.payload.budgetCapUsd : undefined,
        sortieIntervalMs: typeof approval.payload.sortieIntervalMs === 'number' ? approval.payload.sortieIntervalMs : undefined,
        origin: 'approval',
    });
}
export function initSynapse(options) {
    if (!SynapseConfig.enabled && !options.forceEnable)
        return null;
    let opened;
    const requestedStorageRoot = options.requestedStorageRoot ?? options.repoRoot;
    const initialStorage = resolveSynapsePaths({
        requestedRoot: requestedStorageRoot,
        storageRoot: options.storageRoot,
    });
    try {
        opened = openSynapseDb({
            requestedRoot: requestedStorageRoot,
            storageRoot: options.storageRoot,
        });
    }
    catch (err) {
        console.error(`[Synapse] DB unavailable at ${initialStorage.dbPath} — Synapse disabled:`, err?.message);
        return null;
    }
    const { db, paths: storage, reason: storageReason, quarantinePath } = opened;
    const providers = createProviders(options);
    restoreSynapseFromLedger(db, options.repoRoot);
    const stopCompaction = registerCompactionListener(db, providers);
    const hasOperative = process.env.SYNAPSE_OPERATIVE_ID !== 'off';
    const watchdogTimer = hasOperative ? startWatchdogPatrol(db) : undefined;
    const scheduler = hasOperative ? startSortieScheduler(db, providers) : undefined;
    if (hasOperative)
        startLedgerBatchCommitter();
    const blockedListener = (payload) => {
        if (payload.operativeId) {
            db.prepare('UPDATE synapse_operatives SET health_state=\'BLOCKED\', state=\'SUSPENDED\', suspend_reason=\'manual\' WHERE id=?').run(payload.operativeId);
            nexusEventBus.emit('synapse.operative.health.changed', { operativeId: payload.operativeId, healthState: 'BLOCKED' });
            providers.memory.store(`[Architects:Blocked] ${payload.operativeId} ${payload.reason ?? ''}`.trim(), 0.82, ['#synapse', '#blocked'], undefined, 0, { provenance: { agentId: payload.operativeId, source: 'system' } });
        }
    };
    const stallListener = ({ operativeId }) => {
        const operative = getOperative(db, operativeId);
        if (operative) {
            db.prepare('UPDATE synapse_operatives SET health_state=\'STALLED\' WHERE id=?').run(operativeId);
            nexusEventBus.emit('synapse.operative.health.changed', { operativeId, healthState: 'STALLED' });
        }
    };
    const zombieListener = ({ operativeId }) => {
        db.prepare('UPDATE synapse_operatives SET health_state=\'ZOMBIE\', state=\'SUSPENDED\', suspend_reason=\'manual\' WHERE id=?').run(operativeId);
        nexusEventBus.emit('synapse.operative.health.changed', { operativeId, healthState: 'ZOMBIE' });
    };
    const failedConvergenceListener = ({ worklistId, error }) => {
        storeOperationalMemory(providers, `[Architects:ConvergenceFailed] ${worklistId} ${error ?? ''}`.trim(), ['#architects', '#convergence'], {
            source: 'runtime',
            worklistId,
            summary: 'Architects convergence failure was recorded for a Synapse-linked worklist.',
        });
    };
    const unsubBlocked = nexusEventBus.on('architects.workitem.blocked', blockedListener);
    const unsubStall = nexusEventBus.on('architects.sentinel.stall', stallListener);
    const unsubZombie = nexusEventBus.on('architects.sentinel.zombie', zombieListener);
    const unsubConvergence = nexusEventBus.on('architects.convergence.failed', failedConvergenceListener);
    setImmediate(() => nexusEventBus.emit('synapse.ready', { version: '5.0.0' }));
    console.error('[Synapse] Initialized. Operative mode active.');
    return {
        db,
        providers,
        executeMandatePipeline: (mandateText, opts) => executeMandatePipeline(db, mandateText, providers, opts),
        hireOperative: (input) => {
            if (input.requestApproval) {
                const approval = requestApproval(db, {
                    operativeId: input.requestedByOperativeId ?? 'operator',
                    scope: input.strikeTeamId ? 'crew' : 'project',
                    scopeId: input.strikeTeamId ?? options.repoRoot,
                    action: 'hire-operative',
                    description: `Hire ${input.name ?? 'operative'}${input.roleTitle ? ` as ${input.roleTitle}` : ''}`,
                    payload: {
                        name: input.name ?? null,
                        roleTitle: input.roleTitle ?? null,
                        reportsToOperativeId: input.reportsToOperativeId ?? null,
                        skillId: input.skillId ?? null,
                        specialistId: input.specialistId ?? null,
                        strikeTeamId: input.strikeTeamId ?? null,
                        budgetCapUsd: input.budgetCapUsd ?? 10,
                        sortieIntervalMs: input.sortieIntervalMs ?? SynapseConfig.sortieIntervalMs,
                    },
                });
                storeOperationalMemory(providers, `[Synapse:HireRequest] ${approval.description}`, ['#approval', '#hire-request'], {
                    source: 'runtime',
                    operativeId: input.requestedByOperativeId ?? null,
                    strikeTeamId: input.strikeTeamId ?? null,
                    summary: 'Synapse hire request queued for approval.',
                });
                return approval;
            }
            return hireOperativeDirect(db, providers, input);
        },
        updateOperative: (input) => {
            const operative = getOperative(db, input.operativeId);
            if (!operative)
                return null;
            const action = input.action ?? 'profile';
            if (action === 'pause') {
                transitionOperative(db, operative.id, 'SUSPENDED', 'manual');
            }
            else if (action === 'resume') {
                clearOperativeSuspension(db, operative.id);
                transitionOperative(db, operative.id, 'IDLE');
            }
            else if (action === 'terminate') {
                updateOperativeMission(db, operative.id, null, operative.strikeTeamId);
                updateOperativeProfile(db, operative.id, { terminationReason: input.reason ?? 'terminated' });
                transitionOperative(db, operative.id, 'DONE', 'manual');
            }
            else if (action === 'reassign') {
                updateOperativeMission(db, operative.id, input.missionId ?? null, input.strikeTeamId ?? operative.strikeTeamId);
            }
            const updated = updateOperativeProfile(db, operative.id, {
                roleTitle: input.roleTitle,
                reportsToOperativeId: input.reportsToOperativeId,
                skillId: input.skillId,
                specialistId: input.specialistId,
                budgetCapUsd: input.budgetCapUsd,
                sortieIntervalMs: input.sortieIntervalMs,
                terminationReason: action === 'terminate' ? (input.reason ?? 'terminated') : undefined,
            });
            if (updated) {
                storeOperationalMemory(providers, `[Synapse:OperativeUpdate] ${updated.id} ${action}`, ['#operative-update', `#action:${action}`], {
                    source: 'runtime',
                    operativeId: updated.id,
                    strikeTeamId: updated.strikeTeamId,
                    summary: `Synapse operative ${action} action recorded.`,
                });
            }
            return updated;
        },
        triggerHeartbeat: async (operativeId, force = false) => {
            const operative = getOperative(db, operativeId);
            if (!operative) {
                throw new Error(`[Synapse] Unknown operative ${operativeId}`);
            }
            if (operative.state === 'DONE') {
                throw new Error(`[Synapse] Operative ${operativeId} is terminated.`);
            }
            if (operative.state === 'SUSPENDED') {
                if (!force) {
                    throw new Error(`[Synapse] Operative ${operativeId} is suspended. Pass force=true to resume for a manual heartbeat.`);
                }
                clearOperativeSuspension(db, operativeId);
                transitionOperative(db, operativeId, 'IDLE');
            }
            return runSortieByOperativeId(db, operativeId, providers);
        },
        runSortie: (operativeId) => runSortieByOperativeId(db, operativeId, providers),
        requestApproval: (input) => requestApproval(db, input),
        resolveApproval: (approvalId, decision) => {
            const approval = resolveApproval(db, approvalId, decision);
            maybeResolveApprovalHire(db, providers, approval);
            return approval;
        },
        noteMissionProgress: (operativeId, missionId, note) => {
            storeOperationalMemory(providers, `[Synapse:Progress] ${operativeId}:${missionId ?? 'none'} ${note}`, ['#progress'], {
                source: 'operative',
                operativeId,
                workItemId: missionId,
                summary: 'Synapse operative progress note stored in shared memory.',
            });
            return note;
        },
        submitFieldReport: (report) => {
            insertFieldReport(db, report);
            return report;
        },
        getStrikeTeamStatus: (strikeTeamId) => strikeTeamId ? getStrikeTeam(db, strikeTeamId) : getAllStrikeTeams(db),
        getOperativeHealth: (operativeId) => operativeId ? getOperative(db, operativeId) : getAllOperatives(db),
        queryEcho: ({ missionId, missionTitle, currentOperativeId }) => queryEcho(db, missionId ?? null, missionTitle, currentOperativeId, providers.memory),
        getPendingApprovals: () => getPendingApprovals(db),
        getStatus: () => createEngineHealthStatus({
            name: 'synapse',
            available: true,
            ready: true,
            repoRoot: options.repoRoot,
            requestedStorageRoot: storage.requestedRoot,
            storageRoot: storage.storageRoot,
            dbPath: storage.dbPath,
            fallbackApplied: storage.fallbackApplied,
            reason: storageReason ?? storage.fallbackReason,
            quarantinePath,
        }),
        stop: () => {
            stopCompaction();
            if (watchdogTimer)
                clearInterval(watchdogTimer);
            if (scheduler)
                clearInterval(scheduler);
            unsubBlocked();
            unsubStall();
            unsubZombie();
            unsubConvergence();
            db.close();
        },
    };
}
/**
 * Fire a lightweight "warm up" push-dispatch for a newly hired operative.
 * Non-blocking — the promise resolves once the child is spawned; stream
 * progress via nexusEventBus 'dispatch.*' events.
 *
 * Returns null if no headless runtime is available (graceful degradation).
 */
export async function scheduleFirstSortie(opts) {
    const { pushDispatch } = await import('../engines/dispatch/push-dispatch.js');
    const goal = 'Introduce yourself to the codebase. Briefly summarise the main entry point, primary purpose, and top-level directory structure.';
    try {
        return await pushDispatch({
            goal,
            specialistId: opts.operative.specialistId ?? 'engineering.rapid-prototyper',
            operativeId: opts.operative.id,
            budgetCapUsd: opts.budgetCapUsd ?? Math.min(0.50, opts.operative.budgetCapUsd ?? 0.50),
            workingDir: opts.repoRoot,
            repoName: opts.repoName,
            timeoutMs: 120_000, // 2 min warm-up cap
        });
    }
    catch {
        // No runtime available — degrade gracefully, dashboard shows offline state
        return null;
    }
}
