// engine/elicitation/elicit-helper.ts — Unified elicit-or-fallback helper (SPEC-595)
//
// Single call site for any Planu tool that needs a user decision. Routes
// through MCP Elicitation when the connected client supports it; otherwise
// returns the legacy interactiveQuestions[] shape so existing callers and
// legacy clients keep working without change.
import { canElicit } from './capability.js';
/**
 * Build a single enum schema entry `{ field, property }` for insertion into
 * an ElicitationSchema. Keeps the schema assembly at the call site while
 * centralizing enum/enumNames validation and default-value handling.
 */
export function buildEnumSchema(field, enums, enumNames, description, defaultValue) {
    if (enums.length !== enumNames.length) {
        throw new Error(`buildEnumSchema: enums (${String(enums.length)}) and enumNames (${String(enumNames.length)}) length mismatch`);
    }
    const property = {
        type: 'string',
        description,
        enum: [...enums],
        enumNames: [...enumNames],
    };
    if (defaultValue !== undefined && enums.includes(defaultValue)) {
        property.default = defaultValue;
    }
    return { field, property };
}
/**
 * Build a confirm-style schema with `cancel` as the first (default) option.
 * Destructive operations (delete_spec etc.) use this so the default answer
 * on cancel or decline is always "do nothing".
 */
export function buildConfirmSchema(field = 'action', confirmLabel = 'Delete permanently', cancelLabel = 'Cancel (Recommended)', description = 'Confirm or cancel the destructive operation') {
    return buildEnumSchema(field, ['cancel', 'confirm'], [cancelLabel, confirmLabel], description, 'cancel');
}
/**
 * Unified elicit-or-fallback call site.
 *
 * When the connected client supports MCP Elicitation: calls
 * `server.elicitInput({ message, requestedSchema })` and returns
 * `{ mode: 'native', action, content? }`.
 *
 * When the client does NOT support elicitation (or server is unavailable):
 * returns `{ mode: 'fallback', interactiveQuestions }`.
 */
export async function elicitOrFallback(server, message, schema, fallback) {
    const fallbackQuestions = Array.isArray(fallback) ? fallback : [fallback];
    const elicitable = server;
    if (!canElicit(elicitable) || !elicitable?.elicitInput) {
        return { mode: 'fallback', interactiveQuestions: fallbackQuestions };
    }
    try {
        const raw = await elicitable.elicitInput({ message, requestedSchema: schema });
        return normalizeElicitResult(raw);
    }
    catch {
        return { mode: 'fallback', interactiveQuestions: fallbackQuestions };
    }
}
function normalizeElicitResult(raw) {
    if (!raw || typeof raw !== 'object') {
        return { mode: 'native', action: 'decline' };
    }
    const obj = raw;
    const actionField = obj.action;
    const action = isValidAction(actionField) ? actionField : 'decline';
    const content = obj.content;
    if (content && typeof content === 'object') {
        return {
            mode: 'native',
            action,
            content: content,
        };
    }
    return { mode: 'native', action };
}
function isValidAction(value) {
    return value === 'accept' || value === 'cancel' || value === 'decline';
}
/**
 * Convert an array of legacy InteractiveQuestions into a single
 * ElicitationSchema. Used when a tool already has interactiveQuestions in
 * structuredContent and wants to offer native elicitation too — mapping lets
 * the same fallback shape drive both paths.
 *
 * Each question becomes a property whose field name is derived from the
 * header (slugified) and whose enum is the option labels.
 */
export function questionsToFormSchema(questions) {
    const properties = {};
    const required = [];
    for (const q of questions) {
        const field = slugifyField(q.header);
        const labels = q.options.map((o) => o.label);
        const descriptions = q.options.map((o) => `${o.label}: ${o.description}`);
        properties[field] = {
            type: 'string',
            description: q.question,
            enum: labels,
            enumNames: descriptions,
        };
        required.push(field);
    }
    return { type: 'object', properties, required };
}
function slugifyField(header) {
    return header
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '_')
        .replace(/^_+|_+$/g, '')
        .slice(0, 48);
}
//# sourceMappingURL=elicit-helper.js.map