@* Edit modal for a single raw Tag in the v3 /raw tree. Unlike the equipment TagModal there is NO
   driver selector — the driver is fixed by the device the tag lives under, so the owning DriverType is
   passed in and drives the typed TagConfig editor dispatch (TagConfigEditorMap; raw-JSON fallback for
   unmapped drivers, e.g. Galaxy). Edit-only: the host opens it for an existing tree node (TagId), it
   loads via LoadTagForEditAsync, and saves via UpdateTagAsync. Visibility is @bind-Visible; a successful
   save raises OnSaved so the coordinator can refresh the tree. *@
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Commons.Types
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
@inject IRawTreeService Svc

@if (Visible)
{
    <div class="modal-backdrop fade show" style="display:block"></div>
    <div class="modal fade show" tabindex="-1" role="dialog" style="display:block">
        <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
                <EditForm Model="_form" OnValidSubmit="SaveAsync" FormName="rawTagModal">
                    <DataAnnotationsValidator />
                    <div class="modal-header">
                        <h5 class="modal-title">Edit tag</h5>
                        <button type="button" class="btn-close" aria-label="Close" @onclick="CloseAsync"></button>
                    </div>
                    <div class="modal-body">
                        @if (_loadError is not null)
                        {
                            <div class="alert alert-danger py-2">@_loadError</div>
                        }
                        else
                        {
                            <div class="row">
                                <div class="col-md-6 mb-3">
                                    <label class="form-label" for="raw-tag-name">Name</label>
                                    <InputText id="raw-tag-name" @bind-Value="_form.Name" class="form-control form-control-sm"
                                               placeholder="Temperature" />
                                    <ValidationMessage For="@(() => _form.Name)" />
                                    @if (_nameError is not null)
                                    {
                                        <div class="text-danger small">@_nameError</div>
                                    }
                                </div>
                                <div class="col-md-6 mb-3">
                                    <label class="form-label" for="raw-tag-dtype">Data type</label>
                                    <InputSelect id="raw-tag-dtype" @bind-Value="_form.DataType" class="form-select form-select-sm">
                                        @foreach (var dt in DataTypes)
                                        {
                                            <option value="@dt">@dt</option>
                                        }
                                    </InputSelect>
                                </div>
                            </div>
                            <div class="row">
                                <div class="col-md-6 mb-3">
                                    <label class="form-label" for="raw-tag-access">Access level</label>
                                    <InputSelect id="raw-tag-access" @bind-Value="_form.AccessLevel" class="form-select form-select-sm">
                                        <option value="@TagAccessLevel.Read">Read</option>
                                        <option value="@TagAccessLevel.ReadWrite">ReadWrite</option>
                                    </InputSelect>
                                </div>
                                <div class="col-md-6 mb-3">
                                    <label class="form-label">WriteIdempotent</label>
                                    <div class="form-check form-switch">
                                        <InputCheckbox @bind-Value="_form.WriteIdempotent" class="form-check-input" />
                                        <label class="form-check-label">Safe to retry writes</label>
                                    </div>
                                </div>
                            </div>
                            <div class="mb-3">
                                <label class="form-label" for="raw-tag-pgroup">PollGroupId (optional)</label>
                                <InputText id="raw-tag-pgroup" @bind-Value="_form.PollGroupId" class="form-control form-control-sm mono" />
                            </div>
                            <div class="mb-3">
                                <label class="form-label">Tag config <span class="text-muted small">(@DriverType)</span></label>
                                @{
                                    var editorType = TagConfigEditorMap.Resolve(DriverType);
                                }
                                @if (editorType is not null)
                                {
                                    <DynamicComponent Type="editorType" Parameters="BuildEditorParameters()" />
                                }
                                else
                                {
                                    <InputTextArea id="raw-tag-config" @bind-Value="_form.TagConfig" rows="6"
                                                   class="form-control form-control-sm mono"
                                                   placeholder='{ "FullName": "tag_name.AttributeName" }' />
                                    <div class="form-text">Schemaless per driver type. Validated server-side at deploy.</div>
                                }
                            </div>
                        }

                        @if (!string.IsNullOrWhiteSpace(_error))
                        {
                            <div class="text-danger small mt-2">@_error</div>
                        }
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" @onclick="CloseAsync" disabled="@_busy">Cancel</button>
                        <button type="submit" class="btn btn-primary" disabled="@(_busy || _loadError is not null)">
                            @if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
                            Save changes
                        </button>
                    </div>
                </EditForm>
            </div>
        </div>
    </div>
}

@code {
    private static readonly string[] DataTypes =
        ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
         "Int64", "UInt64", "Float", "Double", "String", "DateTime", "Guid", "ByteString"];

    /// <summary>Whether the modal is shown. Two-way (@bind-Visible) so the modal can self-close.</summary>
    [Parameter] public bool Visible { get; set; }

    /// <summary>Raised when <see cref="Visible"/> changes (self-close). Completes the @bind-Visible contract.</summary>
    [Parameter] public EventCallback<bool> VisibleChanged { get; set; }

    /// <summary>The raw tag to edit. The modal loads it via <c>LoadTagForEditAsync</c> on open.</summary>
    [Parameter] public string? TagId { get; set; }

    /// <summary>The owning device's driver type, used for typed TagConfig-editor dispatch.</summary>
    [Parameter] public string? DriverType { get; set; }

    /// <summary>Raised after a successful save so the host can refresh the tree.</summary>
    [Parameter] public EventCallback OnSaved { get; set; }

    private FormModel _form = new();
    private byte[] _rowVersion = Array.Empty<byte>();
    private bool _busy;
    private string? _error;
    private string? _nameError;
    private string? _loadError;

    // Tracks which (open, tag) this modal last loaded for, so unrelated Blazor Server re-renders don't
    // reload + clobber in-progress edits. Null while closed.
    private string? _loadedKey;

    protected override async Task OnParametersSetAsync()
    {
        if (!Visible)
        {
            _loadedKey = null;   // closed → next open reloads fresh
            return;
        }

        var key = TagId;
        if (key == _loadedKey) { return; }   // same open, re-render → preserve in-progress edits
        _loadedKey = key;

        _error = null;
        _nameError = null;
        _loadError = null;

        if (string.IsNullOrEmpty(TagId))
        {
            _loadError = "No tag selected.";
            return;
        }

        var dto = await Svc.LoadTagForEditAsync(TagId);
        // RawTagEditDto is a record struct → a missing tag comes back as default (all-zero), not null.
        if (dto is not { } d || string.IsNullOrEmpty(d.TagId))
        {
            _loadError = "This tag no longer exists.";
            return;
        }

        _form = new FormModel
        {
            Name = d.Name,
            DataType = string.IsNullOrEmpty(d.DataType) ? "Float" : d.DataType,
            AccessLevel = d.AccessLevel,
            WriteIdempotent = d.WriteIdempotent,
            PollGroupId = d.PollGroupId,
            TagConfig = string.IsNullOrWhiteSpace(d.TagConfig) ? "{}" : d.TagConfig,
        };
        _rowVersion = d.RowVersion;
    }

    private IDictionary<string, object> BuildEditorParameters() => new Dictionary<string, object>
    {
        ["ConfigJson"] = _form.TagConfig,
        ["ConfigJsonChanged"] = EventCallback.Factory.Create<string>(this, v => _form.TagConfig = v),
        ["DriverType"] = DriverType ?? "",
        ["GetDriverConfigJson"] = (Func<string>)(() => "{}"),
    };

    private async Task SaveAsync()
    {
        _busy = true;
        _error = null;
        _nameError = null;
        try
        {
            // Client-side name validation (RawPath segment) so a bad name is caught before the round-trip.
            var nameError = RawPaths.ValidateSegment(_form.Name);
            if (nameError is not null)
            {
                _nameError = nameError;
                return;
            }

            // Client-side per-driver config validation (the typed editor's Validate()).
            var configError = TagConfigValidator.Validate(DriverType, _form.TagConfig);
            if (configError is not null)
            {
                _error = configError;
                return;
            }

            var input = new RawTagInput(
                _form.Name,
                _form.DataType,
                _form.AccessLevel,
                _form.WriteIdempotent,
                string.IsNullOrWhiteSpace(_form.PollGroupId) ? null : _form.PollGroupId,
                string.IsNullOrWhiteSpace(_form.TagConfig) ? "{}" : _form.TagConfig);

            var result = await Svc.UpdateTagAsync(TagId!, input, _rowVersion);
            if (result.Ok)
            {
                await OnSaved.InvokeAsync();
                await CloseAsync();
            }
            else
            {
                _error = result.Error;
            }
        }
        finally
        {
            _busy = false;
        }
    }

    private async Task CloseAsync()
    {
        Visible = false;
        _loadedKey = null;
        await VisibleChanged.InvokeAsync(false);
    }

    private sealed class FormModel
    {
        [Required] public string Name { get; set; } = "";
        public string DataType { get; set; } = "Float";
        public TagAccessLevel AccessLevel { get; set; } = TagAccessLevel.Read;
        public bool WriteIdempotent { get; set; }
        public string? PollGroupId { get; set; }
        [Required] public string TagConfig { get; set; } = "{}";
    }
}
