/**
 * HealthStatus — standardised health/quality datum.
 * Contract: see .claude/refactor/CONTRACTS.md §9.
 *
 * Shape (always frozen):
 *   { level: 0|1|2|3, flags: string[], message: string, source: string|null }
 *
 * level 0 = nominal, 3 = unusable. Returned objects are frozen plain
 * objects (not class instances) so they round-trip cleanly through
 * JSON / InfluxDB serialisation.
 */

'use strict';

const LABELS = ['nominal', 'minor', 'major', 'critical'];

function _freeze(level, flags, message, source) {
    return Object.freeze({
        level,
        flags: Object.freeze(flags.slice()),
        message,
        source: source == null ? null : String(source),
    });
}

function _coerceDegradedLevel(level) {
    const n = Math.trunc(Number(level));
    if (!Number.isFinite(n) || n < 1) return 1;
    if (n > 3) return 3;
    return n;
}

function _coerceFlags(flags) {
    if (!Array.isArray(flags)) return [];
    const out = [];
    for (const f of flags) {
        if (f == null) continue;
        out.push(String(f));
    }
    return out;
}

function ok(message, source) {
    return _freeze(
        0,
        [],
        typeof message === 'string' && message.length > 0 ? message : 'nominal',
        source != null ? source : null,
    );
}

function degraded(level, flags, message, source) {
    const lvl = _coerceDegradedLevel(level);
    const f = _coerceFlags(flags);
    const m = typeof message === 'string' && message.length > 0
        ? message
        : LABELS[lvl];
    return _freeze(lvl, f, m, source != null ? source : null);
}

// Merge multiple statuses into one node-level status. Worst level wins
// for level/message/source; flags are concatenated and de-duped.
function compose(statuses) {
    if (!Array.isArray(statuses) || statuses.length === 0) return ok();

    let worst = null;
    const seen = new Set();
    const flags = [];

    for (const s of statuses) {
        if (!s || typeof s !== 'object') continue;
        const lvl = Number.isFinite(s.level) ? s.level : 0;
        if (worst === null || lvl > worst.level) {
            worst = { level: lvl, message: s.message, source: s.source ?? null };
        }
        if (Array.isArray(s.flags)) {
            for (const f of s.flags) {
                if (f == null) continue;
                const k = String(f);
                if (!seen.has(k)) {
                    seen.add(k);
                    flags.push(k);
                }
            }
        }
    }

    if (worst === null) return ok();

    const message = typeof worst.message === 'string' && worst.message.length > 0
        ? worst.message
        : LABELS[Math.max(0, Math.min(3, worst.level))];
    return _freeze(worst.level, flags, message, worst.source);
}

function label(level) {
    const n = Math.trunc(Number(level));
    if (!Number.isFinite(n) || n < 0 || n > 3) return 'unknown';
    return LABELS[n];
}

module.exports = { ok, degraded, compose, label };
