@namespace ScadaLink.CentralUI.Components.Shared

@* Bootstrap-only JSON Schema editor. Two modes:
   - "object"  parameters: edits a top-level object schema (named properties).
   - "value"   return type: edits a single value schema; object/array fall back
               to the same property editor as Mode=object.
   Recurses through methods (not nested components) so we stay in one file. *@

@if (_root.Type == "object" && Mode == "object")
{
    @PropertyList(_root, isRoot: true)
}
else
{
    @ValueRoot(_root)
}

@code {
    /// <summary><c>"object"</c> for parameters, <c>"value"</c> for return type.</summary>
    [Parameter] public string Mode { get; set; } = "object";

    /// <summary>JSON Schema text. Empty/null seeds the mode's default.</summary>
    [Parameter] public string? Value { get; set; }

    [Parameter] public EventCallback<string?> ValueChanged { get; set; }

    private SchemaNode _root = new();
    private string? _lastSeenJson;
    private bool _initialized;

    protected override void OnParametersSet()
    {
        // OnInitialized fires before this on first mount; OnParametersSet runs
        // on every parameter change. Guard against the initial null==null case
        // where the early-exit would skip applying the mode-appropriate default.
        if (_initialized && Value == _lastSeenJson) return;
        _initialized = true;
        _lastSeenJson = Value;
        _root = SchemaBuilderModel.Parse(
            Value,
            Mode == "object" ? SchemaBuilderModel.NewObject() : SchemaBuilderModel.NewValue());
    }

    private async Task Emit()
    {
        var json = SchemaBuilderModel.Serialize(_root);
        _lastSeenJson = json;
        await ValueChanged.InvokeAsync(json);
    }

    private async Task OnTypeChange(SchemaNode node)
    {
        if (node.Type == "array" && node.Items == null)
            node.Items = new SchemaNode { Type = "string" };
        await Emit();
    }

    private async Task AddProperty(SchemaNode parent)
    {
        parent.Properties.Add(new SchemaProperty { Schema = new SchemaNode { Type = "string" } });
        await Emit();
    }

    private async Task RemoveProperty(SchemaNode parent, SchemaProperty prop)
    {
        parent.Properties.Remove(prop);
        await Emit();
    }

    // ── Render helpers ─────────────────────────────────────────────────────────

    /// <summary>
    /// Renders the property list for an object schema node. <paramref name="isRoot"/>
    /// just tweaks the wording on the Add button ("parameter" at root vs "field"
    /// inside a nested object).
    /// </summary>
    private RenderFragment PropertyList(SchemaNode node, bool isRoot = false) => __builder =>
    {
        <div class="border rounded bg-white p-2">
            @if (node.Properties.Count == 0)
            {
                <div class="text-muted small fst-italic px-1 py-2">
                    @(isRoot ? "No parameters defined." : "No fields defined.")
                </div>
            }
            @foreach (var prop in node.Properties)
            {
                <div @key="prop.Id" class="border rounded p-2 mb-2 bg-light-subtle">
                    @PropertyRow(node, prop)
                    @NestedEditor(prop.Schema)
                </div>
            }
            <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => AddProperty(node)">
                + Add @(isRoot ? "parameter" : "field")
            </button>
        </div>
    };

    /// <summary>
    /// One property's compact horizontal row: name, type, (items type if array),
    /// required toggle, remove button. Nested object / array-of-object editors
    /// render below the row via <see cref="NestedEditor"/>.
    /// </summary>
    private RenderFragment PropertyRow(SchemaNode parent, SchemaProperty prop) => __builder =>
    {
        <div class="d-flex flex-wrap align-items-center gap-2">
            <input type="text" class="form-control form-control-sm"
                   style="max-width: 14rem;" placeholder="name"
                   @bind="prop.Name" @bind:event="oninput" @bind:after="Emit" />

            <select class="form-select form-select-sm" style="max-width: 9rem;"
                    @bind="prop.Schema.Type" @bind:after="() => OnTypeChange(prop.Schema)">
                @foreach (var t in SchemaBuilderModel.PrimitiveTypes)
                {
                    <option value="@t">@t</option>
                }
            </select>

            @if (prop.Schema.Type == "array")
            {
                <span class="small text-muted">items:</span>
                <select class="form-select form-select-sm" style="max-width: 9rem;"
                        @bind="prop.Schema.Items!.Type" @bind:after="() => OnTypeChange(prop.Schema.Items!)">
                    @foreach (var t in SchemaBuilderModel.PrimitiveTypes)
                    {
                        <option value="@t">@t</option>
                    }
                </select>
            }

            <div class="form-check form-check-inline mb-0">
                <input class="form-check-input" type="checkbox" id="req-@prop.Id"
                       @bind="prop.Required" @bind:after="Emit" />
                <label class="form-check-label small" for="req-@prop.Id">required</label>
            </div>

            <button type="button"
                    class="btn btn-link btn-sm text-danger p-0 ms-auto"
                    title="Remove" aria-label="Remove field"
                    @onclick="() => RemoveProperty(parent, prop)">
                <i class="bi bi-x-lg"></i>
            </button>
        </div>
    };

    /// <summary>
    /// Renders the indented sub-editor for object / array-of-object properties.
    /// No-op for scalar properties.
    /// </summary>
    private RenderFragment NestedEditor(SchemaNode schema) => __builder =>
    {
        if (schema.Type == "object")
        {
            <div class="ms-3 mt-2">
                @PropertyList(schema)
            </div>
        }
        else if (schema.Type == "array" && schema.Items?.Type == "object")
        {
            <div class="ms-3 mt-2">
                <div class="small text-muted mb-1">item properties:</div>
                @PropertyList(schema.Items)
            </div>
        }
    };

    /// <summary>
    /// Mode=value root: a single type picker. When the user picks <c>object</c>
    /// or <c>array</c> we expose the same nested editors used by Mode=object.
    /// </summary>
    private RenderFragment ValueRoot(SchemaNode node) => __builder =>
    {
        <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
            <label class="form-label mb-0">Return type:</label>
            <select class="form-select form-select-sm" style="max-width: 10rem;"
                    @bind="node.Type" @bind:after="() => OnTypeChange(node)">
                @foreach (var t in SchemaBuilderModel.PrimitiveTypes)
                {
                    <option value="@t">@t</option>
                }
            </select>

            @if (node.Type == "array")
            {
                <label class="form-label mb-0 ms-2">Item type:</label>
                <select class="form-select form-select-sm" style="max-width: 10rem;"
                        @bind="node.Items!.Type" @bind:after="() => OnTypeChange(node.Items!)">
                    @foreach (var t in SchemaBuilderModel.PrimitiveTypes)
                    {
                        <option value="@t">@t</option>
                    }
                </select>
            }
        </div>

        @if (node.Type == "object")
        {
            <div class="text-muted small mb-1">Properties of return value:</div>
            @PropertyList(node)
        }
        else if (node.Type == "array" && node.Items?.Type == "object")
        {
            <div class="text-muted small mb-1">Item properties:</div>
            @PropertyList(node.Items)
        }
    };
}
