// tools/start-hooks/engine.ts — Singleton state + lifecycle handlers for the hook engine.
import { HookEngine } from '../../engine/hooks/hook-engine.js';
import { EventBus } from '../../engine/hooks/event-bus.js';
import { FileWatcher } from '../../engine/hooks/file-watcher.js';
import { ConfigLoader } from '../../engine/hooks/config-loader.js';
import { Debouncer } from '../../engine/hooks/debouncer.js';
import { handleOnImplChange } from '../../engine/hooks/handlers/on-impl-change.js';
import { handleOnSpecChange } from '../../engine/hooks/handlers/on-spec-change.js';
import { handleOnPushCheck, getLastPushCheckResult, } from '../../engine/hooks/handlers/on-push-check.js';
import { handleOnSecurityCheck, isSecuritySensitiveFile, } from '../../engine/hooks/handlers/on-security-check.js';
import { subscribeStatusChangeHandler } from '../../engine/hooks/handlers/on-status-change.js';
import { listSpecs } from '../../storage/spec-store.js';
import { hashProjectPath } from '../../storage/base-store.js';
import { analyzeLivingSpec } from '../../engine/living-spec-analyzer.js';
import { compactResult, formatKeyValue } from '../output-formatter.js';
import { migrateAgentHooksIfNeeded } from '../../engine/hooks/agent-hook-migrator.js';
// ---------------------------------------------------------------------------
// Singleton state
// ---------------------------------------------------------------------------
let engineInstance = null;
let watcherInstance = null;
let debouncerInstance = null;
let configLoaderInstance = null;
export function getEngineInstance() {
    return engineInstance;
}
export function getWatcherInstance() {
    return watcherInstance;
}
export function getConfigLoaderInstance() {
    return configLoaderInstance;
}
export function ensureHooksStarted(projectPath) {
    migrateAgentHooksIfNeeded(projectPath);
    if (engineInstance?.status().running) {
        return;
    }
    void handleStartHooks({
        projectPath,
        hooks: [
            'on-save',
            'on-create',
            'on-delete',
            'on-commit',
            'on-impl-change',
            'on-spec-change',
            'on-push-check',
            'on-security-check',
        ],
    }).catch(() => {
        // Silently swallow — hooks are best-effort
    });
}
// ---------------------------------------------------------------------------
// Start hooks
// ---------------------------------------------------------------------------
export async function handleStartHooks(args) {
    if (engineInstance?.status().running) {
        const status = engineInstance.status();
        return compactResult(formatKeyValue({
            message: 'Hook engine already running',
            running: status.running,
            pendingEvents: status.pendingEvents,
        }, 'Hook Engine'));
    }
    const projectPath = args.projectPath;
    const enabledHooks = args.hooks ?? [
        'on-save',
        'on-create',
        'on-delete',
        'on-impl-change',
        'on-spec-change',
        'on-security-check',
    ];
    configLoaderInstance = new ConfigLoader(projectPath);
    const config = await configLoaderInstance.load();
    const eventBus = new EventBus();
    engineInstance = new HookEngine(eventBus);
    watcherInstance = new FileWatcher({
        rootDir: projectPath,
        include: config.hooks['on-save']?.include,
        exclude: config.hooks['on-save']?.exclude,
    });
    debouncerInstance = new Debouncer((event) => {
        const hookTypes = getApplicableHookTypes(event);
        for (const hookType of hookTypes) {
            if (enabledHooks.includes(hookType)) {
                void engineInstance?.dispatch(event, hookType);
            }
        }
    }, { debounceMs: config.debounceMs, rateLimitPerFileMs: config.rateLimitPerFileMs });
    watcherInstance.on('change', (event) => {
        eventBus.emitEvent('file:change', {
            path: event.path,
            type: event.type,
            timestamp: event.timestamp,
        });
        debouncerInstance?.push(event);
    });
    configLoaderInstance.startWatching();
    engineInstance.start();
    watcherInstance.start();
    registerAdvancedHandlers(engineInstance, enabledHooks, projectPath, config);
    const eventBusFromEngine = engineInstance.getEventBus();
    subscribeStatusChangeHandler(eventBusFromEngine, 
    /* v8 ignore next 3 */ (_projectId) => Promise.resolve(true));
    return compactResult(formatKeyValue({
        message: 'Hook engine started',
        projectPath,
        debounceMs: config.debounceMs,
        rateLimitPerFileMs: config.rateLimitPerFileMs,
    }, 'Hook Engine') + `\n\n**Hooks**: ${enabledHooks.join(', ')}`);
}
// ---------------------------------------------------------------------------
// Stop hooks
// ---------------------------------------------------------------------------
export async function handleStopHooks(_args) {
    await Promise.resolve();
    if (!engineInstance) {
        return compactResult('Hook engine not running');
    }
    engineInstance.stop();
    watcherInstance?.close();
    debouncerInstance?.dispose();
    configLoaderInstance?.close();
    const message = 'Hook engine stopped and resources cleaned up';
    engineInstance = null;
    watcherInstance = null;
    debouncerInstance = null;
    configLoaderInstance = null;
    return compactResult(`Done: ${message}`);
}
// ---------------------------------------------------------------------------
// Hook status
// ---------------------------------------------------------------------------
export async function handleHookStatus() {
    await Promise.resolve();
    if (!engineInstance) {
        return compactResult(formatKeyValue({ running: false, activeHooks: 0, pendingEvents: 0, eventHistory: 0 }, 'Hook Status'));
    }
    const status = engineInstance.status();
    const eventBus = engineInstance.getEventBus();
    return compactResult(formatKeyValue({
        running: status.running,
        activeHooks: status.activeHooks.length,
        pendingEvents: status.pendingEvents,
        eventHistory: eventBus.getFullHistory().length,
    }, 'Hook Status'));
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
function getApplicableHookTypes(event) {
    switch (event.type) {
        case 'modify':
            return ['on-save', 'on-impl-change', 'on-spec-change', 'on-security-check'];
        case 'create':
            return ['on-create', 'on-impl-change', 'on-spec-change', 'on-security-check'];
        case 'delete':
            return ['on-delete'];
        /* v8 ignore next 2 */
        default:
            return [];
    }
}
function specToMinimal(spec) {
    return { id: spec.id, title: spec.title, status: spec.status, progressPath: spec.progressPath };
}
function makeAnalyzeSpecFn() {
    return async (minimal, projectPath) => {
        const specLike = {
            id: minimal.id,
            title: minimal.title,
            slug: minimal.id
                .toLowerCase()
                .replace(/^spec-/, '')
                .replace(/\s+/g, '-'),
            status: minimal.status,
            progressPath: minimal.progressPath,
        };
        const report = await analyzeLivingSpec(specLike, projectPath);
        return {
            completionPct: report.completionPercent,
            progressUpdated: report.progressUpdated,
            criterionResults: report.criteria.map((c) => ({
                criterion: c.criterion,
                met: c.status === 'met',
                evidence: c.evidence,
            })),
        };
    };
}
function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
    if (enabledHooks.includes('on-impl-change')) {
        engine.register({
            name: 'on-impl-change',
            hookType: 'on-impl-change',
            priority: 200,
            execute: async (event) => {
                const projectId = hashProjectPath(projectPath);
                let specs = [];
                try {
                    const fullSpecs = await listSpecs(projectId);
                    specs = fullSpecs.map(specToMinimal);
                }
                catch {
                    // Spec load failure — proceed without analysis
                }
                const analysisResult = await handleOnImplChange(event, projectPath, specs, makeAnalyzeSpecFn(), config.autoMarkDone !== false);
                const firstSpecId = analysisResult
                    ? (analysisResult.specsAnalyzed[0]?.specId ?? null)
                    : null;
                return {
                    hook: 'on-save',
                    specId: firstSpecId,
                    driftDelta: analysisResult ? analysisResult.specsAnalyzed.length : 0,
                    hashChanged: event.type === 'modify' || event.type === 'create',
                    annotationsValid: analysisResult !== undefined,
                };
            },
        });
    }
    if (enabledHooks.includes('on-spec-change')) {
        engine.register({
            name: 'on-spec-change',
            hookType: 'on-spec-change',
            priority: 300,
            execute: async (event) => {
                await handleOnSpecChange(event, projectPath);
                return {
                    hook: 'on-save',
                    specId: null,
                    driftDelta: 0,
                    hashChanged: event.type !== 'delete',
                    annotationsValid: true,
                };
            },
        });
    }
    if (enabledHooks.includes('on-push-check')) {
        engine.register({
            name: 'on-push-check',
            hookType: 'on-push-check',
            priority: 150,
            execute: async (event) => {
                await handleOnPushCheck(event, projectPath);
                const pushResult = getLastPushCheckResult(projectPath);
                const findings = pushResult?.findings ?? [];
                const driftWarnings = findings
                    .filter((f) => f.severity === 'warning')
                    .map((f) => f.message);
                const HIGH_SEVERITY = ['critical', 'high'];
                return {
                    hook: 'on-commit',
                    commitHash: event.path,
                    reconciledSpecs: [],
                    integrityResult: {
                        valid: findings.every((f) => !HIGH_SEVERITY.includes(f.severity)),
                        errors: findings
                            .filter((f) => HIGH_SEVERITY.includes(f.severity))
                            .map((f) => f.message),
                    },
                    driftWarnings,
                };
            },
        });
    }
    if (enabledHooks.includes('on-security-check')) {
        engine.register({
            name: 'on-security-check',
            hookType: 'on-security-check',
            priority: 250,
            execute: async (event) => {
                await handleOnSecurityCheck(event, projectPath);
                const isSensitive = isSecuritySensitiveFile(event.path);
                return {
                    hook: 'on-save',
                    specId: null,
                    driftDelta: 0,
                    hashChanged: event.type !== 'delete',
                    annotationsValid: !isSensitive,
                };
            },
        });
    }
}
//# sourceMappingURL=engine.js.map