/**
 * Pure resolution + validation helpers for `olam dispatch`.
 *
 * Extracted so the operator-facing ergonomics (resolve-by-name,
 * "did you mean" suggestions, mode validation) can be unit-tested
 * without the container/IO surface of the command action.
 */
/** The execution modes `olam dispatch --mode` accepts. */
export const DISPATCH_MODES = ['auto', 'deep', 'fast'];
/**
 * Resolve a world by the user-supplied argument.
 *
 * Precedence (backward-compatible — exact ID still wins):
 *   1. Exact ID match.
 *   2. Exact name match (operators usually remember the name, not the id).
 *
 * Returns the matched world, or `undefined` when nothing matches.
 * When two worlds share a name, the first registered wins — callers
 * should fall back to the unambiguous ID, which is surfaced in the
 * not-found suggestion list.
 */
export function resolveWorld(worlds, arg) {
    const byId = worlds.find((w) => w.id === arg);
    if (byId)
        return byId;
    return worlds.find((w) => w.name === arg);
}
/**
 * Case-insensitive Levenshtein distance, capped — we only care about
 * "close enough to be a typo", not the exact edit count.
 */
function editDistance(a, b) {
    const s = a.toLowerCase();
    const t = b.toLowerCase();
    if (s === t)
        return 0;
    const m = s.length;
    const n = t.length;
    if (m === 0)
        return n;
    if (n === 0)
        return m;
    // Single-row DP. `row[j]` holds the distance for the previous source
    // prefix; `prevDiag` carries the diagonal value across the inner loop.
    const row = Array.from({ length: n + 1 }, (_, i) => i);
    for (let i = 1; i <= m; i += 1) {
        let prevDiag = row[0]; // row[0] before overwrite = D[i-1][0]
        row[0] = i;
        for (let j = 1; j <= n; j += 1) {
            const cost = s.charCodeAt(i - 1) === t.charCodeAt(j - 1) ? 0 : 1;
            const above = row[j]; // D[i-1][j]
            const left = row[j - 1]; // D[i][j-1]
            row[j] = Math.min(left + 1, above + 1, prevDiag + cost);
            prevDiag = above;
        }
    }
    return row[n];
}
/**
 * Rank worlds by how likely the operator meant them, given a typo'd or
 * partial argument. Used to build a "did you mean" hint on not-found.
 *
 * Matching signal (lower score = better):
 *   - substring match on id or name (case-insensitive) → strong signal
 *   - small edit distance to id or name → typo signal
 *
 * Returns at most `limit` candidates, best first. Worlds with no
 * plausible relationship to the argument are dropped, so an empty
 * result is meaningful ("nothing close").
 */
export function suggestWorlds(worlds, arg, limit = 3) {
    const needle = arg.toLowerCase();
    // Typos within ~40% of the token length are plausible; clamp to >=2 so
    // short ids/names still tolerate a one-char slip.
    const threshold = Math.max(2, Math.ceil(needle.length * 0.4));
    const scored = worlds
        .map((w) => {
        const id = w.id.toLowerCase();
        const name = w.name.toLowerCase();
        const substring = id.includes(needle) || name.includes(needle);
        const dist = Math.min(editDistance(arg, w.id), editDistance(arg, w.name));
        // Substring matches sort ahead of pure edit-distance matches.
        const score = substring ? -1000 + dist : dist;
        return { world: w, score, plausible: substring || dist <= threshold };
    })
        .filter((c) => c.plausible)
        .sort((a, b) => a.score - b.score);
    return scored.slice(0, limit).map((c) => c.world);
}
/**
 * Validate a `--mode` value. Returns the canonical mode on success, or
 * an `error` describing the valid set on failure. Unknown modes used to
 * silently fall through to non-deep behaviour; surfacing them prevents
 * a typo (`--mode dep`) from quietly running the wrong execution shape.
 */
export function validateMode(raw) {
    const normalized = raw.trim().toLowerCase();
    if (DISPATCH_MODES.includes(normalized)) {
        return { mode: normalized };
    }
    return {
        error: `Invalid --mode "${raw}". Valid modes: ${DISPATCH_MODES.join(', ')}.`,
    };
}
//# sourceMappingURL=dispatch-resolve.js.map