"""
Pseudo affiché aux autres pairs, mémorisé localement.

**Pourquoi ce module existe (constaté en conditions réelles le 2026-08-01).** Le champ
« Pseudo » était recréé vide à chaque lancement, et la jointure retombait sur le littéral
`"peer"` quand l'utilisateur ne le remplissait pas. Relancer Meshamoto et cliquer
Connecter sans y penser suffisait donc à se faire renommer « peer » pour tout le monde —
et le serveur écrasait l'ancien nom, qui était perdu. La trace en était visible dans la
base de production : un pair nommé `peer` au milieu de pseudos choisis.

Ce n'est pas un défaut cosmétique. Le pseudo alimente le DNS interne
(`xeon.meshamoto`), le nom des favoris Steam et l'affichage des ACL ; le changer
déclenche une rediffusion de la liste de pairs, donc une réécriture du fichier hosts et
un rafraîchissement Steam **chez tous les autres**.

Le défaut « nom de la machine » (point 52) existait déjà, mais uniquement dans les
chemins d'accueil et de code d'invitation — donc au PREMIER lancement seulement. Le
raisonnement était bon et n'avait simplement jamais été appliqué aux suivants.

Réglage purement local : il n'y a pas d'identité par appareil côté serveur, le pseudo est
déclaré à chaque jointure.
"""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Optional

logger = logging.getLogger("meshamoto-client.display_name_settings")

# Assez long pour un nom de machine, assez court pour ne pas casser l'affichage du
# lobby ni le bloc du fichier hosts, qui reprend ce texte tel quel.
MAX_LENGTH = 64


def normaliser(nom: str) -> Optional[str]:
    """
    Nettoie un pseudo saisi, ou None s'il ne reste rien d'exploitable.

    Renvoyer None plutôt qu'une chaîne vide est délibéré : c'est ce qui permet à
    l'appelant de distinguer « l'utilisateur n'a rien choisi » de « l'utilisateur a
    choisi ceci », distinction dont dépend le `COALESCE` côté serveur — un champ vide
    doit conserver le nom connu, pas l'effacer.

    Les sauts de ligne et tabulations sont retirés plutôt que refusés : ils arrivent par
    copier-coller, et le pseudo est réécrit tel quel dans le fichier hosts, où une
    seconde ligne injecterait une entrée arbitraire.
    """
    nettoye = " ".join(nom.split())
    if not nettoye:
        return None
    return nettoye[:MAX_LENGTH]


def load_display_name(path: Path) -> Optional[str]:
    """
    Retourne le pseudo mémorisé, ou None si absent/illisible.

    Un réglage corrompu ne doit jamais empêcher le client de démarrer : on retombe
    silencieusement sur « aucun pseudo mémorisé », que l'appelant complétera par le nom
    de la machine.
    """
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return normaliser(str(data["display_name"]))
    except (OSError, json.JSONDecodeError, KeyError, ValueError, TypeError):
        logger.warning("Impossible de lire %s, pseudo mémorisé ignoré", path)
        return None


def save_display_name(path: Path, nom: str) -> None:
    """Mémorise le pseudo. Un nom vide efface le réglage plutôt que d'écrire du vide."""
    nettoye = normaliser(nom)
    path.parent.mkdir(parents=True, exist_ok=True)
    if nettoye is None:
        path.unlink(missing_ok=True)
        return
    path.write_text(json.dumps({"display_name": nettoye}), encoding="utf-8")
