// engine/elicitation/user-elicitor.ts — SPEC-651: High-level elicitFromUser() wrapper
//
// Single public entry point for any Planu tool that needs a user decision.
// Routing priority:
//   1. Cached decision in conventions.json → return immediately (no IO to client)
//   2. Technical decision (inferred from codebase) → return inferredValue immediately
//   3. Business/policy decision → delegate to elicitOrFallback()
//      - Native elicitation when client declares capability (Cursor, etc.)
//      - InteractiveQuestion[] fallback when client does not support elicitation
//
// ELICIT_TIMEOUT_MS guards against hanging elicitInput calls.
import { elicitOrFallback } from './elicit-helper.js';
import { getCachedDecision, setCachedDecision } from './decision-cache.js';
/** Maximum milliseconds to wait for elicitInput before falling back. */
export const ELICIT_TIMEOUT_MS = 30_000;
/**
 * Elicit a user decision with full progressive enhancement.
 *
 * - Cache hit → `{ mode: 'cached', value }` (no user prompt)
 * - Technical decision → `{ mode: 'cached', value: inferredValue }` (no user prompt)
 * - Native elicitation → `{ mode: 'native', action, content? }`
 * - Fallback → `{ mode: 'fallback', interactiveQuestions }`
 *
 * On native accept, the decision is persisted to conventions.json for future
 * calls.  On cancel/decline or fallback, nothing is cached.
 *
 * Multi-field schemas: each accepted field is cached independently as
 * `${DECISION_CACHE_KEY_PREFIX}${decisionKey}.${fieldName}`.
 *
 * @throws Never — all errors are caught and degraded to fallback.
 */
export async function elicitFromUser(opts) {
    const { decisionKey, decisionType, message, schema, fallback, server, projectPath, inferredValue, } = opts;
    // ── 1. Cache check ──────────────────────────────────────────────────────────
    const cached = await getCachedDecision(projectPath, decisionKey);
    if (cached !== null) {
        return { mode: 'cached', value: cached };
    }
    // ── 2. Technical decision: skip elicitation entirely ──────────────────────
    if (decisionType === 'technical') {
        const value = inferredValue ?? '';
        return { mode: 'cached', value };
    }
    // ── 3. Business / policy decision: native elicitation or fallback ──────────
    const outcome = await elicitWithTimeout(server, message, schema, fallback);
    // ── 4. On native accept: persist each field to cache ───────────────────────
    if (outcome.mode === 'native' && outcome.action === 'accept' && outcome.content) {
        const content = outcome.content;
        const fields = Object.keys(content);
        if (fields.length === 1 && fields[0] === decisionKey) {
            // Single-field schema — cache under the bare decisionKey
            const value = String(content[decisionKey]);
            await setCachedDecision(projectPath, decisionKey, value);
        }
        else {
            // Multi-field schema — cache each field as decisionKey.fieldName
            for (const [fieldName, fieldValue] of Object.entries(content)) {
                const subKey = `${decisionKey}.${fieldName}`;
                await setCachedDecision(projectPath, subKey, String(fieldValue));
            }
        }
    }
    return outcome;
}
/**
 * Wraps `elicitOrFallback` with a timeout.
 * If `elicitInput` does not respond within ELICIT_TIMEOUT_MS, falls back gracefully.
 */
async function elicitWithTimeout(server, message, schema, fallback) {
    const fallbackQuestions = Array.isArray(fallback) ? fallback : [fallback];
    let timeoutId;
    const timeoutPromise = new Promise((resolve) => {
        timeoutId = setTimeout(() => {
            resolve({ mode: 'fallback', interactiveQuestions: fallbackQuestions });
        }, ELICIT_TIMEOUT_MS);
    });
    try {
        const result = await Promise.race([
            elicitOrFallback(server, message, schema, fallback),
            timeoutPromise,
        ]);
        return result;
    }
    finally {
        clearTimeout(timeoutId);
    }
}
//# sourceMappingURL=user-elicitor.js.map