@* Embeddable Fanuc FOCAS driver (channel/protocol) config form body. Endpoint fields (per-device
   HostAddress/Series) moved to FocasDeviceForm in v3 (they live in the Device's DeviceConfig / the
   multi-device Devices collection). Hosted by the routed FocasDriverPage 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.FOCAS

@* 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-3 mb-3">
                <label class="form-label" for="focasTimeoutSec">Timeout (seconds)</label>
                <InputNumber id="focasTimeoutSec" @bind-Value="_form.TimeoutSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Per-operation timeout. Default 2 s.</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="focasProbeEnabled" @bind-Value="_form.ProbeEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="focasProbeEnabled">Probe enabled</label>
                </div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasProbeInterval">Probe interval (s)</label>
                <InputNumber id="focasProbeInterval" @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="focasProbeTimeout">Probe timeout (s)</label>
                <InputNumber id="focasProbeTimeout" @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="focasAdminProbe">Admin probe timeout (s)</label>
                <InputNumber id="focasAdminProbe" @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>

@* Alarm projection *@
<section class="panel rise mt-3" style="animation-delay:.11s">
    <div class="panel-head">Alarm projection</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="focasAlarmEnabled" @bind-Value="_form.AlarmProjectionEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="focasAlarmEnabled">Alarm projection enabled</label>
                </div>
                <div class="form-text">Surfaces FOCAS alarms via IAlarmSource.</div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasAlarmPoll">Alarm poll interval (s)</label>
                <InputNumber id="focasAlarmPoll" @bind-Value="_form.AlarmProjectionPollIntervalSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">One cnc_rdalmmsg2 call per device per tick. Default 2 s.</div>
            </div>
        </div>
    </div>
</section>

@* Handle recycle *@
<section class="panel rise mt-3" style="animation-delay:.14s">
    <div class="panel-head">Handle recycle</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="focasRecycleEnabled" @bind-Value="_form.HandleRecycleEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="focasRecycleEnabled">Handle recycle enabled</label>
                </div>
                <div class="form-text">Proactive FWLIB session recycle to prevent handle pool exhaustion. Default off.</div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasRecycleInterval">Recycle interval (minutes)</label>
                <InputNumber id="focasRecycleInterval" @bind-Value="_form.HandleRecycleIntervalMinutes" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Typical: 30 min (shared pool) or 360 min (single client).</div>
            </div>
        </div>
    </div>
</section>

@* Fixed tree *@
<section class="panel rise mt-3" style="animation-delay:.17s">
    <div class="panel-head">Fixed-node tree</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="focasFixedTree" @bind-Value="_form.FixedTreeEnabled" @bind-Value:after="EmitAsync"
                                   class="form-check-input" />
                    <label class="form-check-label" for="focasFixedTree">Fixed tree enabled</label>
                </div>
                <div class="form-text">Exposes Identity/, Axes/, etc. from cnc_sysinfo/cnc_rdaxisname/cnc_rddynamic2. Default off.</div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasAxisPoll">Axis poll interval (ms)</label>
                <InputNumber id="focasAxisPoll" @bind-Value="_form.FixedTreePollIntervalMs" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">cnc_rddynamic2 cadence per axis. Default 250 ms.</div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasProgramPoll">Program poll interval (s)</label>
                <InputNumber id="focasProgramPoll" @bind-Value="_form.FixedTreeProgramPollIntervalSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Program/mode info cadence. 0 = disabled. Default 1 s.</div>
            </div>
            <div class="col-md-3 mb-3">
                <label class="form-label" for="focasTimerPoll">Timer poll interval (s)</label>
                <InputNumber id="focasTimerPoll" @bind-Value="_form.FixedTreeTimerPollIntervalSeconds" @bind-Value:after="EmitAsync"
                             class="form-control form-control-sm" />
                <div class="form-text">Power-on/cutting/cycle timer cadence. 0 = disabled. Default 30 s.</div>
            </div>
        </div>
    </div>
</section>

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

@code {
    /// <summary>The driver-level DriverConfig JSON (protocol/channel; per-device 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;

    // FOCAS is multi-device; the Devices collection is authored on the Raw tree, not on this form.
    // Preserve whatever devices the persisted config carried so a driver-form load→save round-trip
    // never drops them.
    private IReadOnlyList<FocasDeviceOptions> _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 FocasDriverOptions();
            _form = FormModel.FromOptions(opts);
            _preservedDevices = opts.Devices;
            _lastParsed = DriverConfigJson;
        }
    }

    /// <summary>Serializes the current channel/protocol config to camelCase JSON, re-attaching the
    /// preserved multi-device collection (authored on the Raw tree).</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 FocasDriverOptions? TryDeserialize(string json)
    {
        try { return JsonSerializer.Deserialize<FocasDriverOptions>(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
    {
        // Connection
        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 int AdminProbeTimeoutSeconds { get; set; } = 10;

        // Alarm projection
        public bool AlarmProjectionEnabled { get; set; } = false;
        public int AlarmProjectionPollIntervalSeconds { get; set; } = 2;

        // Handle recycle
        public bool HandleRecycleEnabled { get; set; } = false;
        public int HandleRecycleIntervalMinutes { get; set; } = 60;

        // Fixed tree
        public bool FixedTreeEnabled { get; set; } = false;
        public int FixedTreePollIntervalMs { get; set; } = 250;
        public int FixedTreeProgramPollIntervalSeconds { get; set; } = 1;
        public int FixedTreeTimerPollIntervalSeconds { get; set; } = 30;

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

        public static FormModel FromOptions(FocasDriverOptions o) => new()
        {
            TimeoutSeconds = (int)o.Timeout.TotalSeconds,
            ProbeEnabled = o.Probe.Enabled,
            ProbeIntervalSeconds = (int)o.Probe.Interval.TotalSeconds,
            ProbeTimeoutSeconds = (int)o.Probe.Timeout.TotalSeconds,
            AdminProbeTimeoutSeconds = o.ProbeTimeoutSeconds,
            AlarmProjectionEnabled = o.AlarmProjection.Enabled,
            AlarmProjectionPollIntervalSeconds = (int)o.AlarmProjection.PollInterval.TotalSeconds,
            HandleRecycleEnabled = o.HandleRecycle.Enabled,
            HandleRecycleIntervalMinutes = (int)o.HandleRecycle.Interval.TotalMinutes,
            FixedTreeEnabled = o.FixedTree.Enabled,
            FixedTreePollIntervalMs = (int)o.FixedTree.PollInterval.TotalMilliseconds,
            FixedTreeProgramPollIntervalSeconds = (int)o.FixedTree.ProgramPollInterval.TotalSeconds,
            FixedTreeTimerPollIntervalSeconds = (int)o.FixedTree.TimerPollInterval.TotalSeconds,
        };

        public FocasDriverOptions ToOptions(
            IReadOnlyList<FocasDeviceOptions> devices) => new()
        {
            Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
            Probe = new FocasProbeOptions
            {
                Enabled = ProbeEnabled,
                Interval = TimeSpan.FromSeconds(ProbeIntervalSeconds),
                Timeout = TimeSpan.FromSeconds(ProbeTimeoutSeconds),
            },
            ProbeTimeoutSeconds = AdminProbeTimeoutSeconds,
            AlarmProjection = new FocasAlarmProjectionOptions
            {
                Enabled = AlarmProjectionEnabled,
                PollInterval = TimeSpan.FromSeconds(AlarmProjectionPollIntervalSeconds),
            },
            HandleRecycle = new FocasHandleRecycleOptions
            {
                Enabled = HandleRecycleEnabled,
                Interval = TimeSpan.FromMinutes(HandleRecycleIntervalMinutes),
            },
            FixedTree = new FocasFixedTreeOptions
            {
                Enabled = FixedTreeEnabled,
                PollInterval = TimeSpan.FromMilliseconds(FixedTreePollIntervalMs),
                ProgramPollInterval = TimeSpan.FromSeconds(FixedTreeProgramPollIntervalSeconds),
                TimerPollInterval = TimeSpan.FromSeconds(FixedTreeTimerPollIntervalSeconds),
            },
            Devices = devices,
        };
    }
}
