import React, { useEffect, useState } from "react";
import Label from "../../../Models/DatabaseModels/Label";
import BadDataException from "../../../Types/Exception/BadDataException";
import IconProp from "../../../Types/Icon/IconProp";
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
import Includes from "../../../Types/BaseDatabase/Includes";
import API from "../../Utils/API/API";
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
import ProjectUtil from "../../Utils/Project";
import { ButtonStyleType } from "../Button/Button";
import BasicFormModal from "../FormModal/BasicFormModal";
import Modal from "../Modal/Modal";
import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType";
/**
 * Reusable hook that provides "Add Labels" and "Remove Labels" bulk actions
 * for any ModelTable whose model has a `labels` many-to-many relationship
 * to the Label entity.
 *
 * Usage:
 *   const { bulkActions, modals } = useBulkLabelActions({ modelType: Monitor });
 *   <ModelTable bulkActions={{ buttons: [...bulkActions, ...] }} />
 *   {modals}
 */
function useBulkLabelActions(config) {
    const [labels, setLabels] = useState([]);
    const [showAddModal, setShowAddModal] = useState(false);
    const [showRemoveModal, setShowRemoveModal] = useState(false);
    const [bulkActionProps, setBulkActionProps] = useState(null);
    /*
     * For "Remove Labels" we only want to offer labels that are actually
     * attached to the selected items (not every label in the project). These
     * are computed from the selected items each time the remove modal opens.
     */
    const [removeLabelDropdownOptions, setRemoveLabelDropdownOptions] = useState([]);
    const [isLoadingRemoveLabels, setIsLoadingRemoveLabels] = useState(false);
    useEffect(() => {
        const fetchLabels = async () => {
            try {
                const result = await ModelAPI.getList({
                    modelType: Label,
                    query: {
                        projectId: ProjectUtil.getCurrentProjectId(),
                    },
                    limit: LIMIT_PER_PROJECT,
                    skip: 0,
                    select: {
                        _id: true,
                        name: true,
                        color: true,
                    },
                    sort: {
                        name: SortOrder.Ascending,
                    },
                });
                setLabels(result.data);
            }
            catch (_a) {
                // labels will remain empty; modal will show no options
            }
        };
        fetchLabels();
    }, []);
    const applyLabels = async (labelIds, mode) => {
        if (!bulkActionProps) {
            return;
        }
        const { items, onProgressInfo, onBulkActionStart, onBulkActionEnd } = bulkActionProps;
        // Close the form modal first so the progress modal is visible
        setShowAddModal(false);
        setShowRemoveModal(false);
        onBulkActionStart();
        const totalItems = [...items];
        const inProgressItems = [...items];
        const successItems = [];
        const failedItems = [];
        for (const item of totalItems) {
            inProgressItems.splice(inProgressItems.indexOf(item), 1);
            try {
                if (!item.id) {
                    throw new BadDataException("Item ID not found");
                }
                /*
                 * Fetch current labels for this item so we can merge/subtract
                 * the selected label ids and avoid clobbering existing ones.
                 */
                const currentItem = await ModelAPI.getItem({
                    modelType: config.modelType,
                    id: item.id,
                    select: {
                        labels: {
                            _id: true,
                        },
                    },
                });
                const existingLabelIds = ((currentItem === null || currentItem === void 0 ? void 0 : currentItem.labels) || [])
                    .map((label) => {
                    var _a;
                    return ((_a = label._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
                })
                    .filter((id) => {
                    return id.length > 0;
                });
                let newLabelIds;
                if (mode === "add") {
                    newLabelIds = Array.from(new Set([...existingLabelIds, ...labelIds]));
                }
                else {
                    newLabelIds = existingLabelIds.filter((id) => {
                        return !labelIds.includes(id);
                    });
                }
                /*
                 * No-op short-circuit: if nothing changed, still report as success
                 * so the user sees the item as processed.
                 */
                if (newLabelIds.length === existingLabelIds.length &&
                    newLabelIds.every((id) => {
                        return existingLabelIds.includes(id);
                    })) {
                    successItems.push(item);
                }
                else {
                    await ModelAPI.updateById({
                        id: item.id,
                        modelType: config.modelType,
                        data: {
                            labels: newLabelIds,
                        },
                    });
                    successItems.push(item);
                }
            }
            catch (err) {
                failedItems.push({
                    item: item,
                    failedMessage: API.getFriendlyMessage(err),
                });
            }
            onProgressInfo({
                totalItems: totalItems,
                failed: failedItems,
                successItems: successItems,
                inProgressItems: inProgressItems,
            });
        }
        onBulkActionEnd();
        setBulkActionProps(null);
    };
    /*
     * Build the dropdown options for the "Remove Labels" modal from only the
     * labels actually attached to the selected items. We fetch the selected
     * items (by id) with their labels and take the de-duplicated union, so the
     * modal offers only labels that can really be removed instead of every
     * label in the project.
     */
    const loadLabelsForSelectedItems = async (items) => {
        var _a;
        setIsLoadingRemoveLabels(true);
        try {
            const itemIds = items
                .map((item) => {
                var _a;
                return ((_a = item.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            })
                .filter((id) => {
                return id.length > 0;
            });
            if (itemIds.length === 0) {
                setRemoveLabelDropdownOptions([]);
                return;
            }
            const result = await ModelAPI.getList({
                modelType: config.modelType,
                query: {
                    _id: new Includes(itemIds),
                },
                limit: LIMIT_PER_PROJECT,
                skip: 0,
                select: {
                    labels: {
                        _id: true,
                        name: true,
                        color: true,
                    },
                },
                sort: {},
            });
            // Union of labels across all selected items, de-duplicated by id.
            const labelById = new Map();
            for (const item of result.data) {
                const itemLabels = item.labels || [];
                for (const label of itemLabels) {
                    const id = ((_a = label._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
                    if (id.length > 0) {
                        labelById.set(id, label);
                    }
                }
            }
            const options = Array.from(labelById.values()).map((label) => {
                var _a;
                const option = {
                    label: label.name || "",
                    value: ((_a = label._id) === null || _a === void 0 ? void 0 : _a.toString()) || "",
                };
                if (label.color) {
                    option.color = label.color;
                }
                return option;
            });
            options.sort((a, b) => {
                return a.label.localeCompare(b.label);
            });
            setRemoveLabelDropdownOptions(options);
        }
        catch (_b) {
            // on error, show no options rather than every label in the project
            setRemoveLabelDropdownOptions([]);
        }
        finally {
            setIsLoadingRemoveLabels(false);
        }
    };
    const labelDropdownOptions = labels.map((label) => {
        var _a;
        const option = {
            label: label.name || "",
            value: ((_a = label._id) === null || _a === void 0 ? void 0 : _a.toString()) || "",
        };
        if (label.color) {
            option.color = label.color;
        }
        return option;
    });
    const addLabelsAction = {
        title: "Add Labels",
        buttonStyleType: ButtonStyleType.NORMAL,
        icon: IconProp.Label,
        onClick: async (actionProps) => {
            setBulkActionProps(actionProps);
            setShowAddModal(true);
        },
    };
    const removeLabelsAction = {
        title: "Remove Labels",
        buttonStyleType: ButtonStyleType.NORMAL,
        icon: IconProp.Close,
        onClick: async (actionProps) => {
            setBulkActionProps(actionProps);
            setRemoveLabelDropdownOptions([]);
            setShowRemoveModal(true);
            await loadLabelsForSelectedItems(actionProps.items);
        },
    };
    const closeRemoveModal = () => {
        setShowRemoveModal(false);
        setBulkActionProps(null);
        setRemoveLabelDropdownOptions([]);
    };
    const hasNoLabelsToRemove = !isLoadingRemoveLabels && removeLabelDropdownOptions.length === 0;
    const modals = (React.createElement(React.Fragment, null,
        showAddModal && (React.createElement(BasicFormModal, { title: "Add Labels", description: "Select labels to add to the selected items. Labels already attached to an item will be preserved.", onClose: () => {
                setShowAddModal(false);
                setBulkActionProps(null);
            }, submitButtonText: "Add Labels", onSubmit: async (formData) => {
                await applyLabels(formData.labelIds || [], "add");
            }, formProps: {
                fields: [
                    {
                        field: {
                            labelIds: true,
                        },
                        title: "Select Labels",
                        description: "These labels will be added to each selected item.",
                        fieldType: FormFieldSchemaType.MultiSelectDropdown,
                        required: true,
                        dropdownOptions: labelDropdownOptions,
                    },
                ],
            } })),
        showRemoveModal &&
            (hasNoLabelsToRemove ? (React.createElement(Modal, { title: "No Labels to Remove", description: "The selected items don't have any labels to remove.", icon: IconProp.Label, onClose: closeRemoveModal, closeButtonText: "Close" },
                React.createElement(React.Fragment, null))) : (React.createElement(BasicFormModal, { title: "Remove Labels", description: "Select labels to remove from the selected items. Only labels currently applied to the selected items are shown.", isLoading: isLoadingRemoveLabels, onClose: closeRemoveModal, submitButtonText: "Remove Labels", onSubmit: async (formData) => {
                    await applyLabels(formData.labelIds || [], "remove");
                }, formProps: {
                    fields: [
                        {
                            field: {
                                labelIds: true,
                            },
                            title: "Select Labels",
                            description: "These labels will be removed from each selected item.",
                            fieldType: FormFieldSchemaType.MultiSelectDropdown,
                            required: true,
                            dropdownOptions: removeLabelDropdownOptions,
                        },
                    ],
                } })))));
    return {
        bulkActions: [addLabelsAction, removeLabelsAction],
        modals: modals,
    };
}
export default useBulkLabelActions;
//# sourceMappingURL=BulkLabelActions.js.map