import API from "../../Utils/API/API";
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
import PermissionUtil from "../../Utils/Permission";
import User from "../../Utils/User";
import Loader, { LoaderType } from "../Loader/Loader";
import Pill, { PillSize } from "../Pill/Pill";
import BasicModelForm from "./BasicModelForm";
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
import FileModel from "../../../Models/DatabaseModels/DatabaseBaseModel/FileModel";
import { Black, VeryLightGray } from "../../../Types/BrandColors";
import { getMaxLengthFromTableColumnType } from "../../../Types/Database/ColumnLength";
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
import TableColumnType from "../../../Types/Database/TableColumnType";
import BadDataException from "../../../Types/Exception/BadDataException";
import Permission, { PermissionHelper, } from "../../../Types/Permission";
import Typeof from "../../../Types/Typeof";
import React, { useState } from "react";
import useAsyncEffect from "use-async-effect";
export var FormType;
(function (FormType) {
    FormType[FormType["Create"] = 0] = "Create";
    FormType[FormType["Update"] = 1] = "Update";
})(FormType || (FormType = {}));
const ModelForm = (props) => {
    const [fields, setFields] = useState([]);
    const [isLoading, setLoading] = useState(false);
    const [isFetching, setIsFetching] = useState(false);
    const [isFetchingDropdownOptions, setIsFetchingDropdownOptions] = useState(false);
    const [error, setError] = useState("");
    const [itemToEdit, setItemToEdit] = useState(null);
    const model = new props.modelType();
    const modelAPI = props.modelAPI || ModelAPI;
    const getSelectFields = () => {
        const select = {};
        for (const field of props.fields) {
            const key = field.field
                ? Object.keys(field.field)[0]
                : null;
            if (key &&
                (hasPermissionOnField(key) || field.showEvenIfPermissionDoesNotExist)) {
                select[key] = true;
            }
        }
        return select;
    };
    const getRelationSelect = () => {
        const relationSelect = {};
        for (const field of props.fields) {
            const key = field.field
                ? Object.keys(field.field)[0]
                : null;
            if (key && model.isFileColumn(key)) {
                relationSelect[key] = {
                    file: true,
                    _id: true,
                    fileType: true,
                    name: true,
                };
            }
            else if (key && model.isEntityColumn(key)) {
                relationSelect[key] = field.field[key];
            }
        }
        return relationSelect;
    };
    const hasPermissionOnField = (fieldName) => {
        var _a, _b;
        if (User.isMasterAdmin()) {
            return true; // master admin can do anything.
        }
        let userPermissions = ((_a = PermissionUtil.getGlobalPermissions()) === null || _a === void 0 ? void 0 : _a.globalPermissions) || [];
        if (PermissionUtil.getProjectPermissions() &&
            ((_b = PermissionUtil.getProjectPermissions()) === null || _b === void 0 ? void 0 : _b.permissions) &&
            PermissionUtil.getProjectPermissions().permissions.length > 0) {
            userPermissions = userPermissions.concat(PermissionUtil.getProjectPermissions().permissions.map((i) => {
                return i.permission;
            }));
        }
        userPermissions.push(Permission.Public);
        const fieldPermissions = getFieldPermissions(fieldName);
        if (fieldPermissions &&
            PermissionHelper.doesPermissionsIntersect(userPermissions, fieldPermissions)) {
            return true;
        }
        return false;
    };
    const getFieldPermissions = (fieldName) => {
        var _a, _b;
        const accessControl = model.getColumnAccessControlForAllColumns();
        let fieldPermissions = [];
        if (FormType.Create === props.formType) {
            fieldPermissions = ((_a = accessControl[fieldName]) === null || _a === void 0 ? void 0 : _a.create) || [];
        }
        else {
            fieldPermissions = ((_b = accessControl[fieldName]) === null || _b === void 0 ? void 0 : _b.update) || [];
        }
        return fieldPermissions;
    };
    const setFormFields = async () => {
        var _a, _b;
        let fieldsToSet = [];
        for (const field of props.fields) {
            const fieldObj = field.field || field.overrideField;
            if (!fieldObj) {
                continue;
            }
            const keys = Object.keys(fieldObj);
            if (keys.length > 0) {
                const key = keys[0];
                const hasPermission = hasPermissionOnField(key);
                if ((field.showEvenIfPermissionDoesNotExist || hasPermission) &&
                    fieldsToSet.filter((i) => {
                        const fieldObj = i.field || i.overrideField;
                        if (!fieldObj) {
                            return false;
                        }
                        // check if field already exists. If it does, don't add it.
                        const iKeys = Object.keys(fieldObj);
                        const iFieldKey = iKeys[0];
                        return iFieldKey === key;
                    }).length === 0) {
                    // check if has maxLength
                    if (!((_a = field.validation) === null || _a === void 0 ? void 0 : _a.maxLength) &&
                        ((_b = model.getTableColumnMetadata(key)) === null || _b === void 0 ? void 0 : _b.type)) {
                        field.validation = Object.assign(Object.assign({}, field.validation), { maxLength: getMaxLengthFromTableColumnType(model.getTableColumnMetadata(key).type) });
                    }
                    fieldsToSet.push(Object.assign(Object.assign({}, field), { field: {
                            [key]: true,
                        } }));
                }
            }
        }
        fieldsToSet = await fetchDropdownOptions(fieldsToSet);
        // if there are no fields to set, then show permission error. This is useful when there are no fields to show.
        if (fieldsToSet.length === 0 && props.fields.length > 0) {
            const field = props.fields[0];
            if (field) {
                const fieldName = Object.keys(field.field || {})[0];
                if (fieldName) {
                    const fieldPermissions = getFieldPermissions(fieldName);
                    const columnMetadata = model.getTableColumnMetadata(fieldName);
                    setError(`You don't have enough permissions to ${props.formType === FormType.Create ? "create" : "edit"} ${columnMetadata.title} on ${model.singularName}. You need one of the following permissions: ${fieldPermissions.join(", ")}`);
                }
            }
        }
        setFields(fieldsToSet);
    };
    useAsyncEffect(async () => {
        // set fields.
        await setFormFields();
    }, [props.fields]);
    const fetchItem = async () => {
        if (!props.modelIdToEdit || props.formType !== FormType.Update) {
            throw new BadDataException("Model ID to update not found.");
        }
        let item = await modelAPI.getItem({
            modelType: props.modelType,
            id: props.modelIdToEdit,
            select: Object.assign(Object.assign({}, getSelectFields()), getRelationSelect()),
            requestOptions: {
                overrideRequestUrl: props.fetchItemApiUrl,
            },
        });
        if (!(item instanceof BaseModel) && item) {
            item = BaseModel.fromJSON(item, props.modelType);
        }
        if (!item) {
            setError(`Cannot edit ${(model.singularName || "item").toLowerCase()}. It could be because you don't have enough permissions to read or edit this ${(model.singularName || "item").toLowerCase()}.`);
        }
        const relationSelect = getRelationSelect();
        for (const key in relationSelect) {
            if (!item) {
                continue;
            }
            const isFileColumn = model.isFileColumn(key);
            if (Array.isArray(item[key])) {
                if (isFileColumn) {
                    item[key] = item[key].map((file) => {
                        return BaseModel.fromJSON(file, FileModel);
                    });
                    continue;
                }
                const idArray = [];
                let isModelArray = false;
                for (const itemInArray of item[key]) {
                    if (typeof itemInArray === "object" && itemInArray) {
                        const id = itemInArray["_id"];
                        if (id) {
                            isModelArray = true;
                            idArray.push(id);
                        }
                    }
                }
                if (isModelArray) {
                    item[key] = idArray;
                }
            }
            else if (item[key] && typeof item[key] === "object") {
                if (isFileColumn) {
                    item[key] = BaseModel.fromJSON(item[key], FileModel);
                    continue;
                }
                if (!(item[key] instanceof FileModel)) {
                    const id = item[key]["_id"];
                    if (id) {
                        item[key] = id;
                    }
                }
            }
        }
        setItemToEdit(item);
    };
    const fetchDropdownOptions = async (fields) => {
        var _a, _b;
        setIsFetchingDropdownOptions(true);
        try {
            for (const field of fields) {
                if (field.dropdownModal && field.dropdownModal.type) {
                    const tempModel = new field.dropdownModal.type();
                    const select = {
                        [field.dropdownModal.labelField]: true,
                        [field.dropdownModal.valueField]: true,
                    };
                    let colorColumnName = null;
                    let shouldSelectColorColumn = false;
                    colorColumnName = tempModel.getFirstColorColumn();
                    if (colorColumnName) {
                        select[colorColumnName] = true;
                        shouldSelectColorColumn = true;
                    }
                    const accessControlColumnName = tempModel.getAccessControlColumn();
                    // also select labels, so they can select resources by labels. This is useful for resources like monitors, etc.
                    if (accessControlColumnName) {
                        select[accessControlColumnName] = {
                            _id: true,
                            name: true,
                            color: true,
                        };
                    }
                    const listResult = await modelAPI.getList({
                        modelType: field.dropdownModal.type,
                        query: {},
                        limit: LIMIT_PER_PROJECT,
                        skip: 0,
                        select: select,
                        sort: {},
                    });
                    if (listResult.data && listResult.data.length > 0) {
                        const dropdownOptions = listResult.data.map((item) => {
                            var _a, _b;
                            if (!field.dropdownModal) {
                                throw new BadDataException("Dropdown Modal value mot found");
                            }
                            const option = {
                                label: item[(_a = field.dropdownModal) === null || _a === void 0 ? void 0 : _a.labelField].toString(),
                                value: item[(_b = field.dropdownModal) === null || _b === void 0 ? void 0 : _b.valueField].toString(),
                            };
                            if (colorColumnName && shouldSelectColorColumn) {
                                const color = item.getColumnValue(colorColumnName);
                                if (color) {
                                    option.color = color;
                                }
                            }
                            if (accessControlColumnName) {
                                const labelsForItem = (item[accessControlColumnName] || []).filter((label) => {
                                    return Boolean(label);
                                });
                                if (labelsForItem.length > 0) {
                                    option.labels = labelsForItem;
                                }
                            }
                            return option;
                        });
                        field.dropdownOptions = dropdownOptions;
                        if (accessControlColumnName) {
                            const categories = [];
                            // populate categories.
                            let localLabels = [];
                            for (const item of listResult.data) {
                                const accessControlColumn = accessControlColumnName;
                                const labels = item[accessControlColumn] || [];
                                for (const label of labels) {
                                    if (label && label._id && label.getColumnValue("name")) {
                                        // check if this category already exists.
                                        const existingLabel = localLabels.find((i) => {
                                            var _a, _b;
                                            return ((_a = i._id) === null || _a === void 0 ? void 0 : _a.toString()) === ((_b = label._id) === null || _b === void 0 ? void 0 : _b.toString());
                                        });
                                        if (!existingLabel) {
                                            localLabels.push(label);
                                        }
                                    }
                                }
                            }
                            // sort category by name.
                            localLabels = localLabels.sort((a, b) => {
                                var _a;
                                return a
                                    .getColumnValue("name")
                                    .toString()
                                    .localeCompare(((_a = b.getColumnValue("name")) === null || _a === void 0 ? void 0 : _a.toString()) || "");
                            });
                            // for each of these labels add category.
                            for (const label of localLabels) {
                                categories.push({
                                    id: ((_a = label._id) === null || _a === void 0 ? void 0 : _a.toString()) || "",
                                    title: (React.createElement("span", { className: "mb-1" },
                                        React.createElement(Pill, { size: PillSize.Small, color: label.getColumnValue("color") || Black, text: label.getColumnValue("name") || "" }))),
                                });
                            }
                            // now populate options.
                            const options = [];
                            for (const item of listResult.data) {
                                const accessControlColumn = accessControlColumnName;
                                const labels = item[accessControlColumn] || [];
                                if (labels.length > 0) {
                                    for (const label of labels) {
                                        options.push({
                                            value: item.getColumnValue(field.dropdownModal.valueField),
                                            label: item.getColumnValue(field.dropdownModal.labelField),
                                            categoryId: ((_b = label._id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
                                        });
                                    }
                                }
                                else {
                                    options.push({
                                        value: item.getColumnValue(field.dropdownModal.valueField),
                                        label: item.getColumnValue(field.dropdownModal.labelField),
                                        categoryId: "",
                                    });
                                }
                            }
                            field.selectByAccessControlProps = {
                                categoryCheckboxProps: {
                                    categories: categories,
                                    options: options,
                                },
                                accessControlColumnTitle: tempModel.getTableColumnMetadata(accessControlColumnName)
                                    .title || "",
                            };
                        }
                    }
                    else {
                        field.dropdownOptions = [];
                    }
                }
            }
        }
        catch (err) {
            setError(API.getFriendlyMessage(err));
        }
        setIsFetchingDropdownOptions(false);
        return fields;
    };
    useAsyncEffect(async () => {
        var _a;
        if (props.modelIdToEdit &&
            props.formType === FormType.Update &&
            !props.doNotFetchExistingModel) {
            // get item.
            setLoading(true);
            setIsFetching(true);
            setError("");
            try {
                await fetchItem();
            }
            catch (err) {
                setError(API.getFriendlyMessage(err));
                (_a = props.onError) === null || _a === void 0 ? void 0 : _a.call(props, API.getFriendlyMessage(err));
            }
            setLoading(false);
            setIsFetching(false);
        }
    }, []);
    const getMiscDataProps = (values) => {
        const result = {};
        for (const field of fields) {
            if (field.overrideFieldKey && values[field.overrideFieldKey]) {
                result[field.overrideFieldKey] =
                    values[field.overrideFieldKey] || null;
            }
        }
        return result;
    };
    const onSubmit = async (values, onSubmitSuccessful) => {
        // Ping an API here.
        setError("");
        setLoading(true);
        if (props.onLoadingChange) {
            props.onLoadingChange(true);
        }
        let result;
        try {
            // strip data.
            const valuesToSend = {};
            for (const key in getSelectFields()) {
                valuesToSend[key] = values[key];
            }
            if (props.formType === FormType.Update && props.modelIdToEdit) {
                valuesToSend["_id"] = props.modelIdToEdit.toString();
            }
            const miscDataProps = getMiscDataProps(values);
            // remove those props from valuesToSend
            for (const key in miscDataProps) {
                delete valuesToSend[key];
            }
            for (const key of model.getTableColumns().columns) {
                const tableColumnMetadata = model.getTableColumnMetadata(key);
                if (tableColumnMetadata &&
                    tableColumnMetadata.modelType &&
                    tableColumnMetadata.type === TableColumnType.Entity &&
                    valuesToSend[key] &&
                    typeof valuesToSend[key] === Typeof.String) {
                    const baseModel = new tableColumnMetadata.modelType();
                    baseModel._id = valuesToSend[key];
                    valuesToSend[key] = baseModel;
                }
                if (tableColumnMetadata &&
                    tableColumnMetadata.modelType &&
                    tableColumnMetadata.type === TableColumnType.EntityArray &&
                    Array.isArray(valuesToSend[key]) &&
                    valuesToSend[key].length > 0 &&
                    typeof valuesToSend[key][0] === Typeof.Object &&
                    typeof valuesToSend[key][0].value === Typeof.String) {
                    const arr = [];
                    for (const id of valuesToSend[key]) {
                        arr.push(id.value);
                    }
                    valuesToSend[key] = arr;
                }
                if (tableColumnMetadata &&
                    tableColumnMetadata.modelType &&
                    tableColumnMetadata.type === TableColumnType.EntityArray &&
                    Array.isArray(valuesToSend[key]) &&
                    valuesToSend[key].length > 0 &&
                    typeof valuesToSend[key][0] === Typeof.String) {
                    const arr = [];
                    for (const id of valuesToSend[key]) {
                        const baseModel = new tableColumnMetadata.modelType();
                        baseModel._id = id;
                        arr.push(baseModel);
                    }
                    valuesToSend[key] = arr;
                }
            }
            let tBaseModel = BaseModel.fromJSON(valuesToSend, props.modelType);
            if (props.onBeforeCreate && props.formType === FormType.Create) {
                tBaseModel = await props.onBeforeCreate(tBaseModel, miscDataProps);
            }
            result = await modelAPI.createOrUpdate({
                model: tBaseModel,
                modelType: props.modelType,
                formType: props.formType,
                miscDataProps: miscDataProps,
                requestOptions: Object.assign(Object.assign({}, props.saveRequestOptions), { requestHeaders: props.requestHeaders, overrideRequestUrl: props.createOrUpdateApiUrl }),
            });
            const miscData = result.miscData;
            if (props.onSuccess) {
                // we do props.formType === FormType.Create ? result.data: tBaseModel because update API does not return the updated model.
                props.onSuccess(BaseModel.fromJSONObject(props.formType === FormType.Create ? result.data : tBaseModel, props.modelType), miscData);
            }
            onSubmitSuccessful();
        }
        catch (err) {
            setError(API.getFriendlyMessage(err));
        }
        setLoading(false);
        if (props.onLoadingChange) {
            props.onLoadingChange(false);
        }
    };
    if (isFetching || isFetchingDropdownOptions) {
        return (React.createElement("div", { className: "row flex justify-center mt-20 mb-20" },
            React.createElement(Loader, { loaderType: LoaderType.Bar, color: VeryLightGray, size: 200 })));
    }
    return (React.createElement("div", null,
        React.createElement(BasicModelForm, { values: props.values, title: props.title, description: props.description, disableAutofocus: props.disableAutofocus, model: model, id: props.id, name: props.name, onFormStepChange: props.onFormStepChange, onIsLastFormStep: props.onIsLastFormStep, fields: fields, steps: props.steps, onChange: (values, setNewFormValues) => {
                var _a;
                if (!isLoading) {
                    (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, values, setNewFormValues);
                }
            }, showAsColumns: props.showAsColumns, footer: props.footer, isLoading: isLoading, submitButtonText: props.submitButtonText, cancelButtonText: props.cancelButtonText, onSubmit: onSubmit, submitButtonStyleType: props.submitButtonStyleType, onValidate: props.onValidate, onCancel: props.onCancel, maxPrimaryButtonWidth: props.maxPrimaryButtonWidth, error: error, hideSubmitButton: props.hideSubmitButton, formRef: props.formRef, initialValues: (itemToEdit || props.initialValues), summary: props.summary })));
};
export default ModelForm;
//# sourceMappingURL=ModelForm.js.map