/** @module util/dragdrop */

import $ from "jquery";
import { createRoot } from "react-dom/client";

import { Icon, message } from "@devowl-wp/react-folder-tree";
import { RatingPointer, isRatable } from "@devowl-wp/real-utils";

import { hooks, i18n, request, rmlOpts } from "./index.js";
import { isAttachmentsGalleryEdit } from "../hooks/modal.js";

const CLASS_NAME_APPEND = "aiot-helper-method-append";
const CLASS_NAME_MOVE = "aiot-helper-method-move";

/**
 * jQuery's draggable helper container.
 *
 * @param {object} props Properties
 * @param {int} props.count The count
 * @type React.Element
 */
const DragHelper = ({ count }) => (
    <div>
        <div className={CLASS_NAME_MOVE}>
            <Icon type="swap" /> {i18n(count > 1 ? "move" : "moveOne", { count })}
            <p>{i18n("moveTip")}</p>
        </div>
        <div className={CLASS_NAME_APPEND}>
            <Icon type="copy" /> {i18n(count > 1 ? "append" : "appendOne", { count })}
            <p>{i18n("appendTip")}</p>
        </div>
    </div>
);

/**
 * Enables / Reinitializes the droppable nodes. If a draggable item is dropped
 * here the given posts are moved to the category. You have to provide a ReactJS
 * element to reload the tree.
 *
 * @param {React.Element} element The element
 */
export function droppable(element) {
    const dom = $(element.ref.container).find(".aiot-node.aiot-droppable[data-id!='all']");
    const { attachmentsBrowser } = element;
    dom.droppable({
        activeClass: "aiot-state-default",
        hoverClass: "aiot-state-hover",
        tolerance: "pointer",
        drop: async function (event, ui) {
            const ids = [];
            const toTmp = $(event.target).attr("data-id");
            const to = toTmp === "all" ? toTmp : +toTmp;
            const activeId = element.getSelectedId();
            const elements = [];
            const fnFade = (percent) => elements.forEach((obj) => obj.fadeTo(250, percent));
            const isCopy = $("body").hasClass(CLASS_NAME_APPEND);
            const { store } = element.props;

            // Get dragged items
            iterateDraggedItem(
                ui.draggable,
                element,
                (tr) => {
                    ids.push(+tr.find('input[type="checkbox"]').attr("value"));
                    elements.push(tr);
                },
                (attributes, attachmentsBrowser) => {
                    ids.push(attributes.id);
                    elements.push(attachmentsBrowser.$el.find(`li[data-id="${attributes.id}"]`));
                },
            );
            element.setState({ isTreeLinkDisabled: true }); // Disable tree
            fnFade(0.3);

            // Make folders updateable in grid mode
            if (attachmentsBrowser) {
                // If the target is "Uncategorized" the current folder has to be refreshed, too
                store.addFoldersNeedsRefresh(to);
                to === +rmlOpts.others.rootId && store.addFoldersNeedsRefresh(activeId);
            }

            // Get i18n key
            const isOne = ids.length === 1;
            const i18nProps = {
                count: ids.length,
                category: $(event.target).find(".aiot-node-name").html(),
            };
            const i18nGet = (key) => i18n((isCopy ? "append" : "move") + key + (isOne ? "One" : ""), i18nProps);

            const hide = message.loading(i18nGet("LoadingText"));
            try {
                const { counts, removedFolderIds } = await request({
                    location: {
                        path: "/attachments/bulk/move",
                        method: "PUT",
                    },
                    request: { ids, to, isCopy },
                });

                // Remove the folders which got deleted through the move process
                removedFolderIds &&
                    removedFolderIds.length &&
                    removedFolderIds.forEach((id) => {
                        const obj = store.getTreeItemById(+id);
                        obj && obj.visible(false);
                    });

                /**
                 * Attachment items got moved.
                 *
                 * @event module:util/hooks#attachment/move/finished
                 * @param {int[]} ids The attachment ids
                 * @param {int|string} to The destination folder
                 * @param {boolean} isCopy If true the files were copied (shortcut)
                 * @this module:AppTree~AppTree
                 * @since 4.0.7
                 */
                hooks.call("attachment/move/finished", [ids, to, isCopy], element);

                // Show rating pointer
                isRatable(rmlOpts.slug) && new RatingPointer(rmlOpts.slug, $(event.target));

                message.success(i18nGet("Success"));
                element.fetchCounts(counts);

                // Deselect for the next bulk selection action
                elements.forEach((obj) => {
                    let attachmentPreview = obj.children(".attachment-preview");
                    obj.hasClass("selected") && attachmentPreview.length && attachmentPreview.click();
                });

                // Update items view
                const fadeBack = isCopy || (!isCopy && activeId === to) || activeId === "all";
                fadeBack ? fnFade(1) : elements.forEach((obj) => obj.remove());

                // Refresh view if necessery
                if ((activeId === "all" && isCopy) || (isCopy && activeId === to)) {
                    element.handleReload();
                }

                // Add no media
                if (!element.attachmentsBrowser && !$(".wp-list-table tbody tr").length) {
                    $(".wp-list-table tbody").html(
                        `<tr class="no-items"><td class="colspanchange" colspan="6">${
                            rmlOpts.others.lang.noEntries
                        }</td></tr></tbody>`,
                    );
                }
            } catch (e) {
                message.error(e.responseJSON.message);
                fnFade(1);
            } finally {
                hide();
                element.setState(() => ({
                    isTreeLinkDisabled: false,
                })); // Enable tree
            }
        },
    });
}

/*
 * Iterates through the UI and gets the collection of dragged items.
 *
 * @param {jQuery} ui The draggable ui object
 * @param {React.Element} container The AIOT container
 * @param {function} [listMode] Function to iterate over list mode items (<tr> object)
 * @param {function} [gridMode] Function to iterate over grid mode items (attributes, attachmentsBrowser)
 * @returns {int} The count of selected items
 */
function iterateDraggedItem(ui, { attachmentsBrowser }, listMode, gridMode) {
    if (attachmentsBrowser) {
        // Grid mode
        const selection = attachmentsBrowser.options.selection.models;
        if (selection.length) {
            selection.forEach((model) => {
                gridMode && gridMode(model.attributes, attachmentsBrowser);
            });
            return selection.length;
        } else {
            const id = ui.data("id");
            const { models } = attachmentsBrowser.collection;
            gridMode && gridMode(models.filter((model) => model.id === id)[0], attachmentsBrowser);
            return 1;
        }
    } else {
        // List mode
        const trs = $("#the-list > tr > .check-column > input:checked");
        if (trs.length) {
            trs.each(function () {
                listMode && listMode($(this).parents("tr"));
            });
        } else {
            listMode && listMode(ui);
        }
        return trs.length || 1;
    }
}

/**
 * Make the list table draggable if sort mode is not active.
 *
 * @param {React.Element} element The element
 * @param {boolean} [destroy=false] If true the draggable gets destroyed
 */
export function draggable(element, destroy) {
    // Get selector
    const { attachmentsBrowser } = element;
    const { isMoveable, isWPAttachmentsSortMode } = element.state;
    const attachments = attachmentsBrowser && attachmentsBrowser.attachments;
    const selector = attachmentsBrowser
        ? attachmentsBrowser.$el.find("ul.attachments > li")
        : $("#wpbody-content .wp-list-table tbody tr:not(.no-items)");
    const isGalleryEdit = isAttachmentsGalleryEdit(attachments);

    // Make draggable
    if (
        destroy ||
        !isMoveable ||
        isWPAttachmentsSortMode ||
        isGalleryEdit ||
        // Avoid drag & drop on mobile
        document.documentElement.clientWidth <= 900
    ) {
        try {
            selector.draggable("destroy");
        } catch (e) {
            // Silence is golden.
        }

        // In gallery edit mode enable the built-in sortable
        if (isGalleryEdit) {
            attachments.collection.comparator = undefined;
            attachments.initSortable();
        }
    } else {
        // e.ctrlKey && $("body").addClass("aiot-helper-ctrl");

        const toggleAppendMove = () => {
            // On CTRL holding add class to document body
            const keyDown = () => $("body").addClass(CLASS_NAME_APPEND);
            const keyUp = () => $("body").removeClass(CLASS_NAME_APPEND);
            $(document).on("keydown", keyDown);
            $(document).on("keyup", keyUp);

            keyUp(); // Initially reset once while start dragging

            return () => {
                $(document).off("keydown", keyDown);
                $(document).off("keyup", keyUp);
            };
        };
        let toggleAppendMoveDispatcher;

        selector.draggable({
            revert: "invalid",
            revertDuration: 0,
            appendTo: "body",
            cursorAt: { top: 0, left: 0 },
            cancel: '[contenteditable="true"],:input',
            distance: 10,
            refreshPositions: true,
            helper: (event) => {
                const helper = $('<div class="aiot-helper"></div>').appendTo($("body"));
                const count = iterateDraggedItem($(event.currentTarget), element);
                createRoot(helper.get(0)).render(<DragHelper count={count} />);
                return helper;
            },
            start: () => {
                $("body").addClass("aiot-currently-dragging");
                toggleAppendMoveDispatcher = toggleAppendMove();

                // FIX https://bugs.jqueryui.com/ticket/4261
                $(document.activeElement).blur();
            },
            stop: () =>
                setTimeout(() => {
                    $("body").removeClass("aiot-currently-dragging");
                    toggleAppendMoveDispatcher?.();
                }, 50),
        });
    }
}
