using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.S7;

/// <summary>
///     Siemens S7 native (S7comm / ISO-on-TCP port 102) driver configuration. Bound from the
///     driver's <c>DriverConfig</c> JSON at <c>DriverHost.RegisterAsync</c>. Unlike the Modbus
///     driver the S7 driver uses the PLC's *native* protocol — port 102 ISO-on-TCP rather
///     than Modbus's 502, and S7-specific area codes (DB, M, I, Q) rather than holding-
///     register / coil tables.
/// </summary>
/// <remarks>
///     <para>
///         The driver requires <b>PUT/GET communication enabled</b> in the TIA Portal
///         hardware config for S7-1200/1500. The factory default disables PUT/GET access,
///         so a driver configured against a freshly-flashed CPU will see a hard error
///         (S7.Net surfaces it as <c>Plc.ReadAsync</c> returning <c>ErrorCode.Accessing</c>).
///         The driver maps that specifically to <c>BadNotSupported</c> and flags it as a
///         configuration alert rather than a transient fault — blind Polly retry is wasted
///         effort when the PLC will keep refusing every request.
///     </para>
///     <para>
///         See <c>docs/v2/driver-specs.md</c> §5 for the full specification.
///     </para>
/// </remarks>
public sealed class S7DriverOptions
{
    /// <summary>PLC IP address or hostname.</summary>
    public string Host { get; init; } = "127.0.0.1";

    /// <summary>TCP port. ISO-on-TCP is 102 on every S7 model; override only for unusual NAT setups.</summary>
    public int Port { get; init; } = 102;

    /// <summary>
    ///     CPU family. Determines the ISO-TSAP slot byte that S7.Net uses during connection
    ///     setup — pick the family that matches the target PLC exactly.
    /// </summary>
    public S7CpuType CpuType { get; init; } = S7CpuType.S71500;

    /// <summary>
    ///     Hardware rack number. Almost always 0; relevant only for distributed S7-400 racks
    ///     with multiple CPUs.
    /// </summary>
    public short Rack { get; init; } = 0;

    /// <summary>
    ///     CPU slot. Conventions per family: S7-300 = slot 2, S7-400 = slot 2 or 3,
    ///     S7-1200 / S7-1500 = slot 0 (onboard PN). S7.Net uses this to build the remote
    ///     TSAP. Wrong slot → connection refused during handshake.
    /// </summary>
    public short Slot { get; init; } = 0;

    /// <summary>Connect + per-operation timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(5);

    /// <summary>
    ///     Authored raw tags this driver serves. The deploy artifact hands each authored raw
    ///     <c>Tag</c> as a <see cref="RawTagEntry"/> (RawPath identity + driver <c>TagConfig</c> blob +
    ///     WriteIdempotent flag); the driver maps each through
    ///     <see cref="S7TagDefinitionFactory.FromTagConfig"/> into its RawPath → definition table.
    ///     S7 has a symbol-table protocol but S7.Net does not expose it, so the driver serves exactly
    ///     these authored tags. Address grammar documented in S7AddressParser (PR 63).
    /// </summary>
    public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];

    /// <summary>
    ///     Background connectivity-probe settings. When enabled, the driver runs a tick loop
    ///     that issues <c>S7.Net.Plc.ReadStatusAsync</c> (a CPU-status PDU) every
    ///     <see cref="S7ProbeOptions.Interval"/> and raises <c>OnHostStatusChanged</c> on
    ///     Running ↔ Stopped transitions.
    /// </summary>
    public S7ProbeOptions Probe { get; init; } = new();

    /// <summary>
    ///     Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
    ///     60s server-side maximum; this default is what the form pre-fills for new instances.
    /// </summary>
    [Display(Name = "Probe timeout (seconds)", Description = "Connection test timeout. Default 5s.", GroupName = "Diagnostics")]
    [Range(1, 60)]
    public int ProbeTimeoutSeconds { get; init; } = 5;
}

public sealed class S7ProbeOptions
{
    /// <summary>Gets or sets a value indicating whether probing is enabled.</summary>
    public bool Enabled { get; init; } = true;
    /// <summary>Gets or sets the probe interval.</summary>
    public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
    /// <summary>Gets or sets the probe timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);

}

/// <summary>
///     One S7 variable as exposed by the driver. Addresses use S7.Net syntax — see
///     <c>S7AddressParser</c> (PR 63) for the grammar.
/// </summary>
/// <param name="Name">Tag name; OPC UA browse name + driver full reference.</param>
/// <param name="Address">S7 address string, e.g. <c>DB1.DBW0</c>, <c>M0.0</c>, <c>I0.0</c>, <c>QD4</c>. Grammar documented in <c>S7AddressParser</c> (PR 63).</param>
/// <param name="DataType">Logical data type — drives the underlying S7.Net read/write width.</param>
/// <param name="Writable">When true the driver accepts writes for this tag.</param>
/// <param name="StringLength">For <c>DataType = String</c>: S7-string max length. Default 254 (S7 max).</param>
/// <param name="WriteIdempotent">
///     Flag a tag as safe to replay on
///     write timeout / failure. Default <c>false</c>; writes do not auto-retry. Safe candidates
///     on S7: DB word/dword set-points holding analog values, configuration DBs where the same
///     value can be written again without side-effects. Unsafe: M (merker) bits or Q (output)
///     coils that drive edge-triggered routines in the PLC program.
/// </param>
/// <param name="ArrayCount">
///     Element count when the tag is a 1-D array; <c>null</c> for a scalar. Any value <c>&gt;= 1</c>
///     (including 1) surfaces as a 1-D OPC UA array node.
///     For an equipment tag this is threaded from the <c>TagConfig</c> JSON's <c>arrayLength</c>
///     (honoured only when <c>isArray</c> is true) by <see cref="S7TagDefinitionFactory"/>. When
///     set, the driver issues a single contiguous block read of
///     <c>ArrayCount × element-bytes</c> from the tag's start address and decodes each element
///     into an element-typed CLR array (<c>short[]</c> / <c>int[]</c> / <c>float[]</c> / etc.).
/// </param>
public sealed record S7TagDefinition(
    string Name,
    string Address,
    S7DataType DataType,
    bool Writable = true,
    int StringLength = 254,
    bool WriteIdempotent = false,
    int? ArrayCount = null);

public enum S7DataType
{
    Bool,
    Byte,
    Int16,
    UInt16,
    Int32,
    UInt32,
    Int64,
    UInt64,
    Float32,
    Float64,
    String,
    DateTime,
}
