#include "NodeTextureBuffer.hpp"

#include "MayaFlux/Buffers/BufferProcessingChain.hpp"
#include "MayaFlux/Buffers/Shaders/RenderProcessor.hpp"
#include "MayaFlux/Kinesis/GeometryPrimitives.hpp"
#include "MayaFlux/Portal/Graphics/TextureLoom.hpp"

#include "MayaFlux/Journal/Archivist.hpp"

namespace MayaFlux::Buffers {

NodeTextureBuffer::NodeTextureBuffer(
    std::shared_ptr<Nodes::GpuSync::TextureNode> node,
    std::string binding_name)
    : VKBuffer(
          calculate_quad_vertex_size(),
          Usage::VERTEX,
          Kakshya::DataModality::VERTEX_POSITIONS_3D)
    , m_texture_node(std::move(node))
    , m_binding_name(std::move(binding_name))
{
    RenderConfig default_config;
    default_config.vertex_shader = "texture.vert.spv";
    default_config.fragment_shader = "texture.frag.spv";
    default_config.default_texture_binding = "texSampler";
    default_config.topology = Portal::Graphics::PrimitiveTopology::TRIANGLE_STRIP;
    set_default_render_config(default_config);

    if (!m_texture_node) {
        error<std::invalid_argument>(
            Journal::Component::Buffers,
            Journal::Context::Init,
            std::source_location::current(),
            "Cannot create NodeTextureBuffer with null TextureNode");
    }

    MF_INFO(Journal::Component::Buffers, Journal::Context::Init,
        "Created NodeTextureBuffer '{}' for {}x{} texture ({} bytes)",
        m_binding_name,
        m_texture_node->get_width(),
        m_texture_node->get_height(),
        get_size_bytes());
}

void NodeTextureBuffer::setup_processors(ProcessingToken token)
{
    auto self = std::dynamic_pointer_cast<NodeTextureBuffer>(shared_from_this());

    generate_fullscreen_quad();

    auto& loom = Portal::Graphics::get_texture_manager();
    m_gpu_texture = loom.create_2d(
        m_texture_node->get_width(),
        m_texture_node->get_height(),
        Portal::Graphics::ImageFormat::RGBA32F,
        nullptr,
        1);

    if (!m_gpu_texture) {
        error<std::runtime_error>(
            Journal::Component::Buffers,
            Journal::Context::Init,
            std::source_location::current(),
            "Failed to create GPU texture for NodeTextureBuffer '{}'",
            m_binding_name);
    }

    m_texture_processor = std::make_shared<NodeTextureProcessor>();
    m_texture_processor->set_processing_token(token);

    m_texture_processor->bind_texture_node(
        m_binding_name,
        m_texture_node,
        m_gpu_texture);

    auto chain = get_processing_chain();
    if (!chain) {
        chain = std::make_shared<BufferProcessingChain>();
        set_processing_chain(chain);
    }
    chain->set_preferred_token(token);

    set_default_processor(m_texture_processor);

    MF_INFO(Journal::Component::Buffers, Journal::Context::Init,
        "NodeTextureBuffer '{}' ready: staging={} bytes, GPU texture={}x{}",
        m_binding_name,
        get_size_bytes(),
        m_texture_node->get_width(),
        m_texture_node->get_height());
}

void NodeTextureBuffer::setup_rendering(const RenderConfig& user_config)
{
    if (!user_config.vertex_shader.empty()) {
        m_render_config.vertex_shader = user_config.vertex_shader;
    }
    if (!user_config.fragment_shader.empty()) {
        m_render_config.fragment_shader = user_config.fragment_shader;
    }
    if (!user_config.default_texture_binding.empty()) {
        m_render_config.default_texture_binding = user_config.default_texture_binding;
    }
    if (user_config.topology != Portal::Graphics::PrimitiveTopology::TRIANGLE_STRIP) {
        MF_WARN(Journal::Component::Buffers, Journal::Context::BufferProcessing,
            "NodeTextureBuffer currently only supports TRIANGLE_STRIP topology. Ignoring provided topology.");
    } else {
        if (user_config.topology != m_render_config.topology) {
            m_render_config.topology = user_config.topology;
        }
    }
    m_render_config.target_window = user_config.target_window;

    if (!user_config.additional_textures.empty()) {
        for (const auto& [name, texture] : user_config.additional_textures) {
            m_render_config.additional_textures.emplace_back(name, texture);
        }
    }

    if (!m_render_processor) {
        ShaderConfig shader_config { m_render_config.vertex_shader };
        shader_config.bindings[m_render_config.default_texture_binding] = ShaderBinding(
            0, 1, vk::DescriptorType::eCombinedImageSampler);

        uint32_t binding_index = 1;
        for (const auto& [name, _] : m_render_config.additional_textures) {
            shader_config.bindings[name] = ShaderBinding(
                1, binding_index++, vk::DescriptorType::eCombinedImageSampler);
        }

        m_render_processor = std::make_shared<RenderProcessor>(shader_config);
    }

    m_render_processor->set_fragment_shader(m_render_config.fragment_shader);
    m_render_processor->set_target_window(m_render_config.target_window, std::dynamic_pointer_cast<VKBuffer>(shared_from_this()));
    m_render_processor->set_primitive_topology(m_render_config.topology);

    m_render_processor->bind_texture(m_render_config.default_texture_binding, get_gpu_texture());

    for (const auto& [name, texture] : m_render_config.additional_textures) {
        m_render_processor->bind_texture(name, texture);
    }

    get_processing_chain()->add_final_processor(m_render_processor, shared_from_this());

    MF_INFO(Journal::Component::Buffers, Journal::Context::Init,
        "NodeTextureBuffer '{}' rendering configured: shader={}, topology={}",
        m_binding_name, m_render_config.fragment_shader,
        static_cast<int>(m_render_config.topology));
}

size_t NodeTextureBuffer::calculate_buffer_size(
    const std::shared_ptr<Nodes::GpuSync::TextureNode>& node)
{
    if (!node) {
        return 0;
    }

    size_t size = static_cast<size_t>(node->get_width())
        * static_cast<size_t>(node->get_height())
        * 4
        * sizeof(float);

    if (size == 0) {
        MF_WARN(Journal::Component::Buffers, Journal::Context::BufferManagement,
            "TextureNode has zero dimensions. Using minimum buffer size.");
        return 4096;
    }

    return size;
}

void NodeTextureBuffer::generate_fullscreen_quad()
{
    auto geo = Kinesis::generate_quad();
    m_vertex_bytes.resize(geo.vertices.size() * sizeof(Nodes::TextureQuadVertex));
    std::memcpy(m_vertex_bytes.data(), geo.vertices.data(), m_vertex_bytes.size());
    set_vertex_layout(geo.layout);

    MF_DEBUG(Journal::Component::Buffers, Journal::Context::BufferProcessing,
        "NodeTextureBuffer: generated fullscreen quad");
}

size_t NodeTextureBuffer::calculate_quad_vertex_size()
{
    return 4 * sizeof(Nodes::TextureQuadVertex);
}

} // namespace MayaFlux::Buffers
