#include "pch.h"

#include "legacy_document_layer_services.h"

#include "action.h"
#include "app.h"
#include "legacy_document_canvas_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_dialog_layer_rename.h"

namespace pp::panopainter {
namespace {

struct LegacyActionLayerRename : public Action {
    std::string m_old_name;
    std::string m_new_name;
    Layer* m_layer = nullptr;
    std::shared_ptr<NodeLayer> m_layer_node;

    LegacyActionLayerRename(
        std::string old_name,
        std::string new_name,
        std::shared_ptr<NodeLayer> layer_node,
        Layer* layer)
        : m_old_name(std::move(old_name))
        , m_new_name(std::move(new_name))
        , m_layer(layer)
        , m_layer_node(std::move(layer_node))
    {
    }

    void run() override { }
    size_t memory() override { return 0; }
    void undo() override
    {
        if (m_layer_node)
            m_layer_node->set_name(m_old_name.c_str());
        if (m_layer)
            m_layer->m_name = m_old_name;
    }
    Action* get_redo() override
    {
        return new LegacyActionLayerRename(m_new_name, m_old_name, m_layer_node, m_layer);
    }
};

class LegacyDocumentLayerRenameServices final : public pp::app::DocumentLayerRenameServices {
public:
    LegacyDocumentLayerRenameServices(App& app, const std::shared_ptr<NodeDialogLayerRename>& dialog) noexcept
        : app_(app)
        , dialog_(dialog)
    {
    }

    void record_layer_rename_undo(std::string_view old_name, std::string_view new_name) override
    {
        if (!app_.layers || !app_.layers->m_current_layer || !app_.canvas || !app_.canvas->m_canvas)
            return;

        auto layer_node = std::static_pointer_cast<NodeLayer>(app_.layers->m_current_layer->shared_from_this());
        auto* layer = app_.canvas->m_canvas->m_layers[app_.canvas->m_canvas->m_current_layer_idx].get();
        const std::string old_name_copy(old_name);
        const std::string new_name_copy(new_name);
        ActionManager::add(new LegacyActionLayerRename(
            old_name_copy,
            new_name_copy,
            layer_node,
            layer));
    }

    void set_current_layer_name(std::string_view new_name) override
    {
        if (!app_.layers || !app_.layers->m_current_layer || !app_.canvas || !app_.canvas->m_canvas)
            return;

        auto layer_node = std::static_pointer_cast<NodeLayer>(app_.layers->m_current_layer->shared_from_this());
        auto* layer = app_.canvas->m_canvas->m_layers[app_.canvas->m_canvas->m_current_layer_idx].get();
        const std::string new_name_copy(new_name);
        layer_node->set_name(new_name_copy.c_str());
        layer->m_name = new_name_copy;
    }

    void finish_layer_rename() override
    {
        if (dialog_)
            pp::panopainter::close_legacy_dialog_node(*dialog_);
        app_.hideKeyboard();
    }

private:
    App& app_;
    std::shared_ptr<NodeDialogLayerRename> dialog_;
};

class LegacyDocumentLayerMergeServices final : public pp::app::DocumentLayerMergeServices {
public:
    explicit LegacyDocumentLayerMergeServices(App& app) noexcept
        : app_(app)
    {
    }

    void merge_layers(int from_index, int to_index, bool create_history) override
    {
        if (app_.layers)
            app_.layers->merge(from_index, to_index, create_history);
    }

private:
    App& app_;
};

class LegacyDocumentLayerMenuServices final : public pp::app::DocumentLayerMenuServices {
public:
    explicit LegacyDocumentLayerMenuServices(App& app) noexcept
        : app_(app)
    {
    }

    void clear_current_layer() override
    {
        const auto plan = pp::app::plan_document_canvas_clear(
            app_.canvas && app_.canvas->m_canvas,
            1.0F,
            1.0F,
            1.0F,
            0.0F);
        if (!plan)
            return;

        const auto status = execute_legacy_document_canvas_clear_plan(app_, plan.value());
        if (!status.ok())
            LOG("Canvas clear failed: %s", status.message);
    }

    void show_rename_dialog() override
    {
        app_.dialog_layer_rename();
    }

    void merge_with_lower_layer(int from_index, int to_index) override
    {
        const int layer_count = app_.canvas && app_.canvas->m_canvas
            ? static_cast<int>(app_.canvas->m_canvas->m_layers.size())
            : 0;
        const int animation_duration = Canvas::I
            ? Canvas::I->anim_duration()
            : 0;
        const auto plan = pp::app::plan_document_layer_merge(
            layer_count,
            from_index,
            to_index,
            animation_duration);
        if (!plan)
            return;

        const auto status = execute_legacy_document_layer_merge_plan(app_, plan.value());
        if (!status.ok())
            LOG("Layer merge failed: %s", status.message);
    }

    void show_merge_animated_not_supported() override
    {
        app_.message_box("Not supported", "Merging animated layers is not supported yet.");
    }

private:
    App& app_;
};

class LegacyDocumentLayerOperationServices final : public pp::app::DocumentLayerOperationServices {
public:
    LegacyDocumentLayerOperationServices(
        App& app,
        const std::shared_ptr<Layer>& pending_layer = nullptr) noexcept
        : app_(app)
        , pending_layer_(pending_layer)
    {
    }

    void add_layer(std::string_view name, int insert_index) override
    {
        auto* canvas = legacy_canvas();
        if (!canvas)
            return;

        canvas->layer_add(std::string(name), pending_layer_, insert_index);
        canvas->anim_update();
    }

    void duplicate_layer(int source_index, int insert_index) override
    {
        auto* canvas = legacy_canvas();
        if (!canvas)
            return;

        std::string duplicated_name = "Layer";
        if (app_.layers && !app_.layers->m_layers.empty())
            duplicated_name = app_.layers->m_layers.back()->m_label_text;

        canvas->layer_add(duplicated_name, nullptr, insert_index);
        auto& dst = canvas->m_layers[insert_index];
        auto& src = canvas->m_layers[source_index];
        for (int i = 1; i < src->frames_count(); i++)
            dst->add_frame();
        canvas->anim_update();
        for (int frame = 0; frame < src->frames_count(); frame++)
        {
            for (int i = 0; i < 6; i++)
            {
                if (!src->face(i))
                    continue;
                bool loaded = src->frame(frame).gpu_load();
                dst->frame(frame).gpu_load();
                dst->rtt(i, frame).copy(src->rtt(i));
                dst->face(i, frame) = src->face(i);
                dst->box(i, frame) = src->box(i);
                if (!loaded)
                {
                    dst->frame(frame).gpu_unload();
                    src->frame(frame).gpu_unload();
                }
            }
        }
        dst->m_opacity = src->m_opacity;
        dst->m_blend_mode = src->m_blend_mode;
        dst->m_alpha_locked = src->m_alpha_locked;
    }

    void select_layer(int index) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_current_layer_idx = index;
    }

    void reorder_layer(int from_index, int to_index) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->layer_order(from_index, to_index);
    }

    void remove_layer(int index) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->layer_remove(index);
    }

    void set_layer_opacity(int index, float opacity) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_layers[index]->m_opacity = opacity;
    }

    void set_layer_visibility(int index, bool visible) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_layers[index]->m_visible = visible;
    }

    void set_layer_alpha_lock(int index, bool locked) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_layers[index]->m_alpha_locked = locked;
    }

    void set_layer_blend_mode(int index, int blend_mode) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_layers[index]->m_blend_mode = blend_mode;
    }

    void set_layer_highlight(int index, bool highlighted) override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_layers[index]->m_hightlight = highlighted;
    }

    void mark_unsaved() override
    {
        if (auto* canvas = legacy_canvas())
            canvas->m_unsaved = true;
    }

    void reload_animation_layers() override
    {
        if (app_.animation)
            app_.animation->load_layers();
    }

    void update_title() override
    {
        app_.title_update();
    }

private:
    [[nodiscard]] Canvas* legacy_canvas() const noexcept
    {
        if (app_.canvas && app_.canvas->m_canvas)
            return app_.canvas->m_canvas.get();
        return Canvas::I;
    }

    App& app_;
    std::shared_ptr<Layer> pending_layer_;
};

} // namespace

pp::foundation::Status execute_legacy_document_layer_rename_plan(
    App& app,
    const pp::app::DocumentLayerRenamePlan& plan,
    const std::shared_ptr<NodeDialogLayerRename>& dialog)
{
    LegacyDocumentLayerRenameServices services(app, dialog);
    return pp::app::execute_document_layer_rename_plan(plan, services);
}

void open_legacy_document_layer_rename_dialog(App& app)
{
    auto* overlay_anchor = app.layout[app.main_id];
    if (!overlay_anchor) {
        LOG("Layer rename dialog open failed: main layout anchor is missing");
        return;
    }

    auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogLayerRename>(app);
    dialog->input->set_text(app.layers->m_current_layer->m_label_text);

    app.showKeyboard();

    const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
    if (!overlay) {
        app.hideKeyboard();
        LOG("Layer rename dialog open failed: %s", overlay.status().message);
        return;
    }
    const auto overlay_handle = overlay.value();

    const auto close_dialog = [&app, overlay_anchor, overlay_handle]() {
        const auto close_status =
            pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
        (void)close_status;
        app.hideKeyboard();
    };

    dialog->btn_ok->on_click = [&app, dialog](Node*)
    {
        const auto old_name = app.layers->m_current_layer->m_label_text;
        const auto plan = pp::app::plan_document_layer_rename(old_name, dialog->get_name());
        if (!plan)
            return;
        const auto status = pp::panopainter::execute_legacy_document_layer_rename_plan(app, plan.value(), dialog);
        if (!status.ok())
            LOG("Layer rename failed: %s", status.message);
    };
    dialog->btn_cancel->on_click = [close_dialog](Node*)
    {
        close_dialog();
    };
}

pp::foundation::Status execute_legacy_document_layer_menu_plan(
    App& app,
    const pp::app::DocumentLayerMenuPlan& plan)
{
    LegacyDocumentLayerMenuServices services(app);
    return pp::app::execute_document_layer_menu_plan(plan, services);
}

pp::foundation::Status execute_legacy_document_layer_merge_plan(
    App& app,
    const pp::app::DocumentLayerMergePlan& plan)
{
    LegacyDocumentLayerMergeServices services(app);
    return pp::app::execute_document_layer_merge_plan(plan, services);
}

pp::foundation::Status execute_legacy_document_layer_operation_plan(
    App& app,
    const pp::app::DocumentLayerOperationPlan& plan,
    const std::shared_ptr<Layer>& pending_layer)
{
    LegacyDocumentLayerOperationServices services(app, pending_layer);
    return pp::app::execute_document_layer_operation_plan(plan, services);
}

} // namespace pp::panopainter
