#pragma once

#include "core/AudioEngine.h"

#include <QWidget>
#include <QMap>
#include <QPointer>
#include <QSet>
#include <QStringList>
#include <QVector>

class QComboBox;
class QPushButton;
class QScrollArea;
class QTimer;
class QVBoxLayout;
class QHBoxLayout;
class QGridLayout;

namespace AetherSDR {
class ContainerManager;
class ContainerWidget;
} // namespace AetherSDR

namespace AetherSDR {

class SliceModel;
class RxApplet;
class SMeterWidget;
class CrossNeedleMeterApplet;
class CrossNeedleMeterWidget;
class TunerApplet;
class AmpApplet;
class DemoApplet;
class AcomApplet;
class SpeApplet;
class VkampApplet;
class TxApplet;
class PhoneCwApplet;
class PhoneApplet;
class EqApplet;
class WaveApplet;
class AetherClockApplet;
class MiniPanApplet;
class ClientEqApplet;
class ClientCompApplet;
class ClientGateApplet;
class ClientDeEssApplet;
class ClientTubeApplet;
class ClientPuduApplet;
class ClientReverbApplet;
class ClientRxDspApplet;
class ClientChainApplet;
class CatControlApplet;
class DaxApplet;
class TciApplet;
class DaxIqApplet;
class AntennaGeniusApplet;
class ShackSwitchApplet;
class MeterApplet;
class ProfileSwitcherApplet;
class HealthApplet;
class MqttApplet;
class KiwiSdrApplet;
class FavoritesPickerDialog;
#ifdef HAVE_RADE
class RadeApplet;
#endif

// AppletPanel — right-side panel with a row of toggle buttons at the top,
// an S-Meter gauge below them, and a scrollable stack of applets.
// Multiple applets can be visible simultaneously. Applets can be reordered
// by dragging their title bars (QDrag with custom MIME type).
class AppletPanel : public QWidget {
    Q_OBJECT

public:
    explicit AppletPanel(QWidget* parent = nullptr);

    void setSlice(SliceModel* slice);
    void setAntennaList(const QStringList& ants);
    void setMaxSlices(int maxSlices);
    void clearSliceButtons();
    void updateSliceButtons(const QList<SliceModel*>& slices, int activeSliceId);

    RxApplet*     rxApplet()      { return m_rxApplet; }
    SMeterWidget* sMeterWidget()  { return m_sMeter; }
    CrossNeedleMeterWidget* crossNeedleMeterWidget() const;
    void setMeterTxValues(float forwardWatts, float swr);
    void setStandardMeterTxValues(float forwardWatts, float swr);
    void setStandardRadioMeterTxValues(float forwardWatts,
                                       float forwardWattsInstant,
                                       float swr);
    void setCrossNeedleDirectionalValues(float forwardWatts,
                                         float reflectedWatts,
                                         float swr,
                                         bool reflectedPowerMeasured);
    void setMeterTransmitting(bool transmitting);
    void setMeterPowerScale(int maxWatts, bool amplifierActive);
    TunerApplet*  tunerApplet()   { return m_tunerApplet; }
    AmpApplet*    ampApplet()     { return m_ampApplet; }
    DemoApplet*   demoApplet()    { return m_demoApplet; }
    AcomApplet*   acomApplet()    { return m_acomApplet; }
    SpeApplet*    speApplet()     { return m_speApplet; }
    VkampApplet*  vkampApplet()   { return m_vkampApplet; }
    TxApplet*       txApplet()       { return m_txApplet; }
    PhoneCwApplet*  phoneCwApplet()  { return m_phoneCwApplet; }
    PhoneApplet*    phoneApplet()    { return m_phoneApplet; }
    EqApplet*       eqApplet()       { return m_eqApplet; }
    WaveApplet*     waveApplet() const { return m_waveApplet; }
    AetherClockApplet* aetherClockApplet() const { return m_aetherClockApplet; }
    MiniPanApplet*  miniPanApplet() const { return m_miniPanApplet; }
    // Phase 7.1: each side has its own CEQ applet — clientEqTxApplet()
    // is the original "ceq" tile bound to TX, clientEqRxApplet() is
    // the new "ceq-rx" tile bound to RX.  clientEqApplet() retained as
    // an alias for the TX-side instance so legacy call sites compile
    // unchanged.
    ClientEqApplet* clientEqApplet()   { return m_clientEqTxApplet; }
    ClientEqApplet* clientEqTxApplet() { return m_clientEqTxApplet; }
    ClientEqApplet* clientEqRxApplet() { return m_clientEqRxApplet; }
    // Phase 7.3: CMP-TX retains the legacy accessor; CMP-RX is the new
    // sibling tile bound to AudioEngine::clientCompRx().
    ClientCompApplet* clientCompApplet()   { return m_clientCompApplet; }
    ClientCompApplet* clientCompTxApplet() { return m_clientCompApplet; }
    ClientCompApplet* clientCompRxApplet() { return m_clientCompRxApplet; }
    // Phase 7.2: GATE-TX retains the legacy accessor name; GATE-RX is
    // the new sibling tile bound to AudioEngine::clientGateRx().
    ClientGateApplet* clientGateApplet()   { return m_clientGateApplet; }
    ClientGateApplet* clientGateTxApplet() { return m_clientGateApplet; }
    ClientGateApplet* clientGateRxApplet() { return m_clientGateRxApplet; }
    ClientDeEssApplet* clientDeEssApplet() { return m_clientDeEssApplet; }
    // Phase 7.4: TUBE-TX retains the legacy accessor; TUBE-RX is the new
    // sibling tile bound to AudioEngine::clientTubeRx().
    ClientTubeApplet* clientTubeApplet()   { return m_clientTubeApplet; }
    ClientTubeApplet* clientTubeTxApplet() { return m_clientTubeApplet; }
    ClientTubeApplet* clientTubeRxApplet() { return m_clientTubeRxApplet; }
    // Phase 7.5: PUDU-TX retains the legacy accessor; PUDU-RX is the new
    // sibling tile bound to AudioEngine::clientPuduRx().
    ClientPuduApplet* clientPuduApplet()   { return m_clientPuduApplet; }
    ClientPuduApplet* clientPuduTxApplet() { return m_clientPuduApplet; }
    ClientPuduApplet* clientPuduRxApplet() { return m_clientPuduRxApplet; }
    ClientReverbApplet* clientReverbApplet() { return m_clientReverbApplet; }
    ClientRxDspApplet*  clientRxDspApplet() { return m_clientRxDspApplet; }
    ClientChainApplet* clientChainApplet() { return m_clientChainApplet; }
    CatControlApplet* catControlApplet() { return m_catControlApplet; }
    DaxApplet*      daxApplet()      { return m_daxApplet; }
    TciApplet*      tciApplet()      { return m_tciApplet; }
    DaxIqApplet*    daxIqApplet()    { return m_daxIqApplet; }
    AntennaGeniusApplet* agApplet()  { return m_agApplet; }
    ShackSwitchApplet*   ssApplet()  { return m_ssApplet; }
    MeterApplet*  meterApplet()  { return m_meterApplet; }
    ProfileSwitcherApplet* profileSwitcherApplet() { return m_profApplet; }
    HealthApplet* healthApplet() { return m_healthApplet; }
    KiwiSdrApplet* kiwiSdrApplet() { return m_kiwiSdrApplet; }
#ifdef HAVE_RADE
    RadeApplet*   radeApplet()   { return m_radeApplet; }
#endif
#ifdef HAVE_MQTT
    MqttApplet*   mqttApplet()   { return m_mqttApplet; }
#endif

    // Show/hide the TUNE button and applet based on tuner presence.
    void setTunerVisible(bool visible);

    // Show/hide the AMP button and applet based on amplifier presence.
    void setAmpVisible(bool visible);

    // Show/hide the ACOM button and applet based on a direct ACOM amplifier
    // connection. Independent of setAmpVisible — a station can have both a
    // radio-relayed PGXL and a direct-connected ACOM amplifier at once.
    void setAcomVisible(bool visible);

    // Show/hide the SPE button and applet based on a direct SPE Expert
    // amplifier connection. Independent of setAmpVisible/setAcomVisible for
    // the same multi-amplifier-station reason.
    void setSpeVisible(bool visible);

    // Show/hide the VKAMP button and applet based on a direct VK3AMP
    // amplifier connection. Independent of setAmpVisible/setAcomVisible — a
    // station can have a radio-relayed PGXL, a direct ACOM, and a direct
    // VK3AMP all present at once, each fully independent hardware.
    void setVkampVisible(bool visible);

    // Show/hide the AG button and applet based on Antenna Genius presence.
    void setAgVisible(bool visible);

    // Show/hide the ShackSwitch applet based on device presence.
    void setShackSwitchVisible(bool visible);
    // DEMO's availability edge — its "hardware" is the connected radio
    // being the simulator (#4968 red-team B1).
    void setDemoVisible(bool visible);

    // Show/hide the PROF button and applet based on whether the connected radio
    // has an on-radio profile store (RadioCapabilities::hasProfiles).
    //
    // Unlike TUN/AMP/AG these are NOT markHardwareConditional() at
    // construction: PROF and DAX have always been in defaultButtonOrder() and
    // users have them in saved layouts, so the button starts available and only
    // a connected radio that reports the capability false takes it away. A
    // disconnected session keeps both, which is what the operator saw before.
    void setProfilesVisible(bool visible);
    // Capability passthrough to the Phone/CW applet — same shape as above.
    void setSelectableMicInputs(bool selectable);
    void setMicLevelMeterAvailable(bool available);
    void setRadioFilterWidths(const QList<int>& widthsHz);

    // Show/hide the DAX and DAX-IQ buttons and applets based on whether the
    // connected radio produces per-slice audio / per-pan IQ streams
    // (RadioCapabilities::hasDaxStreams). Same not-markHardwareConditional
    // reasoning as setProfilesVisible above.
    void setDaxStreamsVisible(bool visible);

    // Show/hide the EQ button and applet — the radio's own 8-band hardware
    // equalizer (RadioCapabilities::hasRadioSideDsp). Deliberately does NOT
    // touch the Aetherial RX/TX EQ tiles ("ceq" / "ceq-rx"), which are
    // host-side and are what a radio without a hardware EQ uses instead.
    void setHardwareEqVisible(bool visible);

    // Reset applet order to default
    void resetOrder();

    // Move the vertical scroll bar to the left edge of the panel.  When the
    // applet panel is docked on the left side of the panadapter, this puts
    // the scroll bar against the window edge rather than against the
    // waterfall.  Inner content stays LTR — only the scroll-area direction
    // flips.
    void setScrollBarOnLeft(bool onLeft);

    // Show / hide an applet by ID — used to drive visibility from
    // external state (e.g. the CHAIN widget mirrors DSP bypass onto
    // CEQ and CMP tile visibility).  No-op for unknown IDs.
    void setAppletVisible(const QString& id, bool visible);

    // Phase 7.1+: side filter for the PooDoo Audio sub-containers.
    // ClientChainApplet calls this when the TX/RX tab flips.  Sub-
    // containers tagged for the inactive side are hidden; tiles for
    // the active side are restored to their last visibility state
    // (driven by per-stage bypass).  TX-only tiles (DESS, REVERB)
    // hide entirely on RX.
    enum class PooDooSide { Tx, Rx };
    void setPooDooActiveSide(PooDooSide side);

    // Reorder the TX DSP sub-containers inside the "tx_dsp" parent to
    // mirror the CHAIN's current stage order.  Call whenever the user
    // drags to reorder the chain; the applet tiles follow.
    void setTxDspChainOrder(const QVector<AudioEngine::TxChainStage>& stages);
    void setRxDspChainOrder(const QVector<AudioEngine::RxChainStage>& stages);

    // ── Container system (Phase 4a groundwork, #1713) ───────────
    //
    // The panel owns a ContainerManager and a root sidebar container
    // so Phase 5+ code can nest new applets under "sidebar" without
    // waiting for the full AppletEntry → container migration to
    // finish.  The existing m_appletOrder plumbing remains primary
    // for all legacy applets — these accessors exist so new features
    // can opt in to the container system early.
    ContainerManager* containerManager() { return m_containerMgr; }

    // Canonical applet entry ids in current column order — the same ids the
    // Applet_<ID> keys and AppletOrder use.  The workspace controller feeds
    // these to the legacy-key migration (RFC #4887 phase 3).
    QStringList appletIds() const;

    // The widget palette (phase 6 field request): every applet with its
    // display title and functional category, in panel order.  The category
    // taxonomy lives in ONE table in the .cpp — reshuffling it is a
    // one-line-per-applet edit.
    struct AppletCatalogEntry {
        QString id;
        QString title;
        QString category;
    };
    QList<AppletCatalogEntry> appletCatalog() const;
    // Live hardware availability for one applet (the bar's own record).
    bool appletHardwareAvailable(const QString& id) const;

    // While true, recall-driven visibility changes do NOT write the
    // operator's Applet_<ID> preferences (red-team B2): a workspace switch
    // opens and closes applets in bulk, and persisting those as preference
    // changes rewrote keys the operator never touched — and, because
    // readLegacyLayoutState() feeds resetToClassic()/create-from-Classic,
    // one switch to a blank workspace destroyed Classic itself.  The
    // operator's own clicks (flag false) keep dual-writing as designed.
    void setRecallInProgress(bool on) { m_recallInProgress = on; }
    ContainerWidget*  rootSidebarContainer() { return m_rootSidebar; }

    // Global controls lock — disables wheel/mouse on sidebar sliders (#745)
    bool controlsLocked() const;
    void setControlsLocked(bool locked);

    // One entry per tile in the reorderable applet stack.  `widget`
    // is always a ContainerWidget; `titleBar` is its ContainerTitleBar
    // (kept as a raw pointer so AppletDropArea can compute drop-indicator
    // positions without touching the container internals).
    struct AppletEntry {
        QString id;
        QWidget* widget{nullptr};
        QWidget* titleBar{nullptr};
        QPushButton* btn{nullptr};
    };

    friend class AppletDropArea;

signals:
    // A canvas-mode container was dropped back onto the panel; the workspace
    // controller owns the transition (RFC #4887 phase 3).
    void canvasReturnRequested(const QString& appletId);


protected:
    bool eventFilter(QObject* obj, QEvent* ev) override;

private:
    void rebuildStackOrder();
    void saveOrder();
    int dropIndexFromY(int localY) const;
    void setScrollHandleActive(bool active);

    // ── Button-bar (active + drawer + hidden) ────────────────────────────────
    //
    // Bar model — three buckets, all driven by m_buttonOrder + m_hiddenButtons:
    //
    //   * Active (top kFavoriteCount entries of m_buttonOrder that aren't in
    //     m_hiddenButtons) → favorites row
    //   * Drawer (remaining shown entries, in m_buttonOrder order) → grid below
    //   * Hidden (m_hiddenButtons) → not in the bar at all; their applets
    //     are forced off (Applet_<id>=False) when first moved here
    //
    // Reordering in the picker updates m_buttonOrder; the drawer follows.
    struct BarButton {
        QString      id;     // canonical persistence id (e.g. "P/CW")
        QString      label;  // bar label (e.g. "P/CW", "VUDU")
        QString      tooltip; // hover description for the picker
        QPushButton* btn{nullptr};
        bool         hardwareAvailable{true};
        // Whether this applet opens by default when Applet_<id> is UNSET.
        //
        // Recorded because the re-enable path has to answer "should this be
        // open?" for an applet the operator has never touched, and the honest
        // answer is the applet's own default — not a blanket yes. PROF, DAX and
        // IQ are created closed; assuming otherwise force-opened all three on
        // the first connect and then persisted that. See
        // updateHardwareAvailability().
        bool         defaultOn{true};
    };

    void registerBarButton(const QString& id, const QString& label,
                           const QString& tooltip, QPushButton* btn,
                           bool defaultOn = true);
    void applyBarLayout();
    void setDrawerOpen(bool open);
    void openFavoritesPicker();
    void loadButtonLayout();
    void saveButtonLayout();
    QStringList defaultButtonOrder() const;
    void disableAppletForButton(const QString& id);
    void updateHardwareAvailability(const QString& id,
                                    const QString& appletKey,
                                    bool hardwareVisible);
    // updateHardwareAvailability() plus the container hide it deliberately
    // omits, with the operator's persisted open/closed preference preserved
    // across the round trip. Backs the capability-driven setXVisible() methods.
    void applyCapabilityVisibility(const QString& id,
                                   const QString& appletKey,
                                   bool available);
    void markHardwareConditional(const QString& id);
    void persistVuMeterSettings() const;
    void showStandardMeterContextMenu(QWidget* source, const QPoint& position);
    static const int kFavoriteCount = 5;

    ContainerManager* m_containerMgr{nullptr};
    ContainerWidget*  m_rootSidebar{nullptr};

    // S-Meter sits above the reorderable applet stack (not in
    // m_appletOrder).  Its ContainerWidget lives directly in the
    // sidebar root layout, toggled by m_vuBtn.
    ContainerWidget* m_sMeterContainer{nullptr};
    QPushButton*     m_vuBtn{nullptr};
    SMeterWidget*    m_sMeter{nullptr};
    CrossNeedleMeterApplet* m_crossNeedleApplet{nullptr};
    int              m_vuTxSelect{0};
    int              m_vuRxSelect{0};
    bool             m_vuPeakHoldEnabled{false};
    QString          m_vuPeakDecayRate{QStringLiteral("Medium")};
    QString          m_vuFaceTheme{QStringLiteral("aether-default")};
    RxApplet*    m_rxApplet{nullptr};
    TunerApplet* m_tunerApplet{nullptr};
    AmpApplet*   m_ampApplet{nullptr};
    DemoApplet*  m_demoApplet{nullptr};
    QPushButton* m_ampBtn{nullptr};
    AcomApplet*  m_acomApplet{nullptr};
    QPushButton* m_acomBtn{nullptr};
    SpeApplet*   m_speApplet{nullptr};
    QPushButton* m_speBtn{nullptr};
    VkampApplet* m_vkampApplet{nullptr};
    QPushButton* m_vkampBtn{nullptr};
    TxApplet*      m_txApplet{nullptr};
    PhoneCwApplet* m_phoneCwApplet{nullptr};
    PhoneApplet*   m_phoneApplet{nullptr};
    EqApplet*      m_eqApplet{nullptr};
    WaveApplet*    m_waveApplet{nullptr};
    AetherClockApplet* m_aetherClockApplet{nullptr};
    MiniPanApplet* m_miniPanApplet{nullptr};
    ClientEqApplet* m_clientEqTxApplet{nullptr};
    ClientEqApplet* m_clientEqRxApplet{nullptr};
    ClientCompApplet* m_clientCompApplet{nullptr};
    ClientCompApplet* m_clientCompRxApplet{nullptr};
    ClientGateApplet* m_clientGateApplet{nullptr};
    ClientGateApplet* m_clientGateRxApplet{nullptr};
    ClientDeEssApplet* m_clientDeEssApplet{nullptr};
    ClientTubeApplet* m_clientTubeApplet{nullptr};
    ClientTubeApplet* m_clientTubeRxApplet{nullptr};
    ClientPuduApplet* m_clientPuduApplet{nullptr};
    ClientPuduApplet* m_clientPuduRxApplet{nullptr};
    ClientReverbApplet* m_clientReverbApplet{nullptr};
    ClientRxDspApplet*  m_clientRxDspApplet{nullptr};
    ClientChainApplet* m_clientChainApplet{nullptr};
    CatControlApplet* m_catControlApplet{nullptr};
    DaxApplet*     m_daxApplet{nullptr};
    TciApplet*     m_tciApplet{nullptr};
    DaxIqApplet*   m_daxIqApplet{nullptr};
    AntennaGeniusApplet* m_agApplet{nullptr};
    ShackSwitchApplet*   m_ssApplet{nullptr};
    MeterApplet* m_meterApplet{nullptr};
    ProfileSwitcherApplet* m_profApplet{nullptr};
    HealthApplet* m_healthApplet{nullptr};
    KiwiSdrApplet* m_kiwiSdrApplet{nullptr};
#ifdef HAVE_RADE
    RadeApplet*  m_radeApplet{nullptr};
#endif
#ifdef HAVE_MQTT
    MqttApplet*  m_mqttApplet{nullptr};
#endif
    QPushButton* m_tuneBtn{nullptr};
    QPushButton* m_agBtn{nullptr};
    QPushButton* m_ssBtn{nullptr};
    QVBoxLayout* m_stack{nullptr};
    QScrollArea* m_scrollArea{nullptr};
    QTimer*      m_scrollDimTimer{nullptr};
    QWidget*     m_dropIndicator{nullptr};
    QPushButton* m_lockBtn{nullptr};   // controls-lock toggle (#745)

    // Button-bar widgets — both rows are QGridLayout with the same
    // column count so cell widths match between favorites and drawer.
    QWidget*     m_favRow{nullptr};
    QGridLayout* m_favLayout{nullptr};
    QWidget*     m_drawer{nullptr};
    QGridLayout* m_drawerLayout{nullptr};
    QPushButton* m_drawerToggleBtn{nullptr};
    QVector<BarButton> m_barButtons;
    QStringList  m_buttonOrder;     // shown buttons in user-chosen order
    QSet<QString> m_hiddenButtons;  // ids removed from the bar entirely
    QPointer<FavoritesPickerDialog> m_favoritesPicker;

    // Ordered list of applets (drag-reorderable)
    QVector<AppletEntry> m_appletOrder;
    bool m_recallInProgress{false};
    static const QStringList kDefaultOrder;
};

} // namespace AetherSDR
