using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using MaBibli.Shared.Catalogue;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Entites;
namespace MaBibli.Client.Services;
///
/// Accès à l'API depuis le client Blazor.
///
///
/// Aucun cache : la phase 3 suppose le réseau disponible. Le cache hors-ligne est la phase 6.
///
public sealed class ServiceLivresApi(HttpClient http)
{
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
public async Task> ListerAsync(CritereLivres criteres, CancellationToken ct = default)
{
var parametres = new List();
if (!string.IsNullOrWhiteSpace(criteres.Recherche))
{
parametres.Add($"recherche={Uri.EscapeDataString(criteres.Recherche.Trim())}");
}
if (criteres.Format is { } format)
{
parametres.Add($"format={format}");
}
if (criteres.Statut is { } statut)
{
parametres.Add($"statut={statut}");
}
if (criteres.AuteurId is { } auteurId)
{
parametres.Add($"auteurId={auteurId}");
}
var url = "api/livres" + (parametres.Count > 0 ? "?" + string.Join('&', parametres) : string.Empty);
return await http.GetFromJsonAsync>(url, Json, ct) ?? [];
}
public async Task ObtenirAsync(int id, CancellationToken ct = default)
{
var reponse = await http.GetAsync($"api/livres/{id}", ct);
return reponse.StatusCode == HttpStatusCode.NotFound
? null
: await LireAsync(reponse, ct);
}
public async Task> CreerAsync(EnregistrementLivre saisie, CancellationToken ct = default)
{
var reponse = await http.PostAsJsonAsync("api/livres", saisie, Json, ct);
return await InterpreterAsync(reponse, ct);
}
public async Task> ModifierAsync(int id, EnregistrementLivre saisie, CancellationToken ct = default)
{
var reponse = await http.PutAsJsonAsync($"api/livres/{id}", saisie, Json, ct);
return await InterpreterAsync(reponse, ct);
}
///
/// Change le statut de lecture de l'utilisateur courant ; null le remet à
/// « non commencé ». Le serveur sait qui appelle : le client ne transmet jamais d'identité.
///
public async Task> ChangerStatutAsync(
int id, Statut? statut, CancellationToken ct = default)
{
var reponse = await http.PatchAsJsonAsync(
$"api/livres/{id}/statut", new ChangementStatut { Statut = statut }, Json, ct);
return await InterpreterAsync(reponse, ct);
}
public async Task> ListerAuteursAsync(CancellationToken ct = default) =>
await http.GetFromJsonAsync>("api/auteurs", Json, ct) ?? [];
public async Task ObtenirAuteurAsync(int id, CancellationToken ct = default)
{
var reponse = await http.GetAsync($"api/auteurs/{id}", ct);
return reponse.StatusCode == HttpStatusCode.NotFound
? null
: await LireAsync(reponse, ct);
}
/// Rapprochements d'auteurs ambigus, que le serveur refuse d'appliquer seul.
public async Task> ListerRapprochementsAsync(
CancellationToken ct = default) =>
await http.GetFromJsonAsync>("api/auteurs/rapprochements", Json, ct) ?? [];
public Task AccepterRapprochementAsync(int sourceId, int cibleId, CancellationToken ct = default) =>
DeciderAsync("accepter", sourceId, cibleId, ct);
public Task RefuserRapprochementAsync(int sourceId, int cibleId, CancellationToken ct = default) =>
DeciderAsync("refuser", sourceId, cibleId, ct);
private async Task DeciderAsync(string action, int sourceId, int cibleId, CancellationToken ct)
{
var reponse = await http.PostAsJsonAsync(
$"api/auteurs/rapprochements/{action}",
new DecisionRapprochement { SourceId = sourceId, CibleId = cibleId },
Json,
ct);
return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct);
}
public async Task SupprimerAsync(int id, CancellationToken ct = default)
{
var reponse = await http.DeleteAsync($"api/livres/{id}", ct);
return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct);
}
public async Task ChercherIsbnAsync(string isbn, CancellationToken ct = default)
{
var reponse = await http.GetAsync($"api/isbn/{Uri.EscapeDataString(isbn)}", ct);
return reponse.IsSuccessStatusCode
? await LireAsync(reponse, ct)
: null;
}
public async Task ObtenirUtilisateurAsync(CancellationToken ct = default)
{
try
{
return await http.GetFromJsonAsync("api/moi", Json, ct)
?? UtilisateurCourant.Anonyme;
}
catch (HttpRequestException)
{
return UtilisateurCourant.Anonyme;
}
}
private static async Task LireAsync(HttpResponseMessage reponse, CancellationToken ct)
{
reponse.EnsureSuccessStatusCode();
return await reponse.Content.ReadFromJsonAsync(Json, ct);
}
private static async Task> InterpreterAsync(HttpResponseMessage reponse, CancellationToken ct)
{
if (reponse.IsSuccessStatusCode)
{
var valeur = await reponse.Content.ReadFromJsonAsync(Json, ct);
return new ResultatApi(valeur, null);
}
return new ResultatApi(default, await MessageErreurAsync(reponse, ct));
}
/// Récupère le message d'erreur de l'API pour l'afficher tel quel à l'utilisateur.
private static async Task MessageErreurAsync(HttpResponseMessage reponse, CancellationToken ct)
{
try
{
var corps = await reponse.Content.ReadFromJsonAsync(Json, ct);
if (!string.IsNullOrWhiteSpace(corps?.Erreur))
{
return corps.Erreur;
}
}
catch (Exception)
{
// Corps illisible ou vide : on retombe sur le code HTTP.
}
return reponse.StatusCode switch
{
HttpStatusCode.NotFound => "Ce livre n'existe plus.",
_ => $"L'enregistrement a échoué ({(int)reponse.StatusCode}).",
};
}
private sealed record ErreurApi(string? Erreur);
}
/// Résultat d'un appel d'écriture : la valeur, ou un message d'erreur affichable.
public readonly record struct ResultatApi(T? Valeur, string? Erreur)
{
public bool EstOk => Erreur is null;
}