#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>

template <typename Subject, typename State>
struct Observer {
    // First, we don't need to use polymorphism, so also virtual D'tor is redundant
    using OnUpdate = std::function<void(const Subject&, State)>;

    explicit Observer(OnUpdate fun)
        : _onUpdate(std::move(fun)) {}
    void update(const Subject& subject, State state) {
        std::invoke(_onUpdate, subject, state);
    }

private:
    OnUpdate _onUpdate;
};

class Browser;

enum class BrowserState {
    Closing,
    FocusChanged
};

using BrowserObserver = Observer<Browser, BrowserState>;

class Browser {
public:
    void attach(BrowserObserver* observer) { _observers.emplace(observer); }
    void detach(BrowserObserver* observer) { _observers.erase(observer); }
    void setFocus(bool focus) {
        if (std::exchange(_hasFocus, focus) != focus) {
            notify(BrowserState::FocusChanged);
        }
    }
    bool hasFocus() const { return _hasFocus; }

private:
    void notify(BrowserState state) const {
        std::ranges::for_each(_observers, [state, this](auto* observer) {
            observer->update(*this, state);
        });
    }

    bool _hasFocus{false};
    std::set<BrowserObserver*> _observers;
};

class Extension {
public:
    explicit Extension(const std::string& name)
        : _name(name) {}
    // D'tor is not needed neither the rule of 5

    // dispatch message
    void update(const Browser& browser, BrowserState state) {
        switch (state) {
        case BrowserState::Closing:
            return;
        case BrowserState::FocusChanged: {
            if (browser.hasFocus()) {
                displayExtension();
            }
            return;
        }
        }
    }

private:
    void displayExtension() {
        std::cout << "This is an extension: " << _name << '\n';
    }

    std::string _name;
};

int main() {
    std::unique_ptr<Browser> browser = std::make_unique<Browser>();
    Extension extension1("A");
    Extension extension2("B");
    Extension extension3("C");
    BrowserObserver observer1([&extension1](const Browser& browser, BrowserState state) {
        extension1.update(browser, state);
    });
    BrowserObserver observer2([&extension2](const Browser& browser, BrowserState state) {
        extension2.update(browser, state);
    });
    BrowserObserver observer3([&extension3](const Browser& browser, BrowserState state) {
        extension3.update(browser, state);
    });
    browser->attach(&observer1);
    browser->attach(&observer2);
    browser->attach(&observer3);
    browser->setFocus(true);
}