#include "InteractiveObject.h"
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include <iostream>
#include <string>
#include <algorithm>
#include <Eigen/Geometry>

namespace ZL {
    extern const std::string textureUniformName;
}

namespace ZL {

    void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete) {
        if (isAnimating) return;
        AnimTask task;
        task.type       = AnimTask::Type::Move;
        task.startPos   = position;
        task.startRotY  = rotationY;
        task.startScale = scale;
        task.targetPos  = target;
        task.targetRotY = rotationY;
        task.targetScale = scale;
        task.durationMs  = durationSec * 1000.f;
        task.elapsedMs   = 0.f;
        task.onComplete  = std::move(onComplete);
        animTask    = std::move(task);
        isAnimating = true;
    }

    void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
        if (isAnimating) return;
        AnimTask task;
        task.type        = AnimTask::Type::Rotate;
        task.startPos    = position;
        task.startRotY   = rotationY;
        task.startScale  = scale;
        task.targetPos   = position;
        task.targetRotY  = targetRotY;
        task.targetScale = scale;
        task.durationMs  = durationSec * 1000.f;
        task.elapsedMs   = 0.f;
        task.onComplete  = std::move(onComplete);
        animTask    = std::move(task);
        isAnimating = true;
    }

    void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
        if (isAnimating) return;
        AnimTask task;
        task.type        = AnimTask::Type::Scale;
        task.startPos    = position;
        task.startRotY   = rotationY;
        task.startScale  = scale;
        task.targetPos   = position;
        task.targetRotY  = rotationY;
        task.targetScale = targetScale;
        task.durationMs  = durationSec * 1000.f;
        task.elapsedMs   = 0.f;
        task.onComplete  = std::move(onComplete);
        animTask    = std::move(task);
        isAnimating = true;
    }

    void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) {
        if (isAnimating) return;
        AnimTask task;
        task.type        = AnimTask::Type::Fade;
        task.startPos    = position;
        task.startRotY   = rotationY;
        task.startScale  = alpha;         // reuse startScale to hold start alpha
        task.targetPos   = position;
        task.targetRotY  = rotationY;
        task.targetScale = targetAlpha;   // reuse targetScale to hold target alpha
        task.durationMs  = durationSec * 1000.f;
        task.elapsedMs   = 0.f;
        task.onComplete  = std::move(onComplete);
        animTask    = std::move(task);
        isAnimating = true;
    }

    void InteractiveObject::update(int64_t deltaMs) {
        if (!isAnimating || !animTask) return;

        AnimTask& task = *animTask;
        task.elapsedMs += static_cast<float>(deltaMs);

        const float t = min(task.elapsedMs / task.durationMs, 1.0f);

        switch (task.type) {
            case AnimTask::Type::Move:
                position = task.startPos + (task.targetPos - task.startPos) * t;
                break;
            case AnimTask::Type::Rotate:
                rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
                break;
            case AnimTask::Type::Scale:
                scale = task.startScale + (task.targetScale - task.startScale) * t;
                break;
            case AnimTask::Type::Fade:
                alpha = task.startScale + (task.targetScale - task.startScale) * t;
                break;
        }

        if (t >= 1.0f) {
            // Snap to exact target
            position  = task.targetPos;
            rotationY = task.targetRotY;
            if (task.type == AnimTask::Type::Fade)
                alpha = task.targetScale;  // targetScale reused to hold targetAlpha for Fade
            else
                scale = task.targetScale;

            std::function<void()> cb = std::move(task.onComplete);
            animTask.reset();
            isAnimating = false;

            if (cb) cb();
        }
    }

    void InteractiveObject::drawDarklands(Renderer& renderer) const {
        if (!isActive || !loadedObject.textureDarklands) return;

        if (alpha < 0.999f) {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        }

        renderer.PushMatrix();
        renderer.TranslateMatrix(position);
        if (rotationY != 0.f) {
            renderer.TranslateMatrix(pivot);
            renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
            renderer.TranslateMatrix(-pivot);
        }
        if (scale != 1.f)
            renderer.ScaleMatrix(scale);
        renderer.RenderUniform1i(textureUniformName, 0);
        renderer.RenderUniform1f("uAlpha", alpha);
        glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
        renderer.DrawVertexRenderStruct(loadedObject.mesh);
        renderer.PopMatrix();

        renderer.RenderUniform1f("uAlpha", 1.0f);
    }

    void InteractiveObject::draw(Renderer& renderer) const {
        if (!isActive || !loadedObject.texture) return;

        if (alpha < 0.999f) {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        }

        renderer.PushMatrix();
        renderer.TranslateMatrix(position);
        if (rotationY != 0.f) {
            renderer.TranslateMatrix(pivot);
            renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
            renderer.TranslateMatrix(-pivot);
        }
        if (scale != 1.f)
            renderer.ScaleMatrix(scale);
        renderer.RenderUniform1i(textureUniformName, 0);
        renderer.RenderUniform1f("uAlpha", alpha);
        glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
        renderer.DrawVertexRenderStruct(loadedObject.mesh);
        renderer.PopMatrix();

        // Restore uAlpha so subsequent fog-shader users (character weapons, etc.) are not affected.
        renderer.RenderUniform1f("uAlpha", 1.0f);
    }

} // namespace ZL