// transports/middleware/with-telemetry.ts — JSONL telemetry emitter (SPEC-740)
import { appendFile, mkdir } from 'node:fs/promises';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { shouldSkip } from './overrides.js';
/**
 * Project root resolution — tries to get from input args or falls back to cwd.
 */
function resolveProjectRoot(input, ctx) {
    if (ctx.projectRoot !== undefined) {
        return ctx.projectRoot;
    }
    const pp = input.projectPath;
    if (typeof pp === 'string' && pp.length > 0) {
        return pp;
    }
    return process.cwd();
}
/**
 * Check if telemetry is enabled for the given project root.
 * Reads conventions.json synchronously (fast path for hot tool paths).
 * Defaults to disabled (opt-in stance per SPEC-741).
 */
export function isTelemetryEnabled(projectRoot) {
    // Environment overrides take precedence
    if (process.env.PLANU_TELEMETRY === 'off') {
        return false;
    }
    if (process.env.PLANU_TELEMETRY === 'on') {
        return true;
    }
    // Read conventions.json for opt-in flag
    try {
        const conventionsPath = join(projectRoot, 'planu', 'conventions.json');
        if (!existsSync(conventionsPath)) {
            return false;
        }
        const raw = readFileSync(conventionsPath, 'utf-8');
        const parsed = JSON.parse(raw);
        const telemetry = parsed.telemetry;
        return telemetry?.enabled === true;
    }
    catch {
        return false;
    }
}
/**
 * Emit one JSONL line to planu/.telemetry/calls-<date>.jsonl
 * Fire-and-forget — never blocks the tool response.
 */
async function emitTelemetryEntry(projectRoot, entry) {
    const date = new Date().toISOString().slice(0, 10); // yyyy-mm-dd
    const dir = join(projectRoot, 'planu', '.telemetry');
    const file = join(dir, `calls-${date}.jsonl`);
    try {
        await mkdir(dir, { recursive: true });
        await appendFile(file, JSON.stringify(entry) + '\n', 'utf-8');
    }
    catch {
        // Never block tool execution on telemetry write failure
    }
}
/**
 * withTelemetry middleware: records one JSONL entry per tool call.
 * Gates on telemetry.enabled in conventions.json (SPEC-741 opt-in).
 */
export const withTelemetry = (handler) => {
    return async (input, ctx) => {
        const startMs = Date.now();
        const projectRoot = resolveProjectRoot(input, ctx);
        // Check per-tool override
        if (shouldSkip(ctx.toolName, 'skipTelemetry')) {
            return handler(input, ctx);
        }
        // Check global opt-in gate (SPEC-741)
        const enabled = isTelemetryEnabled(projectRoot);
        let result;
        let outcome = 'ok';
        let errorCode;
        try {
            result = await handler(input, ctx);
            if (result.isError === true) {
                outcome = 'error';
                // Check if it's a budget exceeded result
                if ('budgetExceeded' in result && result.budgetExceeded === true) {
                    outcome = 'budget_exceeded';
                    errorCode = 'budget.session';
                }
            }
        }
        catch (err) {
            outcome = 'error';
            errorCode = err instanceof Error ? err.constructor.name : 'UnknownError';
            throw err;
        }
        finally {
            const durationMs = Date.now() - startMs;
            if (enabled) {
                const entry = {
                    ts: new Date().toISOString(),
                    toolName: ctx.toolName,
                    durationMs,
                    outcome,
                    sessionId: ctx.sessionId,
                    modelId: ctx.modelId ?? 'unknown',
                    ...(errorCode !== undefined ? { errorCode } : {}),
                };
                // Fire-and-forget
                emitTelemetryEntry(projectRoot, entry).catch(() => {
                    // Ignore
                });
            }
        }
        return result;
    };
};
//# sourceMappingURL=with-telemetry.js.map