/**
 * Pure confirmed-state evaluator (PR-B.2, May 2026).
 *
 * Lifted out of monitor-engine.ts so the down/up hysteresis logic is
 * testable without a Prisma round-trip. The engine still owns the
 * side effects (heartbeat persistence, EventBus emission); this
 * module answers one question: given the latest history and the new
 * check, what is the confirmed state and did it transition?
 *
 * Hysteresis policy:
 *   - up → down: requires `requiredDowns` consecutive down rows
 *     (monitor.retries; existing behaviour, preserved)
 *   - down → up: requires `requiredUps` consecutive up rows
 *     (monitor.requiredUps; default 2; NEW in PR-B.2)
 *
 * Before PR-B.2, a single up after a confirmed down immediately
 * cleared the state and emitted monitor.up. A flap inside the 5-min
 * dedup window produced alert→silence→alert noise.
 */
export type ConfirmedState = 'up' | 'down' | 'unknown';

export interface HeartbeatLike {
    status: string;
}

/**
 * `historyBefore` is the rolling window in DESC order (newest first),
 * EXCLUDING the just-saved heartbeat. `latest` is the heartbeat we
 * just wrote. Returns the prev confirmed state (immediately before
 * `latest`) and the next confirmed state (after this check resolves).
 *
 * Implementation: recursive walk. The base case is empty history
 * (prev = 'unknown'); each step computes its own next from the prev
 * one level deeper. History windows in real workloads are bounded
 * (engine reads max(requiredDowns, requiredUps) rows) so depth is
 * shallow.
 */
export function evaluateConfirmedState(
    historyBefore: HeartbeatLike[],
    latest: HeartbeatLike,
    requiredDowns: number,
    requiredUps: number,
): { prev: ConfirmedState; next: ConfirmedState } {
    const prev: ConfirmedState =
        historyBefore.length === 0
            ? 'unknown'
            : evaluateConfirmedState(
                historyBefore.slice(1),
                historyBefore[0],
                requiredDowns,
                requiredUps,
            ).next;

    // Treat 'unknown' as 'up' for transition purposes — cold-start
    // monitors are implicitly considered healthy. The engine never
    // emits alerts on transitions involving 'unknown'.
    const effectivePrev: 'up' | 'down' = prev === 'down' ? 'down' : 'up';

    let next: ConfirmedState;

    if (latest.status === 'down') {
        const downWindow = [latest, ...historyBefore].slice(0, requiredDowns);
        if (downWindow.length === requiredDowns && downWindow.every(h => h.status === 'down')) {
            next = 'down';
        } else if (effectivePrev === 'down') {
            next = 'down';
        } else {
            next = 'up'; // pending down — was up, still up until threshold
        }
    } else if (latest.status === 'up') {
        if (effectivePrev === 'down') {
            const upWindow = [latest, ...historyBefore].slice(0, requiredUps);
            if (upWindow.length === requiredUps && upWindow.every(h => h.status === 'up')) {
                next = 'up';
            } else {
                next = 'down'; // pending recovery — stay down until N ups
            }
        } else {
            next = 'up';
        }
    } else {
        // Maintenance / other statuses: don't transition.
        next = prev;
    }

    return { prev, next };
}
