using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.DeviceForms;
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers;

/// <summary>
///     <c>DriverType → typed driver-config form component</c>, rendered by <c>DriverConfigModal</c> through
///     <c>&lt;DynamicComponent&gt;</c>.
///     <para><b>Why this is a map and not the <c>@switch</c> it replaced.</b> A Razor <c>@switch</c> compiles
///     into <c>BuildRenderTree</c>'s IL, so no test can enumerate its cases — which is exactly how the modal
///     came to be missing a <c>Calculation</c> arm while the sibling driver-picker guard stayed green
///     (<c>deferment.md</c> G-1/G-4). The picker test only works because <c>RawDriverTypeDialog</c> keeps its
///     data in a field the markup enumerates; this type gives the two config modals the same property, and
///     being <c>public</c> it needs none of that test's <c>BindingFlags.NonPublic</c> fragility.</para>
///     <para>Modelled on <c>TagConfigEditorMap</c>. Guarded by <c>DriverFormMapParityTests</c>.</para>
/// </summary>
public static class DriverConfigFormMap
{
    private static readonly IReadOnlyDictionary<string, Type> Map =
        new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            [DriverTypeNames.Modbus] = typeof(ModbusDriverForm),
            [DriverTypeNames.S7] = typeof(S7DriverForm),
            [DriverTypeNames.AbCip] = typeof(AbCipDriverForm),
            [DriverTypeNames.AbLegacy] = typeof(AbLegacyDriverForm),
            [DriverTypeNames.TwinCAT] = typeof(TwinCATDriverForm),
            [DriverTypeNames.FOCAS] = typeof(FocasDriverForm),
            [DriverTypeNames.OpcUaClient] = typeof(OpcUaClientDriverForm),
            [DriverTypeNames.Galaxy] = typeof(GalaxyDriverForm),
            [DriverTypeNames.Sql] = typeof(SqlDriverForm),
            [DriverTypeNames.Mqtt] = typeof(MqttDriverForm),
            [DriverTypeNames.MTConnect] = typeof(MTConnectDriverForm),
            [DriverTypeNames.Calculation] = typeof(CalculationDriverForm),
        };

    /// <summary>The driver types that have a typed config form.</summary>
    public static IReadOnlyCollection<string> MappedDriverTypes => (IReadOnlyCollection<string>)Map.Keys;

    /// <summary>Resolves the form component for a driver type, or null when none is mapped (the modal
    /// then renders its "no typed config form" warning rather than crashing).</summary>
    /// <param name="driverType">The driver type name.</param>
    /// <returns>The form component type, or <see langword="null"/>.</returns>
    public static Type? Resolve(string? driverType) =>
        driverType is not null && Map.TryGetValue(driverType, out var t) ? t : null;
}

/// <summary>
///     <c>DriverType → typed device-config form component</c>, rendered by <c>DeviceModal</c>.
///     <para><b>Not every driver has one, by design.</b> The types in
///     <see cref="SingleConnectionDriverTypes"/> hold ONE connection per driver instance and author it on the
///     driver form, so a per-device endpoint editor would be meaningless. The parity test consults that set
///     rather than demanding total coverage — which keeps it a real guard instead of one that has to be
///     suppressed.</para>
/// </summary>
public static class DeviceFormMap
{
    private static readonly IReadOnlyDictionary<string, Type> Map =
        new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            [DriverTypeNames.Modbus] = typeof(ModbusDeviceForm),
            [DriverTypeNames.S7] = typeof(S7DeviceForm),
            [DriverTypeNames.AbCip] = typeof(AbCipDeviceForm),
            [DriverTypeNames.AbLegacy] = typeof(AbLegacyDeviceForm),
            [DriverTypeNames.TwinCAT] = typeof(TwinCATDeviceForm),
            [DriverTypeNames.FOCAS] = typeof(FocasDeviceForm),
            [DriverTypeNames.OpcUaClient] = typeof(OpcUaClientDeviceForm),
            [DriverTypeNames.Galaxy] = typeof(GalaxyDeviceForm),
            [DriverTypeNames.Mqtt] = typeof(MqttDeviceForm),
        };

    /// <summary>
    ///     Driver types that hold a single connection at the DRIVER level, so they legitimately have no
    ///     per-device form. Galaxy and Mqtt author a connection on the driver form and keep an informational
    ///     device form; Sql (one connection string), MTConnect (one Agent URI) and Calculation (no
    ///     connection at all) have no device form to render.
    ///     <para>Read by <c>DriverConfigModal</c> so the operator is told where the endpoint actually lives —
    ///     it previously hardcoded Galaxy-or-Mqtt and told Sql/MTConnect authors to go and find an endpoint
    ///     field on the device that does not exist (<c>deferment.md</c> G-3).</para>
    /// </summary>
    public static readonly IReadOnlySet<string> SingleConnectionDriverTypes =
        new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            DriverTypeNames.Galaxy,
            DriverTypeNames.Mqtt,
            DriverTypeNames.Sql,
            DriverTypeNames.MTConnect,
            DriverTypeNames.Calculation,
        };

    /// <summary>The driver types that have a typed device form.</summary>
    public static IReadOnlyCollection<string> MappedDriverTypes => (IReadOnlyCollection<string>)Map.Keys;

    /// <summary>Resolves the device form component for a driver type, or null when none is mapped.</summary>
    /// <param name="driverType">The driver type name.</param>
    /// <returns>The form component type, or <see langword="null"/>.</returns>
    public static Type? Resolve(string? driverType) =>
        driverType is not null && Map.TryGetValue(driverType, out var t) ? t : null;

    /// <summary>True when this driver authors its connection on the DRIVER form rather than per-device.</summary>
    /// <param name="driverType">The driver type name.</param>
    /// <returns><see langword="true"/> when the connection is driver-level.</returns>
    public static bool IsSingleConnection(string? driverType) =>
        driverType is not null && SingleConnectionDriverTypes.Contains(driverType);
}
