// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * @file lane-name-suggest.ts
 * WU-2592: Nearest-match suggestions for "Lane not found" errors in
 * lane:edit and lane:remove.
 *
 * Onboarding agents hit dead-end errors when typing lane names that drift
 * from the canonical "Parent: Sublane" format (e.g. "CLI-Commands" vs
 * "CLI/Commands"). The suggestLaneName helper returns the closest lane by
 * Levenshtein distance, gated by a max-distance threshold proportional to
 * input length so short inputs don't spuriously match long lane names.
 */
const SIMILARITY_THRESHOLD = 0.4;
function levenshtein(a, b) {
    if (a === b)
        return 0;
    if (a.length === 0)
        return b.length;
    if (b.length === 0)
        return a.length;
    let prev = new Array(b.length + 1);
    let curr = new Array(b.length + 1);
    for (let j = 0; j <= b.length; j++)
        prev[j] = j;
    for (let i = 1; i <= a.length; i++) {
        curr[0] = i;
        for (let j = 1; j <= b.length; j++) {
            const cost = a[i - 1] === b[j - 1] ? 0 : 1;
            curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
        }
        [prev, curr] = [curr, prev];
    }
    return prev[b.length];
}
/**
 * Return the closest lane name to `input` from `candidates`, or null if none
 * is within the similarity threshold. Exact matches are filtered out — the
 * caller is expected to handle those before asking for a suggestion.
 */
export function suggestLaneName(input, candidates) {
    if (candidates.length === 0)
        return null;
    const needle = input.toLowerCase();
    let best = null;
    for (const candidate of candidates) {
        if (candidate === input)
            continue;
        const distance = levenshtein(needle, candidate.toLowerCase());
        if (best === null || distance < best.distance) {
            best = { name: candidate, distance };
        }
    }
    if (best === null)
        return null;
    const maxAllowed = Math.max(2, Math.floor(input.length * SIMILARITY_THRESHOLD));
    return best.distance <= maxAllowed ? best.name : null;
}
/**
 * Format a consistent "Lane X not found" error body for lane:edit and
 * lane:remove. Always points to lane:list for discovery; adds a "Did you
 * mean" hint when suggestLaneName finds a close match.
 */
export function formatLaneNotFoundError(logPrefix, requestedName, candidates) {
    const suggestion = suggestLaneName(requestedName, candidates);
    const hint = suggestion ? ` Did you mean "${suggestion}"?` : '';
    return (`${logPrefix} Lane "${requestedName}" not found in definitions.${hint}\n` +
        `Run \`pnpm lane:list\` to see available lane names.`);
}
//# sourceMappingURL=lane-name-suggest.js.map