using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;

/// <summary>
/// Maps the driver-agnostic <see cref="DriverDataType"/> onto the gateway's
/// <see cref="HistorianDataType"/> for tag provisioning + historical writes.
/// </summary>
/// <remarks>
/// Only the nine numeric types are historizable on the gateway's analog write path. Two of them
/// fall back to a wider/adjacent historian type because the exact one's write path is not usable
/// upstream: <see cref="DriverDataType.UInt16"/> maps to <see cref="HistorianDataType.Uint4"/> (the
/// historian's <c>UInt2</c> write path is not proven), and <see cref="DriverDataType.Boolean"/> maps
/// to <see cref="HistorianDataType.Int2"/> — the historian's <c>Int1</c> analog-tag creation path is
/// server-degenerate (issue #441 / HistorianGateway #11: <c>EnsureTags(Int1)</c> stores an unusable
/// stub), so Boolean historizes as a small 0/1 integer instead. String / DateTime / Reference are not
/// historized in v1 and throw <see cref="NotSupportedException"/>; callers that want to skip them
/// without catching an exception should consult <see cref="IsHistorizable(DriverDataType)"/> first.
/// </remarks>
internal static class HistorianTypeMapper
{
    /// <summary>Maps a driver data type to the historian data type used for provisioning/writes.</summary>
    /// <param name="dataType">The driver-agnostic data type.</param>
    /// <returns>The matching <see cref="HistorianDataType"/>.</returns>
    /// <exception cref="NotSupportedException">
    /// The type is explicitly deferred (string/datetime/reference) or a future, unclassified member.
    /// </exception>
    public static HistorianDataType ToHistorianDataType(DriverDataType dataType) => dataType switch
    {
        DriverDataType.Boolean => HistorianDataType.Int2, // Int1 analog-tag creation is server-degenerate (issue #441) → store 0/1

        DriverDataType.Int16 => HistorianDataType.Int2,
        DriverDataType.Int32 => HistorianDataType.Int4,
        DriverDataType.Int64 => HistorianDataType.Int8,
        DriverDataType.UInt16 => HistorianDataType.Uint4, // UInt2 write path deferred upstream → widen
        DriverDataType.UInt32 => HistorianDataType.Uint4,
        DriverDataType.UInt64 => HistorianDataType.Uint8,
        DriverDataType.Float32 => HistorianDataType.Float,
        DriverDataType.Float64 => HistorianDataType.Double,
        DriverDataType.String or DriverDataType.DateTime or DriverDataType.Reference =>
            throw new NotSupportedException(
                $"DriverDataType.{dataType} is not historized in v1 " +
                "(string/datetime/reference writes are deferred — gated on the analog SQL write path)."),
        _ => throw new NotSupportedException(
            $"DriverDataType.{dataType} is not classified for historian write mapping — add a HistorianDataType mapping."),
    };

    /// <summary>
    /// True when <paramref name="dataType"/> is one of the nine historizable numeric types — lets the
    /// provisioning hook skip deferred types without catching <see cref="NotSupportedException"/>.
    /// </summary>
    /// <param name="dataType">The driver-agnostic data type.</param>
    /// <returns>True if the type is one of the nine historizable numeric types; otherwise false.</returns>
    public static bool IsHistorizable(DriverDataType dataType) => dataType switch
    {
        DriverDataType.Boolean
            or DriverDataType.Int16
            or DriverDataType.Int32
            or DriverDataType.Int64
            or DriverDataType.UInt16
            or DriverDataType.UInt32
            or DriverDataType.UInt64
            or DriverDataType.Float32
            or DriverDataType.Float64 => true,
        _ => false,
    };
}
