@* Embeddable TwinCAT driver (channel/protocol) config form body. Endpoint/device fields (AMS Net Id:port)
   moved to TwinCATDeviceForm in v3 (they live in Device.DeviceConfig); this form never edits the Devices
   collection — it preserves it verbatim across a load→save. Hosted by the routed TwinCATDriverPage 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.TwinCAT

@* Options *@
<section class="panel rise mt-3" style="animation-delay:.05s">
    <div class="panel-head">Options</div>
    <div style="padding:1rem">
        <div class="row">
            <div class="col-md-3 mb-3">
                <label class="form-label" for="tcTimeoutSec">Timeout (seconds)</label>
                <InputNumber id="tcTimeoutSec" @bind-Value="_form.TimeoutSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Default 2 s per operation.</div>
            </div>
            <div class="col-md-4 mb-3">
                <div class="form-check form-switch mt-4">
                    <InputCheckbox id="tcNativeNotif" @bind-Value="_form.UseNativeNotifications" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="tcNativeNotif">Use native ADS notifications</label>
                </div>
                <div class="form-text">Recommended. Disable for AMS routers with notification limits.</div>
            </div>
            <div class="col-md-4 mb-3">
                <div class="form-check form-switch mt-4">
                    <InputCheckbox id="tcControllerBrowse" @bind-Value="_form.EnableControllerBrowse" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="tcControllerBrowse">Enable controller browse</label>
                </div>
                <div class="form-text">Walks the symbol table via SymbolLoaderFactory at discovery. Default off.</div>
            </div>
        </div>
        <div class="row">
            <div class="col-md-4 mb-3">
                <label class="form-label" for="tcNotifDelay">Notification max delay (ms)</label>
                <InputNumber id="tcNotifDelay" @bind-Value="_form.NotificationMaxDelayMs" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">0 = push immediately. Increase to coalesce high-churn signals.</div>
            </div>
        </div>
    </div>
</section>

@* 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">
            <div class="col-md-3 mb-3">
                <div class="form-check form-switch mt-2">
                    <InputCheckbox id="tcProbeEnabled" @bind-Value="_form.ProbeEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="tcProbeEnabled">Probe enabled</label>
                </div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="tcProbeInterval">Probe interval (s)</label>
                <InputNumber id="tcProbeInterval" @bind-Value="_form.ProbeIntervalSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="tcProbeTimeout">Probe timeout (s)</label>
                <InputNumber id="tcProbeTimeout" @bind-Value="_form.ProbeTimeoutSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="tcAdminProbe">Admin probe timeout (s)</label>
                <InputNumber id="tcAdminProbe" @bind-Value="_form.AdminProbeTimeoutSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Test Connect timeout (1–60 s).</div>
            </div>
        </div>
    </div>
</section>

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

@code {
    /// <summary>The driver-level DriverConfig JSON (protocol/channel; endpoint lives in the device).</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;

    // TwinCAT is multi-device: this form never edits the Devices collection — it preserves whatever was
    // authored (via the device modal on the Raw tree) verbatim across a load→save.
    private IReadOnlyList<TwinCATDeviceOptions> _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 TwinCATDriverOptions();
            _form = FormModel.FromOptions(opts);
            _preservedDevices = opts.Devices;
            _lastParsed = DriverConfigJson;
        }
    }

    /// <summary>Serializes the current channel/protocol config to camelCase JSON, preserving the Devices
    /// collection (authored elsewhere) verbatim.</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 TwinCATDriverOptions? TryDeserialize(string json)
    {
        try { return JsonSerializer.Deserialize<TwinCATDriverOptions>(json, _jsonOpts); }
        catch { return null; }
    }

    // Flat mutable model — all scalar properties settable for Blazor @bind-Value.
    // The Devices collection is kept on the component and passed in on ToOptions().
    public sealed class FormModel
    {
        // Options
        public int TimeoutSeconds { get; set; } = 2;
        public bool UseNativeNotifications { get; set; } = true;
        public bool EnableControllerBrowse { get; set; } = false;
        public int NotificationMaxDelayMs { get; set; } = 0;

        // Probe
        public bool ProbeEnabled { get; set; } = true;
        public int ProbeIntervalSeconds { get; set; } = 5;
        public int ProbeTimeoutSeconds { get; set; } = 2;
        public int AdminProbeTimeoutSeconds { get; set; } = 10;

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

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

        public TwinCATDriverOptions ToOptions(
            IReadOnlyList<TwinCATDeviceOptions> devices) => new()
        {
            Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
            UseNativeNotifications = UseNativeNotifications,
            EnableControllerBrowse = EnableControllerBrowse,
            NotificationMaxDelayMs = NotificationMaxDelayMs,
            Probe = new TwinCATProbeOptions
            {
                Enabled = ProbeEnabled,
                Interval = TimeSpan.FromSeconds(ProbeIntervalSeconds),
                Timeout = TimeSpan.FromSeconds(ProbeTimeoutSeconds),
            },
            ProbeTimeoutSeconds = AdminProbeTimeoutSeconds,
            Devices = devices,
        };
    }
}
