// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef TRYDANMODBUSTCPMASTER_H
#define TRYDANMODBUSTCPMASTER_H

#include "trydanmodbusmaster.h"

#include <QHostAddress>
#include <QList>
#include <QPair>
#include <functional>

class ModbusTcpMaster;
class QModbusReply;

/*!
 * \brief Modbus TCP transport for the V2C Trydan (Étape 1).
 *
 * Wraps a libnymea-modbus ModbusTcpMaster.  All register reads are individual
 * FC3 transactions of exactly 2 registers — never batched — to avoid the
 * overlapping-window firmware bug of the V2C Trydan.
 * cf. modbus.py _read_register and AGENTS.md § Décodage lecture.
 *
 * Writes use FC6 (single-register, uint16, via ModbusTcpMaster::sendWriteRequest).
 *
 * Étape 2 (RTU) will add TrydanModbusRtuMaster on the same TrydanModbusMaster
 * interface without touching this file.
 */
class TrydanModbusTcpMaster : public TrydanModbusMaster
{
    Q_OBJECT

public:
    /*!
     * \param address  IP address of the charger
     * \param port     Modbus TCP port (default 502)
     * \param slaveId  Modbus unit ID (always 1 for Trydan)
     */
    explicit TrydanModbusTcpMaster(const QHostAddress &address,
                                   quint16 port,
                                   quint16 slaveId,
                                   QObject *parent = nullptr);
    ~TrydanModbusTcpMaster() override;

    /*! \brief Underlying TCP master — exposed so the plugin can update the host address. */
    ModbusTcpMaster *modbusTcpMaster() const;

    /*! \brief Update the target IP without reconnecting (call reconnectDevice() separately). */
    void setHostAddress(const QHostAddress &address);

    void connectDevice()    override;
    void disconnectDevice() override;
    bool initialize()       override;
    bool update()           override;

    void writePauseState(quint16 value)   override;
    void writeLock(quint16 value)         override;
    void writeIntensity(quint16 amps)     override;
    void writePauseDynamic(quint16 value) override;

private:
    // Callback type for a single 2-register read result
    using ReadCallback = std::function<void(bool ok, quint16 high, quint16 low)>;
    using ReadStep     = QPair<quint16, ReadCallback>;

    /*!
     * \brief Execute \a steps sequentially, one FC3 read per register pair.
     *
     * The sequence aborts on the first failed read; \a onDone receives false in
     * that case and the error counter is incremented.  On full success \a onDone
     * receives true and the error counter is reset.
     *
     * Sequential execution is required because concurrent reads from the same
     * slave on a Modbus TCP connection would not be incorrect per protocol, but
     * the firmware's overlapping register windows make ordering critical.
     */
    void runReadSequence(QList<ReadStep> steps, std::function<void(bool)> onDone);
    void doNextRead(QList<ReadStep> steps, int index, std::function<void(bool)> onDone);

    /*!
     * \brief Send an FC6 (Write Single Register) request and invoke \a callback.
     * FC6 is used for all writes per the V2C spec (modbus.py _write_register).
     * \param callback Receives (address, success) so callers can emit writeCompleted
     *                 with the originating register address, enabling action handlers
     *                 to distinguish expected writes from background writes.
     */
    void writeFC6(quint16 address, quint16 value,
                  std::function<void(quint16 addr, bool ok)> callback);

    void onConnectionStateChanged(bool connected);

    /*! \brief Increment the error counter and mark unreachable once it hits the limit. */
    void handleError();

    QList<ReadStep> buildInitSteps();
    QList<ReadStep> buildPollSteps();

    ModbusTcpMaster *m_modbusTcpMaster = nullptr;
    quint16          m_slaveId         = 1;
    int              m_errorCount      = 0;
    bool             m_updating        = false;
    bool             m_initializing    = false;

    // 3 consecutive failures → mark unreachable.  With a 5 s timeout and no
    // retry, 3 failed polls abort within 15 s — acceptable for HEMS decisions.
    static constexpr int k_errorLimit  = 3;
};

#endif // TRYDANMODBUSTCPMASTER_H
