@* Embeddable Siemens S7 driver (protocol) config form body. Endpoint fields (Host/Port/Rack/Slot)
   moved to S7DeviceForm in v3 (they live in Device.DeviceConfig). Hosted by the routed
   S7DriverPage 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.Nodes
@using System.Text.Json.Serialization
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.Driver.S7

@* Connection *@
<section class="panel rise mt-3" style="animation-delay:.05s">
    <div class="panel-head">Connection</div>
    <div style="padding:1rem">
        <div class="row">
            <div class="col-md-4 mb-3">
                <label class="form-label" for="s7TimeoutSec">Timeout (seconds)</label>
                <InputNumber id="s7TimeoutSec" @bind-Value="_form.TimeoutSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
            </div>
            <div class="col-md-4 mb-3">
                <label class="form-label" for="s7CpuType">CPU type</label>
                <InputSelect id="s7CpuType" @bind-Value="_form.CpuType" @bind-Value:after="EmitAsync"
                             class="form-select form-select-sm">
                    @foreach (var v in Enum.GetValues<S7CpuType>())
                    {
                        <option value="@v">@v</option>
                    }
                </InputSelect>
                <div class="form-text">Controls ISO-TSAP slot byte during handshake.</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="s7ProbeEnabled" @bind-Value="_form.ProbeEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="s7ProbeEnabled">Probe enabled</label>
                </div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="s7ProbeIntervalSec">Probe interval (s)</label>
                <InputNumber id="s7ProbeIntervalSec" @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="s7ProbeTimeoutSec">Probe timeout (s)</label>
                <InputNumber id="s7ProbeTimeoutSec" @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="s7AdminProbeTimeout">Admin probe timeout (s)</label>
                <InputNumber id="s7AdminProbeTimeout" @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; endpoint lives in the device).</summary>
    [Parameter] public string DriverConfigJson { get; set; } = "{}";
    /// <summary>Fired (camelCase-serialized) whenever a protocol 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;

    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 S7DriverOptions();
            _form = FormModel.FromOptions(opts);
            _lastParsed = DriverConfigJson;
        }
    }

    // v3: the endpoint lives on the Device (DeviceConfig). Strip the endpoint keys so DeviceConfig is the
    // sole endpoint source — a stale driver-level default would otherwise shadow the device on the
    // single-top-level-Host read path as soon as a 2nd device exists (reviewer H1).
    private static readonly string[] EndpointKeys = ["host", "port", "rack", "slot"];

    /// <summary>Serializes the current protocol config to camelCase JSON, with the endpoint keys removed
    /// (the device's DeviceConfig is the sole endpoint source, merged up at deploy/probe time).</summary>
    public string GetConfigJson()
    {
        var node = JsonSerializer.SerializeToNode(_form.ToOptions(), _jsonOpts)!.AsObject();
        foreach (var key in EndpointKeys) node.Remove(key);
        return node.ToJsonString(_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 S7DriverOptions? TryDeserialize(string json)
    {
        try { return JsonSerializer.Deserialize<S7DriverOptions>(json, _jsonOpts); }
        catch { return null; }
    }

    // Flat mutable model — all scalar properties settable for Blazor @bind-Value.
    public sealed class FormModel
    {
        // Connection
        public string Host { get; set; } = "127.0.0.1";
        public int Port { get; set; } = 102;
        public S7CpuType CpuType { get; set; } = S7CpuType.S71500;
        public short Rack { get; set; } = 0;
        public short Slot { get; set; } = 0;
        public int TimeoutSeconds { get; set; } = 5;

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

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

        public static FormModel FromOptions(S7DriverOptions o) => new()
        {
            Host = o.Host,
            Port = o.Port,
            CpuType = o.CpuType,
            Rack = o.Rack,
            Slot = o.Slot,
            TimeoutSeconds = (int)o.Timeout.TotalSeconds,
            ProbeEnabled = o.Probe.Enabled,
            ProbeIntervalSeconds = (int)o.Probe.Interval.TotalSeconds,
            ProbeTimeoutSeconds = (int)o.Probe.Timeout.TotalSeconds,
            AdminProbeTimeoutSeconds = o.ProbeTimeoutSeconds,
        };

        public S7DriverOptions ToOptions() => new()
        {
            Host = Host,
            Port = Port,
            CpuType = CpuType,
            Rack = Rack,
            Slot = Slot,
            Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
            Probe = new S7ProbeOptions
            {
                Enabled = ProbeEnabled,
                Interval = TimeSpan.FromSeconds(ProbeIntervalSeconds),
                Timeout = TimeSpan.FromSeconds(ProbeTimeoutSeconds),
            },
            ProbeTimeoutSeconds = AdminProbeTimeoutSeconds,
        };
    }
}
