@* Embeddable AB CIP driver (protocol/operation) config form body. The Devices collection moved out to
   AbCipDeviceForm in v3 (its devices are separate entities); this form preserves the driver's existing
   Devices across a load→save and the merge reconstructs the array from the device rows at deploy/probe
   time. Hosted by the routed AbCipDriverPage and by DriverConfigModal. 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.AbCip

@* Operation timeout *@
<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 libplctag call timeout. Default 2 s.</div>
            </div>
            <div class="col-md-3">
                <div class="form-check form-switch mt-4">
                    <InputCheckbox @bind-Value="_form.EnableControllerBrowse" @bind-Value:after="EmitAsync" class="form-check-input" id="enableBrowse" />
                    <label class="form-check-label" for="enableBrowse">Enable controller browse</label>
                </div>
                <div class="form-text">Walk the Logix symbol table and surface globals under Discovered/.</div>
            </div>
            <div class="col-md-3">
                <div class="form-check form-switch mt-4">
                    <InputCheckbox @bind-Value="_form.EnableDeclarationOnlyUdtGrouping" @bind-Value:after="EmitAsync" class="form-check-input" id="enableUdtGroup" />
                    <label class="form-check-label" for="enableUdtGroup">Enable declaration-only UDT grouping</label>
                </div>
                <div class="form-text">Only enable when UDT member declaration order matches controller compiled layout.</div>
            </div>
        </div>
    </div>
</section>

@* Alarm projection *@
<section class="panel rise mt-3" style="animation-delay:.08s">
    <div class="panel-head">Alarm projection</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.EnableAlarmProjection" @bind-Value:after="EmitAsync" class="form-check-input" id="enableAlarm" />
                    <label class="form-check-label" for="enableAlarm">Enable ALMD alarm projection</label>
                </div>
                <div class="form-text">Surfaces ALMD tags as alarm conditions via IAlarmSource. Default off.</div>
            </div>
            <div class="col-md-3">
                <label class="form-label">Alarm poll interval (seconds)</label>
                <InputNumber @bind-Value="_form.AlarmPollIntervalSeconds" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
                <div class="form-text">Default 1 s.</div>
            </div>
        </div>
    </div>
</section>

@* Connectivity probe *@
<section class="panel rise mt-3" style="animation-delay:.10s">
    <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 tag path</label>
                <InputText @bind-Value="_form.ProbeTagPath" @bind-Value:after="EmitAsync" class="form-control form-control-sm mono"
                           placeholder="e.g. Program:Main.SomeTag" />
                <div class="form-text">Required when probe is enabled. Leave blank and an operator warning is logged.</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/operation; endpoints live on the devices).</summary>
    [Parameter] public string DriverConfigJson { get; set; } = "{}";
    /// <summary>Fired (camelCase-serialized) whenever a 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;

    // Preserve the driver's existing Devices across a load→save (the device form authors the real endpoints;
    // the merge reconstructs the Devices array from the device rows at deploy/probe time).
    private IReadOnlyList<AbCipDeviceOptions> _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 AbCipDriverOptions();
            _form = FormModel.FromOptions(opts);
            _preservedDevices = opts.Devices;
            _lastParsed = DriverConfigJson;
        }
    }

    /// <summary>Serializes the current protocol/operation config to camelCase JSON, carrying the preserved
    /// Devices array forward (the device form authors the real endpoints; the merge reconstructs the array
    /// from the device rows at deploy/probe time).</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 AbCipDriverOptions? TryDeserialize(string json)
    {
        try { return JsonSerializer.Deserialize<AbCipDriverOptions>(json, _jsonOpts); }
        catch { return null; }
    }

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

        // Alarm projection
        public bool EnableAlarmProjection { get; set; } = false;
        public int AlarmPollIntervalSeconds { get; set; } = 1;

        // Probe
        public bool ProbeEnabled { get; set; } = true;
        public int ProbeIntervalSeconds { get; set; } = 5;
        public int ProbeTimeoutSeconds { get; set; } = 2;
        public string? ProbeTagPath { get; set; }

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

        public static FormModel FromOptions(AbCipDriverOptions o) => new()
        {
            TimeoutSeconds = (int)o.Timeout.TotalSeconds,
            EnableControllerBrowse = o.EnableControllerBrowse,
            EnableDeclarationOnlyUdtGrouping = o.EnableDeclarationOnlyUdtGrouping,
            EnableAlarmProjection = o.EnableAlarmProjection,
            AlarmPollIntervalSeconds = (int)o.AlarmPollInterval.TotalSeconds,
            ProbeEnabled = o.Probe.Enabled,
            ProbeIntervalSeconds = (int)o.Probe.Interval.TotalSeconds,
            ProbeTimeoutSeconds = (int)o.Probe.Timeout.TotalSeconds,
            ProbeTagPath = o.Probe.ProbeTagPath,
            AdminProbeTimeoutSeconds = o.ProbeTimeoutSeconds,
        };

        public AbCipDriverOptions ToOptions(
            IReadOnlyList<AbCipDeviceOptions> devices) => new()
        {
            Devices = devices,
            Probe = new AbCipProbeOptions
            {
                Enabled = ProbeEnabled,
                Interval = TimeSpan.FromSeconds(ProbeIntervalSeconds),
                Timeout = TimeSpan.FromSeconds(ProbeTimeoutSeconds),
                ProbeTagPath = string.IsNullOrWhiteSpace(ProbeTagPath) ? null : ProbeTagPath,
            },
            Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
            EnableControllerBrowse = EnableControllerBrowse,
            EnableAlarmProjection = EnableAlarmProjection,
            AlarmPollInterval = TimeSpan.FromSeconds(AlarmPollIntervalSeconds),
            EnableDeclarationOnlyUdtGrouping = EnableDeclarationOnlyUdtGrouping,
            ProbeTimeoutSeconds = AdminProbeTimeoutSeconds,
        };
    }
}
