/**
 * Uniform step execution with timing, logging, precondition checking,
 * and cache integration.
 *
 * The StepRunner is the single place where cache lookup/record happens.
 * Individual steps declare their cache inputs via cacheInputs(); the
 * runner handles the rest.
 */
/**
 * Execute a single pipeline step with uniform middleware.
 *
 * Order of operations:
 * 1. Precondition check → fail fast on errors
 * 2. Cache lookup (if ctx.cache + step.cacheInputs + !noCache)
 * 3. Execute step logic
 * 4. Cache record (on success)
 * 5. Log result
 */
export async function runStep(step, ctx, state = {}) {
    const start = Date.now();
    // 1. Precondition check
    const issues = step.check(ctx);
    const errors = issues.filter((i) => i.severity === "error");
    if (errors.length > 0) {
        const result = {
            durationMs: Date.now() - start,
            error: `Precondition failed: ${errors.map((e) => e.message).join("; ")}`,
            status: "failed",
        };
        ctx.logger.step(step.name, result);
        return result;
    }
    // 2. Cache lookup
    const canCache = ctx.cache && !ctx.config.noCache && typeof step.cacheInputs === "function";
    let cacheKey;
    if (canCache) {
        try {
            const inputs = step.cacheInputs(ctx);
            const context = step.cacheContext?.(ctx);
            ctx.logger.debug(`[${step.name}] Cache inputs: ${inputs.length} files`);
            if (context?.length) {
                ctx.logger.debug(`[${step.name}] Cache context: ${context.join(", ")}`);
            }
            const key = await ctx.cache.computeKey(inputs, context);
            cacheKey = key;
            ctx.logger.debug(`[${step.name}] Cache key: ${key}`);
            const cached = await ctx.cache.lookup(step.name, key);
            if (cached.hit) {
                ctx.logger.debug(`[${step.name}] Cache HIT — skipping execution`);
                const result = {
                    durationMs: Date.now() - start,
                    status: "success",
                    summary: `Skipped (cached) — ${cached.entry.summary}`,
                };
                ctx.logger.step(step.name, result);
                return result;
            }
            ctx.logger.debug(`[${step.name}] Cache MISS — executing`);
        }
        catch {
            // Cache lookup failure is non-fatal — proceed to execute
            ctx.logger.debug(`[${step.name}] Cache lookup failed — proceeding`);
        }
    }
    else {
        ctx.logger.debug(`[${step.name}] Cache skipped (${!ctx.cache ? "no cache adapter" : ctx.config.noCache ? "--no-cache" : "no cacheInputs"})`);
    }
    // 3. Execute
    try {
        const result = await step.execute(ctx, state);
        // Ensure timing is consistent (step may or may not track its own duration)
        const timed = result.status === "skipped"
            ? result
            : { ...result, durationMs: Date.now() - start };
        ctx.logger.step(step.name, timed);
        // 4. Cache record (on success)
        if (canCache && cacheKey && timed.status === "success") {
            try {
                await ctx.cache.record(step.name, cacheKey, {
                    durationMs: timed.durationMs,
                    outputPaths: [],
                    summary: timed.status === "success" ? (timed.summary ?? step.name) : "",
                });
            }
            catch {
                // Cache record failure is non-fatal
            }
        }
        return timed;
    }
    catch (err) {
        const result = {
            durationMs: Date.now() - start,
            error: err instanceof Error ? err.message : String(err),
            status: "failed",
        };
        ctx.logger.step(step.name, result);
        return result;
    }
}
