using System.Text.Json;
using System.Text.Json.Serialization;

namespace NucleicBroker;

/// <summary>
/// NDJSON JSON-RPC 2.0 plumbing (docs/WINDOWS_PORT.md §3.3): one JSON object per line,
/// requests flow hostd → broker only, notifications broker → hostd only. Binary payloads
/// ride base64 in `b64` fields.
/// </summary>
public static class Rpc
{
    public static readonly JsonSerializerOptions Json = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    };

    /// <summary>A JSON `null`, used as the response id for unidentifiable requests — the
    /// spec requires `"id": null` to be PRESENT, which the ignore-null policy would drop if
    /// we passed a bare null.</summary>
    private static readonly JsonElement NullId = JsonDocument.Parse("null").RootElement;

    // JSON-RPC 2.0 error codes (+ -32000 for structured facade errors, kind in data).
    public const int ParseError = -32700;
    public const int InvalidRequest = -32600;
    public const int MethodNotFound = -32601;
    public const int InvalidParams = -32602;
    public const int InternalError = -32603;
    public const int FacadeError = -32000;

    public static string Response(JsonElement id, object result) =>
        JsonSerializer.Serialize(new { jsonrpc = "2.0", id, result }, Json);

    public static string Error(JsonElement? id, int code, string message, object? data = null) =>
        JsonSerializer.Serialize(
            new { jsonrpc = "2.0", id = id ?? NullId, error = new { code, message, data } },
            Json);

    public static string Notification(string method, object @params) =>
        JsonSerializer.Serialize(new { jsonrpc = "2.0", method, @params }, Json);

    /// <summary>Deserialize a request's params object; throws JsonException on a missing or
    /// mismatched shape (the dispatcher maps that to InvalidParams). Positional records bind
    /// through their constructor, case-insensitively.</summary>
    public static T Params<T>(JsonElement? @params)
    {
        if (@params is not { ValueKind: JsonValueKind.Object } p)
            throw new JsonException("missing params object");
        return JsonSerializer.Deserialize<T>(p.GetRawText(), Json)
            ?? throw new JsonException("params decoded to null");
    }
}
