/**
 * Journal du Yield Manager (/yield/logs) — logique PURE (aucun DOM, aucune traduction).
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * POURQUOI UN STATUT « REFUSÉ » SANS VALEUR D'ENUM
 * ═══════════════════════════════════════════════════════════════════════════
 * `yield_logs.status` est un ENUM MySQL (success / error / dry_run). L'élargir
 * demanderait un `MODIFY ENUM`, qui casse la construction du schéma SQLite des
 * tests (errors.md, 2026-06-09). Un refus reste donc `error` en base, et c'est
 * `details.refused` qui le distingue d'une panne. Cet écran doit faire la
 * différence : une panne se relance, un refus se corrige.
 *
 * Les libellés sont traduits par l'appelant, via une table STATIQUE de clés
 * (`logs_status_success`, …) : une clé construite à la volée échapperait au
 * scanner de YieldTranslationsTest, qui ne lit que les clés littérales.
 */

/** Les statuts qu'affiche le journal. `refused` n'existe qu'ici, jamais en base. */
export type LogStatusKind = 'success' | 'error' | 'dry_run' | 'refused';

/** Variantes du composant Badge. */
export type BadgeVariant = 'default' | 'destructive' | 'secondary' | 'outline';

/**
 * Variante de chaque statut. Le refus est en `outline` RENFORCÉ d'une couleur
 * d'avertissement (cf. REFUSED_BADGE_CLASSES) : un badge gris neutre se lirait
 * « rien à signaler » sur un logement dont rien n'est parti.
 */
export const LOG_STATUS_VARIANTS: Record<LogStatusKind, BadgeVariant> = {
    success: 'default',
    error: 'destructive',
    dry_run: 'secondary',
    refused: 'outline',
};

/** Couleur d'avertissement du badge « Refusé » (même ambre que la fenêtre d'envoi). */
export const REFUSED_BADGE_CLASSES = 'border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-200';

function asRecord(value: unknown): Record<string, unknown> | null {
    return value != null && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
}

/**
 * Une ligne est un REFUS quand le serveur l'a marquée `details.refused === true`.
 *
 * Ou quand elle porte l'ANCIEN marqueur : avant `refused`, les deux seuls
 * écrivains de `details.refus` (le motif, en texte) étaient le refus du
 * préflight du job et celui de `app:yield-run --dry-run` — tous deux des refus.
 * Sans cette lecture, ces lignes-là restaient « Erreur » avec « 91 nuits
 * traitées » (leur `dates_count` comptait les nuits calculées), alors que rien
 * n'était parti.
 */
export function isRefusedLog(details: unknown): boolean {
    const d = asRecord(details);
    if (d == null) return false;
    return d.refused === true || (typeof d.refus === 'string' && d.refus.trim() !== '');
}

/**
 * Ce qu'une ligne de RESTAURATION dit de l'interrupteur : `switched_off` si la
 * restauration a coupé un yield allumé, `already_off` s'il l'était déjà, `null`
 * sur une ligne écrite avant que la restauration n'éteigne le yield (aucun des
 * deux champs). `yield_disabled` vaut toujours true sur les lignes récentes :
 * c'est `yield_was_active` qui départage.
 */
export type RestoreSwitchNote = 'switched_off' | 'already_off';

export function restoreSwitchNote(details: unknown): RestoreSwitchNote | null {
    const d = asRecord(details);
    if (d == null) return null;
    if (d.yield_was_active === true) return 'switched_off';
    if (d.yield_was_active === false || d.yield_disabled === true) return 'already_off';
    return null;
}

/**
 * Le statut À AFFICHER. Un refus prime sur `error` ; un statut inconnu rend
 * `null` (l'appelant affiche alors la valeur brute plutôt qu'un libellé faux).
 */
export function logStatusKind(status: string, details: unknown): LogStatusKind | null {
    if (isRefusedLog(details)) return 'refused';
    if (status === 'success' || status === 'error' || status === 'dry_run') return status;
    return null;
}

/**
 * L'empreinte d'une réponse Rentals United illisible, en une ligne :
 * « unknown · Prices { Surprise, @attributes } » ou « unknown · Prices : string ».
 * De quoi reconnaître la forme d'un coup d'œil ; le détail complet reste en base.
 * `null` si l'empreinte est absente ou inexploitable.
 */
export function describeFingerprint(raw: unknown): string | null {
    const fp = asRecord(raw);
    if (!fp) return null;

    const parts: string[] = [];
    if (typeof fp.shape === 'string' && fp.shape !== '') parts.push(fp.shape);

    if (Array.isArray(fp.prices_keys)) {
        const cles = fp.prices_keys.filter((k): k is string => typeof k === 'string');
        parts.push(`Prices { ${cles.join(', ')} }`);
    } else if (typeof fp.prices_type === 'string' && fp.prices_type !== '') {
        parts.push(`Prices : ${fp.prices_type}`);
    }

    return parts.length > 0 ? parts.join(' · ') : null;
}
