using System.Text.Json.Nodes;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;

/// <summary>Typed working model for an AB Legacy (PCCC) tag's TagConfig JSON (the driver-specific addressing/encoding
/// fields; name/writable live on the Tag entity). Preserves unrecognised JSON keys across a load→save.</summary>
public sealed class AbLegacyTagConfigModel
{
    /// <summary>Which device (<c>AbLegacyDeviceOptions.HostAddress</c>) this tag lives on. Optional.</summary>
    public string DeviceHostAddress { get; set; } = "";

    /// <summary>Canonical PCCC file-address string, e.g. <c>N7:0</c>, <c>B3:0/0</c>. Required.</summary>
    public string Address { get; set; } = "";

    /// <summary>PCCC data type that maps onto SLC / MicroLogix / PLC-5 files.</summary>
    public AbLegacyDataType DataType { get; set; } = AbLegacyDataType.Int;

    /// <summary>Optional writable flag (R2-11); <c>null</c> ⇒ omitted (driver default true), explicit
    /// <c>false</c> ⇒ read-only. Preserved across load→save.</summary>
    public bool? Writable { get; set; }

    private JsonObject _bag = new();

    /// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
    /// every original key (so fields this editor doesn't expose survive a load→save).</summary>
    /// <param name="json">The TagConfig JSON string to parse, or <c>null</c>/empty for a new tag.</param>
    /// <returns>The populated model.</returns>
    public static AbLegacyTagConfigModel FromJson(string? json)
    {
        var o = TagConfigJson.ParseOrNew(json);
        return new AbLegacyTagConfigModel
        {
            DeviceHostAddress = TagConfigJson.GetString(o, "deviceHostAddress") ?? "",
            Address = TagConfigJson.GetString(o, "address") ?? "",
            DataType = TagConfigJson.GetEnum(o, "dataType", AbLegacyDataType.Int),
            Writable = TagConfigJson.GetBoolNullable(o, "writable"),
            _bag = o,
        };
    }

    /// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
    /// (enum as its name string; the optional host address omitted when blank) over the preserved key bag.</summary>
    /// <returns>The serialised TagConfig JSON string.</returns>
    public string ToJson()
    {
        TagConfigJson.Set(_bag, "deviceHostAddress", string.IsNullOrWhiteSpace(DeviceHostAddress) ? null : DeviceHostAddress.Trim());
        TagConfigJson.Set(_bag, "address", Address.Trim());
        TagConfigJson.Set(_bag, "dataType", DataType);
        TagConfigJson.Set(_bag, "writable", Writable);
        return TagConfigJson.Serialize(_bag);
    }

    /// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
    /// <returns>An error message describing the validation failure, or <c>null</c> when valid.</returns>
    public string? Validate()
    {
        if (string.IsNullOrWhiteSpace(Address))
            return "Address is required.";

        // Array-length cap: PCCC data-file elements are limited to AbLegacyArray.MaxElements (256).
        // The driver-agnostic materialiser uses the raw arrayLength to set OPC UA ArrayDimensions, but the
        // driver clamps to 256 at runtime — so an authored length > 256 produces a Part 6 §5.3.2 mismatch.
        // Reject it here at author-time so the error surfaces in the TagModal before the config is saved.
        var isArray = TagConfigJson.GetBool(_bag, "isArray");
        var arrayLength = TagConfigJson.GetInt(_bag, "arrayLength");
        if (isArray && arrayLength > AbLegacyArray.MaxElements)
            return $"AbLegacy array length cannot exceed {AbLegacyArray.MaxElements} (PCCC data-file element limit).";

        return null;
    }
}
