// engine/cascade-hooks/runner.ts — SPEC-714: Cascade runner
// SPEC-772: fast hooks (hook.fast === true) are awaited synchronously (2s budget)
import { HOOKS } from './registry.js';
import { writeSessionJson } from './core/write-session-json.js';
import { appendReleasesPending } from './core/append-releases.js';
const FAST_HOOK_BUDGET_MS = 2000;
export async function runCascade(ctx, opts) {
    // Core actions: awaited in parallel, each with its own 2-second budget
    const releasePendingPromise = ctx.newStatus === 'done' ? appendReleasesPending(ctx) : Promise.resolve({ ok: true });
    const [sessionResult, releasesResult] = await Promise.all([
        writeSessionJson(ctx),
        releasePendingPromise,
    ]);
    const dispatched = [];
    const skipped = [];
    const slowTasks = [];
    const fastTasks = [];
    for (const hook of HOOKS) {
        if (opts.disabledByConfig.has(hook.id) || opts.disabledByEnv.has(hook.id)) {
            skipped.push(hook.id);
            continue;
        }
        if (hook.triggers.length > 0 && !hook.triggers.includes(ctx.newStatus)) {
            continue;
        }
        if (hook.requiresProjectPath && !ctx.projectPath) {
            skipped.push(hook.id);
            continue;
        }
        dispatched.push(hook.id);
        if (hook.fast) {
            // SPEC-772: fast hooks — awaited synchronously with 2s budget
            const hookId = hook.id;
            const handler = hook.handler;
            fastTasks.push(async () => {
                const start = Date.now();
                const timeout = new Promise((resolve) => {
                    setTimeout(() => {
                        resolve('timeout');
                    }, FAST_HOOK_BUDGET_MS);
                });
                try {
                    const race = await Promise.race([handler(ctx).then(() => 'pass'), timeout]);
                    if (race === 'timeout') {
                        return { hookId, result: 'timeout', durationMs: Date.now() - start };
                    }
                    return { hookId, result: 'pass', durationMs: Date.now() - start };
                }
                catch (err) {
                    const error = err instanceof Error ? err.message : String(err);
                    return { hookId, result: 'fail', error, durationMs: Date.now() - start };
                }
            });
        }
        else {
            // Slow hooks: fire-and-forget
            slowTasks.push(hook.handler(ctx).catch((err) => {
                const msg = err instanceof Error ? err.message : String(err);
                console.error(`[planu] hook ${hook.id} failed: ${msg}`);
            }));
        }
    }
    // SPEC-772: await fast hooks synchronously
    const fastHookResults = await Promise.all(fastTasks.map((fn) => fn()));
    // Slow fire-and-forget: intentionally not awaited
    void Promise.allSettled(slowTasks);
    return {
        coreResults: {
            'write-session-json': sessionResult,
            'append-releases-pending': releasesResult,
        },
        hooksDispatched: dispatched,
        hooksSkipped: skipped,
        fastHookResults,
    };
}
//# sourceMappingURL=runner.js.map