using System.Text.Json;
using System.Text.Json.Nodes;

namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;

/// <summary>
/// Compact table projection for <c>template list</c> / <c>template get</c>.
///
/// <para>
/// <c>template get</c> returns a full <c>Template</c> entity — every attribute, alarm,
/// script, and composition inline — which the generic table renderer dumps as one giant
/// cell per template (~171 KB for a real catalogue, unusable in a terminal).
/// <c>template list</c> no longer returns entities at all: the management
/// <c>ListTemplates</c> handler projects in the DATABASE and returns
/// <c>TemplateSummary</c> rows whose children are already reduced to
/// <c>attributeCount</c>/<c>alarmCount</c>/… scalars.
/// </para>
///
/// <para>
/// This projector handles BOTH shapes: it prefers the pre-computed <c>*Count</c>
/// scalar and falls back to the length of the matching child array, so the same
/// column set renders for a summary row and for a full entity. Reading only the
/// arrays (as it originally did) made <c>template list</c> print a wall of zeros
/// once the server switched to summaries.
/// </para>
///
/// <para>
/// JSON output is left untouched (callers pass this only on the table path), and
/// the command's <c>--detail</c> flag skips the projection to render the raw
/// payload as-is.
/// </para>
/// </summary>
internal static class TemplateTableProjection
{
    /// <summary>
    /// Projects a templates JSON response (an array from <c>list</c> or a single object
    /// from <c>get</c>) to its compact summary form. Returns the input unchanged when it
    /// is not JSON or not the expected shape, so the generic renderer's own fallbacks
    /// still apply.
    /// </summary>
    /// <param name="json">The raw success JSON body from the management API.</param>
    /// <returns>Compact JSON (same array/object shape) suitable for table rendering.</returns>
    internal static string ProjectSummary(string json)
    {
        JsonDocument doc;
        try
        {
            doc = JsonDocument.Parse(json);
        }
        catch (JsonException)
        {
            // Not JSON (e.g. a proxy error page) — let the renderer print it verbatim.
            return json;
        }

        using (doc)
        {
            var root = doc.RootElement;
            if (root.ValueKind == JsonValueKind.Array)
            {
                var arr = new JsonArray();
                foreach (var item in root.EnumerateArray())
                    arr.Add(ProjectElement(item));
                return arr.ToJsonString();
            }
            if (root.ValueKind == JsonValueKind.Object)
            {
                return ProjectElement(root).ToJsonString();
            }
            return json;
        }
    }

    /// <summary>Projects a single template object to its compact summary node.</summary>
    private static JsonNode ProjectElement(JsonElement element)
    {
        if (element.ValueKind != JsonValueKind.Object)
            return JsonValue.Create(element.ToString())!;

        // JsonObject preserves insertion order, fixing the column order for the table.
        return new JsonObject
        {
            ["id"] = Int(element, "id"),
            ["name"] = Str(element, "name"),
            ["description"] = Str(element, "description"),
            ["parentTemplateId"] = Int(element, "parentTemplateId"),
            ["isDerived"] = Bool(element, "isDerived"),
            ["#attrs"] = Count(element, "attributeCount", "attributes"),
            ["#alarms"] = Count(element, "alarmCount", "alarms"),
            ["#scripts"] = Count(element, "scriptCount", "scripts"),
            ["#comps"] = Count(element, "compositionCount", "compositions"),
            ["#nativeAlarms"] = Count(element, "nativeAlarmSourceCount", "nativeAlarmSources"),
        };
    }

    private static bool TryGetPropertyCI(JsonElement obj, string name, out JsonElement value)
    {
        foreach (var prop in obj.EnumerateObject())
        {
            if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase))
            {
                value = prop.Value;
                return true;
            }
        }
        value = default;
        return false;
    }

    private static JsonNode? Str(JsonElement obj, string name)
        => TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.String
            ? JsonValue.Create(v.GetString())
            : null;

    private static JsonNode? Int(JsonElement obj, string name)
        => TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n)
            ? JsonValue.Create(n)
            : null;

    private static JsonNode? Bool(JsonElement obj, string name)
        => TryGetPropertyCI(obj, name, out var v) && (v.ValueKind == JsonValueKind.True || v.ValueKind == JsonValueKind.False)
            ? JsonValue.Create(v.GetBoolean())
            : null;

    /// <summary>
    /// Member count for a column, preferring the summary payload's pre-computed
    /// scalar (<paramref name="countName"/>) and falling back to the length of the
    /// full entity's child array (<paramref name="arrayName"/>). Zero when neither
    /// is present.
    /// </summary>
    /// <param name="obj">The template object being projected.</param>
    /// <param name="countName">Scalar count property on a <c>TemplateSummary</c> row.</param>
    /// <param name="arrayName">Child-collection property on a full <c>Template</c> entity.</param>
    /// <returns>The member count as a JSON number.</returns>
    private static JsonNode Count(JsonElement obj, string countName, string arrayName)
    {
        if (TryGetPropertyCI(obj, countName, out var scalar)
            && scalar.ValueKind == JsonValueKind.Number
            && scalar.TryGetInt32(out var n))
        {
            return JsonValue.Create(n);
        }

        return JsonValue.Create(
            TryGetPropertyCI(obj, arrayName, out var v) && v.ValueKind == JsonValueKind.Array
                ? v.GetArrayLength()
                : 0);
    }
}
