#include "pch.h"
#include "legacy_ui_overlay_services.h"

#include "app.h"
#include "node.h"
#include "node_input_box.h"
#include "node_message_box.h"
#include "node_dialog_cloud.h"
#include "node_popup_menu.h"
#include "node_progress_bar.h"
#include "node_settings.h"

#include <memory>
#include <unordered_map>

namespace pp::panopainter {

namespace {

template <class NodeT>
void attach_legacy_app_overlay_with_handle_or_fallback(
    App& app,
    const std::shared_ptr<NodeT>& node) noexcept
{
    if (!node) {
        return;
    }

    if (auto* anchor = app.layout[app.main_id]) {
        const auto overlay = open_legacy_overlay_node_with_handle(*anchor, node);
        if (overlay) {
            return;
        }
    }

    (void)attach_legacy_overlay_node(app, node);
}

struct LegacyOverlayContext {
    pp::ui::NodeHandle root_handle {};
    pp::ui::NodeLifetimeTree tree {};
    std::unique_ptr<pp::ui::UiOverlayLifetime> overlays {};
    std::unordered_map<Node*, pp::ui::NodeHandle> overlay_handles {};
};

using LegacyOverlayContextMap = std::unordered_map<Node*, LegacyOverlayContext>;

LegacyOverlayContextMap& overlay_contexts()
{
    static LegacyOverlayContextMap contexts {};
    return contexts;
}

pp::foundation::Result<LegacyOverlayContext*> get_overlay_context(Node& anchor, bool create_if_missing) noexcept
{
    auto* root = anchor.root();
    if (!root) {
        return pp::foundation::Result<LegacyOverlayContext*>::failure(
            pp::foundation::Status::invalid_argument("legacy overlay root is missing"));
    }

    auto& contexts = overlay_contexts();
    auto it = contexts.find(root);
    if (it == contexts.end()) {
        if (!create_if_missing) {
            return pp::foundation::Result<LegacyOverlayContext*>::failure(
                pp::foundation::Status::invalid_argument("legacy overlay registry is missing"));
        }
        it = contexts.try_emplace(root, LegacyOverlayContext {}).first;
    }

    auto& context = it->second;
    if (!context.overlays) {
        const auto root_handle = context.tree.create_root();
        if (!root_handle) {
            return pp::foundation::Result<LegacyOverlayContext*>::failure(root_handle.status());
        }

        context.root_handle = root_handle.value();
        context.overlays = std::make_unique<pp::ui::UiOverlayLifetime>(context.tree, context.root_handle);
    }

    return pp::foundation::Result<LegacyOverlayContext*>::success(&context);
}

Node* overlay_node_for_handle(
    const LegacyOverlayContext& context,
    pp::ui::NodeHandle overlay) noexcept
{
    for (auto& entry : context.overlay_handles) {
        if (entry.second == overlay) {
            return entry.first;
        }
    }
    return nullptr;
}

void forget_overlay_by_handle(LegacyOverlayContext& context, pp::ui::NodeHandle overlay) noexcept
{
    for (auto it = context.overlay_handles.begin(); it != context.overlay_handles.end();) {
        if (it->second == overlay) {
            it = context.overlay_handles.erase(it);
            break;
        } else {
            ++it;
        }
    }
}

} // namespace

void initialize_legacy_overlay_node(App& app, Node& node)
{
    node.set_manager(&app.layout);
    node.init();
    node.create();
    node.loaded();
}

void destroy_legacy_node(Node& node)
{
    node.destroy();
}

void detach_legacy_node_from_parent(Node& node)
{
    node.remove_from_parent();
}

void close_legacy_dialog_node(Node& node)
{
    const auto context = get_overlay_context(node, false);
    if (context) {
        auto it = context.value()->overlay_handles.find(&node);
        if (it != context.value()->overlay_handles.end()) {
            const auto status = close_legacy_overlay_node(node, it->second);
            if (status.ok()) {
                return;
            }
            context.value()->overlay_handles.erase(it);
        }
    }
    destroy_legacy_node(node);
}

void release_legacy_mouse_capture(Node& node) noexcept
{
    node.mouse_release();
}

void configure_legacy_popup_overlay(Node& node) noexcept
{
    node.m_mouse_ignore = false;
    node.m_flood_events = true;
    node.m_capture_children = false;
}

void activate_legacy_popup_overlay(Node& node) noexcept
{
    configure_legacy_popup_overlay(node);
    node.mouse_capture();
}

void close_legacy_popup_overlay(Node& node) noexcept
{
    release_legacy_mouse_capture(node);
    destroy_legacy_node(node);
}

void close_legacy_overlay_handle_ignoring_status(
    Node& anchor,
    pp::ui::NodeHandle overlay) noexcept
{
    (void)close_legacy_overlay_node(anchor, overlay);
}

void close_legacy_overlay_handle_and_reset(
    Node& anchor,
    pp::ui::NodeHandle& overlay) noexcept
{
    if (overlay.valid()) {
        close_legacy_overlay_handle_ignoring_status(anchor, overlay);
        overlay = {};
    }
}

void close_legacy_overlay_handles_and_reset(
    Node& anchor,
    pp::ui::NodeHandle& popup_overlay,
    pp::ui::NodeHandle& tick_overlay) noexcept
{
    close_legacy_overlay_handle_and_reset(anchor, popup_overlay);
    close_legacy_overlay_handle_and_reset(anchor, tick_overlay);
}

void close_legacy_overlay_handles_if_open(
    Node& anchor,
    const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,
    const pp::foundation::Result<pp::ui::NodeHandle>& tick_overlay) noexcept
{
    if (popup_overlay) {
        close_legacy_overlay_handle_ignoring_status(anchor, popup_overlay.value());
    }
    if (tick_overlay) {
        close_legacy_overlay_handle_ignoring_status(anchor, tick_overlay.value());
    }
}

void close_legacy_dialog_and_hide_keyboard(App& app, Node& node)
{
    close_legacy_dialog_node(node);
    app.hideKeyboard();
}

void close_legacy_popup_panel(
    Node& node,
    const std::function<void(Node*)>& on_close)
{
    release_legacy_mouse_capture(node);
    if (node.m_parent) {
        detach_legacy_node_from_parent(node);
    }
    if (on_close) {
        on_close(&node);
    }
}

pp::foundation::Status attach_legacy_overlay_node(
    App& app,
    const std::shared_ptr<Node>& node) noexcept
{
    if (!node) {
        return pp::foundation::Status::invalid_argument("legacy overlay node is null");
    }

    auto* root = app.layout[app.main_id];
    if (!root) {
        return pp::foundation::Status::invalid_argument("legacy overlay root is missing");
    }

    root->add_child(node);
    return pp::foundation::Status::success();
}

pp::foundation::Status attach_legacy_overlay_node_to_root(
    Node& anchor,
    const std::shared_ptr<Node>& node) noexcept
{
    if (!node) {
        return pp::foundation::Status::invalid_argument("legacy overlay node is null");
    }

    auto* root = anchor.root();
    if (!root) {
        return pp::foundation::Status::invalid_argument("legacy overlay root is missing");
    }

    root->add_child(node);
    return pp::foundation::Status::success();
}

pp::foundation::Result<pp::ui::NodeHandle> open_legacy_overlay_node_with_handle(
    Node& anchor,
    const std::shared_ptr<Node>& node,
    bool modal) noexcept
{
    if (!node) {
        return pp::foundation::Result<pp::ui::NodeHandle>::failure(
            pp::foundation::Status::invalid_argument("legacy overlay node is null"));
    }

    const auto context = get_overlay_context(anchor, true);
    if (!context) {
        return pp::foundation::Result<pp::ui::NodeHandle>::failure(
            context.status());
    }

    auto overlay = context.value()->overlays->open_dialog(modal);
    if (!overlay) {
        return pp::foundation::Result<pp::ui::NodeHandle>::failure(overlay.status());
    }

    const auto status = attach_legacy_overlay_node_to_root(anchor, node);
    if (!status.ok()) {
        (void)context.value()->overlays->close(overlay.value());
        return pp::foundation::Result<pp::ui::NodeHandle>::failure(status);
    }

    context.value()->overlay_handles[node.get()] = overlay.value();
    return pp::foundation::Result<pp::ui::NodeHandle>::success(overlay.value());
}

pp::foundation::Status close_legacy_overlay_node(Node& anchor, pp::ui::NodeHandle overlay) noexcept
{
    const auto context = get_overlay_context(anchor, false);
    if (!context) {
        return pp::foundation::Status::invalid_argument("legacy overlay registry is missing");
    }

    const auto status = context.value()->overlays->close(overlay);
    if (!status.ok()) {
        forget_overlay_by_handle(*context.value(), overlay);
        return status;
    }

    if (auto* raw_overlay = overlay_node_for_handle(*context.value(), overlay)) {
        destroy_legacy_node(*raw_overlay);
    }
    forget_overlay_by_handle(*context.value(), overlay);
    return status;
}

pp::foundation::Result<std::shared_ptr<NodePopupMenu>> add_legacy_popup_menu(
    App& app,
    const char* template_id,
    float x,
    float y,
    float rtl_anchor_width) noexcept
{
    if (!template_id) {
        return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::failure(
            pp::foundation::Status::invalid_argument("legacy popup template id is null"));
    }

    auto* template_root = app.layout[const_hash(template_id)];
    if (!template_root || template_root->m_children.empty()) {
        return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::failure(
            pp::foundation::Status::invalid_argument("legacy popup template is missing"));
    }

    auto popup = template_root->m_children[0]->clone<NodePopupMenu>();
    if (!popup) {
        return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::failure(
            pp::foundation::Status::invalid_argument("legacy popup clone failed"));
    }

    popup->update();
    if (auto* root = app.layout[app.main_id]) {
        if (YGNodeStyleGetDirection(root->y_node) == YGDirectionRTL) {
            x = x - popup->m_size.x + rtl_anchor_width;
        }
    }
    popup->SetPositioning(YGPositionTypeAbsolute);
    popup->SetPosition(x, y);

    const auto status = attach_legacy_overlay_node(app, popup);
    if (!status.ok()) {
        return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::failure(status);
    }

    return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::success(popup);
}

std::shared_ptr<NodeProgressBar> create_legacy_progress_dialog_overlay(
    App& app,
    const pp::app::AppProgressDialogPlan& plan)
{
    auto progress = make_legacy_overlay_node<NodeProgressBar>(app);
    progress->m_progress->SetWidthP(plan.progress_fraction);
    progress->m_title->set_text(plan.title.c_str());
    progress->m_total = plan.total;
    progress->m_count = plan.count;
    attach_legacy_app_overlay_with_handle_or_fallback(app, progress);
    return progress;
}

std::shared_ptr<NodeMessageBox> create_legacy_message_dialog_overlay(
    App& app,
    const pp::app::AppMessageDialogPlan& plan)
{
    auto message = make_legacy_overlay_node<NodeMessageBox>(app);
    message->m_title->set_text(plan.title.c_str());
    message->m_message->set_text(plan.message.c_str());
    message->btn_ok->m_text->set_text(plan.ok_caption.c_str());
    if (plan.show_cancel)
        message->btn_cancel->m_text->set_text(plan.cancel_caption.c_str());
    else
        close_legacy_dialog_node(*message->btn_cancel);
    attach_legacy_app_overlay_with_handle_or_fallback(app, message);
    return message;
}

std::shared_ptr<NodeInputBox> create_legacy_input_dialog_overlay(
    App& app,
    const pp::app::AppInputDialogPlan& plan)
{
    auto input = make_legacy_overlay_node<NodeInputBox>(app);
    input->m_title->set_text(plan.title.c_str());
    input->m_field_name->set_text(plan.field_name.c_str());
    input->btn_ok->m_text->set_text(plan.ok_caption.c_str());
    attach_legacy_app_overlay_with_handle_or_fallback(app, input);
    return input;
}

std::shared_ptr<NodeSettings> create_legacy_settings_dialog_overlay(
    App& app)
{
    auto settings = make_legacy_overlay_node<NodeSettings>(app);
    attach_legacy_app_overlay_with_handle_or_fallback(app, settings);
    return settings;
}

std::shared_ptr<NodeDialogCloud> create_legacy_cloud_browser_dialog_overlay(
    App& app)
{
    auto dialog = make_legacy_overlay_node<NodeDialogCloud>(app);
    attach_legacy_app_overlay_with_handle_or_fallback(app, dialog);
    return dialog;
}

} // namespace pp::panopainter
