using System.Text.Json;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;

/// <summary>
/// Translates JSON Schema documents stored in
/// <c>TemplateScript.ParameterDefinitions</c> / <c>ReturnDefinition</c> into
/// the flat <see cref="ParameterShape"/> / type-name vocabulary used by the
/// rest of the script-analysis pipeline (completions, inlay hints, signature
/// help, hover).
///
/// Lenient: malformed JSON yields an empty result, never an exception.
///
/// Also accepts the legacy pre-migration flat shape
/// (<c>[{name,type,required,itemType?}]</c> for parameters,
/// <c>{type,itemType?}</c> for return) so partially migrated rows don't crash
/// the editor.
/// </summary>
public static class JsonSchemaShapeParser
{
    /// <summary>Parses a JSON Schema or legacy flat-array parameters definition and returns the resulting parameter shapes.</summary>
    /// <param name="json">The JSON string to parse; <c>null</c> or whitespace returns an empty list.</param>
    /// <returns>A read-only list of <see cref="ParameterShape"/> instances parsed from the definition; empty on null, whitespace, or malformed input.</returns>
    public static IReadOnlyList<ParameterShape> ParseParameters(string? json)
    {
        if (string.IsNullOrWhiteSpace(json)) return Array.Empty<ParameterShape>();
        try
        {
            using var doc = JsonDocument.Parse(json);
            return doc.RootElement.ValueKind switch
            {
                JsonValueKind.Array => ParseLegacyParameterArray(doc.RootElement),
                JsonValueKind.Object => ParseJsonSchemaObject(doc.RootElement),
                _ => Array.Empty<ParameterShape>(),
            };
        }
        catch
        {
            return Array.Empty<ParameterShape>();
        }
    }

    /// <summary>Parses a JSON Schema or legacy return-type definition and returns the normalised type name, or <c>null</c> if absent or unrecognised.</summary>
    /// <param name="json">The JSON string to parse; <c>null</c> or whitespace returns <c>null</c>.</param>
    /// <returns>The normalised type name string (e.g. <c>"Boolean"</c>, <c>"List&lt;Integer&gt;"</c>), or <c>null</c> if the input is null, whitespace, malformed, or unrecognised.</returns>
    public static string? ParseReturnType(string? json)
    {
        if (string.IsNullOrWhiteSpace(json)) return null;
        try
        {
            using var doc = JsonDocument.Parse(json);
            if (doc.RootElement.ValueKind != JsonValueKind.Object) return null;
            return ParseReturnSchema(doc.RootElement);
        }
        catch
        {
            return null;
        }
    }

    // ---- JSON Schema branch -------------------------------------------------

    private static IReadOnlyList<ParameterShape> ParseJsonSchemaObject(JsonElement root)
    {
        if (!root.TryGetProperty("properties", out var props) || props.ValueKind != JsonValueKind.Object)
            return Array.Empty<ParameterShape>();

        var requiredSet = new HashSet<string>(StringComparer.Ordinal);
        if (root.TryGetProperty("required", out var req) && req.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in req.EnumerateArray())
            {
                if (item.ValueKind == JsonValueKind.String)
                {
                    var s = item.GetString();
                    if (!string.IsNullOrEmpty(s)) requiredSet.Add(s);
                }
            }
        }

        var result = new List<ParameterShape>();
        foreach (var prop in props.EnumerateObject())
        {
            var name = prop.Name;
            if (string.IsNullOrEmpty(name)) continue;
            var type = MapJsonSchemaType(prop.Value);
            result.Add(new ParameterShape(name, type, requiredSet.Contains(name)));
        }
        return result;
    }

    private static string? ParseReturnSchema(JsonElement schema)
    {
        if (!schema.TryGetProperty("type", out var typeEl)) return null;
        if (typeEl.ValueKind != JsonValueKind.String) return null;
        var type = typeEl.GetString();
        if (string.IsNullOrEmpty(type)) return null;

        // Legacy form: `{type:"List", itemType:"Integer"}` (post-migration this
        // should be `{type:"array", items:{type:"integer"}}`, handled below).
        if (type.Equals("List", StringComparison.OrdinalIgnoreCase))
        {
            if (schema.TryGetProperty("itemType", out var it) && it.ValueKind == JsonValueKind.String)
                return $"List<{NormalizeLegacyType(it.GetString())}>";
            return "List<Object>";
        }

        if (type.Equals("array", StringComparison.OrdinalIgnoreCase))
        {
            if (schema.TryGetProperty("items", out var items) && items.ValueKind == JsonValueKind.Object)
            {
                var inner = MapJsonSchemaType(items);
                return $"List<{inner}>";
            }
            return "List<Object>";
        }

        return MapJsonSchemaTypeName(type);
    }

    private static string MapJsonSchemaType(JsonElement schema)
    {
        if (schema.ValueKind != JsonValueKind.Object) return "Object";
        if (!schema.TryGetProperty("type", out var typeEl) || typeEl.ValueKind != JsonValueKind.String)
            return "Object";

        var type = typeEl.GetString() ?? "";
        if (type.Equals("array", StringComparison.OrdinalIgnoreCase))
        {
            if (schema.TryGetProperty("items", out var items) && items.ValueKind == JsonValueKind.Object)
                return $"List<{MapJsonSchemaType(items)}>";
            return "List<Object>";
        }
        return MapJsonSchemaTypeName(type);
    }

    private static string MapJsonSchemaTypeName(string type) => type.ToLowerInvariant() switch
    {
        "boolean" => "Boolean",
        "integer" => "Integer",
        "number" => "Float",
        "string" => "String",
        "object" => "Object",
        "array" => "List",
        // Legacy aliases (in case a row's been edited by hand pre-migration):
        "bool" => "Boolean",
        "int" or "int32" or "int64" => "Integer",
        "float" or "double" or "decimal" => "Float",
        _ => type,
    };

    // ---- Legacy flat-array branch ------------------------------------------

    private static IReadOnlyList<ParameterShape> ParseLegacyParameterArray(JsonElement root)
    {
        var result = new List<ParameterShape>();
        foreach (var el in root.EnumerateArray())
        {
            if (el.ValueKind != JsonValueKind.Object) continue;
            var name = el.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
            if (string.IsNullOrEmpty(name)) continue;
            var rawType = el.TryGetProperty("type", out var t) ? t.GetString() ?? "String" : "String";
            var required = !el.TryGetProperty("required", out var rq) || rq.ValueKind != JsonValueKind.False;
            result.Add(new ParameterShape(name, NormalizeLegacyType(rawType), required));
        }
        return result;
    }

    private static string NormalizeLegacyType(string? raw)
    {
        if (string.IsNullOrEmpty(raw)) return "String";
        return raw.ToLowerInvariant() switch
        {
            "boolean" or "bool" => "Boolean",
            "integer" or "int" or "int32" or "int64" => "Integer",
            "float" or "double" or "decimal" or "number" => "Float",
            "string" or "datetime" => "String",
            "object" => "Object",
            "list" or "array" => "List",
            _ => raw,
        };
    }
}
