import { headroom } from '../devices/health.js';
/** Headroom rank: lower is better (idle preferred). 'unknown' sorts last among reachable. */
const HEADROOM_ORDER = {
    idle: 0,
    light: 1,
    busy: 2,
    loaded: 3,
    unknown: 4,
};
/** Normalize an os string to a coarse platform token for match comparison. */
function platformOf(os) {
    if (!os)
        return undefined;
    const s = os.toLowerCase();
    if (s.includes('darwin') || s.includes('mac'))
        return 'darwin';
    if (s.includes('win'))
        return 'windows';
    if (s.includes('linux'))
        return 'linux';
    return s;
}
/**
 * Enumerate the fleet + ephemeral targets a session could move to, excluding the
 * interactive machine and the source. A fleet host is eligible only when it is
 * dispatchable and not obviously offline; ephemeral (warm) boxes are always
 * eligible (already provisioned and reachable). `statsByName` supplies live
 * headroom — a missing entry yields 'unknown' (still eligible, just ranked lower).
 */
export function enumerateTargets(hosts, warmBoxes, statsByName, ctx) {
    const excluded = new Set([ctx.selfHostname, ctx.sourceHostname].filter((n) => !!n).map((n) => n.toLowerCase()));
    const fleet = [];
    for (const host of hosts) {
        if (excluded.has(host.name.toLowerCase()))
            continue;
        // dispatchable is absent-means-yes; only an explicit false is disqualifying.
        if (host.dispatchable === false)
            continue;
        if (host.status === 'offline')
            continue;
        fleet.push({
            name: host.name,
            kind: 'fleet',
            os: host.os,
            headroom: headroom(statsByName.get(host.name)),
            host,
        });
    }
    const ephemeral = warmBoxes.map((box) => ({
        name: box.slug,
        kind: 'ephemeral',
        // crabbox boxes are Linux; the address is resolved at use time.
        os: 'linux',
        headroom: headroom(statsByName.get(box.slug)),
        box,
    }));
    return [...fleet, ...ephemeral];
}
/**
 * Rank targets for `--auto`. Order:
 *   1. Platform match with the source (a faithful --resume wants the same OS family).
 *   2. Fleet before ephemeral (prefer a warm worker over spinning a box).
 *   3. Headroom (idle > light > busy > loaded > unknown).
 *   4. Name, for a stable tie-break.
 * Returns a new sorted array; does not mutate the input.
 */
export function rankTargets(targets, ctx) {
    const srcPlatform = platformOf(ctx.sourceOs);
    const score = (t) => {
        const platformMatch = srcPlatform && platformOf(t.os) === srcPlatform ? 0 : 1;
        const kindRank = t.kind === 'fleet' ? 0 : 1;
        return { platformMatch, kindRank, headroomRank: HEADROOM_ORDER[t.headroom], name: t.name };
    };
    return [...targets].sort((a, b) => {
        const sa = score(a);
        const sb = score(b);
        if (sa.platformMatch !== sb.platformMatch)
            return sa.platformMatch - sb.platformMatch;
        if (sa.kindRank !== sb.kindRank)
            return sa.kindRank - sb.kindRank;
        if (sa.headroomRank !== sb.headroomRank)
            return sa.headroomRank - sb.headroomRank;
        return sa.name.localeCompare(sb.name);
    });
}
/**
 * Pick the single best `--auto` target, or null when nothing is eligible. A
 * fully-loaded ('loaded') fleet is still returned (better than failing the
 * migrate); the command layer can offer `--lease` when this is null.
 */
export function pickBestTarget(hosts, warmBoxes, statsByName, ctx) {
    const ranked = rankTargets(enumerateTargets(hosts, warmBoxes, statsByName, ctx), ctx);
    return ranked[0] ?? null;
}
