namespace PSLauncher.Core.SteamVr;

/// <summary>
/// Fusionne récursivement le <c>steamvr.vrsettings</c> bundled dans le ZIP
/// PROSERVE (sous-dossier <c>_steamvr/</c>) dans le fichier SteamVR de l'utilisateur.
///
/// Sémantique de merge (deep, récursive) :
/// <list type="bullet">
///   <item>Clé absente côté cible → ajoutée (clone profond du source)</item>
///   <item>Clé présente des deux côtés, valeurs JSON objet → <b>recursivement fusionnées</b>
///     (les sous-clés cible non touchées par le source sont préservées —
///     critique pour des blocs comme <c>"trackers"</c> qui contiennent
///     potentiellement déjà d'autres devices)</item>
///   <item>Clé présente, valeurs primitives ou de types différents (string,
///     bool, number, array) → la valeur source écrase celle de cible</item>
///   <item>Clés racine côté cible qui ne sont PAS dans le source → 100 %
///     préservées (on ne touche QUE ce que le source apporte)</item>
/// </list>
///
/// Tue préalablement SteamVR + Vive Business Streaming pour éviter qu'ils ne
/// rétablissent leurs valeurs en mémoire au prochain flush vers disque
/// (Vive Business Streaming en premier car il relance SteamVR sinon).
/// </summary>
public interface ISteamVrSettingsDeployer
{
    /// <summary>
    /// Dry-run : détermine si un merge réel modifierait la cible. Permet à
    /// MainViewModel de skipper la phase entière (popup + kill SteamVR/VBS +
    /// merge) quand la cible est déjà à jour. <b>Aucun processus tué, aucun
    /// fichier touché.</b> Bénéfice opérationnel : sur un PC où la conf SteamVR
    /// est déjà conforme à la release courante, la ré-install ne dérange pas
    /// l'opérateur ni les sessions VR éventuellement en cours.
    /// </summary>
    Task<SteamVrMergeCheckResult> CheckMergeNeededAsync(
        string installFolder,
        CancellationToken ct);

    /// <param name="processesToKill">
    /// Liste explicite des noms de processus à terminer avant le merge.
    /// MainViewModel construit cette liste depuis les health checks de type
    /// "Process" (= les processus que l'opérateur surveille déjà = ceux qui
    /// risquent de garder un handle sur le fichier SteamVR ou de relancer
    /// SteamVR). VBS doit y être en tête. Si null ou vide, on retombe sur
    /// la liste par défaut de <see cref="SteamVrConfig.ProcessesToKill"/>.
    /// </param>
    Task<SteamVrMergeResult> MergeAsync(
        string installFolder,
        IReadOnlyList<string>? processesToKill,
        CancellationToken ct);
}

/// <summary>
/// Résultat du dry-run <see cref="ISteamVrSettingsDeployer.CheckMergeNeededAsync"/>.
/// </summary>
public enum SteamVrMergeCheckOutcome
{
    /// <summary>La cible contient déjà toutes les clés du source avec les
    /// mêmes valeurs (deep equality) — aucun changement ne serait apporté.
    /// MainViewModel doit skipper la phase sans déranger l'opérateur.</summary>
    UpToDate,
    /// <summary>Le merge apporterait au moins un changement (clé ajoutée ou
    /// valeur remplacée) — MainViewModel doit proposer le pop-up et procéder.</summary>
    Needed,
    /// <summary>Pas de <c>_steamvr/steamvr.vrsettings</c> dans le ZIP — rien à faire.</summary>
    NoSourceFile,
    /// <summary>Option <c>AutoMerge=false</c> dans la config — l'opérateur l'a désactivé.</summary>
    DisabledByConfig,
    /// <summary>Steam pas détecté sur la machine.</summary>
    SteamNotFound,
    /// <summary>JSON source ou cible invalide. MainViewModel peut log warn.</summary>
    InvalidJson,
}

/// <param name="Outcome">État global du check.</param>
/// <param name="TargetPath">Chemin résolu vers <c>steamvr.vrsettings</c>. Non-null
/// si Steam a été localisé, même si le fichier n'existe pas encore.</param>
/// <param name="RootKeysWouldChange">Nombre de clés racine qui seraient ajoutées
/// ou modifiées. <c>0</c> ssi <see cref="Outcome"/> est <see cref="SteamVrMergeCheckOutcome.UpToDate"/>.
/// Affiché dans le popup pour donner du contexte à l'opérateur.</param>
/// <param name="ErrorMessage">Détail technique en cas d'<see cref="SteamVrMergeCheckOutcome.InvalidJson"/>.</param>
public sealed record SteamVrMergeCheckResult(
    SteamVrMergeCheckOutcome Outcome,
    string? TargetPath,
    int RootKeysWouldChange,
    string? ErrorMessage = null);

public enum SteamVrMergeStatus
{
    /// <summary>Pas de <c>_steamvr/steamvr.vrsettings</c> dans le ZIP, no-op silencieux.</summary>
    SkippedNoSourceFile,
    /// <summary>Option <c>SteamVrConfig.AutoMerge=false</c>, l'opérateur l'a explicitement désactivé.</summary>
    SkippedDisabledByConfig,
    /// <summary>Steam introuvable (ni via registre ni au path par défaut).</summary>
    SteamNotFound,
    /// <summary>Le fichier source ou cible n'est pas un JSON valide.</summary>
    InvalidJson,
    /// <summary>Tout s'est bien passé.</summary>
    Merged,
    /// <summary>Erreur I/O ou autre exception non récupérable.</summary>
    Failed,
}

/// <param name="KeysReplaced">Nombre de clés racine qui existaient déjà côté cible (overwritées).</param>
/// <param name="KeysAdded">Nombre de clés racine ajoutées (n'existaient pas côté cible).</param>
/// <param name="TargetPath">Chemin du fichier modifié. Null si le merge a été skippé.</param>
/// <param name="ProcessesKilled">Nombre total de processus tués avant le merge.</param>
/// <param name="ErrorMessage">Détail technique en cas de Failed / InvalidJson.</param>
public sealed record SteamVrMergeResult(
    SteamVrMergeStatus Status,
    int KeysReplaced,
    int KeysAdded,
    string? TargetPath,
    int ProcessesKilled,
    string? ErrorMessage = null);
