//
//  SwitchboardObject.hpp
//  SwitchboardSDK
//
//  Created by Balazs Kiss on 2025. 01. 22..
//

#pragma once

#include <switchboard/export.h>
#include <switchboard_core/EventEmitter.hpp>
#include <switchboard/SBAny.hpp>
#include <switchboard_core/SwitchboardCore.hpp>
#include <switchboard_core/Configuration.hpp>
#include <switchboard_core/ObjectTypeInfo.hpp>
#include <switchboard_core/Property.hpp>
#include <switchboard_core/Action.hpp>
#include <switchboard_core/Event.hpp>

#include <any>
#include <map>
#include <memory>
#include <string>
#include <switchboard/Result.hpp>

namespace switchboard {

/**
 * @brief SwitchboardObject interface.
 */
class SWITCHBOARDSDK_EXPORT SwitchboardObject : public EventEmitter {
public:

    /**
     * @brief Static type-info fragment for the SwitchboardObject base.
     *        Concrete subtypes should merge this with their own info so the
     *        exported schema includes the inherited properties and actions.
     *        Omits OBJECT_TYPE_FIELD_TYPE / OBJECT_TYPE_FIELD_SUBTYPE — those belong to concrete types.
     */
    static SBAnyMap getObjectTypeInfo() {
        return SBAnyMap({
            { OBJECT_TYPE_FIELD_PROPERTIES,
              SBAnyMap({
                  { "hasActions",
                    SBAnyMap({
                        { PROPERTY_FIELD_TYPE, PROPERTY_TYPE_BOOLEAN },
                        { PROPERTY_FIELD_READ_ONLY, true },
                        { PROPERTY_FIELD_DESCRIPTION, "Whether the node has actions." },
                    }) },
                  { "hasEvents",
                    SBAnyMap({
                        { PROPERTY_FIELD_TYPE, PROPERTY_TYPE_BOOLEAN },
                        { PROPERTY_FIELD_READ_ONLY, true },
                        { PROPERTY_FIELD_DESCRIPTION, "Whether the node has events." },
                    }) },
              }) },
            { OBJECT_TYPE_FIELD_ACTIONS,
              SBAnyMap({
                  { "getState",
                    SBAnyMap({
                        { ACTION_FIELD_DESCRIPTION,
                          "Returns the full state of the object including identity, configuration, and current property values." },
                    }) },
                  { "getConfiguration",
                    SBAnyMap({
                        { ACTION_FIELD_DESCRIPTION, "Returns the configuration schema for this object." },
                    }) },
                  { "getProperties",
                    SBAnyMap({
                        { ACTION_FIELD_DESCRIPTION, "Returns the property schema for this object." },
                    }) },
                  { "getActions",
                    SBAnyMap({
                        { ACTION_FIELD_DESCRIPTION, "Returns the action schema for this object." },
                    }) },
                  { "getEvents",
                    SBAnyMap({
                        { ACTION_FIELD_DESCRIPTION, "Returns the event schema for this object." },
                    }) },
              }) },
        });
    }

    /**
     * @brief SwitchboardObject constructor.
     *
     * @param objectType The type of the object.
     */
    explicit SwitchboardObject(const std::string& objectType);

    /**
     * @brief SwitchboardObject destructor.
     */
    ~SwitchboardObject() override;

    /**
     * @brief Gets the ID of the object.
     *
     * @return The ID of the object.
     */
    std::string getObjectID() const;

    /**
     * @brief Sets the ID of the object.
     *
     * @param objectID The ID to set.
     */
    void setObjectID(const std::string& objectID);

    /**
     * @brief Gets the URI of the object.
     *
     * @return The URI of the object.
     */
    std::string getURI();

    /**
     * @brief Gets the type of the object.
     *
     * @return The type of the object.
     */
    std::string getObjectType() const;

    /**
     * @brief Gets the subtype of the object.
     *
     * @return The subtype of the object, or nullopt if not set.
     */
    std::optional<std::string> getObjectSubtype() const;

    /**
     * @brief Sets the subtype of the object.
     *
     * @param subtype The subtype to set.
     */
    void setObjectSubtype(std::optional<std::string> subtype);

    /**
     * @brief Gets the parent object of the object.
     *
     * @return The parent object of the object.
     */
    SwitchboardObject* getParentObject() const;

    /**
     * @brief Sets the parent object of the object.
     *
     * @param parentObject The parent object to set.
     */
    void setParentObject(SwitchboardObject* parentObject);

    /**
     * @brief Sets a value on the object.
     *
     * @param key The key of the value.
     * @param value The value to set.
     *
     * @return A successful result if the value was set successfully, an error result otherwise.
     */
    virtual Result<void> setValue(const std::string& key, const SBAny& value);

    /**
     * @brief Gets a value from the object.
     *
     * @param key The key of the value.
     *
     * @return The value.
     */
    virtual Result<SBAny> getValue(const std::string& key);

    /**
     * @brief Calls an action on the object.
     *
     * @param actionName The name of the action.
     * @param params The parameters of the action.
     *
     * @return The result of the action.
     */
    virtual Result<SBAny> callAction(const std::string& actionName, const SBAnyMap& params);

    /**
     * @brief Registers a configuration for the object using a static schema and an instance value.
     * @details Copies @p schema and attaches @p value as the CONFIGURATION_FIELD_VALUE entry.
     *          Intended for use with NodeTypeInfo::getConfiguration() so the schema lives once
     *          per type and only the live value is supplied per instance.
     *
     * @param key The key of the configuration.
     * @param schema The static schema for the configuration (typically from NodeTypeInfo).
     * @param value The current/initial value for this instance.
     */
    void registerConfiguration(const std::string& key, const SBAnyMap& schema, const SBAny& value);

    /**
     * @brief Sets a configuration value for the object.
     *
     * @param key The key of the configuration.
     * @param value The value to set.
     */
    void setConfigurationValue(const std::string& key, const SBAny& value);

    /**
     * @brief Registers a property for the object.
     *
     * @details Copies @p schema and attaches @p getter / @p setter as the
     *          PROPERTY_FIELD_GETTER / PROPERTY_FIELD_SETTER entries if non-empty.
     *          Intended for use with NodeTypeInfo::getProperty() so the schema
     *          lives once per type and only the callbacks are supplied per instance.
     *
     * @param key The key of the property.
     * @param schema The static schema for the property (typically from NodeTypeInfo).
     * @param getter The instance-bound getter callable. May be empty for value-backed
     *               properties. Accepted forms: std::function<SBAny()> or
     *               std::function<Result<SBAny>()>.
     * @param setter The instance-bound setter callable. May be empty for read-only
     *               properties. Accepted forms: std::function<void(const SBAny&)> or
     *               std::function<Result<void>(const SBAny&)>.
     */
    void registerProperty(
        const std::string& key,
        const SBAnyMap& schema,
        const SBAny& getter = SBAny(),
        const SBAny& setter = SBAny()
    );

    /**
     * @brief Registers an action for the object.
     *
     * @details Copies @p schema and attaches @p handler as the ACTION_FIELD_HANDLER entry
     *          if non-empty. Intended for use with NodeTypeInfo::getAction() so the schema
     *          lives once per type and only the handler is supplied per instance.
     *
     * @param key The key of the action.
     * @param schema The static schema for the action (typically from NodeTypeInfo).
     * @param handler The instance-bound callable that implements the action. May be empty
     *                for schema-only registrations dispatched via callAction() override.
     */
    void registerAction(const std::string& key, const SBAnyMap& schema, ActionHandler handler = {});

    /**
     * @brief Registers an event for the object.
     *
     * @param eventName The name of the event.
     * @param eventInfo The information about the event.
     */
    void registerEvent(const std::string& eventName, const SBAnyMap& eventInfo);

private:
    class Impl;
    std::unique_ptr<Impl> impl;
};

}
