/** @module store/TreeNode */

import $ from "jquery";
import { flow, getParent, getParentOfType, getRoot, types } from "mobx-state-tree";

import { getTreeParentById, uuid } from "@devowl-wp/react-folder-tree";

import { hooks, request } from "../util/index.js";

/**
 * The store holding general data for folders. The properties are read-only.
 *
 * @see React AIOT TreeNode documentation for properties and defaults
 * @class TreeNode
 */
const TreeNode = types
    .model("RMLTreeNode", {
        id: types.union(types.identifier, types.identifierNumber),
        hash: "",
        className: types.frozen(),
        icon: "folder",
        iconActive: "",
        childNodes: types.optional(types.array(types.late(() => TreeNode)), []),
        title: types.string,
        count: 0,
        attr: types.optional(types.frozen()),
        isTreeLinkDisabled: false,
        checked: false,
        selected: false,
        $busy: false,
        $busyOrder: false,
        $droppable: true,
        $visible: true,
        $rename: false,
        $create: types.optional(types.frozen()),
        contentCustomOrder: 0,
        forceCustomOrder: false,
        // Content order
        lastOrderBy: "",
        orderAutomatically: false,
        // Subfolder order
        lastSubOrderBy: "",
        subOrderAutomatically: false,
        //searchSelected: false,
        //expandedState: true,
        //displayChildren: true,
        //selectedIds: [],
        //onRenameClose: undefined,
        //onAddClose: undefined,
        //onSelect: undefined,
        //onNodePressF2: undefined,
        //onExpand: undefined,
        //onUlRef: undefined
        properties: types.optional(types.frozen()),
        isQueried: true,
    })
    .views((self) => ({
        get indeterminate() {
            return false; // Not implemented, yet
            //if (self.checked) {
            //    return false;
            //}
            //const allChilds = self.childNodes,
            //    checked = allChilds.filter(o => o.checked).length;
            //return checked > 0;
        },

        get parentArray() {
            return getParent(self);
        },

        get nextSibling() {
            const { parentArray } = self;
            const indexOf = parentArray.indexOf(self);
            const nextOf = indexOf + 1;
            return indexOf > -1 ? parentArray[nextOf] : undefined;
        },

        get path() {
            let result = [];
            let parent = self;
            while (parent) {
                result.push(parent);
                try {
                    parent = getParentOfType(parent, TreeNode);
                } catch (e) {
                    parent = undefined;
                }
            }
            return result.reverse();
        },

        get parentId() {
            const root = getRoot(self);
            return getTreeParentById(self.id, root.tree, root.rootId);
        },
    }))
    .actions((self) => ({
        /**
         * Update this node attributes.
         *
         * @param {function} callback The callback with one argument (node draft)
         * @param {boolean} [setHash] If true the hash node is changed so a rerender is forced
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         */
        setter(callback, setHash = false) {
            callback(self);
            setHash && (self.hash = uuid());
        },

        /**
         * Update the checked flag.
         *
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         */
        toggleChecked(flag = !self.checked, children = true) {
            self.checked = flag;
            children && self.childNodes.forEach((n) => n.toggleChecked(flag));

            // Check parents
            if (!flag) {
                const { path } = self;
                path.pop();
                path.forEach((n) => n.toggleChecked(false, false));
            }
        },

        /**
         * Rename folder.
         *
         * @param {string} inputValue The new name
         * @returns {object} Server response
         * @throws Error
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         * @async
         */
        setName: flow(function* (inputValue) {
            self.setter((node) => {
                node.$busy = true;
            });

            try {
                const result = yield request({
                    location: {
                        path: `/folders/${self.id}`,
                        method: "PUT",
                    },
                    request: {
                        name: inputValue,
                    },
                });
                const { id, name, cnt, children, ...rest } = result;
                self.setter((node) => {
                    node.title = name;
                    node.properties = $.extend(true, {}, node.properties, rest);
                    node.$busy = false;
                });
                return result;
            } catch (e) {
                self.setter((node) => {
                    node.$busy = false;
                }, self.id);
                throw e;
            }
        }),

        /**
         * (Pro only) Apply an order to the folder content.
         *
         * @param {string} id The sortable id
         * @param {boolean} [automatically=false] If true the order is applied automatically if new files are added to the folder
         * @returns {boolean}
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         * @async
         */
        applyOrder: flow(function* (id, automatically = false) {
            if (process.env.PLUGIN_CTX === "pro") {
                /* onlypro:start */
                self.setter((node) => {
                    node.$busyOrder = true;
                });

                try {
                    const result = yield request({
                        location: {
                            path: `/folders/${self.id}/content/sortables`,
                            method: "POST",
                        },
                        request: {
                            id,
                            automatically,
                        },
                    });

                    if (result) {
                        id !== "reindex" && id !== "last" && ((self.contentCustomOrder = 1), (self.lastOrderBy = id));
                        automatically && (self.orderAutomatically = true);
                        id === "original" && ((self.contentCustomOrder = 0), (self.orderAutomatically = false));
                        id === "deactivate" && (self.orderAutomatically = false);
                        return true;
                    }
                    return false;
                } finally {
                    self.setter((node) => {
                        node.$busyOrder = false;
                    });
                }
                /* onlypro:end */
            } else {
                return false;
            }
        }),

        /**
         * (Pro only) Apply an order to the childrens.
         *
         * @param {string} [id] The sortable id - if not set the childNodes are sorted again by the last local known orderby
         * @param {boolean} [automatically=false] If true the order is applied automatically if new folders are added as subfolders
         * @returns {boolean}
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         * @async
         * @since 4.4
         */
        applyChildrenOrder: flow(function* (id, automatically = false) {
            if (process.env.PLUGIN_CTX === "pro") {
                /* onlypro:start */
                id &&
                    self.setter((node) => {
                        node.$busyOrder = true;
                    });

                try {
                    if (id) {
                        const result = yield request({
                            location: {
                                path: `/folders/${self.id}/sortables`,
                                method: "POST",
                            },
                            request: {
                                id,
                                automatically,
                            },
                        });

                        if (result) {
                            id !== "reindex" && id !== "last" && (self.lastSubOrderBy = id);
                            automatically && (self.subOrderAutomatically = true);
                            id === "original" && (self.lastSubOrderBy = "");
                            (id === "deactivate" || id === "original") && (self.subOrderAutomatically = false);
                        }
                    }

                    /**
                     * (Pro only) This action is called when subfolders of a folder are successfully
                     * ordered by a criterium. You have to implement the .setter() for the new
                     * children's order.
                     *
                     * @event module:util/hooks#folder/children/order/$id
                     * @param {TreeNode[]} children The children of this folder, you have to apply .compare() because it is not reloaded from server
                     * @param {function} setChildren A function where you pass one argument: childNodes with new sorting
                     * @this module:store/TreeNode~TreeNode
                     * @since 4.4.0
                     */
                    hooks.call(
                        `folder/children/order/${id || self.lastSubOrderBy}`,
                        [
                            self.childNodes,
                            (newChildNodes) => self.setter((n) => n.childNodes.replace(newChildNodes), true),
                        ],
                        self,
                    );
                    return true;
                } finally {
                    id &&
                        self.setter((node) => {
                            node.$busyOrder = false;
                        });
                }
                /* onlypro:end */
            } else {
                return false;
            }
        }),

        /**
         * Toggle the view of this folder.
         *
         * @param boolean state
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         * @since 4.0.9
         */
        visible(state) {
            self.$visible = state;
        },

        /**
         * Permanently delete folder.
         *
         * @returns {string|int} The parent id
         * @throws Error
         * @memberof module:store/TreeNode~TreeNode
         * @instance
         * @async
         */
        trash: flow(function* () {
            self.setter((node) => {
                node.$busy = true;
            });

            try {
                yield request({
                    location: {
                        path: `/folders/${self.id}`,
                        method: "DELETE",
                    },
                });
                self.visible(false);
            } finally {
                self.setter((node) => {
                    node.$busy = false;
                });
            }
        }),
    }));

export default TreeNode;
