sap.ui.define([
    'sap/ui/model/FilterOperator',
    'sap/ui/model/Filter',
    'sap/suite/ui/generic/template/js/AnnotationHelper'
], function(FilterOperator, Filter, AnnotationHelper) {
    'use strict';

    const mReverseOperator = {};
    fillReverseOperator();

    function fillReverseOperator() {
        mReverseOperator[FilterOperator.EQ] = FilterOperator.NE;
        mReverseOperator[FilterOperator.Contains] = FilterOperator.NotContains;
        mReverseOperator[FilterOperator.EndsWith] = FilterOperator.NotEndsWith;
        mReverseOperator[FilterOperator.StartsWith] = FilterOperator.NotStartsWith;
        mReverseOperator[FilterOperator.BT] = FilterOperator.NB;

        mReverseOperator[FilterOperator.NE] = FilterOperator.EQ;
        mReverseOperator[FilterOperator.NotContains] = FilterOperator.Contains;
        mReverseOperator[FilterOperator.NotEndsWith] = FilterOperator.EndsWith;
        mReverseOperator[FilterOperator.NotStartsWith] = FilterOperator.StartsWith;
        mReverseOperator[FilterOperator.NB] = FilterOperator.BT;
    }

    function getFiltersForDataFetching(sKey, keySpecificSelectedResult) {
        const aFilters = [];

        for (let i = 0; i < keySpecificSelectedResult.length; ++i) {
            const result = keySpecificSelectedResult[i];
            if (result.operator === FilterOperator.BT || result.operator === FilterOperator.NB) {
                aFilters.push(new Filter({
                    path: sKey,
                    operator: result.operator,
                    value1: result.selectedValues[0],
                    value2: result.selectedValues[1]
                }));
            } else {
                for (let j = 0; j < result.selectedValues.length; ++j) {
                    aFilters.push(new Filter({
                        path: sKey,
                        operator: result.operator,
                        value1: result.selectedValues[j]
                    }));
                }
            }
        }
        return aFilters;
    }

    function isComparitveOperator(sFilter) {
        return sFilter === FilterOperator.BT || sFilter === FilterOperator.NB ||
               sFilter === FilterOperator.GT || sFilter === FilterOperator.LT ||
               sFilter === FilterOperator.GE || sFilter === FilterOperator.LE;
    }

    function isNegationOperator(sOperator) {
        return sOperator === FilterOperator.NE || sOperator === FilterOperator.NotContains ||
               sOperator === FilterOperator.NotEndsWith || sOperator === FilterOperator.NotStartsWith ||
               sOperator === FilterOperator.NB;
    }

    function getkeySpecifiedResultFromFilters(oFilter) {
        let values = [];
        if (oFilter.getOperator() === FilterOperator.BT || oFilter.getOperator() === FilterOperator.NB) {
            values = [{
                value: oFilter.getValue1(),
                description: oFilter.getValue1()
            },{
                value: oFilter.getValue2(),
                description: oFilter.getValue2()
            }];
        } else {
            values.push({
                value: oFilter.getValue1(),
                description: oFilter.getValue1()
            });
        }
        return {
            operator: oFilter.getOperator(),
            selectedValues: values
        };
    }

    async function fetchRecordsFromValueList(oValueList, oFilter, sKey, oSFBModel,oTemplateUtils) {
        return new Promise((resolve, reject) => {
            let oValueListDefaultBinding = oValueList[""];
            let sValueListEntity = oValueListDefaultBinding.CollectionPath.String;

            if (Array.isArray(oValueListDefaultBinding.Parameters)) {
                let oParameter = oValueListDefaultBinding.Parameters.find(oParam => (oParam.RecordType === "com.sap.vocabularies.Common.v1.ValueListParameterInOut" && oParam.LocalDataProperty.PropertyPath === sKey));
                if (oParameter) {
                    sKey = oParameter.ValueListProperty.String;
                }
            }

            let bIsNotOperator = isNegationOperator(oFilter.getOperator());
            if (bIsNotOperator) {
                oFilter = new Filter({
                    path: sKey,
                    operator: mReverseOperator[oFilter.getOperator()],
                    value1: oFilter.getValue1()
                });
            } else {
                oFilter = new Filter({
                    path: sKey,
                    operator: oFilter.getOperator(),
                    value1: oFilter.getValue1()
                });
            }

            oSFBModel.read("/" + sValueListEntity, {
                filters: [oFilter],
                success: async function(oResponse) {
                    const aResults = oResponse.results;
                    let aResponse;
                    try {
                        if (aResults.length > 0) {
                            aResponse = await getDescriptionForResponse(oResponse.results, sValueListEntity, sKey, oSFBModel,oTemplateUtils,oFilter);
                        } else {
                            aResponse = await fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils);
                        }
                        resolve({
                            operator: (bIsNotOperator) ? mReverseOperator[oFilter.getOperator()] : oFilter.getOperator(),
                            selectedValues: aResponse
                        });
                    } catch (error) {
                        reject(error);
                    }
                },
                error: async function(oError) {
                    //If the error is because of some property validation, we can try to do a fuzzy search
                    try {
                        const aResponse = await fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils);
                        resolve({
                            operator: (bIsNotOperator) ? mReverseOperator[oFilter.getOperator()] : oFilter.getOperator(),
                            selectedValues: aResponse
                        });
                    } catch (error) {
                        reject(error);
                    }
                }
            });
        });
    }

    async function fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils) {
        return new Promise((resolve, reject) => {
            oSFBModel.read("/" + sValueListEntity, {
                urlParameters: {
                    search: oFilter.getValue1()
                },
                success: async function(oResponse) {
                    let aResults = oResponse.results;
                    if (aResults.length == 0) {
                        //If it dosent have any records, the return the original value
                        resolve([{
                            value: oFilter.getValue1(),
                            description: oFilter.getValue1()
                        }]);
                    } else {
                        try {
                            aResults = await getDescriptionForResponse(aResults, sValueListEntity, sKey, oSFBModel,oTemplateUtils,oFilter);
                            resolve(aResults);
                        } catch (oError) {
                            reject(oError);
                        }
                    }
                },
                error: function(oError) {
                    reject(oError);
                }
            });
        });
    }

    function performClientSideSearch(aResults, oFilter,sKey,sDescriptionField) {
        if (!sKey || !sDescriptionField) {
            return aResults;
        }
        if (oFilter.getOperator() === FilterOperator.EQ || oFilter.getOperator() === FilterOperator.NE) {
            const aFilteredResults = aResults.filter(result => result[sKey].toString().toLowerCase() === oFilter.getValue1().toString().toLowerCase() || result[sDescriptionField].toString().toLowerCase() === oFilter.getValue1().toString().toLowerCase());
            return aFilteredResults.length > 0 ? aFilteredResults : aResults;
        }
        return aResults;
    }

    async function getDescriptionForResponse(aResults, sValueListEntity, sKey, oSFBModel, oTemplateUtils,oFilter) {
        return new Promise((resolve, reject) => {
            const aResultsWithTextArrangement = [];

            let oSFBMetaModel = oSFBModel.getMetaModel();
            let oValueHelpEntity = oTemplateUtils.oCommonUtils.getMetaModelEntityType(sValueListEntity);
            let oValueHelpProperty = oSFBMetaModel.getODataProperty(oValueHelpEntity, sKey);
            let sTextArrangementPath = AnnotationHelper.getTextArrangementPath(oValueHelpProperty);
            let sTextArrangement = AnnotationHelper.getTextArrangementForEasyFilter(oValueHelpEntity, oValueHelpProperty);
            let navigationProperty = null;
            let sEntityForTextDescription = sValueListEntity;

            if (sTextArrangementPath && sTextArrangementPath.split("/").length > 1) {
                navigationProperty = sTextArrangementPath.split("/")[0];
                sEntityForTextDescription = oSFBMetaModel.getODataAssociationSetEnd(oValueHelpEntity, navigationProperty).entitySet;
                sTextArrangementPath = sTextArrangementPath.split("/")[1];
            }

            aResults = performClientSideSearch(aResults, oFilter,sKey,sTextArrangementPath);

            if (sValueListEntity === sEntityForTextDescription) {
                aResults.forEach((result) => {
                    aResultsWithTextArrangement.push({
                        value: result[sKey],
                        description: AnnotationHelper.getTextArrangementFinalString(result, sKey, sTextArrangementPath, sTextArrangement)
                    });
                });
                resolve(aResultsWithTextArrangement);
            } else {
                let aAllMatchedResults = aResults.map((result) => {
                    return result[sKey];
                });
                let aMatchedFilterResults = aAllMatchedResults.map((matchedResult) => {
                    return new Filter({
                        path: sKey,
                        operator: FilterOperator.EQ,
                        value1: matchedResult
                    });
                });

                // The below code only be executed when the textDescription and ValueList entities are different
                oSFBModel.read("/" + sEntityForTextDescription, {
                    filters: aMatchedFilterResults,
                    success: function(oResponse) {
                        let aResults = oResponse.results;

                        aMatchedFilterResults.forEach((oFilter) => {
                            let result = aResults.find((res) => res[sKey] === oFilter.getValue1());
                            aResultsWithTextArrangement.push({
                                value: oFilter.getValue1(),
                                description: (result) ? AnnotationHelper.getTextArrangementFinalString(result, sKey, sTextArrangementPath, sTextArrangement) : oFilter.getValue1()
                            });
                        });
                        resolve(aResultsWithTextArrangement);
                    },
                    error: function(oError) {
                        reject(oError);
                    }
                });
            }
        });
    }

        /**
         * The primary responsibility of this function is to convert the given values into their corresponding
         * IDs and Descriptions, respecting the defined TextArrangement.
         *
         * This process is carried out in two phases:
         *
         *  Fetching the ID:
         *  1) Make a $filter call using the provided value and operator.
         *     - If this returns results, we proceed to fetch the corresponding descriptions for these IDs.
         *  2) If no results are returned, make a $search (fuzzy search) call instead.
         *
         *  Fetching the Description:
         *  1) The description is determined by the sap:text/com.sap.vocabularies.UI.v1.TextArrangement
         *     annotation of the ValueHelp entity, not from the main entity.
         *  2) To fetch the description, we use a $filter call, since the IDs were already retrieved in the first step.
         *
         * Edge cases with negation operators:
         *  1) For negation operators such as NB, NotStartsWith, etc., we make a batch call using the
         *     positive equivalent of these operators (e.g., BT, StartsWith).
         *  2) After receiving the results, we return them with the original negation operator
         *     (e.g., NB, NotStartsWith).
        */

    function fetchDataForKey(sKey, keySpecificSelectedResult, oState, oController, oTemplateUtils) {
        return new Promise(async(resolve, reject) => {
            try {
                const aFinalResponse = [];
                const oOwnerFilterControl = oState.oSmartFilterbar;
                const sEntitySet = oController.getOwnerComponent().getEntitySet();
                const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(sEntitySet);
                const oSFBModel = oOwnerFilterControl.getModel();
                const oSFBMetaModel = oSFBModel.getMetaModel();
                const aFilters = getFiltersForDataFetching(sKey, keySpecificSelectedResult);
                const oProperty = oSFBMetaModel.getODataProperty(oEntityType, sKey);
                const sPropertyPath = oSFBMetaModel.getODataProperty(oEntityType, sKey, true);
                const isValueHelpTableAvailable = AnnotationHelper.isValueHelpTableAvailable(oProperty);

                if (!isValueHelpTableAvailable) {
                    // When there is no valueList associate, just return the original value in CodeList type
                    aFilters.forEach(oFilter => aFinalResponse.push(getkeySpecifiedResultFromFilters(oFilter)));
                    resolve(aFinalResponse);
                }

                // Get value help lists from metadata
                const oPropertyContext = oSFBMetaModel.createBindingContext(sPropertyPath);
                const oValueList = await oSFBMetaModel.getODataValueLists(oPropertyContext);

                // Process each filter in parallel
                const pPromises = [];

                for (const oFilter of aFilters) {
                    if (isComparitveOperator(oFilter.getOperator())) {
                        // For comparative operators, just add the original values
                        aFinalResponse.push(getkeySpecifiedResultFromFilters(oFilter));
                    } else {
                        // For other operators, fetch records from value list
                        pPromises.push(fetchRecordsFromValueList(oValueList, oFilter, sKey, oSFBModel, oTemplateUtils));
                    }
                }

                // Wait for all promises to resolve
                if (pPromises.length > 0) {
                    const aResponses = await Promise.all(pPromises);
                    aResponses.forEach(aResponse => {
                        aFinalResponse.push(aResponse);
                    });
                }

                resolve(aFinalResponse);
            } catch (error) {
                reject(error); // Re-throw to allow caller to handle
            }
        });
    }

    return {
        fetchDataForKey: fetchDataForKey,
        getFiltersForDataFetching: getFiltersForDataFetching,
        isComparitveOperator: isComparitveOperator,
        isNegationOperator: isNegationOperator,
        getReverseOperator: function(operator) {
            return mReverseOperator[operator];
        }
    };
});