namespace RobotNet.Script;

/// <summary>
/// Client CC‑Link IE Basic (SLMP over TCP/UDP) dùng để kết nối PC ↔ PLC Mitsubishi qua Ethernet thường,
/// hỗ trợ đọc/ghi device, polling chu kỳ và gửi lệnh SLMP thô.
/// </summary>
public interface ICcLinkIeBasicClient : IAsyncDisposable, IDisposable
{
    /// <summary>
    /// Trạng thái đã kết nối phiên transport (TCP/UDP) tới PLC hay chưa.
    /// </summary>
    bool IsConnected { get; }

    /// <summary>
    /// Số lần thử gửi lại khi lỗi tạm thời/timeout.
    /// </summary>
    int RetryCount { get; set; }

    /// <summary>
    /// Ngắt kết nối transport và giải phóng tài nguyên liên quan.
    /// </summary>
    /// <param name="ct">Token hủy bất đồng bộ.</param>
    Task DisconnectAsync(CancellationToken ct = default);

    /// <summary>
    /// Đọc mảng bit (X/Y/M/L/S/B...) từ PLC.
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu.</param>
    /// <param name="count">Số bit cần đọc.</param>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Mảng giá trị bit.</returns>
    Task<bool[]> ReadBitsAsync(DeviceCode device, int startAddress, int count, CancellationToken ct = default);

    /// <summary>
    /// Ghi mảng bit (X/Y/M/L/S/B...) vào PLC.
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu.</param>
    /// <param name="values">Dãy giá trị bit cần ghi.</param>
    /// <param name="ct">Token hủy.</param>
    Task WriteBitsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<bool> values, CancellationToken ct = default);

    /// <summary>
    /// Đọc mảng word (D/W/R/ZR/B...) từ PLC.
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu (đơn vị word).</param>
    /// <param name="wordCount">Số word cần đọc.</param>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Mảng word (UInt16).</returns>
    Task<ushort[]> ReadWordsAsync(DeviceCode device, int startAddress, int wordCount, CancellationToken ct = default);

    /// <summary>
    /// Ghi mảng word (D/W/R/ZR/B...) vào PLC.
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu (đơn vị word).</param>
    /// <param name="words">Dãy word cần ghi.</param>
    /// <param name="ct">Token hủy.</param>
    Task WriteWordsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<ushort> words, CancellationToken ct = default);

    /// <summary>
    /// Đọc mảng double‑word (UInt32) từ PLC (áp dụng cho vùng hỗ trợ dword).
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu (đơn vị dword).</param>
    /// <param name="dwordCount">Số dword cần đọc.</param>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Mảng dword (UInt32).</returns>
    Task<uint[]> ReadDWordsAsync(DeviceCode device, int startAddress, int dwordCount, CancellationToken ct = default);

    /// <summary>
    /// Ghi mảng double‑word (UInt32) vào PLC (áp dụng cho vùng hỗ trợ dword).
    /// </summary>
    /// <param name="device">Loại device code.</param>
    /// <param name="startAddress">Địa chỉ bắt đầu (đơn vị dword).</param>
    /// <param name="dwords">Dãy dword cần ghi.</param>
    /// <param name="ct">Token hủy.</param>
    Task WriteDWordsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<uint> dwords, CancellationToken ct = default);

    /// <summary>
    /// Đọc ngẫu nhiên nhiều điểm word rời rạc (mixed areas) trong một lần.
    /// </summary>
    /// <param name="points">Danh sách (device, address) cần đọc.</param>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Mảng word theo thứ tự điểm yêu cầu.</returns>
    Task<ushort[]> ReadRandomWordsAsync((DeviceCode Device, int Address)[] points, CancellationToken ct = default);

    /// <summary>
    /// Ghi ngẫu nhiên nhiều điểm word rời rạc (mixed areas) trong một lần.
    /// </summary>
    /// <param name="points">Danh sách (device, address) cần ghi.</param>
    /// <param name="values">Giá trị word theo thứ tự điểm.</param>
    /// <param name="ct">Token hủy.</param>
    Task WriteRandomWordsAsync((DeviceCode Device, int Address)[] points, ReadOnlyMemory<ushort> values, CancellationToken ct = default);

    /// <summary>
    /// Sự kiện bắn ra sau mỗi chu kỳ polling hoàn tất.
    /// </summary>
    event EventHandler<PollUpdatedEventArgs>? Polled;

    /// <summary>
    /// Bắt đầu polling chu kỳ một hoặc nhiều vùng device.
    /// </summary>
    /// <param name="options">Tùy chọn polling (chu kỳ, vùng, giới hạn kích thước).</param>
    /// <param name="ct">Token hủy.</param>
    void StartPolling(PollOptions options, CancellationToken ct = default);

    /// <summary>
    /// Dừng polling chu kỳ.
    /// </summary>
    /// <param name="ct">Token hủy.</param>
    Task StopPollingAsync(CancellationToken ct = default);

    /// <summary>
    /// Lấy trạng thái liên kết/SLMP gần nhất (end code, thống kê thời gian phản hồi...).
    /// </summary>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Thông tin trạng thái IE Basic.</returns>
    Task<IeBasicStatus> GetStatusAsync(CancellationToken ct = default);

    /// <summary>
    /// Kiểm tra liên lạc PLC (ví dụ node monitor) ở mức nhanh/gọn.
    /// </summary>
    /// <param name="ct">Token hủy.</param>
    /// <returns>true nếu phản hồi hợp lệ, ngược lại false.</returns>
    Task<bool> PingAsync(CancellationToken ct = default);

    /// <summary>
    /// Truy vấn thông tin nhận dạng module/CPU (nếu PLC cho phép).
    /// </summary>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Thông tin định danh CPU/module.</returns>
    Task<ModuleIdentity> IdentifyAsync(CancellationToken ct = default);

    /// <summary>
    /// Gửi lệnh SLMP thô (mở rộng/đặc thù) và nhận phản hồi.
    /// </summary>
    /// <param name="command">Mã lệnh SLMP.</param>
    /// <param name="payload">Payload SLMP theo định dạng frame chọn (3E/4E).</param>
    /// <param name="ct">Token hủy.</param>
    /// <returns>Phản hồi SLMP gồm end code và payload.</returns>
    Task<SlmpResponse> SendSlmpAsync(SlmpCommand command, ReadOnlyMemory<byte> payload, CancellationToken ct = default);
}

/// <summary>
/// Loại transport cho CC‑Link IE Basic (UDP hoặc TCP).
/// </summary>
public enum IeBasicTransport
{
    /// <summary>Giao tiếp qua UDP (thường dùng cho IE Basic).</summary>
    Udp,

    /// <summary>Giao tiếp qua TCP.</summary>
    Tcp
}

/// <summary>
/// Định dạng frame SLMP.
/// </summary>
public enum SlmpFrameFormat
{
    /// <summary>Khung 3E (Format 3E).</summary>
    Format3E,

    /// <summary>Khung 4E (Format 4E).</summary>
    Format4E
}

/// <summary>
/// Device code (vùng thiết bị) cấp ứng dụng.
/// </summary>
public enum DeviceCode
{
    /// <summary>Input (bit).</summary>
    X,
    /// <summary>Output (bit).</summary>
    Y,
    /// <summary>Internal relay (bit).</summary>
    M,
    /// <summary>Latch relay (bit).</summary>
    L,
    /// <summary>Link relay (bit/word tùy CPU).</summary>
    B,
    /// <summary>Step relay (bit).</summary>
    S,
    /// <summary>Data register (word).</summary>
    D,
    /// <summary>Link register (word).</summary>
    W,
    /// <summary>File register (word).</summary>
    R,
    /// <summary>Extended file register (word).</summary>
    ZR
}

/// <summary>
/// Tham số logic phía local (PC/sender) khi gửi SLMP.
/// </summary>
/// <param name="NetworkNo">Số mạng logic.</param>
/// <param name="ModuleIoNo">Địa chỉ I/O module logic (thường 0x03FF).</param>
/// <param name="MultidropNo">Số đa điểm (nếu dùng).</param>
/// <param name="StationNo">Số trạm logic.</param>
public readonly record struct IeBasicLocal(
    byte NetworkNo,
    ushort ModuleIoNo,
    byte MultidropNo,
    byte StationNo
);

/// <summary>
/// Cấu hình đầu xa: địa chỉ IP/Port và tham số logic đích cho SLMP.
/// </summary>
/// <param name="Host">Địa chỉ IP hoặc hostname PLC/module.</param>
/// <param name="Port">Cổng SLMP (ví dụ 5007).</param>
/// <param name="NetworkNo">Số mạng logic đích.</param>
/// <param name="ModuleIoNo">Địa chỉ I/O module logic đích.</param>
/// <param name="MultidropNo">Số đa điểm (nếu dùng).</param>
/// <param name="StationNo">Số trạm logic đích.</param>
public readonly record struct IeBasicRemote(
    string Host,
    int Port,
    byte NetworkNo,
    ushort ModuleIoNo,
    byte MultidropNo,
    byte StationNo
);

/// <summary>
/// Tùy chọn khởi tạo/kết nối IE Basic client.
/// </summary>
public sealed record IeBasicClientOptions(
    string Host,
    int Port,
    byte NetworkNo,
    ushort ModuleIoNo,
    byte MultidropNo,
    byte StationNo
)
{
    /// <summary>Transport: UDP hoặc TCP.</summary>
    public IeBasicTransport Transport { get; init; } = IeBasicTransport.Udp;

    /// <summary>Định dạng frame SLMP (3E/4E).</summary>
    public SlmpFrameFormat FrameFormat { get; init; } = SlmpFrameFormat.Format3E;

    /// <summary>Tham số logic phía local.</summary>
    public IeBasicLocal Local { get; init; } =
        new IeBasicLocal(0, 0x03FF, 0, 0);

    /// <summary>Timeout cho mỗi yêu cầu.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromMilliseconds(1000);

    /// <summary>Số lần retry khi lỗi tạm thời.</summary>
    public int RetryCount { get; init; } = 2;

    /// <summary>Tự động reconnect nếu mất liên kết.</summary>
    public bool AutoReconnect { get; init; } = true;

    /// <summary>
    /// Quy ước ghép cặp word trong DWord/Float (true: little‑endian word order).
    /// </summary>
    public bool LittleEndianWordOrder { get; init; } = true;
}

/// <summary>
/// Tùy chọn cho polling chu kỳ các vùng device.
/// </summary>
public sealed record PollOptions
{
    /// <summary>Khoảng thời gian giữa hai lần poll.</summary>
    public TimeSpan Interval { get; init; } = TimeSpan.FromMilliseconds(20);

    /// <summary>Danh sách vùng cần poll (device, địa chỉ bắt đầu, số lượng).</summary>
    public (DeviceCode Device, int Start, int Count)[] Areas { get; init; } = [];

    /// <summary>Giới hạn số word tối đa mỗi chu kỳ (để tránh gói quá lớn).</summary>
    public int MaxWordsPerCycle { get; init; } = 256;

    /// <summary>Gom vùng theo từng loại device để tối ưu số gói.</summary>
    public bool AlignAreasByDevice { get; init; } = true;
}

/// <summary>
/// Dữ liệu trả về sau mỗi chu kỳ polling.
/// </summary>
public sealed class PollUpdatedEventArgs : EventArgs
{
    /// <summary>Thời điểm khung dữ liệu được cập nhật.</summary>
    public DateTimeOffset Timestamp { get; init; }

    /// <summary>Mảng bit (nếu có vùng bit được cấu hình).</summary>
    public ReadOnlyMemory<bool>? Bits { get; init; }

    /// <summary>Mảng word (nếu có vùng word được cấu hình).</summary>
    public ReadOnlyMemory<ushort>? Words { get; init; }

    /// <summary>Mảng dword (nếu có vùng dword được cấu hình).</summary>
    public ReadOnlyMemory<uint>? DWords { get; init; }
}

/// <summary>
/// Trạng thái liên kết/SLMP để chẩn đoán và theo dõi.
/// </summary>
public sealed record IeBasicStatus
{
    /// <summary>Liên kết hiện “đang lên” (có phản hồi) hay không.</summary>
    public bool LinkUp { get; init; }

    /// <summary>Số lỗi liên tiếp gần nhất.</summary>
    public int ConsecutiveErrors { get; init; }

    /// <summary>End code SLMP của lỗi gần nhất (nếu có).</summary>
    public SlmpEndCode? LastEndCode { get; init; }

    /// <summary>Mô tả lỗi gần nhất (nếu có).</summary>
    public string? LastErrorText { get; init; }

    /// <summary>Thời gian phản hồi trung bình ước tính.</summary>
    public TimeSpan? AvgRtt { get; init; }

    /// <summary>Thời gian phản hồi lớn nhất quan sát.</summary>
    public TimeSpan? MaxRtt { get; init; }
}

/// <summary>
/// Thông tin nhận dạng CPU/module (nếu PLC cho phép truy vấn).
/// </summary>
public sealed record ModuleIdentity
{
    /// <summary>Dòng CPU (ví dụ iQ‑R, iQ‑F).</summary>
    public string? CpuSeries { get; init; }

    /// <summary>Model cụ thể (ví dụ R04ENCPU).</summary>
    public string? CpuModel { get; init; }

    /// <summary>Phiên bản firmware.</summary>
    public string? Firmware { get; init; }

    /// <summary>Nhà sản xuất (thường là Mitsubishi Electric).</summary>
    public string? Vendor { get; init; }

    /// <summary>Thông tin bổ sung khác (nếu có).</summary>
    public string? AdditionalInfo { get; init; }
}

/// <summary>
/// Mã lệnh SLMP rút gọn (có thể mở rộng khi hiện thực adapter).
/// </summary>
public enum SlmpCommand : ushort
{
    /// <summary>Đọc device theo dải liên tục (bit/word).</summary>
    ReadDevice = 0x0401,

    /// <summary>Ghi device theo dải liên tục (bit/word).</summary>
    WriteDevice = 0x1401,

    /// <summary>Đọc ngẫu nhiên nhiều điểm.</summary>
    ReadRandom = 0x0403,

    /// <summary>Ghi ngẫu nhiên nhiều điểm.</summary>
    WriteRandom = 0x1402,

    /// <summary>Node monitor / kiểm tra liên lạc.</summary>
    NodeMonitor = 0x0619,

    /// <summary>Truy vấn thông tin thiết bị/PLC.</summary>
    DeviceInfo = 0x0601
}

/// <summary>
/// Mã end code SLMP (hoàn thành/thất bại), gồm cả mã nội bộ ánh xạ lỗi transport/timeout.
/// </summary>
public enum SlmpEndCode : ushort
{
    /// <summary>Hoàn thành thành công.</summary>
    Completed = 0x0000,

    /// <summary>Lệnh không hợp lệ.</summary>
    InvalidCommand = 0xC059,

    /// <summary>Truy cập bị từ chối.</summary>
    AccessDenied = 0xC056,

    /// <summary>Lỗi phạm vi device.</summary>
    DeviceRangeError = 0xC051,

    /// <summary>Thiết bị bận.</summary>
    Busy = 0xCEE0,

    /// <summary>Quá thời gian chờ (gán nội bộ).</summary>
    Timeout = 0xFFFF,

    /// <summary>Lỗi transport/socket (gán nội bộ).</summary>
    TransportError = 0xFFFE,

    /// <summary>Lỗi không xác định (gán nội bộ).</summary>
    Unknown = 0xFFFD
}

/// <summary>
/// Phản hồi SLMP thô trả về từ <see cref="ICcLinkIeBasicClient.SendSlmpAsync"/>.
/// </summary>
public sealed record SlmpResponse
{
    /// <summary>End code của phản hồi.</summary>
    public SlmpEndCode EndCode { get; init; }

    /// <summary>Payload nhị phân trả về (theo frame 3E/4E).</summary>
    public ReadOnlyMemory<byte> Payload { get; init; }

    /// <summary>Thời điểm nhận gói.</summary>
    public DateTimeOffset Timestamp { get; init; }
}
