@using ScadaLink.CentralUI.ScriptAnalysis
@using System.Text.Json

@*
    Renders an input row per declared parameter so the user can supply values
    for a script test run. Primitive types get typed inputs (text / number /
    checkbox); Object and List fall back to a JSON textarea with inline parse
    errors. The companion SchemaBuilder edits the schema; this edits values.
*@

@if (Shapes.Count == 0)
{
    <div class="text-muted small fst-italic">No parameters declared.</div>
}
else
{
    <div class="d-flex flex-column gap-2">
        @foreach (var shape in Shapes)
        {
            <div class="row g-2 align-items-center">
                <div class="col-sm-4">
                    <label class="form-label small mb-0" for="@FieldId(shape)">
                        <code>@shape.Name</code>
                        <span class="text-muted ms-1">@shape.Type@(shape.Required ? "" : "?")</span>
                    </label>
                </div>
                <div class="col-sm-8">
                    @RenderInput(shape)
                    @if (_parseErrors.TryGetValue(shape.Name, out var err))
                    {
                        <div class="text-danger small mt-1">@err</div>
                    }
                </div>
            </div>
        }
    </div>
}

@code {
    [Parameter] public string? ParameterDefinitions { get; set; }
    [Parameter] public Dictionary<string, object?> Values { get; set; } = new();
    [Parameter] public EventCallback<Dictionary<string, object?>> ValuesChanged { get; set; }

    private IReadOnlyList<ParameterShape> Shapes =>
        ScriptParameterNames.ParseShapes(ParameterDefinitions);

    private readonly Dictionary<string, string> _rawText = new();
    private readonly Dictionary<string, string> _parseErrors = new();

    private static string FieldId(ParameterShape shape) => $"param-{shape.Name}";

    private RenderFragment RenderInput(ParameterShape shape) => __builder =>
    {
        switch (shape.Type)
        {
            case "Boolean":
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="@FieldId(shape)"
                           checked="@AsBool(shape.Name)"
                           @onchange="e => SetBool(shape.Name, (bool)(e.Value ?? false))" />
                </div>
                break;

            case "Integer":
                <input class="form-control form-control-sm" type="number" step="1" id="@FieldId(shape)"
                       value="@AsRaw(shape.Name)"
                       @oninput="e => SetNumeric(shape.Name, (string?)e.Value, integerOnly: true)" />
                break;

            case "Float":
                <input class="form-control form-control-sm" type="number" step="any" id="@FieldId(shape)"
                       value="@AsRaw(shape.Name)"
                       @oninput="e => SetNumeric(shape.Name, (string?)e.Value, integerOnly: false)" />
                break;

            case "String":
                <input class="form-control form-control-sm" type="text" id="@FieldId(shape)"
                       value="@AsRaw(shape.Name)"
                       @oninput="e => SetString(shape.Name, (string?)e.Value)" />
                break;

            default: // Object, List, List<...>, unknown
                <textarea class="form-control form-control-sm font-monospace" rows="3" id="@FieldId(shape)"
                          placeholder='@($"JSON {shape.Type.ToLowerInvariant()}")'
                          @oninput="e => SetJson(shape.Name, (string?)e.Value)">@AsRaw(shape.Name)</textarea>
                break;
        }
    };

    private string AsRaw(string name) =>
        _rawText.TryGetValue(name, out var raw) ? raw : "";

    private bool AsBool(string name) =>
        Values.TryGetValue(name, out var v) && v is bool b && b;

    private async Task SetString(string name, string? raw)
    {
        _rawText[name] = raw ?? "";
        _parseErrors.Remove(name);
        Values[name] = raw ?? "";
        await ValuesChanged.InvokeAsync(Values);
    }

    private async Task SetBool(string name, bool value)
    {
        _parseErrors.Remove(name);
        Values[name] = value;
        await ValuesChanged.InvokeAsync(Values);
    }

    private async Task SetNumeric(string name, string? raw, bool integerOnly)
    {
        _rawText[name] = raw ?? "";
        if (string.IsNullOrWhiteSpace(raw))
        {
            _parseErrors.Remove(name);
            Values.Remove(name);
            await ValuesChanged.InvokeAsync(Values);
            return;
        }
        if (integerOnly && long.TryParse(raw, out var i))
        {
            _parseErrors.Remove(name);
            Values[name] = i;
        }
        else if (!integerOnly && double.TryParse(raw,
            System.Globalization.NumberStyles.Float,
            System.Globalization.CultureInfo.InvariantCulture, out var d))
        {
            _parseErrors.Remove(name);
            Values[name] = d;
        }
        else
        {
            _parseErrors[name] = integerOnly ? "Not a valid integer." : "Not a valid number.";
            Values.Remove(name);
        }
        await ValuesChanged.InvokeAsync(Values);
    }

    private async Task SetJson(string name, string? raw)
    {
        _rawText[name] = raw ?? "";
        if (string.IsNullOrWhiteSpace(raw))
        {
            _parseErrors.Remove(name);
            Values.Remove(name);
            await ValuesChanged.InvokeAsync(Values);
            return;
        }
        try
        {
            using var doc = JsonDocument.Parse(raw);
            Values[name] = JsonElementToObject(doc.RootElement.Clone());
            _parseErrors.Remove(name);
        }
        catch (JsonException ex)
        {
            _parseErrors[name] = $"JSON parse error: {ex.Message}";
            Values.Remove(name);
        }
        await ValuesChanged.InvokeAsync(Values);
    }

    private static object? JsonElementToObject(JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.String => element.GetString(),
            JsonValueKind.Number => element.TryGetInt64(out var i) ? (object)i : element.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Null => null,
            JsonValueKind.Array => element.EnumerateArray().Select(JsonElementToObject).ToList(),
            JsonValueKind.Object => element.EnumerateObject()
                .ToDictionary(p => p.Name, p => JsonElementToObject(p.Value)),
            _ => null
        };
    }
}
