// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once

#include <QObject>
#include <QDateTime>
#include <QList>
#include <QString>
#include "iloadadapter.h"

class Thing;
class ThingManager;

/*!
 * \brief Adaptateur pour chauffe-eau ou tout relais N paliers (interface relay-stages).
 *
 * Pilote en production N Things powerswitch nymea : \c m_relayMapping[stage] contient
 * la liste des ThingIds à mettre ON pour ce palier (les autres sont mis OFF).
 *
 * Exemple — chauffe-eau 2400W, 2 résistances Waveshare :
 *   stage 0 : {} → A=OFF, B=OFF
 *   stage 1 : {"thingId-A"} → A=ON, B=OFF  (1200 W)
 *   stage 2 : {"thingId-A", "thingId-B"} → A=ON, B=ON  (2400 W)
 *
 * \invariant applyAction() rejette silencieusement toute action dont \c reason est vide.
 * \invariant applyAction() applique les verrous anti-rebond \c minOnS / \c minOffS
 *   SAUF si \c action.force == true (réservé L2 watchdog).
 * \invariant Le stage est écrêté à [0, stages().size()-1] avant envoi matériel.
 * \invariant Seul le kind Stage est traité ; les autres kinds retournent sans effet.
 */
class EcsRelayAdapter : public QObject, public ILoadAdapter
{
    Q_OBJECT
public:
    /*!
     * \brief Constructeur.
     * \param thingManager  Gestionnaire nymea pour résoudre les ThingIds en Things.
     * \param id            Identifiant logique de la charge (ThingId de l'objet ECS dans nymea,
     *                      ou identifiant arbitraire unique pour le mock).
     * \param label         Nom lisible affiché dans les logs et l'app.
     * \param stages        Puissances en W par palier, index 0 = off : [0, 1200, 2400].
     * \param relayMapping  relayMapping[i] = liste de ThingIds powerswitch ON pour le palier i.
     * \param minOnS        Durée minimale ON (s) — anti-rebond.
     * \param minOffS       Durée minimale OFF (s) — anti-rebond.
     * \param priority      Rang dans le waterfall (OPTIMIZER_PROTOCOL §5) : valeur plus BASSE
     *                      = servi en premier (rang 1 = premier servi).
     * \param parent        Propriétaire Qt.
     */
    explicit EcsRelayAdapter(ThingManager *thingManager,
                              const QString &id,
                              const QString &label,
                              const QList<int> &stages,
                              const QList<QList<QString>> &relayMapping,
                              int minOnS,
                              int minOffS,
                              int priority,
                              QObject *parent = nullptr);

    /*!
     * \brief Description statique de la charge.
     * \return LoadDescriptor avec adapter="relay-stages", stages, minOnS/minOffS, priority.
     */
    LoadDescriptor descriptor() const override;

    /*!
     * \brief Télémétrie runtime : puissance mesurée, stage courant, lastSwitch.
     * \return LoadTelemetry avec currentPowerW issu de la somme des Things actifs.
     */
    LoadTelemetry  telemetry()   const override;

    /*!
     * \brief Construit l'entrée loads[] §5 du SurplusContext.
     * \param now Temps de cycle (\c ctx.timestamp) — source unique pour minStage/maxStage.
     * \return LoadContext incluant declared, limits et télémétrie ECS (stage, currentPowerW,
     *   lastSwitch, et la fenêtre de verrou \c minStage/maxStage évaluée à \p now).
     */
    LoadContext    toLoadContext(const QDateTime &now) const override;

    /*!
     * \brief Applique un changement de palier sur les relais.
     *
     * \param action LoadAction de kind Stage. Autres kinds : retour sans effet.
     * \param now    Temps de cycle (\c ctx.timestamp) — MÊME source que toLoadContext(),
     *   utilisée pour l'évaluation des verrous et l'estampille \c m_lastSwitch.
     * \return L'action après écrêtage (stage borné à [0, stages.size()-1]).
     *
     * \invariant Si \c action.reason est vide → retour sans effet (log warning).
     * \invariant Si verrous anti-rebond actifs ET \c action.force == false → retour sans effet (log).
     * \invariant Si \c action.force == true → bypass verrous (L2 watchdog uniquement).
     * \invariant Toute modification de stage met à jour \c m_lastSwitch (= \p now).
     */
    LoadAction applyAction(const LoadAction &action, const QDateTime &now) override;

    /*! \brief Stage courant (0 = off). */
    int currentStage() const { return m_currentStage; }

private:
    /*!
     * \brief Fenêtre de paliers autorisée à l'instant \p now par les verrous minOn/minOff.
     * \param now      Temps de cycle.
     * \param[out] minStage Plancher : palier courant si minOn non écoulé (puissance engagée
     *   non-coupable), sinon 0.
     * \param[out] maxStage Plafond : 0 si à l'arrêt et minOff non écoulé (redémarrage interdit),
     *   sinon le palier le plus haut.
     */
    void lockWindow(const QDateTime &now, int &minStage, int &maxStage) const;

    bool lockActive(int newStage, const QDateTime &now) const;
    void applyRelayStage(int stage);

    ThingManager            *m_thingManager;
    QString                  m_id;
    QString                  m_label;
    QList<int>               m_stages;        //!< Puissances W par palier, [0]=off.
    QList<QList<QString>>    m_relayMapping;  //!< ThingIds ON par palier.
    int                      m_minOnS;
    int                      m_minOffS;
    int                      m_priority;

    int                      m_currentStage = 0;
    QDateTime                m_lastSwitch;    //!< Dernier changement de palier (null = jamais).
    QDateTime                m_lastActionAt;
};
