@* Embeddable AB Legacy driver (channel/protocol) config form body. The Devices collection is authored
   as separate v3 entities (the merge rebuilds the Devices array at deploy/probe time), so this form does
   NOT edit it — the driver's existing devices are preserved verbatim. Hosted by the routed
   AbLegacyDriverPage. Exposes GetConfigJson() for the parent to pull at save + Test-connect time, and
   fires DriverConfigJsonChanged for @bind support. *@
@using System.Text.Json
@using System.Text.Json.Serialization
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy
@using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies

@* Operation settings *@
<section class="panel rise mt-3" style="animation-delay:.06s">
    <div class="panel-head">Operation settings</div>
    <div style="padding:1rem">
        <div class="row g-3">
            <div class="col-md-3">
                <label class="form-label">Timeout (seconds)</label>
                <InputNumber @bind-Value="_form.TimeoutSeconds" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
                <div class="form-text">Default read/write timeout. Default 2 s.</div>
            </div>
        </div>
    </div>
</section>

@* Connectivity probe *@
<section class="panel rise mt-3" style="animation-delay:.08s">
    <div class="panel-head">Connectivity probe</div>
    <div style="padding:1rem">
        <div class="row g-3">
            <div class="col-md-3">
                <div class="form-check form-switch mt-4">
                    <InputCheckbox @bind-Value="_form.ProbeEnabled" @bind-Value:after="EmitAsync" class="form-check-input" id="probeEnabled" />
                    <label class="form-check-label" for="probeEnabled">Enabled</label>
                </div>
            </div>
            <div class="col-md-3">
                <label class="form-label">Interval (seconds)</label>
                <InputNumber @bind-Value="_form.ProbeIntervalSeconds" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
                <div class="form-text">Default 5 s.</div>
            </div>
            <div class="col-md-3">
                <label class="form-label">Timeout (seconds)</label>
                <InputNumber @bind-Value="_form.ProbeTimeoutSeconds" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
                <div class="form-text">Default 2 s.</div>
            </div>
            <div class="col-md-3">
                <label class="form-label">Probe address</label>
                <InputText @bind-Value="_form.ProbeAddress" @bind-Value:after="EmitAsync" class="form-control form-control-sm mono"
                           placeholder="S:0" />
                <div class="form-text">PCCC file address to read for probe. Default S:0 (status file word 0).</div>
            </div>
            <div class="col-md-3">
                <label class="form-label">Admin UI probe timeout (seconds)</label>
                <InputNumber @bind-Value="_form.AdminProbeTimeoutSeconds" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
                <div class="form-text">Max 60. Used by Test Connect. Default 5.</div>
            </div>
        </div>
    </div>
</section>

<DriverResilienceSection ResilienceConfig="@ResilienceConfig" ResilienceConfigChanged="OnResilienceChanged" />

@code {
    /// <summary>The driver-level DriverConfig JSON (protocol/channel; devices live on separate v3 entities).</summary>
    [Parameter] public string DriverConfigJson { get; set; } = "{}";
    /// <summary>Fired (camelCase-serialized) whenever a channel field changes — enables @bind-DriverConfigJson.</summary>
    [Parameter] public EventCallback<string> DriverConfigJsonChanged { get; set; }
    /// <summary>The per-instance resilience-pipeline overrides JSON, or null.</summary>
    [Parameter] public string? ResilienceConfig { get; set; }
    /// <summary>Fired when the resilience overrides change.</summary>
    [Parameter] public EventCallback<string?> ResilienceConfigChanged { get; set; }

    private static readonly JsonSerializerOptions _jsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
        WriteIndented = false,
        Converters = { new JsonStringEnumConverter() },
    };

    private FormModel _form = new();
    private string? _lastParsed;

    // Devices are separate v3 entities — this form doesn't edit them, but preserves the driver's existing
    // Devices array verbatim across a load→save (the merge rebuilds it at deploy/probe time).
    private IReadOnlyList<AbLegacyDeviceOptions> _preservedDevices = [];

    protected override void OnParametersSet()
    {
        // Re-parse only when the inbound value actually changed (don't clobber in-progress edits).
        if (!string.Equals(_lastParsed, DriverConfigJson, StringComparison.Ordinal))
        {
            var opts = TryDeserialize(DriverConfigJson) ?? new AbLegacyDriverOptions();
            _form = FormModel.FromOptions(opts);
            _preservedDevices = opts.Devices;
            _lastParsed = DriverConfigJson;
        }
    }

    /// <summary>Serializes the current channel/protocol config to camelCase JSON, preserving the driver's
    /// existing Devices array (authored as separate v3 entities).</summary>
    public string GetConfigJson()
        => JsonSerializer.Serialize(_form.ToOptions(_preservedDevices), _jsonOpts);

    private async Task EmitAsync()
    {
        var json = GetConfigJson();
        _lastParsed = json;
        await DriverConfigJsonChanged.InvokeAsync(json);
    }

    private async Task OnResilienceChanged(string? r)
    {
        ResilienceConfig = r;
        await ResilienceConfigChanged.InvokeAsync(r);
    }

    private static AbLegacyDriverOptions? TryDeserialize(string json)
    {
        try { return JsonSerializer.Deserialize<AbLegacyDriverOptions>(json, _jsonOpts); }
        catch { return null; }
    }

    // Flat mutable model — all scalar properties settable for Blazor @bind-Value.
    // Collections (Devices, Tags) are kept on the component and passed in on ToOptions().
    public sealed class FormModel
    {
        // Operation
        public int TimeoutSeconds { get; set; } = 2;

        // Probe
        public bool ProbeEnabled { get; set; } = true;
        public int ProbeIntervalSeconds { get; set; } = 5;
        public int ProbeTimeoutSeconds { get; set; } = 2;
        public string? ProbeAddress { get; set; } = "S:0";

        // Admin UI probe timeout
        public int AdminProbeTimeoutSeconds { get; set; } = 5;

        // Persistence
        public string? ResilienceConfig { get; set; }
        public byte[] RowVersion { get; set; } = [];

        public static FormModel FromOptions(AbLegacyDriverOptions o) => new()
        {
            TimeoutSeconds = (int)o.Timeout.TotalSeconds,
            ProbeEnabled = o.Probe.Enabled,
            ProbeIntervalSeconds = (int)o.Probe.Interval.TotalSeconds,
            ProbeTimeoutSeconds = (int)o.Probe.Timeout.TotalSeconds,
            ProbeAddress = o.Probe.ProbeAddress,
            AdminProbeTimeoutSeconds = o.ProbeTimeoutSeconds,
        };

        public AbLegacyDriverOptions ToOptions(
            IReadOnlyList<AbLegacyDeviceOptions> devices) => new()
        {
            Devices = devices,
            Probe = new AbLegacyProbeOptions
            {
                Enabled = ProbeEnabled,
                Interval = TimeSpan.FromSeconds(ProbeIntervalSeconds),
                Timeout = TimeSpan.FromSeconds(ProbeTimeoutSeconds),
                ProbeAddress = string.IsNullOrWhiteSpace(ProbeAddress) ? null : ProbeAddress,
            },
            Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
            ProbeTimeoutSeconds = AdminProbeTimeoutSeconds,
        };
    }
}
