#include "Object.h"
#include "../Components/Component.h"
#include "../Components/SpriteComponent.h"
#include "../Components/CameraComponent.h"
#include "../Components/LightComponent.h"
#include "../Components/TilemapComponent.h"
#include "../Components/TextComponent.h"
#include "../Components/ScriptComponent.h"
#include "../Components/ParticleComponent.h"
#include "../Components/AnimationComponent.h"
#include "../Components/AudioPlayerComponent.h"
#include "../Components/PathFollowerComponent.h"
#include "../Components/TextComponent.h"

#include "../core/utils/Logging.h"
#include "../utils/UID.h"

#include <algorithm>

std::unordered_set<int> Object::usedIDs;

Object::Object(const std::string &name)
    : name(name), localPosition(0.0f, 0.0f), localRotationDeg(0.0f), uid(), visable(true)
{
    if (usedIDs.count(uid.id))
    {
        Logger::LogWarning("Constructor: Duplicate ID %d detected. Assigning a new ID.", uid.id);
        uid.id = GetNextAvailableID();
    }
    usedIDs.insert(uid.id);
    parent = nullptr;
}

Object::Object(const Object &other)
    : name(other.name + " (Copy)"),
      localPosition(other.localPosition),
      localRotationDeg(other.localRotationDeg),
      visable(other.visable),
      layer(other.layer)
{
    this->uid = UID::Generate();

    this->parent = parent;
    this->isCopy = true;

    this->children.clear();

    for (const auto &comp : other.components)
    {
        if (comp)
        {
            auto clone = comp;
            clone->SetOwner(this);
            this->components.push_back(std::shared_ptr<Component>(clone));
        }
    }

    for (const auto &child : other.children)
    {
        auto childCopy = std::make_shared<Object>(*child);
        childCopy->SetParent(this);
        this->children.push_back(childCopy);
    }
}

Object::~Object()
{
    usedIDs.erase(uid.id);
}

void Object::SetParent(Object *newParent)
{
    if (parent)
        parent->RemoveChild(this);
    parent = newParent;
}

void Object::AddChild(std::shared_ptr<Object> child)
{
    child->SetParent(this);
    children.push_back(child);
}

void Object::RemoveChild(Object *child)
{
    children.erase(std::remove_if(children.begin(), children.end(),
                                  [child](const std::shared_ptr<Object> &obj)
                                  {
                                      return obj.get() == child;
                                  }),
                   children.end());
}

glm::vec2 Object::GetLocalPosition() const
{
    return localPosition;
}

void Object::SetLocalPosition(glm::vec2 pos)
{
    localPosition = pos;
}

glm::vec2 Object::GetWorldPosition() const
{
    if (parent)
    {
        float parentRotation = glm::radians(parent->GetWorldRotation());
        glm::vec2 rotated = glm::rotate(localPosition, parentRotation);
        return parent->GetWorldPosition() + rotated;
    }
    return localPosition;
}

void Object::SetWorldPosition(const core::types::Vec2 &worldPos)
{
    if (Object *parent = GetParent())
    {
        core::types::Vec2 parentPos = parent->GetWorldPosition();
        core::types::Vec2 offset = worldPos - parentPos;

        float parentDeg = parent->GetWorldRotation();
        float rad = -parentDeg * (PI / 180.0f);
        float c = std::cos(rad);
        float s = std::sin(rad);

        core::types::Vec2 local{
            offset.x * c - offset.y * s,
            offset.x * s + offset.y * c};

        SetLocalPosition(local);
    }
    else
    {
        SetLocalPosition(worldPos);
    }
}

float Object::GetLocalRotation() const
{
    return localRotationDeg;
}

void Object::SetLocalRotation(float deg)
{
    localRotationDeg = deg;
}

float Object::GetWorldScale() const
{
    return 1.0f;
}

float Object::GetWorldRotation() const
{
    if (parent)
        return parent->GetWorldRotation() + localRotationDeg;
    return localRotationDeg;
}

bool Object::GetVisable() const
{
    return visable;
}

void Object::SetVisable(bool state)
{
    visable = state;
}

const std::string &Object::GetName() const { return name; }
void Object::SetName(const std::string &n) { name = n; }
std::vector<std::shared_ptr<Object>> &Object::GetChildren() { return children; }
Object *Object::GetParent() const
{
    if (!this)
    {
        Logger::LogError("Object::GetParent() called on nullptr");
        return nullptr;
    }
    return parent;
}

void Object::Save(YAML::Emitter &out) const
{

    out << YAML::BeginMap;
    out << YAML::Key << "name" << YAML::Value << name;
    out << YAML::Key << "uid" << YAML::Value << uid.uuid;
    out << YAML::Key << "id" << YAML::Value << uid.id;
    out << YAML::Key << "position" << YAML::Flow << YAML::BeginSeq << localPosition.x << localPosition.y << YAML::EndSeq;
    out << YAML::Key << "rotation" << YAML::Value << localRotationDeg;
    out << YAML::Key << "layer" << YAML::Value << layer;
    out << YAML::Key << "visable" << YAML::Value << visable;

    out << YAML::Key << "isPrefab" << YAML::Value << isPrefab;
    out << YAML::Key << "prefabUAID" << YAML::Value << prefabUAID;

    out << YAML::Key << "components" << YAML::Value << YAML::BeginSeq;
    for (const auto &comp : components)
    {
        comp->Save(out);
    }
    out << YAML::EndSeq;

    out << YAML::Key << "children" << YAML::Value << YAML::BeginSeq;
    for (const auto &child : children)
        child->Save(out);
    out << YAML::EndSeq;

    out << YAML::EndMap;
}

void Object::Load(const YAML::Node &node)
{

    name = node["name"].as<std::string>();
    uid.uuid = node["uid"] ? node["uid"].as<std::string>() : GenerateUUID();
    int loadedID = node["id"] ? node["id"].as<int>() : 0;

    auto pos = node["position"];
    if (pos && pos.IsSequence() && pos.size() == 2)
        localPosition = {pos[0].as<float>(), pos[1].as<float>()};

    localRotationDeg = node["rotation"] ? node["rotation"].as<float>() : 0.0f;
    layer = node["layer"] ? node["layer"].as<int>() : 0;
    visable = node["visable"] ? node["visable"].as<bool>() : true;

    isPrefab = node["isPrefab"] ? node["isPrefab"].as<bool>() : false;
    prefabUAID = node["prefabUAID"] ? node["prefabUAID"].as<UAssetID>() : -1;

    if (isPrefab && prefabUAID != -1)
    {
        auto asset = AssetManager::GetAssetByID(prefabUAID);
        if (asset && asset->type == AssetType::Prefab)
        {
            auto prefabInfo = dynamic_cast<const PrefabAssetInfo *>(asset);
            if (prefabInfo)
            {
                auto prefabInstance = prefabInfo->Instantiate();
                if (prefabInstance)
                {
                    *this = *prefabInstance;
                    parent = nullptr;
                    SetPrefab(true);
                    SetPrefabUAID(prefabUAID);
                }
                else
                {
                    Logger::LogError("[Object::Load] Failed to instantiate prefab UAID %llu", prefabUAID);
                }
            }
            else
            {
                Logger::LogError("[Object::Load] Asset was not a valid PrefabAssetInfo");
            }
        }
        else
        {
            Logger::LogError("[Object::Load] Could not find prefab asset for UAID %llu", prefabUAID);
        }
    }


    components.clear();
    if (node["components"])
    {
        for (const auto &compNode : node["components"])
        {
            try
            {
                std::string type = compNode["type"].as<std::string>();

                if (type == "SpriteComponent")
                    AddComponent<SpriteComponent>()->Load(compNode);
                else if (type == "CameraComponent")
                    AddComponent<CameraComponent>()->Load(compNode);
                else if (type == "LightComponent")
                    AddComponent<LightComponent>()->Load(compNode);
                else if (type == "TilemapComponent")
                    AddComponent<TilemapComponent>()->Load(compNode);
                else if (type == "TextComponent")
                    AddComponent<TextComponent>()->Load(compNode);
                else if (type == "ScriptComponent")
                    AddComponent<ScriptComponent>()->Load(compNode);
                else if (type == "ParticleComponent")
                    AddComponent<ParticleComponent>()->Load(compNode);
                else if (type == "AnimationComponent")
                    AddComponent<AnimationComponent>()->Load(compNode);
                else if (type == "AudioPlayerComponent")
                    AddComponent<AudioPlayerComponent>()->Load(compNode);
                else if (type == "PathFollowerComponent")
                    AddComponent<PathFollowerComponent>()->Load(compNode);
                else if (type == "TextComponent")
                    AddComponent<TextComponent>()->Load(compNode);
                else
                {
                    Logger::LogError("Invalid Componenet type '%s' found in component '%s'(%d)", type.c_str(), name.c_str(), uid.id);
                }
            }
            catch (const std::exception &e)
            {
                Logger::LogError("Failed to load '%s' (ID: %d). %s", name.c_str(), uid.id, e.what());
            }
        }
    }

    children.clear();
    if (node["children"])
    {
        for (const auto &childNode : node["children"])
        {
            auto child = std::make_shared<Object>("Child");
            child->Load(childNode);
            AddChild(child);
        }
    }
}
