// engine/hooks/core.ts — Hook registry: load, match, execute (SPEC-071)
// Hooks are LAZY — executed when a tool fires an event, not via daemon.
// Recursion guard: max depth = 3 to prevent infinite loops.
import { matchesFilter } from './triggers.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MAX_DEPTH = 3;
// ---------------------------------------------------------------------------
// Interpolation
// ---------------------------------------------------------------------------
/**
 * Interpolate {{key}} placeholders in a string using values from the payload
 * and a flat context map.
 */
function interpolate(template, context) {
    return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
        const value = context[key];
        if (value === undefined || value === null) {
            return `{{${key}}}`;
        }
        if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
            return String(value);
        }
        return JSON.stringify(value);
    });
}
/**
 * Flatten the event payload into a string-keyed context map for interpolation.
 * Uses Object.assign to produce a plain Record without type assertion.
 */
function payloadToContext(payload) {
    return Object.assign({}, payload);
}
// ---------------------------------------------------------------------------
// Action execution
// ---------------------------------------------------------------------------
/**
 * Execute a single hook action. Returns the action result.
 * This function does NOT throw — errors are captured in the result.
 */
function executeAction(action, payload) {
    const context = payloadToContext(payload);
    try {
        switch (action.type) {
            case 'log_message': {
                const message = interpolate(action.message, context);
                console.warn(`[Planu Hook] ${message}`);
                return { actionType: action.type, status: 'success', output: message };
            }
            case 'notify': {
                const message = interpolate(action.message, context);
                console.warn(`[Planu Hook Notify] ${message}`);
                return { actionType: action.type, status: 'success', output: message };
            }
            case 'set_variable': {
                const value = interpolate(action.value, context);
                return {
                    actionType: action.type,
                    status: 'success',
                    output: `Set ${action.key}=${value}`,
                };
            }
            case 'run_tool': {
                // Tools are invoked via fire-and-forget — we log intent but do not
                // actually call the MCP server recursively from within the engine.
                const toolArgs = JSON.stringify(action.args);
                const message = `Would invoke tool '${action.tool}' with args: ${toolArgs}`;
                console.warn(`[Planu Hook run_tool] ${message}`);
                return { actionType: action.type, status: 'success', output: message };
            }
        }
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        return {
            actionType: action.type,
            status: 'failed',
            errorMessage,
        };
    }
}
// ---------------------------------------------------------------------------
// Hook dispatch
// ---------------------------------------------------------------------------
/**
 * Execute a single hook for the given payload.
 * Returns a complete execution log entry.
 */
export function executeHook(hook, payload, depth) {
    const startMs = Date.now();
    const logId = `log-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    const actionResults = [];
    for (const action of hook.actions) {
        const result = executeAction(action, payload);
        actionResults.push(result);
    }
    const allSucceeded = actionResults.every((r) => r.status === 'success');
    return {
        id: logId,
        hookId: hook.id,
        hookName: hook.name,
        trigger: payload.trigger,
        payload,
        status: allSucceeded ? 'success' : 'failed',
        depth,
        actionResults,
        executedAt: new Date().toISOString(),
        durationMs: Date.now() - startMs,
    };
}
// ---------------------------------------------------------------------------
// Registry dispatch
// ---------------------------------------------------------------------------
/**
 * Dispatch an event to all matching enabled hooks.
 * Fire-and-forget — errors in hooks are captured in the log, never thrown.
 * Returns execution logs for all fired hooks.
 *
 * @param hooks - List of all hooks for the project
 * @param payload - Event payload to dispatch
 * @param depth - Current recursion depth (starts at 0)
 */
export function dispatchEvent(hooks, payload, depth = 0) {
    if (depth >= MAX_DEPTH) {
        console.warn(`[Planu Hooks] Recursion guard triggered at depth ${depth} for trigger '${payload.trigger}'. Stopping.`);
        return [];
    }
    const matchingHooks = hooks.filter((hook) => hook.enabled && hook.trigger === payload.trigger && matchesFilter(hook, payload));
    const logs = [];
    for (const hook of matchingHooks) {
        try {
            const log = executeHook(hook, payload, depth);
            logs.push(log);
        }
        catch (err) {
            const errorMessage = err instanceof Error ? err.message : String(err);
            console.error(`[Planu Hooks] Unexpected error in hook '${hook.id}': ${errorMessage}`);
        }
    }
    return logs;
}
/**
 * Simulate hook execution without side effects (dry run).
 * Returns which hooks would fire and what actions they would execute.
 */
export function dryRunDispatch(hooks, payload) {
    return hooks.map((hook) => {
        if (!hook.enabled) {
            return { hook, wouldFire: false, reason: 'Hook is disabled' };
        }
        if (hook.trigger !== payload.trigger) {
            return {
                hook,
                wouldFire: false,
                reason: `Trigger mismatch: hook='${hook.trigger}', event='${payload.trigger}'`,
            };
        }
        if (!matchesFilter(hook, payload)) {
            return { hook, wouldFire: false, reason: 'Filter conditions not met' };
        }
        return { hook, wouldFire: true };
    });
}
//# sourceMappingURL=core.js.map