#include "pch.h"
#include "app.h"
#include "node_panel_grid.h"
#include "node_icon.h"
#include "node_dialog_open.h"
#include "node_text.h"
#include "node_progress_bar.h"
#include "node_dialog_picker.h"
#include "node_panel_floating.h"
#include "app_core/about_menu.h"
#include "app_core/app_preferences.h"
#include "app_core/brush_ui.h"
#include "app_core/canvas_tool_ui.h"
#include "app_core/document_layer.h"
#include "app_core/document_canvas.h"
#include "app_core/document_export.h"
#include "app_core/file_menu.h"
#include "app_core/app_status.h"
#include "app_core/main_toolbar.h"
#include "app_core/tools_menu.h"
#include "legacy_app_preference_services.h"
#include "legacy_app_shell_services.h"
#include "legacy_brush_ui_services.h"
#include "legacy_canvas_tool_services.h"
#include "legacy_document_layer_services.h"
#include "legacy_history_services.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.h"
#include "serializer.h"
#include "font.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"

#include <vector>

namespace {

bool apply_brush_color_plan(App& app, glm::vec4 color, bool update_quick, bool update_color_panel)
{
    return pp::panopainter::apply_legacy_brush_color_plan(app, color, update_quick, update_color_panel);
}

bool apply_brush_texture_plan(App& app, pp::app::BrushUiTextureSlot slot, const std::string& path, const std::string& thumb)
{
    return pp::panopainter::apply_legacy_brush_texture_plan(app, slot, path, thumb);
}

bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
{
    return pp::panopainter::apply_legacy_brush_preset_plan(app, brush);
}

bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
{
    return pp::panopainter::apply_legacy_document_export_menu_plan(app, kind);
}

void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
{
    pp::panopainter::apply_legacy_file_menu_command(app, command);
}

std::shared_ptr<NodePopupMenu> add_menu_popup(
    App& app,
    const char* template_id,
    glm::vec2 position,
    float rtl_anchor_width)
{
    const auto popup = pp::panopainter::add_legacy_popup_menu(
        app,
        template_id,
        position.x,
        position.y,
        rtl_anchor_width);
    if (!popup) {
        LOG("Popup menu '%s' failed: %s", template_id ? template_id : "<null>", popup.status().message);
        return nullptr;
    }
    return popup.value();
}

pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
    pp::app::DocumentLayerMenuCommand command,
    App& app)
{
    const bool has_current_layer = app.layers && app.layers->m_current_layer;
    const int current_index = app.canvas && app.canvas->m_canvas
        ? app.canvas->m_canvas->m_current_layer_idx
        : 0;
    const int animation_duration = Canvas::I
        ? Canvas::I->anim_duration()
        : 0;
    const std::string current_name = has_current_layer
        ? app.layers->m_current_layer->m_label_text
        : std::string {};
    std::string lower_name;
    if (app.canvas && app.canvas->m_canvas && current_index > 0
        && current_index - 1 < static_cast<int>(app.canvas->m_canvas->m_layers.size()))
    {
        lower_name = app.canvas->m_canvas->m_layers[current_index - 1]->m_name;
    }

    const auto plan = pp::app::plan_document_layer_menu(
        command,
        has_current_layer,
        current_index,
        animation_duration,
        current_name,
        lower_name);
    if (plan)
        return plan.value();
    return {};
}

[[nodiscard]] bool should_open_tools_panel(const pp::app::ToolsPanelPlan& plan) noexcept
{
    return plan.action == pp::app::ToolsPanelAction::open_floating_panel;
}

void apply_tools_panel_chrome(NodePanelFloating& panel, const pp::app::ToolsPanelPlan& plan)
{
    if (plan.width > 0 && plan.height > 0) {
        panel.SetSize(static_cast<float>(plan.width), static_cast<float>(plan.height));
    } else {
        if (plan.width > 0)
            panel.SetWidth(static_cast<float>(plan.width));
        if (plan.height > 0)
            panel.SetHeight(static_cast<float>(plan.height));
    }
    if (plan.min_width > 0)
        panel.SetMinWidth(static_cast<float>(plan.min_width));
    if (plan.min_height > 0)
        panel.SetMinHeight(static_cast<float>(plan.min_height));
    panel.m_title->set_text(plan.title.data());
    panel.m_droppable = plan.droppable;
}

void execute_main_toolbar_plan(App& app, const pp::app::MainToolbarPlan& plan)
{
    pp::panopainter::execute_legacy_main_toolbar_plan(app, plan);
}

void execute_about_menu_plan(App& app, const pp::app::AboutMenuPlan& plan)
{
    pp::panopainter::execute_legacy_about_menu_plan(app, plan);
}

void execute_tools_menu_plan(App& app, const pp::app::ToolsMenuPlan& plan)
{
    pp::panopainter::execute_legacy_tools_menu_plan(app, plan);
}

void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenuPlan& plan)
{
    const auto status = pp::panopainter::execute_legacy_document_layer_menu_plan(app, plan);
    if (!status.ok())
        LOG("Layer menu action failed: %s", status.message);
}

void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan)
{
    const auto status = pp::panopainter::execute_legacy_document_layer_merge_plan(app, plan);
    if (!status.ok())
        LOG("Layer merge failed: %s", status.message);
}

void execute_document_layer_operation_plan(
    App& app,
    const pp::app::DocumentLayerOperationPlan& plan,
    const std::shared_ptr<class Layer>& pending_layer = nullptr)
{
    const auto status = pp::panopainter::execute_legacy_document_layer_operation_plan(app, plan, pending_layer);
    if (!status.ok())
        LOG("Layer operation failed: %s", status.message);
}

} // namespace

void App::title_update()
{
    if (auto docname = layout[main_id]->find<NodeText>("txt-docname"))
    {
        const auto title = pp::app::make_document_title(
            doc_name,
            canvas->m_canvas->m_unsaved,
            canvas->m_canvas->m_width);
        docname->set_text(title.c_str());
    }
    if (auto node = layout[main_id]->find<NodeText>("txt-dpi"))
    {
        const auto label = pp::app::make_dpi_label(zoom);
        node->set_text(label.c_str());
    }
}

void App::init_toolbar_main()
{
    if (auto* button = layout[main_id]->find<NodeButton>("btn-anim"))
    {
        button->on_click = [this, button](Node*) {
            if (canvas)
            {
                //canvas->m_canvas->export_anim();
            }
        };
    }
    if (auto* button = layout[main_id]->find<NodeButton>("btn-open"))
    {
        button->on_click = [this, button](Node*) {
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::open_document);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButton>("btn-save"))
    {
        button->on_click = [this, button](Node*) {
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::save_document);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-undo"))
    {
        button->on_click = [this, button](Node*) {
            const auto history = pp::panopainter::legacy_history_snapshot();
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::undo,
                history.undo_count);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-redo"))
    {
        button->on_click = [this, button](Node*) {
            const auto history = pp::panopainter::legacy_history_snapshot();
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::redo,
                0,
                history.redo_count);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-clean-memory"))
    {
        button->on_click = [this](Node*) {
            const auto history = pp::panopainter::legacy_history_snapshot();
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::clear_history,
                history.undo_count,
                history.redo_count,
                history.memory_bytes);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButton>("btn-clear"))
    {
        button->on_click = [this](Node*) {
            //exit(0); 
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::clear_canvas,
                0,
                0,
                0,
                static_cast<bool>(canvas));
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButton>("btn-popup"))
    {
        button->on_click = [this](Node*) {
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::show_message_box);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-settings"))
    {
        button->on_click = [this](Node*) {
            const auto plan = pp::app::plan_main_toolbar_command(
                pp::app::MainToolbarCommand::show_settings);
            if (plan)
                execute_main_toolbar_plan(*this, plan.value());
        };
    }
}

template <class T> std::shared_ptr<T> create_panel(LayoutManager& manager)
{
    std::shared_ptr<T> ret;
    ret = std::make_shared<T>();
    ret->set_manager(&manager);
    ret->init();
    ret->create();
    ret->loaded();
    return ret;
}

void App::init_sidebar()
{
    sidebar = layout[main_id]->find<NodeBorder>("sidebar");
    canvas = layout[main_id]->find<NodeCanvas>("paint-canvas");
    quick = layout[main_id]->find<NodePanelQuick>("panel-quick");
    floatings_container = layout[main_id]->find<Node>("floatings");

    //brushes = layout[main_id]->find<NodePanelBrush>("panel-brush");
    //layers = layout[main_id]->find<NodePanelLayer>("panel-layer");
    //color = layout[main_id]->find<NodePanelColor>("panel-color");
    //stroke = layout[main_id]->find<NodePanelStroke>("panel-stroke");

    //brushes = find_or_create_panel<NodePanelBrush>(panels);
    layers = create_panel<NodePanelLayer>(layout);
    color = create_panel<NodePanelColor>(layout);
    stroke = create_panel<NodePanelStroke>(layout);
    grid = create_panel<NodePanelGrid>(layout);
    presets = create_panel<NodePanelBrushPreset>(layout);
    animation = create_panel<NodePanelAnimation>(layout);
    //presets = find_or_create_panel<NodePanelBrushPreset>(panels);

    canvas->m_canvas->on_mode_changed = [this](kCanvasMode prev, kCanvasMode mode) {
        quick_mode_state[prev] = quick->get_state();
        if (quick_mode_state.find(mode) != quick_mode_state.end())
            quick->set_state(quick_mode_state[mode], true);
        else
            quick->reset_state(true);
        brush_update(true, true);
    };
    color->on_color_changed = [this](Node* target, glm::vec4 color) {
        apply_brush_color_plan(*this, color, true, false);
    };

    stroke->on_brush_changed = [this](Node* target, const std::string& path, const std::string& thumb) {
        apply_brush_texture_plan(*this, pp::app::BrushUiTextureSlot::tip, path, thumb);
    };
    stroke->on_pattern_changed = [this](Node*target, const std::string& path, const std::string& thumb) {
        apply_brush_texture_plan(*this, pp::app::BrushUiTextureSlot::pattern, path, thumb);
    };
    stroke->on_dual_changed = [this](Node*target, const std::string& path, const std::string& thumb) {
        apply_brush_texture_plan(*this, pp::app::BrushUiTextureSlot::dual, path, thumb);
    };
    stroke->on_stroke_change = [this](Node*) {
        const auto status = pp::panopainter::execute_legacy_brush_stroke_changed_plan(*this);
        if (!status.ok())
            LOG("Brush stroke settings action failed: %s", status.message);
    };

    quick->on_color_change = [this](Node*, glm::vec3 c) {
        apply_brush_color_plan(*this, glm::vec4(c, 1.f), false, true);
    };
    quick->on_flow_change = [this](Node*, float value) {
        stroke->set_flow(value, true, true);
    };
    quick->on_size_change = [this](Node*, float value) {
        stroke->set_size(value, true, true);
    };
    quick->on_brush_change = [this](Node*, std::shared_ptr<Brush> b) {
        apply_brush_preset_plan(*this, b);
    };

    layers->on_layer_add = [this](Node*, std::shared_ptr<class Layer> layer, int index) {
        const auto plan = pp::app::plan_document_layer_add(
            static_cast<int>(Canvas::I->m_layers.size()),
            index,
            layers->m_layers.back()->m_label_text);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value(), layer);
    };

    layers->on_layer_duplicate = [this](Node*, int source_index) {
        const auto plan = pp::app::plan_document_layer_duplicate(
            static_cast<int>(Canvas::I->m_layers.size()),
            source_index);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_change = [this](Node*, int, int new_idx) {
        const auto plan = pp::app::plan_document_layer_select(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            new_idx);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_order = [this](Node*, int old_idx, int new_idx) {
        const auto plan = pp::app::plan_document_layer_reorder(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            old_idx,
            new_idx);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_delete = [this](Node*, int idx) {
        const auto plan = pp::app::plan_document_layer_remove(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_opacity_changed = [this](Node*, int idx, float value) {
        const auto plan = pp::app::plan_document_layer_opacity(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx,
            value);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_visibility_changed = [this](Node*, int idx, bool visible) {
        const auto plan = pp::app::plan_document_layer_visibility(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx,
            visible);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };
    
    layers->on_layer_alpha_lock_changed = [this](Node*, int idx, bool locked) {
        const auto plan = pp::app::plan_document_layer_alpha_lock(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx,
            locked);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };
    
    layers->on_layer_blend_mode_changed = [this](Node*, int idx, int mode) {
        const auto plan = pp::app::plan_document_layer_blend_mode(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx,
            mode);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };

    layers->on_layer_highlight_changed = [this](Node*, int idx, bool highlight) {
        const auto plan = pp::app::plan_document_layer_highlight(
            static_cast<int>(canvas->m_canvas->m_layers.size()),
            idx,
            highlight);
        if (!plan)
            return;
        execute_document_layer_operation_plan(*this, plan.value());
    };
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-stroke"))
    {
        button->on_click = [this, button](Node*) {
            auto screen = layout[main_id]->m_size;
            glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
            if (stroke->m_parent)
            {
                if (auto fp = dynamic_cast<NodePanelFloating*>(stroke->m_parent->m_parent))
                {
                    pp::panopainter::detach_legacy_node_from_parent(*stroke);
                    pp::panopainter::close_legacy_dialog_node(*fp);
                }
            }
            (void)pp::panopainter::attach_legacy_overlay_node(*this, stroke);
            stroke->SetSize(350, glm::max(100.f, screen.y - pos.y - 50.f));
            stroke->find("title")->SetVisibility(true);
            auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(*layout[main_id]);
            tick->SetPositioning(YGPositionTypeAbsolute);
            tick->SetSize(32, 16);
            tick->SetPosition(pos.x - 16, pos.y);
            tick->set_image("data/ui/popup-tick-up.png");
            (void)pp::panopainter::attach_legacy_overlay_node(*this, tick);
            layout[main_id]->update();

            stroke->SetPosition(pos.x - stroke->m_size.x / 2.f, pos.y + 16);
            stroke->SetPositioning(YGPositionTypeAbsolute);
            pp::panopainter::activate_legacy_popup_overlay(*stroke);
            auto scroll = stroke->find<NodeScroll>("scroller");
            //scroll->SetHeight(glm::max(100.f, screen.y - pos.y - 200.f));

            pp::panopainter::bind_legacy_popup_close_destroys_overlay(*stroke, tick);
        };
    }
    //if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-brush"))
    //{
    //    button->on_click = [this, button](Node*) {
    //        panels->get_child_index(brushes.get()) == -1 ? panels->add_child(brushes) : panels->remove_child(brushes.get());
    //        panels->fix_scroll();
    //        button->set_color(panels->get_child_index(brushes.get()) == -1 ? color_button_normal : color_button_hlight);
    //    };
    //}
    //if (auto* button = layout[main_id]->find<NodeButton>("btn-brush-preset"))
    //{
    //    button->on_click = [this, button](Node*) {
    //        panels->get_child_index(presets.get()) == -1 ? panels->add_child(presets) : panels->remove_child(presets.get());
    //        panels->fix_scroll();
    //        button->set_color(panels->get_child_index(presets.get()) == -1 ? color_button_normal : color_button_hlight);
    //    };
    //}
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-color"))
    {
        button->on_click = [this, button](Node*) {
            auto screen = layout[main_id]->m_size;
            glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
            (void)pp::panopainter::attach_legacy_overlay_node(*this, color);
            color->find("title")->SetVisibility(true);
            color->SetSize(350, 350);
            auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(*layout[main_id]);
            tick->SetPositioning(YGPositionTypeAbsolute);
            tick->SetSize(32, 16);
            tick->SetPosition(pos.x - 16, pos.y);
            tick->set_image("data/ui/popup-tick-up.png");
            (void)pp::panopainter::attach_legacy_overlay_node(*this, tick);
            layout[main_id]->update();

            color->SetPosition(pos.x - color->m_size.x / 2.f, pos.y + 16);
            color->SetPositioning(YGPositionTypeAbsolute);
            pp::panopainter::activate_legacy_popup_overlay(*color);

            pp::panopainter::bind_legacy_popup_close_destroys_overlay(*color, tick);
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-layer"))
    {
        button->on_click = [this, button](Node*) {
            auto screen = layout[main_id]->m_size;
            glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
            layers->find("title")->SetVisibility(true);
            layers->SetSize(350, YGUndefined);
            if (layers->m_parent)
            {
                if (auto fp = dynamic_cast<NodePanelFloating*>(layers->m_parent->m_parent))
                {
                    pp::panopainter::detach_legacy_node_from_parent(*layers);
                    pp::panopainter::close_legacy_dialog_node(*fp);
                }
            }
            (void)pp::panopainter::attach_legacy_overlay_node(*this, layers);
            auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(*layout[main_id]);
            tick->SetPositioning(YGPositionTypeAbsolute);
            tick->SetSize(32, 16);
            tick->SetPosition(pos.x - 16, pos.y);
            tick->set_image("data/ui/popup-tick-up.png");
            (void)pp::panopainter::attach_legacy_overlay_node(*this, tick);
            layout[main_id]->update();

            layers->SetPosition(pos.x - layers->m_size.x / 2.f, pos.y + 16);
            layers->SetPositioning(YGPositionTypeAbsolute);
            pp::panopainter::activate_legacy_popup_overlay(*layers);
            auto scroll = layers->find<NodeScroll>("layers-container");
            scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 200.f));

            pp::panopainter::bind_legacy_popup_close_destroys_overlay(*layers, tick);
        };
    }
    if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-grids-panel"))
    {
        button->on_click = [this, button](Node*) {
            auto screen = layout[main_id]->m_size;
            glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
            grid->find("title")->SetVisibility(true);
            grid->SetSize(350, YGUndefined);
            if (grid->m_parent)
            {
                if (auto fp = dynamic_cast<NodePanelFloating*>(grid->m_parent->m_parent))
                {
                    pp::panopainter::detach_legacy_node_from_parent(*grid);
                    pp::panopainter::close_legacy_dialog_node(*fp);
                }
            }
            (void)pp::panopainter::attach_legacy_overlay_node(*this, grid);
            auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(*layout[main_id]);
            tick->SetPositioning(YGPositionTypeAbsolute);
            tick->SetSize(32, 16);
            tick->SetPosition(pos.x - 16, pos.y);
            tick->set_image("data/ui/popup-tick-up.png");
            (void)pp::panopainter::attach_legacy_overlay_node(*this, tick);
            layout[main_id]->update();

            grid->SetPosition(pos.x - grid->m_size.x / 2.f, pos.y + 16);
            grid->SetPositioning(YGPositionTypeAbsolute);
            pp::panopainter::activate_legacy_popup_overlay(*grid);
            auto scroll = grid->find<NodeScroll>("scroller");
            scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 250.f));

            pp::panopainter::bind_legacy_popup_close_destroys_overlay(*grid, tick);
        };
    }
}
[[nodiscard]] bool current_canvas_mode_is_draw(App& app) noexcept
{
    return app.canvas && app.canvas->m_canvas && app.canvas->m_canvas->m_current_mode == kCanvasMode::Draw;
}

template<class T>
void execute_canvas_tool_toolbar_binding(
    App& app,
    const pp::app::CanvasToolToolbarBinding& binding,
    T* button)
{
    const auto plan = pp::app::plan_canvas_tool_toolbar_binding_action(
        binding,
        current_canvas_mode_is_draw(app));
    const auto status = binding.action == pp::app::CanvasToolToolbarAction::select_mode
        ? pp::panopainter::execute_legacy_canvas_tool_plan(app, plan, button)
        : pp::panopainter::execute_legacy_canvas_tool_plan(app, plan);
    if (!status.ok())
        LOG("Canvas toolbar action failed: %s", status.message);
}

template<class T>
void bind_canvas_tool_toolbar_button(
    App& app,
    const pp::app::CanvasToolToolbarBinding& binding,
    T* button)
{
    button->on_click = [&app, binding, button](Node*) {
        execute_canvas_tool_toolbar_binding(app, binding, button);
    };
}

void App::init_toolbar_draw()
{
    const auto toolbar = pp::app::plan_canvas_tool_toolbar();
    bool apply_default_tool = false;
    for (const auto& binding : toolbar.bindings) {
        if (binding.custom_button) {
            if (auto* button = layout[main_id]->find<NodeButtonCustom>(binding.button_id.data())) {
                bind_canvas_tool_toolbar_button(*this, binding, button);
                apply_default_tool = apply_default_tool || binding.applies_default_on_init;
            }
        } else {
            if (auto* button = layout[main_id]->find<NodeButton>(binding.button_id.data())) {
                bind_canvas_tool_toolbar_button(*this, binding, button);
                apply_default_tool = apply_default_tool || binding.applies_default_on_init;
            }
        }
    }

    if (apply_default_tool) {
        const auto default_plan = pp::app::plan_canvas_tool_select(toolbar.default_mode);
        const auto status = pp::panopainter::execute_legacy_canvas_tool_plan(*this, default_plan);
        if (!status.ok())
            LOG("Canvas default tool action failed: %s", status.message);
    }
}

void App::init_menu_file()
{
    if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-file"))
    {
        menu_file->on_click = [=](Node*) {
            glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
            auto popup = add_menu_popup(*this, "file-menu", pos, menu_file->m_size.x);
            if (!popup)
                return;

            if (auto b = popup->find<NodeButtonCustom>("file-newdoc"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::new_document);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-import"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::import_image);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-open"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::open_project);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-browse"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::browse_cloud);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-save"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::save);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-save-as"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_as);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-save-ver"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_version);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-export"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::export_jpeg);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-export-tick"))
                b->on_click = [this, b, popup](Node*) {
                    glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
                    auto subpopup = add_menu_popup(*this, "file-submenu-export", pos, b->m_size.x);
                    if (!subpopup)
                        return;
                    subpopup->find<NodeButtonCustom>("file-submenu-export-png")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::png);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-layers")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::layers);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-cube")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::cube_faces);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-depth")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::depth);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-anim")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_frames);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-anim-mp4")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_mp4);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
                    subpopup->find<NodeButtonCustom>("file-submenu-export-timelapse")->on_click = [this, subpopup, popup](Node*) {
                        apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::timelapse);
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                        pp::panopainter::close_legacy_popup_overlay(*subpopup);
                    };
            };
            if (auto b = popup->find<NodeButtonCustom>("file-share"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::share);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-resize"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::resize);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-cloud-upload"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_upload);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
            if (auto b = popup->find<NodeButtonCustom>("file-cloud-browse"))
                b->on_click = [this, popup](Node*) {
                    apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_browse);
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                };
        };
    }
}

void App::init_menu_edit()
{
    if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-edit"))
    {
        menu_file->on_click = [=](Node*) {
            glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
            auto popup = add_menu_popup(*this, "edit-menu", pos, menu_file->m_size.x);
            if (!popup)
                return;
        };
    }
}

void App::init_menu_tools()
{
    auto main = layout[main_id];

    if (auto menu_exp = main->find<NodeButtonCustom>("menu-tools"))
    {
        menu_exp->on_click = [this, menu_exp, main](Node*) {
            glm::vec2 pos = menu_exp->m_pos + glm::vec2(0, menu_exp->m_size.y);
            auto popup_exp = add_menu_popup(*this, "tools-menu", pos, menu_exp->m_size.x);
            if (!popup_exp)
                return;

            if (auto tick = popup_exp->find<NodeButtonCustom>("tools-panels")) tick->on_click = [this, popup_exp](Node* b)
            {
                const auto menu_plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::panels);
                if (menu_plan.action != pp::app::ToolsMenuAction::show_panels_submenu)
                    return;

                glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
                auto popup_time = add_menu_popup(*this, "panels-menu", pos, b->m_size.x);
                if (!popup_time)
                    return;

                auto visible = [this](Node* panel) {
                    if (!panel)
                        return false;
                    for (auto& c : floatings_container->m_children)
                    {
                        if (auto fp = std::static_pointer_cast<NodePanelFloating>(c))
                        {
                            if (fp->m_container->is_child(panel))
                                return true;
                        }
                    }
                    return false;
                };

                popup_time->find<NodeButtonCustom>("panel-presets")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::presets,
                        visible(floating_presets.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Presets;
                    apply_tools_panel_chrome(*fpanel, plan);
                    if (!floating_presets)
                    {
                        floating_presets = fpanel->m_container->add_child_ref<NodePanelBrushPreset>();
                        floating_presets->SetHeightP(100);
                        //floating_presets->SetFlexGrow(1);
                        //floating_presets->find("toolbar")->destroy();
                        floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
                            apply_brush_preset_plan(*this, b);
                        };
                    }
                    else
                    {
                        fpanel->m_container->add_child(floating_presets);
                    }
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };

                popup_time->find<NodeButtonCustom>("panel-color")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::color,
                        visible(floating_color.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Color;
                    apply_tools_panel_chrome(*fpanel, plan);
                    if (!floating_color)
                    {
                        floating_color = fpanel->m_container->add_child_ref<NodePanelColor>();
                        floating_color->SetHeightP(100);
                        //floating_color->SetMinHeight(300);
                        if (plan.hides_embedded_title)
                            floating_color->find("title")->SetVisibility(false);
                        floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
                            apply_brush_color_plan(*this, color, false, false);
                        };
                    }
                    else
                    {
                        fpanel->m_container->add_child(floating_color);
                    }
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
                popup_time->find<NodeButtonCustom>("panel-color-adv")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::color_advanced,
                        visible(floating_picker.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::ColorAdv;
                    apply_tools_panel_chrome(*fpanel, plan);
                    if (!floating_picker)
                    {
                        floating_picker = fpanel->m_container->add_child_ref<NodeColorPicker>();
                        //floating_picker->find("title")->SetVisibility(false);
                        //floating_picker->SetHeightP(100);
                        //floating_picker->SetWidth(250);
                        floating_picker->m_autohide = false;
                        floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
                            apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
                        };
                    }
                    else
                    {
                        fpanel->m_container->add_child(floating_picker);
                    }
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
                popup_time->find<NodeButtonCustom>("panel-layers")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::layers,
                        visible(layers.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Layers;
                    apply_tools_panel_chrome(*fpanel, plan);
                    fpanel->m_container->add_child(layers);
                    layers->SetPositioning(YGPositionTypeRelative);
                    layers->SetPosition(0, 0);
                    layers->SetWidthP(100);
                    layers->SetHeightP(100);
                    layers->SetFlexShrink(0);
                    if (plan.hides_embedded_title)
                        layers->find("title")->SetVisibility(false);

                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
                popup_time->find<NodeButtonCustom>("panel-brush")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::brush,
                        visible(stroke.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Brush;
                    fpanel->m_container->add_child(stroke);
                    apply_tools_panel_chrome(*fpanel, plan);
                    stroke->SetPositioning(YGPositionTypeRelative);
                    stroke->SetPosition(0, 0);
                    stroke->SetWidthP(100);
                    stroke->SetHeightP(100);
                    if (plan.hides_embedded_title)
                        stroke->find("title")->SetVisibility(false);

                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
                popup_time->find<NodeButtonCustom>("panel-grids")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::grids,
                        visible(grid.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Grids;
                    fpanel->m_container->add_child(grid);
                    apply_tools_panel_chrome(*fpanel, plan);
                    grid->SetPositioning(YGPositionTypeRelative);
                    grid->SetPosition(0, 0);
                    grid->SetWidthP(100);
                    grid->SetHeightP(100);
                    if (plan.hides_embedded_title)
                        grid->find("title")->SetVisibility(false);

                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
                popup_time->find<NodeButtonCustom>("panel-animation")->on_click = [this, popup_time, popup_exp, visible](Node*) {
                    const auto plan = pp::app::plan_tools_panel(
                        pp::app::ToolsPanel::animation,
                        visible(animation.get()));
                    if (!should_open_tools_panel(plan))
                        return;
                    auto fpanel = floatings_container->add_child<NodePanelFloating>();
                    fpanel->m_class = NodePanelFloating::kClass::Animation;
                    fpanel->m_container->add_child(animation);
                    apply_tools_panel_chrome(*fpanel, plan);
                    animation->SetPositioning(YGPositionTypeRelative);
                    animation->SetPosition(0, 0);
                    animation->SetWidthP(100);
                    animation->SetHeightP(100);

                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    pp::panopainter::close_legacy_popup_overlay(*popup_time);
                };
            };

            if (auto options = popup_exp->find<NodeButtonCustom>("tools-options")) options->on_click = [this, options, main](Node* b)
            {
                const auto menu_plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::options);
                if (menu_plan.action != pp::app::ToolsMenuAction::show_options_submenu)
                    return;

                glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
                auto popup_time = add_menu_popup(*this, "options-menu", pos, b->m_size.x);
                if (!popup_time)
                    return;

                if (auto ui_scale = popup_time->find<NodeComboBox>("tools-ui-scale"))
                {
                    std::vector<float> scale_options;
                    scale_options.reserve(ui_scale->m_data.size());
                    for (int i = 0; i < ui_scale->m_data.size(); i++)
                        scale_options.push_back(ui_scale->get_float(i));

                    const auto selection = pp::app::plan_scale_option_selection(App::I->zoom, scale_options);
                    if (selection.has_selection)
                        ui_scale->set_index(static_cast<int>(selection.index));

                    ui_scale->on_select = [ui_scale](Node* target, int index)
                    {
                        const auto status = pp::panopainter::execute_legacy_ui_scale_preference(
                            *App::I,
                            ui_scale->get_float(index));
                        if (!status.ok())
                            LOG("UI scale preference failed: %s", status.message);
                    };
                }

                if (auto vp_scale = popup_time->find<NodeComboBox>("tools-vp-scale"))
                {
                    std::vector<float> scale_options;
                    scale_options.reserve(vp_scale->m_data.size());
                    for (int i = 0; i < vp_scale->m_data.size(); i++)
                        scale_options.push_back(vp_scale->get_float(i));

                    const auto selection = pp::app::plan_scale_option_selection(App::I->canvas->m_density, scale_options);
                    if (selection.has_selection)
                        vp_scale->set_index(static_cast<int>(selection.index));

                    vp_scale->on_select = [vp_scale](Node* target, int index)
                    {
                        const auto status = pp::panopainter::execute_legacy_viewport_scale_preference(
                            *App::I,
                            vp_scale->get_float(index));
                        if (!status.ok())
                            LOG("Viewport scale preference failed: %s", status.message);
                    };
                }

                if (auto rtl_btn = popup_time->find<NodeButtonCustom>("tools-rtl"))
                {
                    NodeCheckBox* cb = rtl_btn->find<NodeCheckBox>("tools-rtl-check");
                    cb->set_value(ui_rtl, false);

                    rtl_btn->on_click = [this, rtl_btn](Node* b)
                    {
                        NodeCheckBox* cb = rtl_btn->find<NodeCheckBox>("tools-rtl-check");
                        cb->set_value(!cb->checked, true);
                    };
                
                    rtl_btn->find<NodeCheckBox>("tools-rtl-check")->on_value_changed = [this, main](Node*, bool checked) 
                    {
                        const auto status = pp::panopainter::execute_legacy_interface_direction_preference(
                            *this,
                            checked);
                        if (!status.ok())
                            LOG("Interface direction preference failed: %s", status.message);
                    };
                }

                if (auto vr_btn = popup_time->find<NodeButtonCustom>("tools-vr"))
                {
                    NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-check");
                    cb->set_value(has_vr);

                    vr_btn->on_click = [this, vr_btn](Node* b)
                    {
                        NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-check");
                        cb->set_value(!cb->checked, true);
                    };

                    vr_btn->find<NodeCheckBox>("tools-vr-check")->on_value_changed = [this, main](Node* target, bool checked)
                    {
                        const auto status = pp::panopainter::execute_legacy_vr_mode_preference(
                            *this,
                            checked);
                        if (!status.ok()) {
                            auto cb = static_cast<NodeCheckBox*>(target);
                            cb->set_value(false);
                            message_box("VR Failed", "Couldn't start Virtual Reality mode");
                        }
                    };
                }

                if (auto vr_btn = popup_time->find<NodeButtonCustom>("tools-vr-controllers"))
                {
                    NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-controllers-check");
                    cb->set_value(vr_controllers_enabled);

                    vr_btn->on_click = [this, vr_btn](Node* b)
                    {
                        NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-controllers-check");
                        cb->set_value(!cb->checked, true);
                    };

                    vr_btn->find<NodeCheckBox>("tools-vr-controllers-check")->on_value_changed = [this, main](Node* target, bool checked)
                    {
                        const auto status = pp::panopainter::execute_legacy_vr_controllers_preference(
                            *this,
                            checked);
                        if (!status.ok())
                            LOG("VR controllers preference failed: %s", status.message);
                    };
                }

                if (auto btn = popup_time->find<NodeButtonCustom>("tools-timelapse"))
                {
                    NodeCheckBox* cb = btn->find<NodeCheckBox>("tools-timelapse-check");
                    cb->set_value(
                        pp::panopainter::read_legacy_startup_preferences(vr_controllers_enabled).auto_timelapse,
                        false);

                    btn->on_click = [this, btn](Node* b)
                    {
                        NodeCheckBox* cb = btn->find<NodeCheckBox>("tools-timelapse-check");
                        cb->set_value(!cb->checked, true);
                    };

                    btn->find<NodeCheckBox>("tools-timelapse-check")->on_value_changed = [this, main](Node*, bool checked)
                    {
                        const auto status = pp::panopainter::execute_legacy_timelapse_preference(
                            *this,
                            checked);
                        if (!status.ok())
                            LOG("Timelapse preference failed: %s", status.message);
                    };
                }

                if (auto mode = popup_time->find<NodeComboBox>("tools-show-cursor"))
                {
                    mode->set_index(pp::panopainter::read_legacy_canvas_preferences().cursor_mode);

                    mode->on_select = [mode](Node* target, int index)
                    {
                        const auto status = pp::panopainter::execute_legacy_canvas_cursor_mode_preference(
                            *App::I,
                            index);
                        if (!status.ok())
                            LOG("Cursor mode preference failed: %s", status.message);
                    };
                }
            };

            popup_exp->find<NodeButtonCustom>("clear-grids")->on_click = [this, popup_exp](Node*) {
                const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::clear_grids);
                execute_tools_menu_plan(*this, plan);
                if (plan.closes_root_popup)
                {
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                }
            };

            popup_exp->find<NodeButtonCustom>("camera-reset")->on_click = [this, popup_exp](Node*) {
                const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::reset_camera);
                execute_tools_menu_plan(*this, plan);
                if (plan.closes_root_popup)
                {
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                }
            };

            popup_exp->find<NodeButtonCustom>("shortcuts")->on_click = [this, popup_exp](Node*) {
                const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::shortcuts);
                execute_tools_menu_plan(*this, plan);
                if (plan.closes_root_popup)
                {
                    pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                }
            };

/*
            popup_exp->find<NodeButtonCustom>("mp4test")->on_click = [this, popup_exp](Node*) {
                dialog_export_mp4();
                pp::panopainter::close_legacy_popup_overlay(*popup_exp);
            };
*/

            if (platform_supports_sonarpen())
            {
                popup_exp->find<NodeButtonCustom>("sonarpen")->on_click = [this, popup_exp](Node*) {
                    const auto plan = pp::app::plan_tools_menu_command(
                        pp::app::ToolsMenuCommand::sonarpen,
                        platform_supports_sonarpen());
                    execute_tools_menu_plan(*this, plan);
                    if (plan.closes_root_popup)
                    {
                        pp::panopainter::close_legacy_popup_overlay(*popup_exp);
                    }
                };
            }
        };
    }
}

void App::init_menu_about()
{
    if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-about"))
    {
        menu_file->on_click = [=](Node*) {
            glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
            auto popup = add_menu_popup(*this, "about-menu", pos, menu_file->m_size.x);
            if (!popup)
                return;

            popup->find<NodeButtonCustom>("about-app")->on_click = [this, popup](Node*) {
                const auto plan = pp::app::plan_about_menu_command(
                    pp::app::AboutMenuCommand::about_app,
                    g_version_major,
                    g_version_minor,
                    g_version_fix);
                execute_about_menu_plan(*this, plan);
                if (plan.closes_root_popup)
                {
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                }
            };

            popup->find<NodeButtonCustom>("about-doc")->on_click = [this, popup](Node*) {
//                 auto path = Asset::absolute("data/doc/test.pdf");
//                 display_file(path);
                const auto plan = pp::app::plan_about_menu_command(
                    pp::app::AboutMenuCommand::help_guide,
                    g_version_major,
                    g_version_minor,
                    g_version_fix);
                execute_about_menu_plan(*this, plan);
                if (plan.closes_root_popup)
                {
                    pp::panopainter::close_legacy_popup_overlay(*popup);
                }
            };

            if (auto item = popup->find<NodeButtonCustom>("about-news"))
            {
                if (auto text = item->find<NodeText>("menu-label"))
                {
                    const auto plan = pp::app::plan_about_menu_command(
                        pp::app::AboutMenuCommand::whats_new,
                        g_version_major,
                        g_version_minor,
                        g_version_fix);
                    text->set_text(plan.label.c_str());
                }
                item->on_click = [this, popup](Node*) {
                    const auto plan = pp::app::plan_about_menu_command(
                        pp::app::AboutMenuCommand::whats_new,
                        g_version_major,
                        g_version_minor,
                        g_version_fix);
                    execute_about_menu_plan(*this, plan);
                    if (plan.closes_root_popup)
                    {
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                    }
                };
            }
            
            if (auto b = popup->find<NodeButtonCustom>("about-crash"))
            {
                b->on_click = [this, popup](Node*) {
                    const auto plan = pp::app::plan_about_menu_command(
                        pp::app::AboutMenuCommand::induce_crash,
                        g_version_major,
                        g_version_minor,
                        g_version_fix);
                    execute_about_menu_plan(*this, plan);
                    if (plan.closes_root_popup)
                    {
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                    }
                };
            }

            if (auto b = popup->find<NodeButtonCustom>("about-perf"))
            {
                b->on_click = [this, popup](Node*) {
                    const auto plan = pp::app::plan_about_menu_command(
                        pp::app::AboutMenuCommand::performance_test,
                        g_version_major,
                        g_version_minor,
                        g_version_fix,
                        true,
                        Canvas::I != nullptr);
                    execute_about_menu_plan(*this, plan);
                    if (plan.closes_root_popup)
                    {
                        pp::panopainter::close_legacy_popup_overlay(*popup);
                    }
                };
            }
        };
    }
}

void App::brush_update(bool update_color, bool update_brush)
{
//     brushes->select_brush(canvas->m_brush->id);
//     stroke->set_params(canvas->m_brush);
    render_task_async([this, update_color, update_brush]
    {
        pp::app::BrushUiRefreshInput input;
        input.update_color = update_color;
        input.update_brush = update_brush;
        auto current_brush = Canvas::I ? Canvas::I->m_current_brush : nullptr;
        input.has_current_brush = current_brush != nullptr;
        input.has_floating_picker = floating_picker != nullptr;
        input.has_floating_color_panel = floating_color != nullptr;
        if (input.has_current_brush)
        {
            input.tip_flow = current_brush->m_tip_flow;
            input.tip_size = current_brush->m_tip_size;
            input.r = current_brush->m_tip_color.r;
            input.g = current_brush->m_tip_color.g;
            input.b = current_brush->m_tip_color.b;
            input.a = current_brush->m_tip_color.a;
        }

        const auto view = pp::app::plan_brush_ui_refresh(input);
        if (!view) {
            LOG("Brush UI refresh failed: %s", view.status().message);
            return;
        }

        if (view.value().updates_stroke_controls)
        {
            stroke->update_controls();
        }
        if (view.value().updates_quick_flow)
        {
            quick->m_slider_flow->set_value(stroke->m_tip_flow->get_value());
        }
        if (view.value().updates_quick_size)
        {
            quick->m_slider_size->set_value(stroke->m_tip_size->get_value());
        }
        if (view.value().updates_quick_brush_preview && current_brush)
        {
            *quick->m_button_brush_current_preview->m_brush = *current_brush;
            quick->m_button_brush_current_preview->draw_stroke();
        }
        if (view.value().updates_quick_color)
        {
            const glm::vec4 color(view.value().r, view.value().g, view.value().b, view.value().a);
            quick->m_button_color_current_inner->m_color = color;
            if (view.value().updates_floating_picker)
                floating_picker->set_color(color);
            if (view.value().updates_floating_color_panel)
                floating_color->set_color(color);
        }
    }, true);
}

void App::init_menu_layer()
{
    if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-layers"))
    {
        menu_file->on_click = [=](Node*) {
            glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
            auto popup = add_menu_popup(*this, "layers-menu", pos, menu_file->m_size.x);
            if (!popup)
                return;

            popup->find<NodeButtonCustom>("layer-clear")->on_click = [this, popup](Node*) {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
                execute_document_layer_menu_plan(*this, plan);
                pp::panopainter::close_legacy_popup_overlay(*popup);
            };
            {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
                popup->find<NodeButtonCustom>("layer-clear")->
                    find<NodeText>("menu-label")->
                    set_text(plan.label.c_str());
            }

            popup->find<NodeButtonCustom>("layer-rename")->on_click = [this, popup](Node*) {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
                execute_document_layer_menu_plan(*this, plan);
                pp::panopainter::close_legacy_popup_overlay(*popup);
            };
            {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
                popup->find<NodeButtonCustom>("layer-rename")->
                    find<NodeText>("menu-label")->
                    set_text(plan.label.c_str());
            }

            popup->find<NodeButtonCustom>("layer-merge")->on_click = [this, popup](Node*) {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
                execute_document_layer_menu_plan(*this, plan);
                pp::panopainter::close_legacy_popup_overlay(*popup);
            };
            {
                const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
                popup->find<NodeButtonCustom>("layer-merge")->
                    find<NodeText>("menu-label")->
                    set_text(plan.label.c_str());
            }

        };
    }
}

void App::initLayout()
{
    LOG("initializing layout statics");
    NodeBorder::static_init();
    NodeImage::static_init();
    NodeIcon::static_init();
    NodeStrokePreview::static_init();

    static std::vector<std::shared_ptr<Layer>> saved_layers;
    layout.on_reloading = [&] {
        saved_layers = std::move(Canvas::I->m_layers);
        ui_save();
        NodeStrokePreview::empty_queue();
    };

    layout.on_loaded = [&] (bool reloaded) {
        LOG("initializing layout updating after load %d x %d zoom %f", (int)width, (int)height, zoom);
        layout[main_id]->update(width, height, zoom);

        LOG("initializing layout components");
 
        init_sidebar();

        if (reloaded)
        {
            for (const auto& l : saved_layers)
                layers->add_layer(l->m_name.c_str(), false, true, l);
        }
        else
        {
            layers->add_layer("Default", false, true);
            Canvas::I->m_unsaved = false;
        }
        
        init_toolbar_draw();
        init_toolbar_main();
        init_menu_file();
        init_menu_edit();
        init_menu_layer();
        init_menu_tools();
        init_menu_about();

        // set version string
        if (auto* version_label = layout[main_id]->find<NodeText>("version"))
        {
            version_label->set_text(g_version);
        }

        const auto renderer_features = ShaderManager::render_device_features();
        const auto renderer_diagnostics = pp::app::plan_renderer_diagnostics({
            .framebuffer_fetch = renderer_features.framebuffer_fetch,
            .float32_render_targets = renderer_features.float32_render_targets,
            .float32_linear_filtering = renderer_features.float32_linear_filtering,
            .float16_render_targets = renderer_features.float16_render_targets,
        });

        if (auto x = layout[main_id]->find<NodeBorder>("ext-fbf"))
        {
            x->m_color = renderer_diagnostics.framebuffer_fetch.supported ?
                glm::vec4(0, 1, 0, 1) :
                glm::vec4(1, 0, 0, 1);
        }

        if (auto x = layout[main_id]->find<NodeBorder>("ext-flt"))
        {
            if (renderer_diagnostics.floating_point_targets.supported)
            {
                if (auto t = x->find<NodeText>("ext-flt-text"))
                {
                    t->set_text(std::string(renderer_diagnostics.floating_point_targets.label));
                }
                x->m_color = glm::vec4(0, 1, 0, 1);
            }
            else
            {
                x->m_color = glm::vec4(1, 0, 0, 1);
            }
        }

        dialog_whatsnew(false);

        brush_update(true, true);
        
        // hacky thing to make the toolbar buttons not steal events when moving cursor fast
        if (auto* toolbar = layout[main_id]->find<Node>("toolbar"))
            toolbar->m_flood_events = true;

        NodeImage* n = new NodeImage;
        n->m_path = "data/ui/p-black.png";
        n->m_tex_id = const_hash("data/ui/p-black.png");
        n->SetSize(30, 45);
        n->create();

        NodeButtonCustom* butt = new NodeButtonCustom;
        butt->create();
        butt->add_child(n);
        butt->SetPositioning(YGPositionTypeAbsolute);
        butt->set_color({ 0, 0, 0, 0 });
        //n->SetPosition(100, 100);
        YGNodeStyleSetPosition(butt->y_node, YGEdgeBottom, 8);
        YGNodeStyleSetPosition(butt->y_node, YGEdgeLeft, 10);
        //butt->SetSize(30, 45);
        layout[main_id]->add_child(butt);

        butt->on_click = [this](Node*){
            toggle_ui();
        };

        ui_restore();

        redraw = true;
    };

    LOG("initializing layout xml");
    if (layout.m_loaded)
    {
        LOG("restore layout");
        layout.restore_context();
    }
    else
        layout.load("data/layout.xml");
    LOG("initializing layout completed");

    LOG("initializing layout designer xml");
    layout_designer.on_loaded = [&](bool reloaded) {
        layout_designer.create();
        //layout_designer[main_id]->add_child(layout_designer.instantiate("shortcuts"));
        auto p = layout_designer[main_id]->add_child<NodeShortcuts>();
        //p->SetPosition(300, 300);
        //p->SetSize(600, 400);
        //p->m_container->add_child<NodePanelAnimation>();
    };
    //layout_designer.load("data/dialogs/shortcuts.xml");
}

void App::set_ui_scale(float scale)
{
    const auto plan = pp::app::plan_ui_scale(scale, display_density);
    zoom = plan.scale;
    FontManager::change_scale(plan.font_scale);
    pp::panopainter::save_legacy_ui_scale_preference(plan.scale);
    App::I->title_update();
}

void App::set_ui_rtl(bool rtl)
{
    const auto plan = pp::app::plan_interface_direction(rtl);
    ui_rtl = plan.direction == pp::app::InterfaceDirection::right_to_left;
    layout[main_id]->find("central-row")->SetRTL(
        ui_rtl ? YGDirectionRTL : YGDirectionLTR);
}

bool App::get_ui_rtl() const
{
    return ui_rtl;
}

void App::ui_save()
{
    Serializer::Descriptor d;
    d.class_id = "ui-state";
    
    Serializer::List list_floatings;
    for (auto const& c : layout[main_id]->find("floatings")->m_children)
    {
        if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
        {
            auto fd = list_floatings.add<Serializer::Descriptor>();
            fd->class_id = "ui-flt";
            fd->set("pos", Serializer::Vec2(f->GetPosition()));
            fd->set("size", Serializer::Vec2(f->m_size));
            fd->set("class", Serializer::Integer((int)f->m_class));
            fd->set("title", Serializer::CString(f->m_title->m_text));
        }
    }
    d.set("floatings", list_floatings);

    Serializer::List list_drop_left;
    for (auto const& c : layout[main_id]->find("drop-left")->m_children)
    {
        if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
        {
            auto fd = list_drop_left.add<Serializer::Descriptor>();
            fd->class_id = "ui-dpl";
            fd->set("size", Serializer::Vec2(f->m_size));
            fd->set("class", Serializer::Integer((int)f->m_class));
            fd->set("title", Serializer::CString(f->m_title->m_text));
        }
    }
    d.set("drop-left", list_drop_left);

    Serializer::List list_drop_right;
    for (auto const& c : layout[main_id]->find("drop-right")->m_children)
    {
        if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
        {
            auto fd = list_drop_right.add<Serializer::Descriptor>();
            fd->class_id = "ui-dpr";
            fd->set("size", Serializer::Vec2(f->m_size));
            fd->set("class", Serializer::Integer((int)f->m_class));
            fd->set("title", Serializer::CString(f->m_title->m_text));
        }
    }
    d.set("drop-right", list_drop_right);

    pp::panopainter::set_legacy_ui_state_preferences(d, ui_rtl);
    save_platform_ui_state();

    pp::panopainter::save_legacy_preferences();
}

void App::ui_restore()
{
    const auto preferences = pp::panopainter::read_legacy_ui_preferences();
    if (preferences.has_rtl)
        set_ui_rtl(preferences.rtl);
    
    if (!preferences.state)
        return;

    auto floatings = layout[main_id]->find_ref("floatings");
    auto drop_left = layout[main_id]->find_ref("drop-left");
    auto drop_right = layout[main_id]->find_ref("drop-right");
    auto d = preferences.state;
    for (auto const& l : d->get<Serializer::List>("floatings")->items)
    {
        auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
        auto pos = ld->value<Serializer::Vec2>("pos");
        auto size = ld->value<Serializer::Vec2>("size");
        auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
        auto f = floatings->add_child<NodePanelFloating>();
        std::string title = "Floating Panel";
        ld->value<Serializer::CString>("title", title);
        f->m_title->set_text(title.c_str());
        switch (cls)
        {
        case NodePanelFloating::kClass::Presets:
        {
            floating_presets = f->m_container->add_child_ref<NodePanelBrushPreset>();
            floating_presets->SetHeightP(100);
            //floating_presets->find("toolbar")->destroy();
            floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
                apply_brush_preset_plan(*this, b);
            };
            break;
        }
        case NodePanelFloating::kClass::Color:
        {
            floating_color = f->m_container->add_child_ref<NodePanelColor>();
            floating_color->SetHeightP(100);
            floating_color->find("title")->SetVisibility(false);
            floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
                apply_brush_color_plan(*this, color, false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::ColorAdv:
        {
            floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
            floating_picker->m_autohide = false;
            floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
                apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::Layers:
            f->m_container->add_child(layers);
            f->SetMinHeight(100);
            f->SetHeight(300);
            layers->find("title")->SetVisibility(false);
            layers->SetPositioning(YGPositionTypeRelative);
            layers->SetPosition(0, 0);
            layers->SetWidthP(100);
            layers->SetHeightP(100);
            layers->SetFlexShrink(0);
            break;
        case NodePanelFloating::kClass::Brush:
            f->m_container->add_child(stroke);
            stroke->find("title")->SetVisibility(false);
            stroke->SetPositioning(YGPositionTypeRelative);
            stroke->SetPosition(0, 0);
            stroke->SetWidthP(100);
            stroke->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Grids:
            f->m_container->add_child(grid);
            grid->find("title")->SetVisibility(false);
            grid->SetPositioning(YGPositionTypeRelative);
            grid->SetPosition(0, 0);
            grid->SetWidthP(100);
            grid->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Animation:
            f->m_container->add_child(animation);
            f->m_droppable = false;
            //grid->find("title")->SetVisibility(false);
            animation->SetPositioning(YGPositionTypeRelative);
            animation->SetPosition(0, 0);
            animation->SetWidthP(100);
            animation->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Generic:
        default:
            f->m_container->add_child<Node>();
            break;
        }
        f->m_class = cls;
        f->SetSize(size);
        f->SetPosition(pos);
        f->SetPositioning(YGPositionTypeAbsolute);
    }

    for (auto const& l : d->get<Serializer::List>("drop-left")->items)
    {
        auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
        auto size = ld->value<Serializer::Vec2>("size");
        auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
        auto f = drop_left->add_child<NodePanelFloating>();
        std::string title = "Floating Panel";
        ld->value<Serializer::CString>("title", title);
        f->m_title->set_text(title.c_str());
        switch (cls)
        {
        case NodePanelFloating::kClass::Presets:
        {
            auto floating_presets = f->m_container->add_child<NodePanelBrushPreset>();
            floating_presets->SetHeightP(100);
            //floating_presets->find("toolbar")->destroy();
            floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
                apply_brush_preset_plan(*this, b);
            };
            break;
        }
        case NodePanelFloating::kClass::Color:
        {
            auto floating_color = f->m_container->add_child<NodePanelColor>();
            floating_color->SetHeightP(100);
            pp::panopainter::destroy_legacy_node(*floating_color->find("title"));
            floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
                apply_brush_color_plan(*this, color, false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::ColorAdv:
        {
            floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
            floating_picker->m_autohide = false;
            floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
                apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::Layers:
            f->m_container->add_child(layers);
            layers->SetPositioning(YGPositionTypeRelative);
            layers->SetPosition(0, 0);
            layers->SetWidthP(100);
            layers->SetHeightP(100);
            layers->SetFlexShrink(0);
            break;
        case NodePanelFloating::kClass::Brush:
            f->m_container->add_child(stroke);
            stroke->SetPositioning(YGPositionTypeRelative);
            stroke->SetPosition(0, 0);
            stroke->SetWidthP(100);
            stroke->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Grids:
            f->m_container->add_child(grid);
            grid->SetPositioning(YGPositionTypeRelative);
            grid->SetPosition(0, 0);
            grid->SetWidthP(100);
            grid->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Generic:
        default:
            f->m_container->add_child<Node>();
            break;
        }
        f->m_class = cls;
        f->m_dock = drop_left;
        f->SetPositioning(YGPositionTypeRelative);
        f->SetPosition(0, 0);
        f->SetSize(size);
    }

    for (auto const& l : d->get<Serializer::List>("drop-right")->items)
    {
        auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
        auto size = ld->value<Serializer::Vec2>("size");
        auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
        auto f = drop_right->add_child<NodePanelFloating>();
        std::string title = "Floating Panel";
        ld->value<Serializer::CString>("title", title);
        f->m_title->set_text(title.c_str());
        switch (cls)
        {
        case NodePanelFloating::kClass::Presets:
        {
            auto floating_presets = f->m_container->add_child<NodePanelBrushPreset>();
            floating_presets->SetHeightP(100);
            //floating_presets->find("toolbar")->destroy();
            floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
                apply_brush_preset_plan(*this, b);
            };
            break;
        }
        case NodePanelFloating::kClass::Color:
        {
            auto floating_color = f->m_container->add_child<NodePanelColor>();
            floating_color->SetHeightP(100);
            pp::panopainter::destroy_legacy_node(*floating_color->find("title"));
            floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
                apply_brush_color_plan(*this, color, false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::ColorAdv:
        {
            floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
            floating_picker->m_autohide = false;
            floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
                apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
            };
            break;
        }
        case NodePanelFloating::kClass::Layers:
            f->m_container->add_child(layers);
            layers->SetPositioning(YGPositionTypeRelative);
            layers->SetPosition(0, 0);
            layers->SetWidthP(100);
            layers->SetHeightP(100);
            layers->SetFlexShrink(0);
            break;
        case NodePanelFloating::kClass::Brush:
            f->m_container->add_child(stroke);
            stroke->SetPositioning(YGPositionTypeRelative);
            stroke->SetPosition(0, 0);
            stroke->SetWidthP(100);
            stroke->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Grids:
            f->m_container->add_child(grid);
            grid->SetPositioning(YGPositionTypeRelative);
            grid->SetPosition(0, 0);
            grid->SetWidthP(100);
            grid->SetHeightP(100);
            break;
        case NodePanelFloating::kClass::Generic:
        default:
            f->m_container->add_child<Node>();
            break;
        }
        f->m_class = cls;
        f->m_dock = drop_right;
        f->SetPositioning(YGPositionTypeRelative);
        f->SetPosition(0, 0);
        f->SetSize(size);
    }
}
