
#pragma once

#include <string>
#include <vector>
#include <memory>
#include <yaml-cpp/yaml.h>
#include "Components/Component.h"

namespace OX
{
    class GameObject
    {
    public:
        explicit GameObject(const std::string &name = "");

        ~GameObject();

        // Name
        void setName(const std::string &name);

        [[nodiscard]] const std::string &name() const;

        // Hierarchy
        [[nodiscard]] GameObject *parent() const;

        void addChild(std::unique_ptr<GameObject> child);

        std::unique_ptr<GameObject> removeChild(GameObject *child);

        [[nodiscard]] const std::vector<std::unique_ptr<GameObject> > &children() const;

        // Components
        void addComponent(std::unique_ptr<Component> comp);

        bool IsAncestorOf(const GameObject *other) const;


        void addExistingChild(GameObject *child);

        template<typename T>
        T *GetComponent() const;

        template<typename T>
        std::unique_ptr<Component> RemoveComponent();

        [[nodiscard]] const std::vector<std::unique_ptr<Component> > &components() const;

        // Serialization
        [[nodiscard]] YAML::Node Serialize() const;

        void Deserialize(const YAML::Node &node);

    private:
        std::string m_name;
        GameObject *m_parent = nullptr;
        std::vector<std::unique_ptr<GameObject> > m_children;
        std::vector<std::unique_ptr<Component> > m_components;
    };

    template<typename T>
    T *GameObject::GetComponent() const
    {
        for (auto &c: m_components) {
            if (auto ptr = dynamic_cast<T *>(c.get()))
                return ptr;
        }
        return nullptr;
    }

    template<typename T>
    std::unique_ptr<Component> GameObject::RemoveComponent()
    {
        auto it = std::find_if(m_components.begin(), m_components.end(),
                               [](const std::unique_ptr<Component> &c)
                               {
                                   return dynamic_cast<T *>(c.get()) != nullptr;
                               });
        if (it == m_components.end())
            return nullptr;

        auto out = std::move(*it);
        out->OnDetach();
        m_components.erase(it);
        return out;
    }
} // namespace OX
