ConSyst.pageLoading = (function () {
    const pendingTasks = new Map();
    const maxWaitMilliseconds = 15000;
    const revealTransitionMilliseconds = 180;
    let taskSequence = 0;
    let domReady = document.readyState !== 'loading';
    let revealScheduled = false;
    let revealStarted = false;
    let revealed = false;

    function mark(name) {
        if (window.performance?.mark) {
            window.performance.mark(name);
        }
    }

    function getPageContent() {
        return Array.from(document.querySelectorAll('[data-cst-page-content]'));
    }

    function getLoaders() {
        return Array.from(document.querySelectorAll('[data-cst-page-loader]'));
    }

    function getAnimationFrame() {
        const requestFrame = window.requestAnimationFrame || function (callback) {
            return window.setTimeout(callback, 16);
        };

        return new Promise(resolve => {
            requestFrame(() => requestFrame(resolve));
        });
    }

    function reveal(reason = 'ready') {
        if (revealed || revealStarted) {
            return;
        }

        revealStarted = true;
        getLoaders().forEach(element => {
            element.classList.add('cst-page-loading-fading');
        });

        const transitionMilliseconds = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
            ? 0
            : revealTransitionMilliseconds;
        window.setTimeout(function () {
            revealed = true;
            mark('cst-page-content-ready');
            document.body?.classList.remove('cst-page-loading');
            getPageContent().forEach(element => element.setAttribute('aria-busy', 'false'));
            getLoaders().forEach(element => {
                element.classList.add('cst-page-loading-hidden');
                element.setAttribute('aria-hidden', 'true');
            });
            window.dispatchEvent(new CustomEvent('cst:page-ready', { detail: { reason } }));
            mark('cst-page-loader-hidden');

            if (window.performance?.measure) {
                try {
                    window.performance.measure('cst-page-loading', 'cst-page-loading-start', 'cst-page-loader-hidden');
                }
                catch (error) {
                    // Performance marks are diagnostic only and must never block the page.
                }
            }
        }, transitionMilliseconds);
    }

    function scheduleReveal() {
        if (revealScheduled || revealed) {
            return;
        }

        revealScheduled = true;
        setTimeout(async function () {
            revealScheduled = false;
            if (revealed || !domReady || pendingTasks.size > 0) {
                return;
            }

            await getAnimationFrame();
            if (!revealed && domReady && pendingTasks.size === 0) {
                reveal('ready');
            }
        }, 0);
    }

    function track(name, taskOrPromise) {
        if (revealed) {
            return Promise.resolve();
        }

        const taskId = `${name || 'page-task'}-${++taskSequence}`;
        let taskResult;
        try {
            taskResult = typeof taskOrPromise === 'function' ? taskOrPromise() : taskOrPromise;
        }
        catch (error) {
            console.error(`Initial page task failed: ${name || taskId}`, error);
            scheduleReveal();
            return Promise.resolve();
        }

        const task = Promise.resolve(taskResult)
            .catch(error => {
                console.error(`Initial page task failed: ${name || taskId}`, error);
            })
            .finally(() => {
                pendingTasks.delete(taskId);
                scheduleReveal();
            });

        pendingTasks.set(taskId, task);
        return task;
    }

    function initialize() {
        mark('cst-page-loading-start');
        mark('cst-page-navigation-ready');

        if (domReady) {
            scheduleReveal();
        }

        document.addEventListener('DOMContentLoaded', function () {
            domReady = true;
            mark('cst-page-dom-ready');
            scheduleReveal();
        }, { once: true });

        window.addEventListener('load', function () {
            mark('cst-page-window-loaded');
            scheduleReveal();
        }, { once: true });

        window.setTimeout(function () {
            if (!revealed) {
                console.warn('Page loading overlay timed out; revealing the page.', { pendingTasks: pendingTasks.size });
                reveal('timeout');
            }
        }, maxWaitMilliseconds);
    }

    initialize();

    return {
        track,
        trackInitial: track,
        reveal,
        isReady: () => revealed
    };
})();

ConSyst.splitEmailAddresses = function (value) {
    return String(value ?? '').split(/[;,\r\n\t]/).map(email => email.trim()).filter(email => email);
};

ConSyst.formSubmitGuard = (function () {
    const submitControlSelector = 'button[type="submit"], button:not([type]), input[type="submit"], button.btn-save';
    const lockedAttribute = 'data-cst-submit-locked';
    const heldAttribute = 'data-cst-submit-guard-held';
    const disabledAttribute = 'data-cst-submit-guard-disabled';
    const standaloneLockedAttribute = 'data-cst-submit-control-locked';
    const externalControlsByForm = new WeakMap();

    function getSubmitControls(form) {
        const controls = Array.from(form?.querySelectorAll(submitControlSelector) ?? [])
            .filter(control => !control.closest('.wysiwyg'));
        const externalControls = externalControlsByForm.get(form) ?? [];

        return controls.concat(externalControls.filter(control => !controls.includes(control)));
    }

    function lock(form, submitter) {
        if (!form || form.getAttribute(lockedAttribute) === 'true') {
            return false;
        }

        if (submitter && !form.contains(submitter)) {
            externalControlsByForm.set(form, [submitter]);
        }

        form.setAttribute(lockedAttribute, 'true');

        // Defer the visual disable until after the click's default submit action
        // has started so the clicked submitter can still contribute its value.
        window.setTimeout(function () {
            if (form.getAttribute(lockedAttribute) !== 'true' || !document.documentElement.contains(form)) {
                return;
            }

            getSubmitControls(form).forEach(function (control) {
                if (!control.disabled) {
                    control.setAttribute(disabledAttribute, 'true');
                    control.disabled = true;
                }
            });
        }, 0);

        return true;
    }

    function lockControl(control) {
        if (!control || control.getAttribute(standaloneLockedAttribute) === 'true') {
            return false;
        }

        control.setAttribute(standaloneLockedAttribute, 'true');
        window.setTimeout(function () {
            if (control.getAttribute(standaloneLockedAttribute) !== 'true' || !document.documentElement.contains(control)) {
                return;
            }

            if (!control.disabled) {
                control.setAttribute(disabledAttribute, 'true');
                control.disabled = true;
            }
        }, 0);

        return true;
    }

    function unlock(form) {
        if (!form) {
            return;
        }

        getSubmitControls(form).forEach(function (control) {
            if (control.getAttribute(disabledAttribute) === 'true') {
                control.disabled = false;
                control.removeAttribute(disabledAttribute);
            }
        });
        form.removeAttribute(lockedAttribute);
        form.removeAttribute(heldAttribute);
        externalControlsByForm.delete(form);
    }

    function unlockControl(control) {
        if (!control) {
            return;
        }

        if (control.getAttribute(disabledAttribute) === 'true') {
            control.disabled = false;
            control.removeAttribute(disabledAttribute);
        }
        control.removeAttribute(standaloneLockedAttribute);
    }

    function hold(form) {
        if (form) {
            form.setAttribute(heldAttribute, 'true');
        }
    }

    function isHeld(form) {
        return form?.getAttribute(heldAttribute) === 'true';
    }

    function isLocked(form) {
        return form?.getAttribute(lockedAttribute) === 'true';
    }

    function ensureLocked(form) {
        return isLocked(form) || lock(form);
    }

    return { lock, lockControl, unlock, unlockControl, hold, isHeld, isLocked, ensureLocked };
})();

$(document).ready(function () {

    ConSyst.isFormChanged = false;
    $(document).on('change', '#form[method="post"] select', function () {
        ConSyst.formChanged();
    });
    $(document).on('input', '#form[method="post"] input, #form[method="post"] textarea, #form[method="post"] [contenteditable="true"]', function () {
        ConSyst.formChanged();
    });
    $('#topNavbar .nav-link').on('click', function () {
        $('#sidenav-left .scroll-section.show').removeClass('show');
    });
    $(document).on('click', 'form[data-cst-current-return-url] button[type="submit"], form[data-cst-current-return-url] input[type="submit"]', function () {
        ConSyst.updateCurrentReturnUrl(this.form);
    });
    $(document).on('submit', 'form[data-cst-current-return-url]', function () {
        ConSyst.updateCurrentReturnUrl(this);
    });
    // Use capture so inline onclick handlers cannot trigger a second submit before
    // the delegated handler gets a chance to run.
    document.addEventListener('click', function (event) {
        const target = event.target instanceof Element
            ? event.target.closest('form button[type="submit"], form button:not([type]), form input[type="submit"], button.btn-save')
            : null;

        if (!target || target.closest('.wysiwyg')) {
            return;
        }

        const form = target.form || (target.matches('button.btn-save') ? document.getElementById('form') : null);
        const locked = form
            ? ConSyst.formSubmitGuard.lock(form, target)
            : ConSyst.formSubmitGuard.lockControl(target);

        if (!locked) {
            event.preventDefault();
            event.stopImmediatePropagation();
        }
    }, true);
    $(document).on('submit', 'form', function () {
        ConSyst.formSubmitGuard.ensureLocked(this);
    });
    document.addEventListener('invalid', function (event) {
        ConSyst.formSubmitGuard.unlock(event.target?.form);
    }, true);
    document.addEventListener('submit', function (event) {
        const form = event.target;
        ConSyst.formSubmitGuard.ensureLocked(form);
        window.setTimeout(function () {
            if (event.defaultPrevented && !ConSyst.formSubmitGuard.isHeld(form)) {
                ConSyst.formSubmitGuard.unlock(form);
            }
        }, 0);
    }, true);
    $(window).on('beforeunload', function (event) {
        if (ConSyst.isFormChanged) {
            // Set a custom message
            const confirmationMessage = ConSyst.localizeText('You have unsaved changes. Are you sure you want to leave this page?');

            // Standard for most browsers, but might give deprecation warnings
            /* jshint ignore:start */
            // eslint-disable-next-line no-console
            //event.returnValue = confirmationMessage;
            /* jshint ignore:end */

            // For some older browsers
            // For some older browsers
            return confirmationMessage;
        }
    });
    let lastInteraction = null;
    let lastInteractionTime = 0;

    if (ConSyst.isMobile()) {
        document.addEventListener('pointerdown', (evt) => {
            lastInteraction = evt.pointerType;
            const now = new Date();
            lastInteractionTime = now.getSeconds() * 1000 + now.getMilliseconds();
        }, true);

        $(document).on('blur', 'input', function (evt) {
            const now = new Date();
            const timeSinceLastInteraction = now.getSeconds() * 1000 - lastInteractionTime;
            if (lastInteraction === 'touch' && timeSinceLastInteraction < 10) {
                // Treat this as a click
                // Omit navigation key function
            } else {
                // Treat this as a key
                const current = $(this);
                ConSyst.navigationKeyFunction(evt, current, 9, false);
            }
        });
    }

    ConSyst.setInputsAutoCaps();
    ConSyst.setInputsKeys();
    ConSyst.setDateInputsBlur();

    // Don't deactivate form labels, make them fixed on top
    var inputs = $('.form-outline .form-control');
    inputs.addClass("active");
    inputs.on('blur', function () {
        var label = $(this).siblings('.form-label');
        label.addClass('active');
        $(this).addClass('active');
        //$(".select-input").addClass("active");
    });

    // Run backend alerts automatically
    var alerts = $(".alert");
    for (var i = 0; i < alerts.length; i++) {
        animation = mdb.Animate.getInstance(alerts[i]);
        if (animation != null) {
            animation.stopAnimation();
            animation.dispose();
        }
        animation = mdb.Animate.getOrCreateInstance(alerts[i]);
        animation.changeAnimationType('fade-in-right');
        animation.startAnimation();
        alert = new mdb.Alert(alerts[i]);
        alert.show();
    }

    //Modal Events
    ConSyst.setModalEvents('.modal');


    if (ConSyst.isLogged) {
        // Unit search initialization
        function calculateSimilarityIndex(searchString, resultString) {
            const normalizedSearchString = (searchString ?? "").toString().toUpperCase();
            const normalizedResultString = (resultString ?? "").toString().toUpperCase();

            if (normalizedSearchString === "" || !normalizedResultString.includes(normalizedSearchString)) {
                return 0;
            }
            else {
                return normalizedResultString.length > 0 ? normalizedSearchString.length / normalizedResultString.length : 0;
            }
        }

        function getGlobalSearchType(searchValue) {
            const searchPrefix = searchValue.substring(0, 2).toLowerCase();

            return searchPrefix === "u:" ?
                "Unit"
                : searchPrefix === "t:" ?
                    "Transaction"
                    : searchPrefix === "i:" ?
                        "Invoice"
                        : null;
        }

        function getGlobalSearchTerm(searchValue, searchType) {
            return searchType == null ? searchValue : searchValue.substring(2).trim();
        }

        function parseGlobalSearchResult(serviceResult) {
            if (serviceResult.ICode >= 0) {
                try {
                    return serviceResult.SValue != null && serviceResult.SValue !== "" ? JSON.parse(serviceResult.SValue) : [];
                }
                catch (error) {
                    console.error("Error parsing global search results:", error);
                    ConSyst.showMessage(ConSyst.localizeText("Error parsing search results"), 'danger');
                    return [];
                }
            }

            if (serviceResult.SCode != "N/A") {
                ConSyst.showMessage(serviceResult.SName, 'danger');
            }

            return [];
        }

        function sortGlobalSearchResults(results) {
            return results.sort((a, b) => b.ISearchSimilarity - a.ISearchSimilarity);
        }

        function setGlobalSearchTypeResults(searchType, results) {
            if (searchType === "Unit") {
                ConSyst.menuUnitResults = results;
            }
            else if (searchType === "Transaction") {
                ConSyst.menuDocResults = results;
            }
            else if (searchType === "Invoice") {
                ConSyst.menuInvoiceResults = results;
            }
        }

        function setGlobalAutocompleteNoResultsMessage(isSearchComplete) {
            if (ConSyst.globalSearchAutocomplete != null && ConSyst.globalSearchAutocomplete._options != null) {
                ConSyst.globalSearchAutocomplete._options.noResults = isSearchComplete
                    ? ConSyst.localizeText("No results found")
                    : ConSyst.localizeText("Please wait while we complete your search");
            }
        }

        function updateGlobalAutocompleteResults(results, searchToken, isSearchComplete) {
            if (searchToken !== ConSyst.globalSearchToken) {
                return;
            }

            ConSyst.globalSearchResults = sortGlobalSearchResults(results);
            setGlobalAutocompleteNoResultsMessage(isSearchComplete);

            if (ConSyst.globalSearchAutocomplete != null && typeof ConSyst.globalSearchAutocomplete._updateResults === "function") {
                ConSyst.globalSearchAutocomplete._updateResults(ConSyst.globalSearchResults);
            }
        }

        function getGlobalSearchServiceCalls(searchType, searchValue, isGlobalInputVal) {
            const searchServices = {
                Unit: "GetUnitSearchResults",
                Transaction: "GetTransactionSearchResults",
                Invoice: "GetInvoiceSearchResults"
            };

            const selectedSearchTypes = searchType == null ? Object.keys(searchServices) : [searchType];

            return selectedSearchTypes.map((selectedSearchType) => ({
                SearchType: selectedSearchType,
                Promise: ConSyst.callService("Unit", "GET", searchServices[selectedSearchType], { search: searchValue, isGlobal: isGlobalInputVal }, {}, "json")
                    .catch((error) => {
                        console.error("Error calling global search service:", error);
                        return { ICode: -1, SCode: "N/A", SName: "", SValue: null };
                    })
            }));
        }

        function buildUnitSearchResults(unitResults, searchValue) {
            var units = parseGlobalSearchResult(unitResults);

            const unitMap = units.reduce((accumulator, item) => {
                if (item.SUnit != null) {
                    if (!accumulator[item.SUnit]) {
                        accumulator[item.SUnit] = {
                            SResultType: "Unit",
                            SId: item.SUnit,
                            SUnit: item.SUnit,
                            SOldUnit: item.SOldUnit,
                            SUnitStatus: item.SUnitStatus,
                            SUnitStatusClass: item.SUnitStatusClass,
                            SUnitStatusIcon: item.SUnitStatusIcon,
                            SRepairStatus: item.SRepairStatus,
                            SRepairStatusClass: item.SRepairStatusClass,
                            SRepairStatusIcon: item.SRepairStatusIcon,
                            SDepot: item.SDepot,
                            SCustomer: item.SCustomer,
                            SCustomerName: item.SCustomerName,
                            ICustomer: item.ICustomer,
                            SGrade: item.SGrade,
                            BOnHold : item.BOnHold,
                            SUnitStatusName: item.SUnitStatusName,
                            SStatusNotes: item.SStatusNotes,
                            SOnHoldReason: item.SOnHoldReason,
                            TIngateDate: item.TIngateDate,
                            IDocCount: item.IDocCount,
                            ISearchSimilarity: Math.max(
                                calculateSimilarityIndex(searchValue, item.SUnit),
                                calculateSimilarityIndex(searchValue, item.SOldUnit ?? "")
                            )
                        };
                    }
                }
                return accumulator;
            }, {});

            return Object.values(unitMap);
        }

        function buildTransactionSearchResults(transactionResults, searchValue) {
            var transactions = parseGlobalSearchResult(transactionResults);

            const docGroups = transactions.reduce((accumulator, item) => {
                var itemType = item.IAcceptance != null ? `${ConSyst.localizeText('Acceptance')}` : (
                    item.IGateEventIn != null ? `${ConSyst.localizeText('Inbound')}` : (
                        item.IBooking != null ? `${ConSyst.localizeText('Booking')}` : (
                            item.IGateEventOut != null ? `${ConSyst.localizeText('Outbound')}` : (
                                item.IEstimate != null ? `${ConSyst.localizeText('Estimate')}` : (
                                    item.IRepair != null ? `${ConSyst.localizeText('Repair')}` : ``)))));
                var itemCode = item.SAcceptance ?? item.SGateEventIn ?? item.SBooking ?? item.SGateEventOut ?? item.SEstimate ?? item.SRepair;
                var itemId = item.IAcceptance ?? item.IGateEventIn ?? item.IBooking ?? item.IGateEventOut ?? item.IEstimate ?? item.IRepair;

                if (itemId == null || itemType === "") {
                    return accumulator;
                }

                var itemKey = `${itemType[0]}:${itemId}`;

                if (!accumulator[itemKey]) {
                    accumulator[itemKey] = {
                        IId: itemId,
                        SCode: itemCode,
                        SType: itemType,
                        SUnit: item.SUnit,
                        SDepot: item.SDepot,
                        SCustomer: item.SCustomer,
                        SCustomerName: item.SCustomerName,
                        ICustomer: item.ICustomer,
                        TDocTime: item.TDocTime,
                        IUnitCount: 0,
                        Units: [],
                        UnitSimilarities: []
                    };
                }

                if (item.SUnit != null && item.SUnit != "") {
                    accumulator[itemKey].IUnitCount++;
                    if (!accumulator[itemKey].Units.includes(item.SUnit)) {
                        accumulator[itemKey].Units.push(item.SUnit);
                        accumulator[itemKey].UnitSimilarities.push(calculateSimilarityIndex(searchValue, item.SUnit));
                    }
                }

                return accumulator;
            }, {});

            return Object.keys(docGroups).map(docKey => {
                const bestUnitSimilarity = docGroups[docKey].UnitSimilarities.length > 0 ? Math.max(...docGroups[docKey].UnitSimilarities) : 0;
                const bestUnitIndex = docGroups[docKey].UnitSimilarities.indexOf(bestUnitSimilarity);

                return {
                    SResultType: "Transaction",
                    SId: docGroups[docKey].IId.toString(),
                    IDoc: docGroups[docKey].IId,
                    SCode: docGroups[docKey].SCode,
                    IUnitCount: docGroups[docKey].IUnitCount,
                    SType: docGroups[docKey].SType,
                    SDocKey: docKey,
                    SUnit: bestUnitIndex >= 0 ? docGroups[docKey].Units[bestUnitIndex] : docGroups[docKey].SUnit,
                    SDepot: docGroups[docKey].SDepot,
                    SCustomer: docGroups[docKey].SCustomer,
                    SCustomerName: docGroups[docKey].SCustomerName,
                    ICustomer: docGroups[docKey].ICustomer,
                    TDocTime: docGroups[docKey].TDocTime,
                    ISearchSimilarity: Math.max(
                        calculateSimilarityIndex(searchValue, docGroups[docKey].SCode),
                        calculateSimilarityIndex(searchValue, docGroups[docKey].IId.toString())
                    )
                };
            });
        }

        function buildInvoiceSearchResults(invoiceResults, searchValue) {
            var invoices = parseGlobalSearchResult(invoiceResults);

            return invoices.map((item) => ({
                SResultType: "Invoice",
                SId: item.IInvoice.toString(),
                IDoc: item.IInvoice,
                SCode: item.SInvoice,
                SType: ConSyst.localizeText('Invoice'),
                SDocKey: `V:${item.IInvoice}`,
                SUnit: item.SUnit,
                SDepot: item.SDepot,
                SCustomer: item.SCustomer,
                SCustomerName: item.SCustomerName,
                ICustomer: item.ICustomer,
                TDocTime: item.TDocTime,
                FTotal: item.FTotal,
                ISearchSimilarity: Math.max(
                    calculateSimilarityIndex(searchValue, item.SInvoice),
                    calculateSimilarityIndex(searchValue, item.SOverageCode),
                    calculateSimilarityIndex(searchValue, item.SAltCode),
                    calculateSimilarityIndex(searchValue, item.SAltOverageCode),
                    calculateSimilarityIndex(searchValue, item.IInvoice.toString())
                )
            }));
        }

        function buildGlobalSearchResults(searchType, serviceResult, searchValue) {
            if (searchType === "Unit") {
                return buildUnitSearchResults(serviceResult, searchValue);
            }
            else if (searchType === "Transaction") {
                return buildTransactionSearchResults(serviceResult, searchValue);
            }
            else if (searchType === "Invoice") {
                return buildInvoiceSearchResults(serviceResult, searchValue);
            }

            return [];
        }

        function getGlobalSearchDocumentUrl(value) {
            if (value.SResultType == "Invoice") {
                return `/Invoice/Edit/${value.IDoc}`;
            }
            else if (value.SType == ConSyst.localizeText('Acceptance')) {
                return `/Acceptance/Edit/${value.IDoc}`;
            }
            else if (value.SType == ConSyst.localizeText('Inbound')) {
                return `/GateEvent/Edit/${value.IDoc}?type=I`;
            }
            else if (value.SType == ConSyst.localizeText('Booking')) {
                return `/Booking/Edit/${value.IDoc}`;
            }
            else if (value.SType == ConSyst.localizeText('Outbound')) {
                return `/GateEvent/Edit/${value.IDoc}?type=O`;
            }
            else if (value.SType == ConSyst.localizeText('Estimate')) {
                return `/Estimate/Edit/${value.IDoc}`;
            }
            else if (value.SType == ConSyst.localizeText('Repair')) {
                return `/Repair/Edit/${value.IDoc}?unitId=${value.SUnit}`;
            }

            return `/Unit`;
        }

        function getGlobalSearchDocumentLabel(value) {
            if (value.SResultType == "Invoice") {
                return ConSyst.localizeText('Invoice');
            }
            else if (value.SType == ConSyst.localizeText('Repair')) {
                return ConSyst.localizeText('Work Order');
            }

            return value.SType;
        }

        ConSyst.lastSearchVal = null;
        ConSyst.lastSearchTime = null;
        ConSyst.globalSearchResults = [];
        ConSyst.globalSearchToken = 0;
        ConSyst.menuUnitResults = [];
        ConSyst.menuDocResults = [];
        ConSyst.menuInvoiceResults = [];
        const asyncUnitAutocompleteJq = $('#menuUnitSearchDiv');
        const asyncUnitAutocomplete = asyncUnitAutocompleteJq[0];
        if (!asyncUnitAutocomplete) {
            ConSyst.globalSearchAutocomplete = null;
            return;
        }
        const asyncUnitFilter = async (query) => {
            var isGlobalInputVal = $("#menuUnitSearchIsGlobal").val() == "true" ? true : false;
            var currentSearchVal = query.trim();
            var currentSearchToken = ++ConSyst.globalSearchToken;
            ConSyst.lastSearchTime = new Date();
            ConSyst.lastSearchVal = currentSearchVal;
            ConSyst.globalSearchResults = [];
            ConSyst.menuUnitResults = [];
            ConSyst.menuDocResults = [];
            ConSyst.menuInvoiceResults = [];

            const searchType = getGlobalSearchType(currentSearchVal);
            const searchValue = getGlobalSearchTerm(currentSearchVal, searchType);
            const searchServiceCalls = getGlobalSearchServiceCalls(searchType, searchValue, isGlobalInputVal);
            var allResults = [];
            var completedSearchCount = 0;

            await Promise.all(searchServiceCalls.map(async (serviceCall) => {
                const serviceResult = await serviceCall.Promise;
                const serviceResults = buildGlobalSearchResults(serviceCall.SearchType, serviceResult, searchValue);
                setGlobalSearchTypeResults(serviceCall.SearchType, serviceResults);
                completedSearchCount++;

                if (currentSearchToken === ConSyst.globalSearchToken) {
                    allResults.push(...serviceResults);
                    updateGlobalAutocompleteResults(allResults, currentSearchToken, completedSearchCount === searchServiceCalls.length);
                }
            }));

            ConSyst.lastSearchTime = null;
            if (currentSearchToken === ConSyst.globalSearchToken) {
                setGlobalAutocompleteNoResultsMessage(true);
            }

            return currentSearchToken === ConSyst.globalSearchToken ? sortGlobalSearchResults(allResults) : ConSyst.globalSearchResults;
        };
        // Search input initialization
        ConSyst.globalSearchAutocomplete = new mdb.Autocomplete(asyncUnitAutocomplete, {
            debounce: 300,
            filter: asyncUnitFilter,
            displayValue: (value) => value.SId,
            noResults: ConSyst.localizeText("No results found"),
            threshold: 999,
            itemContent: (value) => {
                if ("SDocKey" in value) {
                    // Document display with enhanced information
                    var linkHtml = `<a href="#" onclick="window.open('${getGlobalSearchDocumentUrl(value)}');" class="btn btn-sm btn-rounded btn-primary"><i class="fas fa-eye"></i> ${getGlobalSearchDocumentLabel(value)}</a>`;

                    var depotBadge = value.SDepot ? `<span class="badge badge-info ms-1" title="${ConSyst.localizeText('Depot')}"><i class="fas fa-warehouse"></i> ${value.SDepot}</span>` : "";
                    var customerBadge = value.SCustomer ? `<span class="badge badge-secondary ms-1" title="${ConSyst.localizeText('Customer')}"><i class="fas fa-user"></i> ${value.SCustomer}</span>` : "";
                    var dateBadge = value.TDocTime ? `<span class="badge badge-light ms-1" title="${ConSyst.localizeText('Document Date')}"><i class="fas fa-calendar"></i> ${new Date(value.TDocTime).toLocaleDateString()}</span>` : "";
                    var detailText = value.SResultType == "Invoice"
                        ? (value.SUnit ? ` - ${value.SUnit}` : "")
                        : ` - ${value.IUnitCount} ${ConSyst.localizeText('Units')}`;

                    var itemHtml = `
                    <div class="autocomplete-custom-item-content container">
                        <div class="autocomplete-custom-item-title">
                            <div class="d-flex justify-content-between align-items-center">
                                <div>
                                    <b>${value.SCode}</b>${detailText}
                                </div>
                                <div class="d-flex flex-wrap gap-1">
                                    ${depotBadge}${customerBadge}${dateBadge}
                                </div>
                            </div>
                        </div>
                        <div class="autocomplete-custom-item-subtitle d-flex justify-content-between">
                            <div>${linkHtml}</div>
                        </div>
                    </div>`;
                    return itemHtml;
                } else {
                    // Unit display with enhanced information
                    var isRepairStat = ConSyst.repairableShortUnitStatuses.includes(value.SUnitStatus);
                    var statusCode = !isRepairStat ? value.SUnitStatus : value.SRepairStatus ?? value.SUnitStatus;
                    var statusIcon = !isRepairStat ? value.SUnitStatusIcon : value.SRepairStatusIcon ?? value.SUnitStatusIcon;
                    var statusClass = !isRepairStat ? value.SUnitStatusClass : value.SRepairStatusClass ?? value.SUnitStatusClass;
                    var statusColor = statusClass.replace("badge-", "");

                    if (value.BOnHold) {
                        statusColor = "danger";
                        statusCode = "OH";
                        statusIcon = "fa-lock";
                    }

                    var statusTitle = value.BOnHold
                        ? (!value.SOnHoldReason ? `${value.SUnitStatusName || 'N/A'} (${ConSyst.localizeText('On-Hold')})` : `${value.SUnitStatusName || 'N/A'} (${ConSyst.localizeText('On-Hold')}: ${value.SOnHoldReason})`)
                        : (!value.SStatusNotes ? (value.SUnitStatusName || 'N/A') : `${value.SUnitStatusName}: ${value.SStatusNotes}`);

                    var depotBadge = value.SDepot ? `<span class="badge badge-info ms-1" title="${ConSyst.localizeText('Depot')}"><i class="fas fa-warehouse"></i> ${value.SDepot}</span>` : "";
                    var customerBadge = value.SCustomer ? `<span class="badge badge-secondary ms-1" title="${ConSyst.localizeText('Customer')}: ${value.SCustomerName || value.SCustomer}"><i class="fas fa-user"></i> ${value.SCustomer}</span>` : "";
                    var gradeBadge = value.SGrade ? `<span class="badge badge-primary ms-1" title="${ConSyst.localizeText('Grade')}"><i class="fas fa-star"></i> ${value.SGrade}</span>` : "";
                    var ingateBadge = value.TIngateDate ? `<span class="badge badge-success ms-1" title="${ConSyst.localizeText('Last Ingate')}"><i class="fas fa-sign-in-alt"></i> ${new Date(value.TIngateDate).toLocaleDateString()}</span>` : "";
                    var onHoldBadge = value.BOnHold ? `<span class="badge badge-danger ms-1" title="${ConSyst.localizeText('On Hold')}"><i class="fas fa-lock"></i> ${ConSyst.localizeText('Hold')}</span>` : "";

                    var itemHtml = `
                    <div class="autocomplete-custom-item-content container">
                        <div class="autocomplete-custom-item-title">
                            <div class="d-flex justify-content-between align-items-center flex-wrap">
                                <div class="mb-1">
                                    <a onclick="window.open('/Unit/Inquiry/${value.SUnit}','_blank');" class="btn btn-sm btn-rounded btn-${statusColor}" title="${statusTitle}">
                                        <i class="fas ${statusIcon}"></i> ${statusCode}: ${value.SUnit}
                                    </a>
                                </div>
                                <div class="d-flex flex-wrap gap-1 align-items-center">
                                    ${depotBadge}${customerBadge}${gradeBadge}${ingateBadge}${onHoldBadge}
                                    <span class="badge badge-light ms-1" title="${ConSyst.localizeText('Transaction Count')}">
                                        <i class="fas fa-file-alt"></i> ${value.IDocCount} ${ConSyst.localizeText('Trans.')}
                                    </span>
                                </div>
                            </div>
                        </div>
                    </div>`;
                    return itemHtml;
                }
            },
            customContent: `
            <div class="autocomplete-custom-content"></div>
            `,
        });
        asyncUnitAutocomplete.addEventListener('update.mdb.autocomplete', (event) => {
            //ConSyst.menuUnitResults = event.results;
            if (event.results != null) {
                var menuSearchAutocompleteSelector = `#autocomplete-dropdown-${$(event.currentTarget).attr("id")}`;
                setTimeout(() => {
                    var customContentContainer = $(menuSearchAutocompleteSelector).find('.autocomplete-custom-content');
                    let unitsArray = event.results.filter(obj => obj.SResultType == "Unit" || (obj.SResultType == null && obj.hasOwnProperty('SUnit') && !obj.hasOwnProperty('IDoc')));
                    let transArray = event.results.filter(obj => obj.SResultType == "Transaction" || (obj.SResultType == null && obj.hasOwnProperty('IDoc')));
                    let invoiceArray = event.results.filter(obj => obj.SResultType == "Invoice");
                    var customContentHtml = `
                    <div class="border-top d-flex">
                        <span><b>${ConSyst.localizeText('Units:')}</b> ${unitsArray.length}</span>,
                        <span class="ms-1"><b>${ConSyst.localizeText('Transactions:')}</b> ${transArray.length}</span>,
                        <span class="ms-1"><b>${ConSyst.localizeText('Invoices:')}</b> ${invoiceArray.length}</span>
                    </div>`;
                    customContentContainer.html(customContentHtml);

                }, 0);
            }
        });
        asyncUnitAutocomplete.addEventListener('itemSelect.mdb.autocomplete', (event) => {
            if ("SDocKey" in event.value) {
                var value = event.value;
                var resultLink = getGlobalSearchDocumentUrl(value);
                //window.open(resultLink, '_self');
                location.href = resultLink;
            }
            else {
                location.href = `/Unit/Inquiry/${event.value.SUnit}`;
            }
        });


        //On enter key of global search input, start search
        $("#menuUnitSearch").on("keydown", function (e) {
            // e.which is 13 for Enter
            if (e.which === 13 || e.key =="Enter" || e.key=="enter") {
                e.preventDefault();              // prevent form submission or other defaults
                var query = $(this).val();
                ConSyst.globalSearch(query);
            }
        });


    }
    ConSyst.loadTooltips();
    ConSyst.captureCurrentNavigationStep(false);
    ConSyst.captureCurrentListState();
    ConSyst.ListSearch?.initialize();
    ConSyst.ListTable?.initializeAll();
    ConSyst.ListTable?.observe();
    ConSyst.renderPageBreadcrumbs();
    window.addEventListener('popstate', function () {
        ConSyst.refreshCurrentReturnUrls();
        ConSyst.captureCurrentNavigationStep(true);
        ConSyst.captureCurrentListState();
        ConSyst.renderPageBreadcrumbs();
    });

    // Global ALT+S handler for saving
    $(document).on('keydown', function (event) {
        // Check for ALT+S combination (consistent key detection)
        if (event.altKey && event.code === 'KeyS') {
            event.preventDefault();

            // Find all modals on the page
            const modals = document.querySelectorAll('.modal');

            // Find the active modal (the one that is currently shown)
            const activeModal = Array.from(modals).find(m => {
                const style = window.getComputedStyle(m);
                return style.display !== 'none' && m.classList.contains('show');
            });
            if (activeModal) {
                // Find the save button in the active modal
                const saveButton = activeModal.querySelector('.btn-save-shortcut');
                // If the save button is found and not disabled, trigger a click on it
                if (saveButton && !saveButton.disabled) {
                    saveButton.click();
                }
            } else {

                // First check if we're on a page with a form#form (the main form from _Form.cshtml)
                const editForm = document.querySelector('form#form[method="post"]');

                if (!editForm) {
                    // No main form found, so we're probably on an index/listing page
                    // This handles Index.cshtml case where we don't want ALT+S to work
                    return;
                }

                // If no modal is active, submit the main form
                const mainForm = document.querySelector('form#form') || document.querySelector('form');

                if (mainForm) {
                    $(mainForm).trigger("submit");
                }
            }
        }
    });

    // Handle form submission with Ctrl + Enter
    $('form').on('keydown', function (event) {
        if (event.ctrlKey && (event.code === 'Enter' || event.code === 'NumpadEnter'))
        {
            event.preventDefault(); // Add this to prevent double submission
            $(this).trigger("submit");
        }
        else if (event.code === 'Enter' || event.code === 'NumpadEnter')
        {
            if ($(this).attr("method") !== "get")
            {
                if (!$(".wysiwyg-content").is(":focus") && !$("textarea").is(":focus"))
                {
                    event.preventDefault();
                }
            }
        }
    });

    // Define global keys
    ConSyst.globalKeys = ["Equal", "Enter", "NumpadEnter", "F1", "F2", "KeyM", "KeyO", "KeyX", "KeyU",
        "KeyY", "KeyA", "KeyB", "KeyE", "KeyR", "KeyG",
        "KeyD", "KeyF", "KeyP", "PageDown", "PageUp"];

    // General keydown handling everywhere
    $(document).on("keydown", function (event) {
        // Check if the pressed key is in globalKeys
        if (ConSyst.globalKeys.includes(event.code)) {
            switch (event.code) {
                case 'Equal':
                    if ($("#btnAdd").attr("disabled") || $("#btnAdd").length === 0) {
                        return;
                    }
                    $("#btnAdd").trigger("click");
                    event.preventDefault();
                    break;

                case 'Enter':
                case 'NumpadEnter':
                    $('button:focus').trigger("click");
                    const focusedLink = $('a:focus');
                    if (focusedLink.length > 0) {
                        window.location.href = focusedLink.attr('href');
                    }
                    break;

                case 'F1':
                    ConSyst.useInputHelp = !ConSyst.useInputHelp;
                    ConSyst.loadTooltips();
                    event.preventDefault();
                    break;

                case 'F2':
                    $("#menuUnitSearch").trigger("focus");
                    break;

                case 'KeyM': // Alt + M
                    if (event.altKey) {
                        var sidenavLeft = mdb.Sidenav.getInstance(document.getElementById('sidenav-left'));
                        sidenavLeft.toggle();
                    }
                    break;

                case 'KeyO': // Alt + O
                    if (event.altKey) {
                        var sidenavRight = mdb.Sidenav.getInstance(document.getElementById('sidenav-right'));
                        sidenavRight.show();
                    }
                    break;

                case 'KeyX': // Alt + X
                    if (event.altKey) {
                        var jqLink = $("i.fa-file-excel").parent("a.sidenav-link");
                        if (jqLink.length > 0) {
                            jqLink[0].click();
                        }
                    }
                    break;

                case 'KeyU': // Alt + U
                    if (event.altKey) {
                        window.location.href = "/Unit";
                    }
                    break;

                case 'KeyY': // Alt + Y
                    if (event.altKey) {
                        window.location.href = "/YardTruck";
                    }
                    break;

                case 'KeyA': // Alt + A
                    if (event.altKey) {
                        window.location.href = "/Acceptance";
                    }
                    break;

                case 'KeyB': // Alt + B
                    if (event.altKey) {
                        window.location.href = "/Booking";
                    }
                    break;

                case 'KeyE': // Alt + E
                    if (event.altKey) {
                        event.preventDefault();
                        window.location.href = "/Estimate";
                    }
                    break;

                case 'KeyR': // Alt + R
                    if (event.altKey) {
                        event.preventDefault();
                        window.location.href = "/Repair";
                    }
                    break;

                case 'KeyG': // Alt + G
                    if (event.altKey) {
                        window.location.href = "/GateEvent";
                    }
                    break;

                case 'KeyD': // Alt + D
                    if (event.altKey) {
                        event.preventDefault();
                        ConSyst.redirectToPage('/Estimate');
                    }
                    break;

                case 'KeyF': // Alt + F
                    if (event.altKey) {
                        event.preventDefault();
                        $("#menuUnitSearch").trigger("focus");
                    }
                    break;

                case 'KeyP': // Alt + P
                    if (event.altKey) {
                        var jqLink = $("i.fa-print").parent("a.sidenav-link");
                        if (jqLink.length > 0) {
                            jqLink[0].click();
                        }
                    }
                    break;

                case 'PageDown': // Alt + PageDown
                    if (event.altKey) {
                        event.preventDefault();
                        $("#previous-page").trigger("click");
                    }
                    break;

                case 'PageUp': // Alt + PageUp
                    if (event.altKey) {
                        event.preventDefault();
                        $("#next-page").trigger("click");
                    }
                    break;

                default:
                    break;
            }
        }
    });

    $('#next-page').on('click', function (event) {
        event.preventDefault();
        // Perform any additional actions if needed
        isDisabled = $(this).parent().hasClass("disabled");
        if(!isDisabled){
            location.href = $(this).attr('href');
        }
    });
    $('#previous-page').on('click', function (event) {
        event.preventDefault();
        // Perform any additional actions if needed
        isDisabled = $(this).parent().hasClass("disabled");
        if (!isDisabled) {
            location.href = $(this).attr('href');
        }
    });
    // Wait dialog on form submit
    $(document).on('submit', 'form:not(.json-form)', async function (e) {
        const form = $(this);
        var waitTitle = form.attr('data-cst-wait-title');
        var waitMessage = form.attr('data-cst-wait-message');
        var waitColor = form.attr('data-cst-wait-color');
        var waitMethod = form.attr('data-cst-wait-method');
        if (waitMethod == "" || waitMethod == null) {
            waitMethod = (form.attr('method') || 'get').toLowerCase() == "post" ? "normal" : "ajax";
        }

        if ((form.attr("method") || "").toLowerCase() == "post") {
            ConSyst.isFormChanged = false;
        }

        if (waitMethod == "ajax") {
            ConSyst.formSubmitGuard.hold(form[0]);
            e.preventDefault();
            let response = null;
            let newUrl = null;

            try {
                response = await ConSyst.waitResultModal(async () => {
                    const formData = form.serialize();
                    const actionUrl = form.attr('action');
                    const methodType = form.attr('method') || 'POST';

                    const result = await $.ajax({
                        url: actionUrl,
                        type: methodType,
                        data: formData,
                        dataType: 'html', // Expect HTML response from the server
                        timeout: 300000,
                        statusCode: {
                            400: function (jqXHR) {
                                if (jqXHR.getResponseHeader("Location") != null) {
                                    window.location.href = jqXHR.getResponseHeader("Location");
                                }
                                else {
                                    ConSyst.showMessage(ConSyst.localizeText("There was an issue with your request. Contact support for more information"), "danger", null, null, null, null, 'top');
                                }
                            },
                            401: function (jqXHR) {
                                if (jqXHR.getResponseHeader("Location") != null) {
                                    window.location.href = jqXHR.getResponseHeader("Location");
                                }
                                else {
                                    ConSyst.showMessage(ConSyst.localizeText("There was an issue with your credentials. Please try to sign out and then sign in again"), "danger", null, null, null, null, 'top');
                                }
                            },
                            403: function (jqXHR) {
                                if (jqXHR.getResponseHeader("Location") != null) {
                                    window.location.href = jqXHR.getResponseHeader("Location");
                                }
                                else {
                                    ConSyst.showMessage(ConSyst.localizeText("You're not authorized for this resource. Contact support for more information"), "danger", null, null, null, null, 'top');
                                }
                            },
                            500: function (jqXHR) {
                                if (jqXHR.getResponseHeader("Location") != null) {
                                    window.location.href = jqXHR.getResponseHeader("Location");
                                }
                                else {
                                    ConSyst.showMessage(ConSyst.localizeText("Unexpected error occurred. Contact support for more information"), "danger", null, null, null, null,'top' );
                                }
                            }
                        }
                    });
                    newUrl = $(result).filter('meta[name="url"]').attr('content');
                    return result;
                }, waitTitle ?? ConSyst.localizeText('Saving'), waitMessage ?? ConSyst.localizeText('Saving your changes. Please wait...'), waitColor);

                // The wait modal must be fully cleaned up before replacing its document.
                if (response == null) {
                    ConSyst.formSubmitGuard.unlock(form[0]);
                    return;
                }
                document.open();
                document.write(response);
                document.close();

                if (newUrl) {
                    history.pushState({}, '', newUrl);
                }
            }
            catch (error) {
                console.error('Form submission failed:', error);
                ConSyst.formSubmitGuard.unlock(form[0]);
                const waitModalElement = document.getElementById('genericWaitResultModal');
                if (waitModalElement) {
                    const waitModal = mdb.Modal.getInstance(waitModalElement) || mdb.Modal.getOrCreateInstance(waitModalElement);
                    waitModal.hide();
                }
                if (error?.statusText === 'timeout') {
                    ConSyst.showMessage(ConSyst.localizeText('The request took too long to finish. Refresh the page to check whether your changes were saved.'), 'warning', null, null, null, null, 'top');
                }
            }
        }
        else {
            const [modalJq, modal] = await ConSyst.showResultModal(waitTitle ?? ConSyst.localizeText('Saving'), waitMessage ?? ConSyst.localizeText('Saving your changes. Please wait...'), waitColor);
        }
    });

    Cookie.set('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
    const offset = new Date().getTimezoneOffset();
    Cookie.set('tz_offset', offset);
    for (var a = 0; a < alertsToDismiss.length; a++) {
        ConSyst.dismissAlert($(`#${alertsToDismiss[a]} button`)[0], 5000);
    }

    $("textarea").each(function (index, element) {
        var elementJq = $(element);
        var width = elementJq.width();
        if (width > 0) {
            element.style.height = 'auto';
            var scrollHeight = element.scrollHeight;
            if (scrollHeight <= 60) {
                scrollHeight = 60;
            }
            else if (scrollHeight >= 200) {
                scrollHeight = 200;
            }
            elementJq.height(scrollHeight);
        }
    });
    const tDocTimeInput = $("#TDocTime");
    if (tDocTimeInput != undefined) {

        tDocTimeInput.on("blur change", function () {
            let val = $(this).val();
            if (!val) return;

            // Try to extract year from start of string (handles both "2025" and "2025-07-01T11:56")
            let yearMatch = val.match(/^(\d{2,4})/);
            if (yearMatch) {
                let year = parseInt(yearMatch[1], 10);
                let newYear = year;
                if (year === 25) {
                    newYear = 2025;
                }
                else if (year === 100) {
                    newYear = 2000;
                }
                // Only update if newYear is a valid 4-digit year between 1900 and 2100
                if (typeof newYear === 'number' && newYear >= 1900 && newYear <= 2100 && newYear.toString().length === 4) {
                    // Replace only the year part at the start
                    let newVal = val.replace(/^(\d{2,4})/, newYear);
                    $(this).val(newVal);
                }
            }
        });
    }
    $(document).on('click', 'th', function (event) {
        event.preventDefault();
        var $th = $(this);
        var sortAttribute = $th.attr('data-cst-sort-field') || $th.find('[data-mdb-sort]').attr('data-mdb-sort');
        if (!sortAttribute || !sortAttribute.includes('-sort')) {
            return;
        }
        var $table = $th.closest('.datatable');
        if ($table[0]?._cstListTableOptions?.onSort) {
            return;
        }
        // Show wait modal instead of message toast
        ConSyst.showResultModal(
            ConSyst.localizeText('Loading'),
            ConSyst.localizeText('Your query is being loaded. Please wait...'),
            'primary'
        );


        // Get current query parameters
        var queryParams = ConSyst.getQueryParams();
        var tableSort = $table.attr('data-cst-sort-field');
        var tableSortOrder = $table.attr('data-cst-sort-order');

        // Determine the new sort order
        var currentSort = queryParams.sort || tableSort;
        var currentOrder = queryParams.sortOrder || tableSortOrder;
        var sortOrder = "asc"; // default

        if (currentSort === sortAttribute) {
            // Toggle order
            sortOrder = (currentOrder === "asc") ? "desc" : "asc";
        }

        // Update sort parameters
        queryParams.sort = sortAttribute;
        queryParams.sortOrder = sortOrder;

        // Build new URL with updated parameters
        var newUrl = ConSyst.buildUrlWithParams(window.location.pathname, queryParams);

        // Navigate to new URL to update the table
        window.location.href = newUrl;
    });
    // Add the missing function to build URL with parameters
    ConSyst.buildUrlWithParams = function (basePath, params) {
        // basePath puede ser "/Unit" o una URL completa
        const url = new URL(basePath, window.location.origin);
        const sp = url.searchParams;

        for (const key of Object.keys(params))
        {
            const val = params[key];
            sp.delete(key);
            if (val === undefined || val === null)
            {
                continue;
            }
            if (Array.isArray(val)) {
                if (val.length && typeof val[0] === 'object' && val[0] !== null)
                {
                    val.forEach((obj, i) => {
                        for (const prop in obj) {
                            const v = obj[prop];
                            if (v !== undefined && v !== null && v !== '') {
                                sp.append(`${key}[${i}].${prop}`, String(v));
                            }
                        }
                    });
                }
                else
                {
                    val.forEach(v => {
                        if (v !== undefined && v !== null && v !== '') sp.append(key, String(v));
                    });
                }
                continue;
            }
            if (val !== '') sp.set(key, String(val));
        }

        return url.toString();
    };

    // delete select duplicated fake-value
    $('select').each(function (index, element) {
        var selectJq = $(element);
        var fakeValue = selectJq.parent().find(".form-outline").find(".select-fake-value");
        if (fakeValue != null && fakeValue.length>1) {
            //remove last fake-value
            fakeValue.last().remove();
        }
    });

});

ConSyst.globalSearch = function (search = null, isGlobal=false) {
    if (search == null) {
        search = $("#menuUnitSearch").val();
    }
    $("#menuUnitSearchIsGlobal").val(isGlobal);
    if (search != null) {

        search = search.trim()

        ConSyst.globalSearchAutocomplete.initSearch(search);
    }
}
// Add this somewhere in your ConSyst object or a common JS file
ConSyst.isMobile = function () {
    return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop|BlackBerry/i.test(navigator.userAgent);
};

ConSyst.parseString = function (str) {
    if (str == null || str == "null") {
        return null;
    }
    return str;
}

ConSyst.formChanged = function () {
    ConSyst.isFormChanged = true;
    $(".btn-save").removeAttr("disabled");
}

ConSyst.sleep = (milliseconds) => {
    return new Promise(resolve => setTimeout(resolve, milliseconds));
}

ConSyst.tooltipElementSelector = function (container = "") {
    return [
        `${container}*:not(option):not(svg):not(path):not(title)[title]`,
        `${container}*:not(option):not(svg):not(path):not(title)[data-mdb-original-title]`
    ].join(", ");
};

ConSyst.markMdbTooltips = function (tooltipsJq) {
    tooltipsJq.each(function (index, element) {
        if (!element.matches("option, svg, path") && !element.hasAttribute("data-mdb-toggle")) {
            element.setAttribute("data-mdb-toggle", "tooltip");
        }
    });
};

ConSyst.observeTooltips = function () {
    if (ConSyst.tooltipObserver || typeof MutationObserver === "undefined" || !document.body) {
        return;
    }

    var observer = new MutationObserver(function (mutations) {
        var addedNodesWithTooltips = false;
        var tooltipSelector = ConSyst.tooltipElementSelector();
        mutations.forEach(function (mutation) {
            if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
                if (Array.from(mutation.addedNodes).some(function (node) {
                    if (node.nodeType !== 1 && node.nodeType !== 11) {
                        return false;
                    }

                    return (node.matches && node.matches(tooltipSelector))
                        || (node.querySelector && node.querySelector(tooltipSelector) != null);
                })) {
                    addedNodesWithTooltips = true;
                }
            }
            else if (mutation.type === "attributes" && mutation.attributeName === "title") {
                var element = mutation.target;
                var title = element.getAttribute("title");
                if (element.isConnected && title != null && title !== "" && element.matches("*:not(option):not(svg):not(path)")) {
                    element.setAttribute("data-mdb-original-title", title);
                    ConSyst.loadTooltips(element, null, null, true);
                }
            }
        });

        if (addedNodesWithTooltips) {
            ConSyst.loadTooltips();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["title"]
    });
    ConSyst.tooltipObserver = observer;
};

ConSyst.loadTooltips = function (selector = null, opts = null, container = null, reset=false) {
    if (container != null && container != "") {
        container += " ";
    }
    else {
        container = "";
    }
    if (selector == null) {
        selector = ConSyst.tooltipElementSelector(container);
    }
    var tooltipsJq = $(selector);
    if (!ConSyst.useInputHelp) {
        tooltipsJq.filter(".form-outline").removeAttr("title");
        tooltipsJq.filter("input").removeAttr("title");
        tooltipsJq = tooltipsJq.not("input");
        tooltipsJq = tooltipsJq.not(".form-outline");
    }
    ConSyst.markMdbTooltips(tooltipsJq);
    tooltipsJq.each(async function (index, element) {
        var rowOpts = opts;
        if (rowOpts == null) {
            rowOpts = {};
            rowOpts.placement = 'auto';
            rowOpts.trigger = 'hover';
            rowOpts.delay = { "show": 700, "hide": 10 };
            rowOpts.sanitizeFn = ConSyst.sanitizeHtml;
            rowOpts.html = true;
        }
        var origTitle = $(element).attr("data-mdb-original-title") ?? null;
        if (origTitle != null && origTitle != "") {
            rowOpts.title = origTitle;
        }

        var tooltip = mdb.Tooltip.getInstance(element);
        if (tooltip == null) {
            var newTooltip = new mdb.Tooltip(element, rowOpts);
            //if ($(element).is("span")) {
            //    // add <i> icon to span
            //    $(element).append(' <i class="fas fa-circle-info text-primary"></i>')
            //}
            if ($(element).is(".form-outline")) {
                var labelJq = $(element).find("label");
                // add <i> icon to span
                infoIconJq = labelJq.find("i.fa-circle-info");
                if (infoIconJq.length == 0) {
                    labelJq.append(' <i class="fas fa-circle-info text-primary"></i>');
                }

            }
            //if (!('ontouchstart' in window)) {
            //    $(element).mouseenter(function () {
            //        newTooltip.enable();
            //    });
            //    $(element).mouseleave(async function () {
            //        newTooltip.disable();
            //    });
            //}
            //else {

            //    let longPressTimer;
            //    $(element).on('touchstart', function (e) {
            //        longPressTimer = setTimeout(function () {
            //            newTooltip.toggle();
            //        }, 1000); // 1000 milliseconds (1 second)
            //    });

            //    $(element).on('touchend', function (e) {
            //        clearTimeout(longPressTimer);
            //    });

            //    $(element).on('touchcancel', function (e) {
            //        clearTimeout(longPressTimer);
            //    });
            //}
        }
        else {
            if (reset) {
                tooltip.dispose();
                var newTooltip = new mdb.Tooltip(element, rowOpts);
                //if ($(element).is("span")) {
                //    // add <i> icon to span
                //    $(element).append(' <i class="fas fa-circle-info text-primary"></i>')
                //}
                if ($(element).is(".form-outline")) {
                    var labelJq = $(element).find("label");
                    // add <i> icon to span
                    infoIconJq = labelJq.find("i.fa-circle-info");
                    if (infoIconJq.length == 0) {
                        labelJq.append(' <i class="fas fa-circle-info text-primary"></i>');
                    }

                }
            }
        }

    });
    ConSyst.observeTooltips();
}

ConSyst.setModalEvents = function (selector) {
    var modalJq = $(selector);
    modalJq.each(function (index, element) {
        mdb.Modal.getOrCreateInstance(element);
    });
    // Modal default behaviour (auto stack)
    modalJq.off('.cstModalEvents').on('show.mdb.modal.cstModalEvents', function (modalEvent) {
        ConSyst.openedModals++;
        var modalZIndex = 1050 + (5 * ConSyst.openedModals);
        var modalJq = $(modalEvent.currentTarget)
        modalJq.css('z-index', modalZIndex);
        //console.log(`show ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
    });
    modalJq.on('shown.mdb.modal.cstModalEvents', function (modalEvent) {
        ConSyst.setInputsKeys();
        var modalJq = $(modalEvent.currentTarget);
        var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
        shouldHide = modalJq.data("should-hide");
        if (shouldHide) {
            modalJq.data("should-hide", null);
            modalJq.removeAttr("data-should-hide");
            modal.hide();
            //console.log(`* should-hide shown ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
        }
        else {
            var modalZIndex = parseInt($(modalEvent.currentTarget).css('z-index'));
            var backdropJq = $(modal._backdrop._element);
            backdropJq.css('z-index', modalZIndex - 4);
        }
        //console.log(`shown ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
    });
    modalJq.on('hide.mdb.modal.cstModalEvents', function (modalEvent) {
        ConSyst.openedModals--;
        modalJq = $(modalEvent.currentTarget);
        var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
        // Restore the added backdrop z-index
        var backdropJq = $(modal._backdrop._element);
        backdropJq.css('z-index', 1050);
        // Check remaining modals and update its z-index accordingly
        var maxZIndex = 1050 + (5 * ConSyst.openedModals);
        var modalsJq = $(".modal.show");
        modalsJq.each(function (index, element) {
            var modalJq = $(element);
            modalZIndex = parseInt(modalJq.css('z-index'));
            if (modalZIndex > maxZIndex) {
                modalJq.css('z-index', maxZIndex);
                var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
                var backdropJq = $(modal._backdrop._element);
                backdropJq.css('z-index', maxZIndex-4);
            }
        });

        //console.log(`hide ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
    });
    modalJq.on('hidden.mdb.modal.cstModalEvents', function (modalEvent) {
        modalJq = $(modalEvent.currentTarget);
        // Restore the added backdrop z-index
        modalJq.css('z-index', 1055);

        shouldHide = modalJq.data("should-hide");
        if (shouldHide) {
            modalJq.data("should-hide", null);
            modalJq.removeAttr("data-should-hide");
            //console.log(`* should-hide hidden ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
        }
        if (ConSyst.focusInputAfterModal != null) {
            $(ConSyst.focusInputAfterModal).trigger("focus");
            ConSyst.focusInputAfterModal = null;
        }
        //console.log(`hidden ${modalJq.attr("id")}, ${ConSyst.openedModals}`);
    });
}

ConSyst.buildUnitTypeFilterFunction = function (unitCategories) {
    return (value) => {
        return unitTypes.filter((item) => {
            valueParts = value.split(" ");
            return unitCategories.includes(item.SCategory) && (valueParts.every(subStr => item.SCode.toLowerCase().includes(subStr.toLowerCase())) || valueParts.every(subStr => item.SName.toLowerCase().includes(subStr.toLowerCase())));
        });
    };
}

function calculateSimilarityIndex(searchString, resultString) {
    if (!resultString || !searchString) return 0;
    if (!resultString.toUpperCase().includes(searchString.toUpperCase())) {
        return 0;
    } else {
        return resultString.length > 0 ? searchString.length / resultString.length : 0;
    }
}

ConSyst.unitTypeFilterFunction = (value) => {
    // Split criteria by comma and trim whitespace
    const criteria = value.split(',').map(crit => crit.trim()).filter(Boolean);

    // Filter items matching any of the criteria
    let filtered = unitTypes.filter((item) => {
        return criteria.some(crit => {
            const critParts = crit.split(" ").filter(Boolean);
            return critParts.every(subStr =>
                item.SCode.toLowerCase().includes(subStr.toLowerCase()) ||
                item.SName.toLowerCase().includes(subStr.toLowerCase())
            );
        });
    });

    // Calculate similarity and sort
    filtered = filtered.map(item => {
        item._similarity = Math.max(
            ...criteria.map(crit => Math.max(
                calculateSimilarityIndex(crit, item.SCode),
                calculateSimilarityIndex(crit, item.SName)
            ))
        );
        return item;
    });

    filtered.sort((a, b) => {
        // Sort first by similarity descending
        if (b._similarity !== a._similarity) {
            return b._similarity - a._similarity;
        }
        // Then by SCode alphabetically
        return a.SCode.localeCompare(b.SCode);
    });

    // Clean up temporary field
    filtered.forEach(item => { delete item._similarity; });

    return filtered;
};



// Html sanitization function
ConSyst.sanitizeHtml = function (input) {
    // Create a temporary jQuery object holding the HTML
    const $temp = $('<div>').html(input);

    // Define harmful tags and attributes
    const harmfulTags = ['script', 'object', 'embed', 'link'];
    const harmfulAttrs = ['onload', 'onclick', 'onerror'];

    // Remove harmful tags
    harmfulTags.forEach(tag => {
        $temp.find(tag).remove();
    });

    // Remove harmful attributes from all elements
    $temp.find('*').each(function () {
        const $element = $(this);
        harmfulAttrs.forEach(attr => {
            $element.removeAttr(attr);
        });
    });

    // Serialize back to HTML string
    let sanitizedHtml = $temp.html();

    // Replace &quot; with "
    sanitizedHtml = sanitizedHtml.replace(/&quot;/g, '"');

    return sanitizedHtml;
}

ConSyst.unescapeViewStringForJs = function (viewSafeString) {
    if (viewSafeString == null) {
        return null;
    }
    // Decode HTML entities

    let decodedString = viewSafeString.replaceAll('"', '\\"')

    decodedString = decodedString.replace(/&quot;/g, '"');
    decodedString = decodedString.replace(/&amp;/g, '&');
    decodedString = decodedString.replace(/&lt;/g, '<');
    decodedString = decodedString.replace(/&gt;/g, '>');
    decodedString = decodedString.replace(/&nbsp;/g, ' ');
    decodedString = decodedString.replace(/&apos;/g, "'");

    //decodedString = decodedString.replace(/&copy;/g, '©');
    //decodedString = decodedString.replace(/&reg;/g, '®');
    //decodedString = decodedString.replace(/&trade;/g, '™');
    //decodedString = decodedString.replace(/&euro;/g, '€');
    //decodedString = decodedString.replace(/&dollar;/g, '$');
    //decodedString = decodedString.replace(/&pound;/g, '£');
    //decodedString = decodedString.replace(/&yen;/g, '¥');
    //decodedString = decodedString.replace(/&cent;/g, '¢');
    //decodedString = decodedString.replace(/&sect;/g, '§');
    //decodedString = decodedString.replace(/&copy;/g, '©');
    //decodedString = decodedString.replace(/&reg;/g, '®');

    return decodedString;
}

// Tool function
ConSyst.isSameOrigin = function (url) {
    const parsedUrl = new URL(url, window.location.origin);
    return parsedUrl.origin === window.location.origin;
}

ConSyst.TablePreferences = (function () {
    const defaultExpirationDays = 365;

    function cookiePart(value) {
        return encodeURIComponent(`${value || 'all'}`)
            .replace(/[^a-zA-Z0-9_.~-]/g, '_')
            .slice(0, 80);
    }

    function buildKey(prefix, parts = []) {
        return [prefix, ...parts.map(cookiePart)].join('.');
    }

    function parseJson(value) {
        if (!value) {
            return null;
        }

        const candidates = [value];
        try {
            candidates.push(decodeURIComponent(value));
        }
        catch {
            // Keep the raw value as the only parse candidate.
        }

        for (const candidate of candidates) {
            try {
                return JSON.parse(candidate);
            }
            catch {
                // Try the alternate encoded form.
            }
        }

        return null;
    }

    function readCookie(name) {
        if (window.Cookie && window.Cookie.get) {
            return window.Cookie.get(name);
        }

        return document.cookie
            .split('; ')
            .map(function (cookie) {
                return cookie.split('=');
            })
            .filter(function (parts) {
                return decodeURIComponent(parts[0]) === name;
            })
            .map(function (parts) {
                return decodeURIComponent(parts.slice(1).join('='));
            })[0];
    }

    function readJson(name) {
        return parseJson(readCookie(name));
    }

    function writeJson(name, value, expires = defaultExpirationDays) {
        if (window.Cookie && window.Cookie.set) {
            window.Cookie.set(name, value, { expires });
            return;
        }

        document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(JSON.stringify(value))}; path=/; max-age=${expires * 24 * 60 * 60}`;
    }

    return {
        buildKey,
        parseJson,
        readJson,
        writeJson
    };
})();

ConSyst.ListTable = (function () {
    const cookiePrefix = 'cstListColumnOrderV1';
    const draggingClass = 'cst-list-column-dragging';
    const dropTargetClass = 'cst-list-column-drop-target';
    const headerSelector = 'thead.datatable-header th, thead th';
    let observer = null;

    function getDataTable(table) {
        if (!table || table.dataset.cstNativeListTable === 'true' || !window.mdb || !mdb.Datatable) {
            return null;
        }

        return mdb.Datatable.getInstance(table)
            || (mdb.Datatable.getOrCreateInstance ? mdb.Datatable.getOrCreateInstance(table) : new mdb.Datatable(table));
    }

    function getScope(table) {
        return table.dataset.cstListTableScope || table.id || 'table';
    }

    function getTablePreferenceTab(table) {
        if (table.dataset.cstPreferenceTab) {
            return table.dataset.cstPreferenceTab;
        }

        const formScope = table.closest('[data-cst-list-scope]')?.dataset.cstListScope;
        if (formScope) {
            return formScope;
        }

        const pane = table.closest('.tab-pane[id]');
        if (pane) {
            const tab = Array.from(document.querySelectorAll('[data-cst-list-tab-scope]'))
                .find(candidate => candidate.getAttribute('href') === `#${pane.id}`);
            return tab?.dataset.cstListTabScope || pane.id;
        }

        return 'all';
    }

    function getTableLabel(table) {
        const configuredLabel = table?.dataset.cstColumnVisibilityLabel?.trim();
        if (configuredLabel) {
            return configuredLabel;
        }

        return (table?.id || 'Table')
            .replace(/(?:Table|Grid)$/i, '')
            .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
            .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
            .replace(/[_-]+/g, ' ')
            .replace(/\s+/g, ' ')
            .trim() || 'Table';
    }

    function getPreferenceKey(table) {
        const user = table.dataset.cstPreferenceUser || document.body?.dataset?.cstPreferenceUser || 'browser';
        const depot = table.dataset.cstPreferenceDepot || 'all';
        const customer = table.dataset.cstPreferenceCustomer || 'all';
        const tab = getTablePreferenceTab(table);
        const prefix = table.dataset.cstColumnOrderCookiePrefix || cookiePrefix;
        const parts = prefix === 'cstYardTruckColumnOrderV1'
            ? [user, depot, customer, table.dataset.cstPreferenceTable || getScope(table)]
            : [ConSyst.controllerName || window.location.pathname, getScope(table), user, depot, customer, tab];
        return ConSyst.TablePreferences.buildKey(prefix, parts);
    }

    function getColumnField(header, column, index) {
        return header.dataset.cstColumnKey
            || column?.field
            || header.dataset.mdbField
            || `column-${index}`;
    }

    function disableClientSort(dataTable) {
        if (!dataTable?._options) {
            return;
        }

        dataTable._options.sortField = null;
        dataTable._options.sortOrder = '';
    }

    function getSortableField(header) {
        if (header.dataset.mdbSort === 'false' || header.getAttribute('data-mdb-sort') === 'false') {
            return null;
        }

        const sortElement = header.querySelector('[data-mdb-sort]');
        const field = sortElement?.getAttribute('data-mdb-sort') || header.dataset.cstSortField || header.dataset.mdbField;
        return field && field.includes('-sort') ? field : null;
    }

    function setupHeaders(table, dataTable) {
        const tooltip = ConSyst.localizeText ? ConSyst.localizeText('Drag to reorder column') : 'Drag to reorder column';
        const columns = Array.isArray(dataTable?._columns) ? dataTable._columns : [];
        $(table).find(headerSelector).each(function (index) {
            const column = columns[index];
            const field = getColumnField(this, column, index);
            const sortDisabled = this.dataset.mdbSort === 'false' || this.getAttribute('data-mdb-sort') === 'false';
            const sortField = sortDisabled ? null : (getSortableField(this) || (field.includes('-sort') ? field : null));
            const header = $(this)
                .addClass('cst-list-column-header')
                .attr('draggable', 'true')
                .attr('title', tooltip)
                .attr('data-cst-column-field', field);

            if (sortField && !this.querySelector('[data-mdb-sort]')) {
                const icon = document.createElement('i');
                icon.setAttribute('data-mdb-sort', sortField);
                icon.className = 'datatable-sort-icon fas fa-arrow-up';
                this.appendChild(icon);
            }
            if (sortField) {
                header.attr('data-cst-sort-field', sortField);
            }
        });
        ConSyst.updateSortIcons?.();
    }

    function clearDragState(table) {
        table._cstDraggedColumn = null;
        $(table).find(`.${draggingClass}, .${dropTargetClass}`).removeClass(`${draggingClass} ${dropTargetClass}`);
    }

    function closeTableTooltips(table) {
        if (!window.mdb?.Tooltip) {
            return;
        }

        $(table).find('[data-mdb-original-title], [title]').each(function () {
            const tooltip = mdb.Tooltip.getInstance(this);
            if (tooltip) {
                tooltip.hide();
                tooltip.dispose();
            }
        });
    }

    function suppressNextSortClick(table) {
        table._cstSuppressSortClick = true;
        window.setTimeout(function () {
            table._cstSuppressSortClick = false;
        }, 300);
    }

    function bindDragEvents(table, dataTable) {
        $(table)
            .off('click.cstListTable', 'thead th')
            .on('click.cstListTable', 'thead th', function (event) {
                if (!table._cstSuppressSortClick) {
                    return;
                }

                table._cstSuppressSortClick = false;
                event.preventDefault();
                event.stopPropagation();
            })
            .off('dragstart.cstListTable', headerSelector)
            .on('dragstart.cstListTable', headerSelector, function (event) {
                const field = this.dataset.cstColumnField;
                if (!field) {
                    event.preventDefault();
                    return;
                }

                table._cstDraggedColumn = field;
                this.classList.add(draggingClass);
                const originalEvent = event.originalEvent;
                if (originalEvent?.dataTransfer) {
                    originalEvent.dataTransfer.effectAllowed = 'move';
                    originalEvent.dataTransfer.setData('text/plain', field);
                }
            })
            .off('dragover.cstListTable', headerSelector)
            .on('dragover.cstListTable', headerSelector, function (event) {
                if (!table._cstDraggedColumn) {
                    return;
                }

                event.preventDefault();
                $(table).find(`.${dropTargetClass}`).removeClass(dropTargetClass);
                this.classList.add(dropTargetClass);
            })
            .off('drop.cstListTable', headerSelector)
            .on('drop.cstListTable', headerSelector, function (event) {
                event.preventDefault();
                const draggedField = table._cstDraggedColumn;
                const targetField = this.dataset.cstColumnField;
                const rect = this.getBoundingClientRect();
                const originalEvent = event.originalEvent;
                const placeAfter = originalEvent && originalEvent.clientX > rect.left + (rect.width / 2);
                clearDragState(table);
                if (moveColumn(table, dataTable, draggedField, targetField, placeAfter)) {
                    suppressNextSortClick(table);
                }
            })
            .off('dragend.cstListTable', headerSelector)
            .on('dragend.cstListTable', headerSelector, function () {
                clearDragState(table);
            });
    }

    function bindRender(table, dataTable) {
        $(table)
            .off('render.mdb.datatable.cstListTable')
            .on('render.mdb.datatable.cstListTable', function () {
                setupHeaders(table, dataTable);
                applyColumnVisibility(table, dataTable);
                bindDragEvents(table, dataTable);
                bindSort(table);
            });
    }

    function bindSort(table) {
        $(table)
            .off('click.cstListTableSort', 'thead th')
            .on('click.cstListTableSort', 'thead th', function (event) {
                const callback = table._cstListTableOptions?.onSort;
                if (typeof callback !== 'function' || table._cstSuppressSortClick) {
                    return;
                }

                const sortField = getSortableField(this) || this.dataset.cstSortField;
                if (!sortField) {
                    return;
                }

                event.preventDefault();
                event.stopPropagation();
                const url = new URL(window.location.href);
                const currentSort = url.searchParams.get('sort');
                const currentOrder = url.searchParams.get('sortOrder') || 'asc';
                const sortOrder = currentSort === sortField && currentOrder === 'asc' ? 'desc' : 'asc';
                callback({ sort: sortField, sortOrder });
            });
    }

    function normalizeOrder(order) {
        return Array.isArray(order) ? order.map(String).filter(Boolean) : [];
    }

    function readSavedOrder(table) {
        const serverOrder = ConSyst.TablePreferences.parseJson(table.dataset.cstColumnOrderSaved || table.dataset.yardTruckColumnOrderSaved);
        const cookieOrder = ConSyst.TablePreferences.readJson(getPreferenceKey(table));
        return normalizeOrder(serverOrder || cookieOrder);
    }

    function writeSavedOrder(table, order) {
        ConSyst.TablePreferences.writeJson(getPreferenceKey(table), normalizeOrder(order));
    }

    function getVisibilityPreferenceKey(table) {
        const user = table.dataset.cstPreferenceUser || document.body?.dataset?.cstPreferenceUser || 'browser';
        const depot = table.dataset.cstPreferenceDepot || 'all';
        const customer = table.dataset.cstPreferenceCustomer || 'all';
        const tab = getTablePreferenceTab(table);
        const prefix = table.dataset.cstColumnVisibilityCookiePrefix || 'cstListColumnVisibilityV1';
        const parts = prefix === 'cstYardTruckColumnVisibilityV1'
            ? [user, depot, customer, table.dataset.cstPreferenceTable || getScope(table)]
            : [ConSyst.controllerName || window.location.pathname, getScope(table), user, depot, customer, tab];
        return ConSyst.TablePreferences.buildKey(prefix, parts);
    }

    function getColumnDefinitions(table, dataTable) {
        const columns = Array.isArray(dataTable?._columns) ? dataTable._columns : [];
        return Array.from(table.querySelectorAll('thead th')).map((header, index) => {
            const field = getColumnField(header, columns[index], index);
            const labelElement = header.cloneNode(true);
            labelElement.querySelectorAll('[data-mdb-sort], .datatable-sort-icon').forEach(element => element.remove());
            return { field, label: labelElement.textContent?.trim() || field };
        }).filter(column => column.field);
    }

    function readSavedVisibility(table) {
        const saved = ConSyst.TablePreferences.readJson(getVisibilityPreferenceKey(table));
        return Array.isArray(saved) ? normalizeOrder(saved) : null;
    }

    function getColumnVisibility(table) {
        const dataTable = table?._cstListTableInstance || getDataTable(table);
        const definitions = getColumnDefinitions(table, dataTable);
        const allFields = definitions.map(column => column.field);
        const saved = readSavedVisibility(table);
        if (saved === null) {
            return allFields;
        }

        const knownFields = new Set(allFields);
        return saved.filter(field => knownFields.has(field));
    }

    function writeSavedVisibility(table, visibleFields) {
        ConSyst.TablePreferences.writeJson(getVisibilityPreferenceKey(table), normalizeOrder(visibleFields));
    }

    function applyColumnVisibility(table, dataTable, visibleFields = null) {
        if (!table) {
            return;
        }

        const definitions = getColumnDefinitions(table, dataTable);
        const visible = new Set(visibleFields === null ? getColumnVisibility(table) : normalizeOrder(visibleFields));
        const headers = Array.from(table.querySelectorAll('thead th'));
        headers.forEach((header, index) => {
            const field = definitions[index]?.field || getColumnField(header, dataTable?._columns?.[index], index);
            const display = visible.has(field) ? '' : 'none';
            header.style.display = display;
            table.querySelectorAll('tbody tr').forEach(row => {
                const cell = row.children[index];
                if (cell) {
                    cell.style.display = display;
                }
            });
        });
    }

    function setColumnVisibility(table, visibleFields) {
        if (!table) {
            return;
        }

        const dataTable = table._cstListTableInstance || getDataTable(table);
        const definitions = getColumnDefinitions(table, dataTable);
        const knownFields = new Set(definitions.map(column => column.field));
        const normalizedFields = normalizeOrder(visibleFields).filter(field => knownFields.has(field));
        writeSavedVisibility(table, normalizedFields);
        applyColumnVisibility(table, dataTable, normalizedFields);
        table.dispatchEvent(new CustomEvent('cstColumnVisibilityChanged', {
            detail: { visibleFields: normalizedFields }
        }));
    }

    function initializeColumnVisibilityModal(options = {}) {
        const modal = document.querySelector(options.modalSelector);
        const list = modal?.querySelector('[data-cst-column-visibility-list]');
        if (!modal || !list || modal.dataset.cstColumnVisibilityInitialized === 'true') {
            return;
        }

        const resolveTables = () => {
            if (typeof options.getTables === 'function') {
                return Array.from(options.getTables() || []).filter(Boolean);
            }

            return (options.tableSelectors || [])
                .map(selector => typeof selector === 'string' ? document.querySelector(selector) : selector)
                .filter(Boolean);
        };

        const render = () => {
            const tables = resolveTables();
            list.replaceChildren();
            tables.forEach(table => {
                const dataTable = table._cstListTableInstance || getDataTable(table);
                const definitions = getColumnDefinitions(table, dataTable);
                const visibleFields = new Set(getColumnVisibility(table));
                const section = document.createElement('div');
                section.className = 'col-md-6 mb-4';
                const title = document.createElement('h6');
                title.className = 'border-bottom pb-2';
                title.textContent = getTableLabel(table);
                section.appendChild(title);

                definitions.forEach(column => {
                    const wrapper = document.createElement('div');
                    wrapper.className = 'form-check mb-2';
                    const input = document.createElement('input');
                    input.type = 'checkbox';
                    input.className = 'form-check-input';
                    input.id = `${table.id}-column-${column.field}`;
                    input.checked = visibleFields.has(column.field);
                    input.addEventListener('change', () => {
                        const nextVisible = getColumnVisibility(table);
                        if (input.checked) {
                            nextVisible.push(column.field);
                        }
                        else {
                            const index = nextVisible.indexOf(column.field);
                            if (index >= 0) {
                                nextVisible.splice(index, 1);
                            }
                        }
                        setColumnVisibility(table, nextVisible);
                    });
                    const label = document.createElement('label');
                    label.className = 'form-check-label';
                    label.setAttribute('for', input.id);
                    label.textContent = column.label;
                    wrapper.append(input, label);
                    section.appendChild(wrapper);
                });
                list.appendChild(section);
            });
        };

        const setTriggerState = (isOpen) => {
            document.querySelectorAll('[data-cst-column-visibility-trigger]').forEach(trigger => {
                const target = document.querySelector(trigger.dataset.cstColumnVisibilityModal);
                if (target !== modal) {
                    return;
                }

                trigger.classList.toggle('btn-primary', isOpen);
                trigger.classList.toggle('btn-outline-primary', !isOpen);
                trigger.classList.toggle('active', isOpen);
                trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
            });
        };

        $(modal)
            .off('show.mdb.modal.cstColumnVisibility hidden.mdb.modal.cstColumnVisibility')
            .on('show.mdb.modal.cstColumnVisibility shown.mdb.modal.cstColumnVisibility', () => {
                render();
                setTriggerState(true);
            })
            .on('hidden.mdb.modal.cstColumnVisibility', () => setTriggerState(false));
        modal.querySelector('[data-cst-column-visibility-select-all]')?.addEventListener('click', () => {
            resolveTables().forEach(table => setColumnVisibility(table, getColumnDefinitions(table, table._cstListTableInstance || getDataTable(table)).map(column => column.field)));
            render();
        });
        modal.querySelector('[data-cst-column-visibility-deselect-all]')?.addEventListener('click', () => {
            resolveTables().forEach(table => setColumnVisibility(table, []));
            render();
        });
        modal.querySelector('[data-cst-column-order-reset]')?.addEventListener('click', () => {
            resolveTables().forEach(table => reset(table));
            render();
        });
        render();
        setTriggerState(false);
        modal.dataset.cstColumnVisibilityInitialized = 'true';

        document.querySelectorAll('[data-cst-column-visibility-trigger]').forEach(trigger => {
            trigger.addEventListener('click', () => {
                const target = document.querySelector(trigger.dataset.cstColumnVisibilityModal);
                if (target && window.mdb?.Modal) {
                    setTriggerState(true);
                    mdb.Modal.getOrCreateInstance(target).show();
                }
            });
        });
    }

    function applyColumnOrder(table, dataTable, order) {
        if (!Array.isArray(dataTable?._columns) || !Array.isArray(dataTable?._rows)) {
            return false;
        }

        const columns = dataTable._columns;
        const indexesByField = new Map(columns.map((column, index) => [column.field, index]));
        const orderedFields = [];
        normalizeOrder(order).forEach(field => {
            if (indexesByField.has(field) && !orderedFields.includes(field)) {
                orderedFields.push(field);
            }
        });
        columns.forEach(column => {
            if (!orderedFields.includes(column.field)) {
                orderedFields.push(column.field);
            }
        });

        const currentFields = columns.map(column => column.field);
        if (orderedFields.join('|') === currentFields.join('|')) {
            return false;
        }

        const orderedIndexes = orderedFields.map(field => indexesByField.get(field));
        dataTable._columns = orderedIndexes.map(index => columns[index]);
        dataTable._rows = dataTable._rows.map(row => orderedIndexes.map(index => Array.isArray(row) ? row[index] : row[columns[index].field]));
        disableClientSort(dataTable);
        if (typeof dataTable._renderTable === 'function') {
            dataTable._renderTable();
        }
        else if (typeof dataTable.update === 'function') {
            dataTable.update({ columns: dataTable._columns, rows: dataTable._rows }, { sortField: null, sortOrder: '' });
        }
        return true;
    }

    function moveColumn(table, dataTable, draggedField, targetField, placeAfter) {
        if (!draggedField || !targetField || draggedField === targetField || !Array.isArray(dataTable?._columns)) {
            return false;
        }

        const nextOrder = dataTable._columns.map(column => column.field).filter(field => field !== draggedField);
        const targetIndex = nextOrder.indexOf(targetField);
        if (targetIndex === -1) {
            return false;
        }

        nextOrder.splice(targetIndex + (placeAfter ? 1 : 0), 0, draggedField);
        closeTableTooltips(table);
        const moved = applyColumnOrder(table, dataTable, nextOrder);
        if (moved) {
            writeSavedOrder(table, dataTable._columns.map(column => column.field));
            setupHeaders(table, dataTable);
        }
        return moved;
    }

    function initialize(table, options = {}) {
        if (!table) {
            return null;
        }

        const existingOptions = table._cstListTableOptions || {};
        table._cstListTableOptions = { ...existingOptions, ...options };
        const dataTable = options.dataTable || table._cstListTableInstance || getDataTable(table);
        if (!dataTable) {
            applyColumnVisibility(table, null);
            return null;
        }

        table._cstListTableInstance = dataTable;
        if (table.dataset.cstListTableInitialized === 'true') {
            setupHeaders(table, dataTable);
            bindDragEvents(table, dataTable);
            bindSort(table);
            return dataTable;
        }

        table.dataset.cstListTableInitialized = 'true';
        disableClientSort(dataTable);
        bindRender(table, dataTable);
        bindDragEvents(table, dataTable);
        const defaultOrder = Array.isArray(dataTable._columns) ? dataTable._columns.map(column => column.field) : [];
        table._cstDefaultColumnOrder = defaultOrder;
        const savedOrder = readSavedOrder(table);
        if (savedOrder.length) {
            applyColumnOrder(table, dataTable, savedOrder);
        }
        setupHeaders(table, dataTable);
        applyColumnVisibility(table, dataTable);
        bindSort(table);
        return dataTable;
    }

    function initializeAll(root = document) {
        if (ConSyst.isMobile?.()) {
            return;
        }

        root.querySelectorAll?.('[data-cst-list-table]').forEach(table => initialize(table));
    }

    function observe() {
        if (observer || !window.MutationObserver) {
            return;
        }

        observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => mutation.addedNodes.forEach(node => {
                if (node.nodeType !== Node.ELEMENT_NODE) {
                    return;
                }
                const containingTable = node.closest?.('[data-cst-list-table]');
                if (containingTable) {
                    initialize(containingTable);
                }
                if (node.matches?.('[data-cst-list-table]')) {
                    initialize(node);
                }
                initializeAll(node);
            }));
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    function reset(table) {
        if (!table) {
            return;
        }
        ConSyst.TablePreferences.writeJson(getPreferenceKey(table), []);
        const dataTable = table._cstListTableInstance || getDataTable(table);
        if (dataTable && table._cstDefaultColumnOrder?.length) {
            applyColumnOrder(table, dataTable, table._cstDefaultColumnOrder);
            setupHeaders(table, dataTable);
            applyColumnVisibility(table, dataTable);
        }
    }

    return { initialize, initializeAll, initializeColumnVisibilityModal, getColumnDefinitions, getColumnVisibility, setColumnVisibility, observe, reset };
})();

ConSyst.IndexEnrichment = (function () {
    function getFields(table, field) {
        return Array.from(table.querySelectorAll('[data-cst-index-enrichment-field]'))
            .filter(element => element.dataset.cstIndexEnrichmentField === field);
    }

    function isVisible(field) {
        const cell = field.closest('td, th');
        return !!cell
            && cell.style.display !== 'none'
            && window.getComputedStyle(cell).display !== 'none';
    }

    function renderNumber(field, value, options = {}) {
        const span = document.createElement('span');
        if (options.title) {
            span.setAttribute('data-mdb-toggle', 'tooltip');
            span.setAttribute('data-mdb-placement', 'top');
            span.title = options.title;
        }
        const icon = document.createElement('i');
        icon.className = `fas ${options.icon || 'fa-hashtag'} ${options.color || 'text-secondary'} me-1`;
        span.appendChild(icon);
        span.appendChild(document.createTextNode(Number(value) || 0));
        field.replaceChildren(span);
        if (options.title && typeof mdb !== 'undefined' && mdb.Tooltip) {
            mdb.Tooltip.getOrCreateInstance(span);
        }
    }

    function renderError(field) {
        field.replaceChildren();
        const icon = document.createElement('i');
        icon.className = 'fas fa-circle-exclamation text-danger';
        icon.title = ConSyst.localizeText?.('Unable to load') || 'Unable to load';
        field.appendChild(icon);
    }

    function initialize(table, options = {}) {
        if (!table || !options.field || !options.endpoint) {
            return null;
        }

        const state = {
            results: new Map(),
            errors: new Set(),
            loading: false,
            queued: false,
            timer: null
        };
        const getKey = options.getKey || (field => field.dataset.cstIndexEnrichmentKey);

        const applyResults = () => {
            getFields(table, options.field).forEach(field => {
                const key = String(getKey(field) ?? '');
                if (!state.results.has(key)) {
                    return;
                }
                if (state.errors.has(key)) {
                    renderError(field);
                    return;
                }
                if (typeof options.renderValue === 'function') {
                    options.renderValue(field, state.results.get(key));
                }
                else {
                    field.textContent = state.results.get(key) ?? '';
                }
            });
        };

        const load = async () => {
            const fields = getFields(table, options.field).filter(isVisible);
            applyResults();
            const missingFields = fields.filter(field => {
                const key = String(getKey(field) ?? '');
                return key && !state.results.has(key);
            });
            if (missingFields.length === 0) {
                return;
            }
            if (state.loading) {
                state.queued = true;
                return;
            }

            const uniqueFields = [...new Map(missingFields.map(field => [String(getKey(field)), field])).values()];
            const keys = uniqueFields.map(getKey).map(key => String(key));
            state.loading = true;
            try {
                const endpoint = typeof options.endpoint === 'function'
                    ? options.endpoint(keys, uniqueFields)
                    : options.endpoint;
                const response = await fetch(endpoint, {
                    method: 'GET',
                    headers: { Accept: 'application/json' },
                    credentials: 'same-origin',
                    cache: 'no-store'
                });
                if (!response.ok) {
                    throw new Error(`Index enrichment request failed with status ${response.status}`);
                }
                const payload = await response.json();
                const items = Array.isArray(payload) ? payload : payload?.items || [];
                const returnedKeys = new Set();
                items.forEach(item => {
                    const key = String(options.resultKey ? options.resultKey(item) : (item.key ?? item.Key));
                    if (!key) {
                        return;
                    }
                    returnedKeys.add(key);
                    state.errors.delete(key);
                    state.results.set(key, options.resultValue ? options.resultValue(item) : (item.value ?? item.Value));
                });
                keys.forEach(key => {
                    if (!returnedKeys.has(key)) {
                        state.results.set(key, 0);
                    }
                });
                applyResults();
            }
            catch (error) {
                keys.forEach(key => state.errors.add(key));
                getFields(table, options.field).forEach(field => {
                    if (keys.includes(String(getKey(field)))) {
                        renderError(field);
                    }
                });
                console.error(`Error loading ${options.field} index enrichment:`, error);
            }
            finally {
                state.loading = false;
                if (state.queued) {
                    state.queued = false;
                    schedule();
                }
            }
        };

        const schedule = () => {
            if (state.timer != null) {
                window.clearTimeout(state.timer);
            }
            state.timer = window.setTimeout(() => {
                state.timer = null;
                load();
            }, 0);
        };

        table._cstIndexEnrichmentState = state;
        $(table)
            .off('render.mdb.datatable.cstIndexEnrichment')
            .on('render.mdb.datatable.cstIndexEnrichment', schedule);
        table.addEventListener('cstColumnVisibilityChanged', schedule);
        schedule();
        return state;
    }

    return { initialize, renderNumber };
})();

ConSyst.getListStateKey = function (listKey = null) {
    const resolvedListKey = listKey || (ConSyst.controllerName ? `${ConSyst.controllerName}.Index` : null);
    return resolvedListKey ? `ConSyst.ListState.${resolvedListKey}` : null;
}

ConSyst.navigationTrailKey = 'ConSyst.NavigationTrail';
ConSyst.navigationTrailMaxItems = 5;
ConSyst.navigationTrailMemory = [];

ConSyst.getPageTitleText = function () {
    const titleElement = document.querySelector('#divTitleText h4');
    return titleElement?.textContent?.trim() || document.title.replace(/\s*-\s*ConSyst\s*$/i, '').trim();
}

ConSyst.getCurrentRelativeUrl = function () {
    return `${window.location.pathname}${window.location.search}`;
}

ConSyst.updateCurrentReturnUrl = function (form) {
    if (!form) {
        return;
    }

    const action = new URL(form.getAttribute('action') || window.location.href, window.location.origin);
    action.searchParams.set('returnUrl', ConSyst.getCurrentRelativeUrl());
    form.setAttribute('action', `${action.pathname}${action.search}${action.hash}`);
}

ConSyst.refreshCurrentReturnUrls = function () {
    document.querySelectorAll('form[data-cst-current-return-url]').forEach(form => {
        ConSyst.updateCurrentReturnUrl(form);
    });
}

ConSyst.getNavigationTrail = function () {
    if (!window.sessionStorage) {
        return [...ConSyst.navigationTrailMemory];
    }

    try {
        const rawValue = sessionStorage.getItem(ConSyst.navigationTrailKey);
        const trail = rawValue ? JSON.parse(rawValue) : [];
        if (Array.isArray(trail) && trail.length > 0) {
            ConSyst.navigationTrailMemory = [...trail];
            return trail;
        }

        return [...ConSyst.navigationTrailMemory];
    }
    catch {
        return [...ConSyst.navigationTrailMemory];
    }
}

ConSyst.saveNavigationTrail = function (trail) {
    const normalizedTrail = trail.slice(-ConSyst.navigationTrailMaxItems);
    ConSyst.navigationTrailMemory = [...normalizedTrail];

    if (!window.sessionStorage) {
        return;
    }

    sessionStorage.setItem(ConSyst.navigationTrailKey, JSON.stringify(normalizedTrail));
}

ConSyst.upsertNavigationTrailEntry = function (entry, replaceCurrent = false) {
    if (!entry?.url) {
        return;
    }

    const normalizedEntry = {
        url: entry.url,
        title: entry.title || ConSyst.getPageTitleText(),
        controller: entry.controller || ConSyst.controllerName || '',
        action: entry.action || ConSyst.actionName || '',
        updatedAt: new Date().toISOString()
    };

    const trail = ConSyst.getNavigationTrail();
    const lastEntry = trail[trail.length - 1];

    if (!lastEntry) {
        trail.push(normalizedEntry);
    }
    else if (replaceCurrent || lastEntry.url === normalizedEntry.url) {
        trail[trail.length - 1] = { ...lastEntry, ...normalizedEntry };
    }
    else {
        trail.push(normalizedEntry);
    }

    ConSyst.saveNavigationTrail(trail);
}

ConSyst.captureCurrentNavigationStep = function (replaceCurrent = false) {
    ConSyst.upsertNavigationTrailEntry({
        url: ConSyst.getCurrentRelativeUrl(),
        title: ConSyst.getPageTitleText(),
        controller: ConSyst.controllerName,
        action: ConSyst.actionName
    }, replaceCurrent);
}

ConSyst.buildUrl = function (path, params = {}) {
    const searchParams = new URLSearchParams();

    Object.entries(params || {}).forEach(([key, value]) => {
        if (value == null || value === '') {
            return;
        }

        if (Array.isArray(value)) {
            value
                .filter(item => item != null && item !== '')
                .forEach(item => searchParams.append(key, item));
            return;
        }

        searchParams.append(key, value);
    });

    const queryString = searchParams.toString();
    return queryString ? `${path}?${queryString}` : path;
}

ConSyst.rememberListState = function (listKey, url, title = null) {
    const storageKey = ConSyst.getListStateKey(listKey);
    if (!storageKey || !window.sessionStorage) {
        return;
    }

    sessionStorage.setItem(storageKey, JSON.stringify({
        url,
        title: title || ConSyst.getPageTitleText(),
        updatedAt: new Date().toISOString()
    }));
}

ConSyst.getRememberedListState = function (listKey = null) {
    const storageKey = ConSyst.getListStateKey(listKey);
    if (!storageKey || !window.sessionStorage) {
        return null;
    }

    try {
        const rawValue = sessionStorage.getItem(storageKey);
        return rawValue ? JSON.parse(rawValue) : null;
    }
    catch {
        return null;
    }
}

ConSyst.captureCurrentListState = function (listKey = null, title = null) {
    if (ConSyst.actionName !== 'Index') {
        return;
    }

    const resolvedListKey = listKey || `${ConSyst.controllerName}.Index`;
    const currentUrl = ConSyst.getCurrentRelativeUrl();
    ConSyst.rememberListState(resolvedListKey, currentUrl, title);
}

ConSyst.syncListHistory = function (listKey, path, params = {}, replace = false, title = null) {
    const url = ConSyst.buildUrl(path, params);
    const state = {
        cstListKey: listKey,
        cstUrl: url
    };

    if (replace) {
        window.history.replaceState(state, '', url);
    }
    else {
        window.history.pushState(state, '', url);
    }

    ConSyst.refreshCurrentReturnUrls();
    ConSyst.rememberListState(listKey, url, title);
    ConSyst.upsertNavigationTrailEntry({
        url,
        title: title || ConSyst.getPageTitleText(),
        controller: ConSyst.controllerName,
        action: ConSyst.actionName
    }, true);
    ConSyst.renderPageBreadcrumbs();
    return url;
}

ConSyst.ListSearch = {
    scopedTabHandlers: {},

    getScopeKey: function (scope = 'default') {
        return `${ConSyst.controllerName}.Index.${scope}`;
    },

    getScopedState: function (scope = 'default') {
        return ConSyst.getRememberedListState(ConSyst.ListSearch.getScopeKey(scope));
    },

    rememberScopedState: function (scope, url, title = null) {
        ConSyst.rememberListState(ConSyst.ListSearch.getScopeKey(scope), url, title);
    },

    syncScopedHistory: function (scope, path, params = {}, replace = false, title = null) {
        const url = ConSyst.syncListHistory(ConSyst.ListSearch.getScopeKey(scope), path, params, replace, title);
        const form = ConSyst.ListSearch.getIndexForm(scope);
        ConSyst.ListSearch.updateAdvancedFilterCount(form);
        ConSyst.ListSearch.closeAdvancedFilterModal();
        return url;
    },

    clearScopedState: function (scope) {
        const key = ConSyst.getListStateKey(ConSyst.ListSearch.getScopeKey(scope));
        if (key) window.sessionStorage?.removeItem(key);
    },

    clearScope: function (scope = null) {
        const url = new URL(window.location.href);
        url.search = '';
        if (scope) {
            url.searchParams.set('listTab', scope);
        }
        url.searchParams.set('firstLoad', 'False');
        window.location.assign(`${url.pathname}${url.search}`);
    },

    registerScopedTabHandler: function (scope, handler) {
        if (scope && typeof handler === 'function') {
            ConSyst.ListSearch.scopedTabHandlers[scope] = handler;
        }
    },

    attachReturnUrlToForms: function () {
        if ((ConSyst.actionName || '').toLowerCase() === 'index') return;
        const returnUrl = ConSyst.getReturnUrl?.();
        if (!returnUrl) return;

        document.querySelectorAll('form[method="post"], form:not([method])').forEach(form => {
            if (form.querySelector('input[name="returnUrl"]')) return;
            const action = new URL(form.getAttribute('action') || window.location.href, window.location.origin);
            if (action.origin !== window.location.origin) return;
            const input = document.createElement('input');
            input.type = 'hidden';
            input.name = 'returnUrl';
            input.value = returnUrl;
            input.dataset.cstReturnUrl = 'true';
            form.appendChild(input);
        });
    },

    getIndexForm: function (scope = null) {
        const currentPath = window.location.pathname.toLowerCase();
        const activeScope = scope || new URLSearchParams(window.location.search).get('listTab');
        const forms = Array.from(document.querySelectorAll('form'));
        const matchingForms = forms.filter(form => {
            const method = (form.getAttribute('method') || 'get').toLowerCase();
            if (method !== 'get') return false;

            const action = new URL(form.getAttribute('action') || window.location.href, window.location.origin);
            return action.pathname.toLowerCase() === currentPath
                || action.pathname.toLowerCase().endsWith(`/${(ConSyst.controllerName || '').toLowerCase()}/index`);
        });

        if (activeScope) {
            const scopedForm = matchingForms.find(form => form.dataset.cstListScope === activeScope);
            if (scopedForm) return scopedForm;
        }

        return matchingForms.find(form => form.dataset.cstListScope === 'default') || matchingForms[0] || null;
    },

    getQueryFromForm: function (form) {
        const params = new URLSearchParams();
        if (!form) return params;

        const formData = new FormData(form);
        for (const [key, value] of formData.entries()) {
            if (key === '__RequestVerificationToken' || value == null || value === '') continue;
            params.append(key, value);
        }

        form.querySelectorAll('select[multiple][name]').forEach(select => {
            params.delete(select.name);
            const values = Array.from(select.selectedOptions || [])
                .map(option => option.value?.trim())
                .filter(value => value);
            if (values.length > 0) {
                params.set(select.name, values.join(','));
            }
        });

        return params;
    },

    getFieldValueSignature: function (field) {
        if (!field) return '';

        if (field.tagName === 'SELECT' && field.multiple) {
            return Array.from(field.selectedOptions || [])
                .map(option => option.value)
                .join('\u001f');
        }

        return field.value ?? '';
    },

    rememberInitialFormValues: function (form) {
        if (!form) return;

        Array.from(form.elements).forEach(field => {
            if (!field.name || field.dataset.cstInitialValue !== undefined) return;
            field.dataset.cstInitialValue = ConSyst.ListSearch.getFieldValueSignature(field);
        });
    },

    removeUnchangedDefaultFilters: function (form, params, preserveDepotFilters = true) {
        if (!form) return params;

        const ignored = new Set(['quickSearch', 'firstLoad', 'page', 'listTab', 'sort', 'sortOrder', 'returnUrl', '__RequestVerificationToken']);
        const existingUrl = new URLSearchParams(window.location.search);
        const changedNames = new Set(Array.from(form.elements)
            .filter(field => field.name
                && (field.dataset.cstUserChanged === 'true'
                    || (field.dataset.cstInitialValue !== undefined
                        && ConSyst.ListSearch.getFieldValueSignature(field) !== field.dataset.cstInitialValue)))
            .map(field => field.name));

        for (const field of Array.from(form.elements)) {
            if (!field.name || ignored.has(field.name) || existingUrl.has(field.name)) continue;
            const isDepotFilter = preserveDepotFilters && field.name.toLowerCase().startsWith('depot');
            if (!changedNames.has(field.name) && !isDepotFilter) params.delete(field.name);
        }

        return params;
    },

    getQuickInput: function () {
        return document.querySelector('[data-cst-quick-search-input]');
    },

    getCurrentListUrl: function () {
        return `${window.location.pathname}${window.location.search}`;
    },

    buildRowActionUrl: function (href) {
        const target = new URL(href, window.location.origin);
        if (target.origin !== window.location.origin || target.pathname.toLowerCase().endsWith('/index')) return null;

        target.searchParams.set('returnUrl', ConSyst.ListSearch.getCurrentListUrl());
        return `${target.pathname}${target.search}${target.hash}`;
    },

    showLoadingModal: function () {
        return ConSyst.showResultModal(
            ConSyst.localizeText('Loading'),
            ConSyst.localizeText('Your query is being loaded. Please wait...'),
            'primary'
        );
    },

    getAdvancedFieldNames: function (form) {
        const ignored = new Set(['quickSearch', 'firstLoad', 'page', 'listTab', 'sort', 'sortOrder', 'returnUrl', '__RequestVerificationToken']);
        return new Set(Array.from(form?.elements || [])
            .map(field => field.name)
            .filter(name => name && !ignored.has(name)));
    },

    hasAdvancedChanges: function (form) {
        const advancedFieldNames = ConSyst.ListSearch.getAdvancedFieldNames(form);
        return Array.from(form?.elements || [])
            .some(field => advancedFieldNames.has(field.name)
                && (field.dataset.cstUserChanged === 'true'
                    || (field.dataset.cstInitialValue !== undefined
                        && ConSyst.ListSearch.getFieldValueSignature(field) !== field.dataset.cstInitialValue)));
    },

    hasActiveAdvancedFilters: function (form = null) {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const activeForm = form || ConSyst.ListSearch.getIndexForm(activeScope);
        return ConSyst.ListSearch.hasAdvancedValues(activeForm)
            || ConSyst.ListSearch.hasAdvancedChanges(activeForm);
    },

    clearQuickSearch: function () {
        const quickInput = ConSyst.ListSearch.getQuickInput();
        if (!quickInput) return;

        quickInput.value = '';
        quickInput.dispatchEvent(new Event('input', { bubbles: true }));
    },

    getQuickSearchValue: function () {
        const quickInput = ConSyst.ListSearch.getQuickInput();
        const quickValue = quickInput?.value?.trim() || '';
        if (quickValue && ConSyst.ListSearch.hasActiveAdvancedFilters()) {
            ConSyst.ListSearch.clearQuickSearch();
            return '';
        }

        return quickValue;
    },

    submit: function (mode = 'advanced') {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const form = ConSyst.ListSearch.getIndexForm(activeScope);
        const quickInput = ConSyst.ListSearch.getQuickInput();
        const isQuickSearch = mode === 'quick';
        const rawQuickValue = quickInput?.value?.trim() || '';
        const hasAdvancedFilters = ConSyst.ListSearch.hasActiveAdvancedFilters(form);
        if (!isQuickSearch && rawQuickValue && hasAdvancedFilters) {
            ConSyst.ListSearch.clearQuickSearch();
        }

        if (form && quickInput && !form.contains(quickInput)) {
            const quickMirror = document.createElement('input');
            quickMirror.type = 'hidden';
            quickMirror.name = 'quickSearch';
            quickMirror.dataset.cstQuickSearchMirror = 'true';
            form.appendChild(quickMirror);
            form.addEventListener('submit', () => {
                quickMirror.value = quickInput.value.trim();
            });
        }
        const params = ConSyst.ListSearch.getQueryFromForm(form);
        const quickValue = isQuickSearch ? rawQuickValue : (hasAdvancedFilters ? '' : rawQuickValue);
        ConSyst.ListSearch.removeUnchangedDefaultFilters(form, params, !quickValue || hasAdvancedFilters);
        if (isQuickSearch) {
            ConSyst.ListSearch.getAdvancedFieldNames(form).forEach(name => {
                const field = Array.from(form?.elements || []).find(element => element.name === name);
                if (!name.toLowerCase().startsWith('depot') && field?.dataset.cstListQuickPreserve !== 'true') {
                    params.delete(name);
                }
            });
        }
        params.delete('page');
        params.set('firstLoad', 'False');
        const listTab = new URLSearchParams(window.location.search).get('listTab');
        if (listTab) params.set('listTab', listTab);
        params.delete('quickSearch');
        if (quickValue) params.set('quickSearch', quickValue);

        const action = form?.getAttribute('action') || window.location.pathname;
        const url = new URL(action, window.location.origin);
        url.search = params.toString();
        ConSyst.ListSearch.showLoadingModal();
        window.location.assign(url.toString());
    },

    clearSortPreference: function () {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const quickControl = document.querySelector('[data-cst-list-quick-search]');
        const cookieNames = new Set();
        const sortCookie = quickControl?.dataset.cstSortPreferenceCookie;
        if (sortCookie) {
            cookieNames.add(sortCookie);
        }
        const sortController = quickControl?.dataset.cstSortPreferenceController || ConSyst.controllerName;
        const sortUser = quickControl?.dataset.cstSortPreferenceUser || 'browser';
        const sortScope = activeScope || 'index';
        if (sortController) {
            cookieNames.add(ConSyst.TablePreferences.buildKey('cstListSortV1', [sortController, sortScope, sortUser]));
        }

        if (ConSyst.controllerName === 'YardTruck') {
            document.querySelectorAll('[data-cst-preference-user][data-cst-preference-depot]').forEach(table => {
                cookieNames.add(ConSyst.TablePreferences.buildKey('cstYardTruckRowSortV1', [
                    table.dataset.cstPreferenceUser,
                    table.dataset.cstPreferenceDepot
                ]));
            });
        }

        cookieNames.forEach(name => {
            document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; path=/`;
        });

        document.querySelectorAll('form[data-cst-list-scope] input[name="sort"], form[data-cst-list-scope] input[name="sortOrder"]').forEach(field => {
            field.value = '';
            field.dispatchEvent(new Event('change', { bubbles: true }));
        });

        if (quickControl) {
            quickControl.dataset.cstServerSortApplied = 'false';
            quickControl.removeAttribute('data-cst-server-sort');
            quickControl.removeAttribute('data-cst-server-sort-order');
        }

        const url = new URL(window.location.href);
        url.searchParams.delete('sort');
        url.searchParams.delete('sortOrder');
        window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
    },

    clearAll: function (resetSort = false) {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const form = ConSyst.ListSearch.getIndexForm(activeScope);
        const quickInput = ConSyst.ListSearch.getQuickInput();
        if (quickInput) {
            quickInput.value = '';
            quickInput.dispatchEvent(new Event('input', { bubbles: true }));
        }
        if (resetSort) {
            ConSyst.ListSearch.clearSortPreference();
        }
        ConSyst.ListSearch.showLoadingModal();
        const legacyClearButton = form?.querySelector('[id^="clear-search-button"]');
        if (legacyClearButton) {
            legacyClearButton.click();
            return;
        }

        const clearStateKey = ConSyst.getListStateKey(`${ConSyst.controllerName}.Index`) + '.clear';
        const listStateKey = ConSyst.getListStateKey(`${ConSyst.controllerName}.Index`);
        if (listStateKey) window.sessionStorage?.removeItem(listStateKey);
        window.sessionStorage?.setItem(clearStateKey, 'true');
        const action = form?.getAttribute('action') || window.location.pathname;
        window.location.assign(new URL(action, window.location.origin).toString());
    },

    hasAdvancedValues: function (form) {
        if (!form) return false;
        const ignored = new Set(['quickSearch', 'firstLoad', 'page', 'listTab', 'sort', 'sortOrder', 'returnUrl', '__RequestVerificationToken']);
        const params = new URLSearchParams(window.location.search);
        const fieldNames = new Set(Array.from(form.elements).map(field => field.name).filter(Boolean));
        for (const [key, value] of params.entries()) {
            if (ignored.has(key) || !fieldNames.has(key) || value === '') continue;

            const field = Array.from(form.elements).find(element => element.name === key);
            if (field?.dataset.cstAdvancedDefaultValue !== undefined
                && value === field.dataset.cstAdvancedDefaultValue) {
                continue;
            }

            return true;
        }
        return false;
    },

    getAdvancedFilterCount: function (form) {
        if (!form) return 0;

        const ignored = new Set(['quickSearch', 'firstLoad', 'page', 'listTab', 'sort', 'sortOrder', 'returnUrl', '__RequestVerificationToken']);
        const params = new URLSearchParams(window.location.search);
        const fieldsByName = new Map();
        Array.from(form.elements).forEach(field => {
            if (field.name && !fieldsByName.has(field.name)) {
                fieldsByName.set(field.name, field);
            }
        });

        const activeFilters = new Set();
        for (const [key, value] of params.entries()) {
            const normalizedValue = value?.trim?.() || '';
            if (ignored.has(key) || !normalizedValue || !fieldsByName.has(key)) {
                continue;
            }

            const field = fieldsByName.get(key);
            if (field.dataset.cstAdvancedDefaultValue !== undefined
                && normalizedValue === field.dataset.cstAdvancedDefaultValue) {
                continue;
            }

            activeFilters.add(key);
        }

        return activeFilters.size;
    },

    updateAdvancedFilterCount: function (form = null) {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const activeForm = form || ConSyst.ListSearch.getIndexForm(activeScope);
        const count = ConSyst.ListSearch.getAdvancedFilterCount(activeForm);
        document.querySelectorAll('[data-cst-advanced-filter-count]').forEach(counter => {
            counter.textContent = count.toString();
            counter.classList.toggle('badge-primary', count === 0);
            counter.classList.toggle('badge-warning', count > 0);
            counter.closest('[data-cst-advanced-toggle]')?.classList.toggle('border-warning', count > 0);
            counter.setAttribute('aria-label', `${ConSyst.localizeText('Active filters')}: ${count}`);
        });
        return count;
    },

    closeAdvancedFilterModal: function () {
        const modal = document.querySelector('[data-cst-advanced-modal-content].show');
        if (!modal || !window.mdb?.Modal) {
            return;
        }

        mdb.Modal.getOrCreateInstance(modal).hide();
    },

    setAdvancedToggleState: function (control, isOpen) {
        if (!control) return;

        control.classList.toggle('btn-primary', isOpen);
        control.classList.toggle('btn-outline-primary', !isOpen);
        control.classList.toggle('active', isOpen);
        control.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
    },

    isSortOnlyState: function (url, form = null) {
        try {
            const searchParams = new URL(url, window.location.origin).searchParams;
            const nonSortParameters = ['sort', 'sortOrder', 'page', 'firstLoad', 'listTab'];
            const defaultValues = new Map(Array.from(form?.elements || [])
                .filter(field => field.name && field.dataset.cstAdvancedDefaultValue !== undefined)
                .map(field => [field.name, field.dataset.cstAdvancedDefaultValue]));
            return Array.from(searchParams.entries()).every(([key, value]) =>
                nonSortParameters.includes(key) || defaultValues.get(key) === value);
        }
        catch {
            return false;
        }
    },

    ensureAdvancedClearButton: function (panel) {
        const ownerForm = panel?.closest('form');
        if (!panel
            || panel.querySelector('[data-cst-advanced-clear]')
            || ownerForm?.dataset.cstListSearchSingleClear === 'true') {
            return;
        }

        const clearButton = document.createElement('button');
        clearButton.type = 'button';
        clearButton.className = 'btn btn-primary';
        clearButton.dataset.cstAdvancedClear = 'true';
        clearButton.title = ConSyst.localizeText('Clear & Reload');
        clearButton.setAttribute('aria-label', ConSyst.localizeText('Clear filters and reset sorting'));
        const icon = document.createElement('i');
        icon.className = 'fa fa-arrows-rotate';
        clearButton.appendChild(icon);
        clearButton.addEventListener('click', event => {
            event.preventDefault();
            ConSyst.ListSearch.clearAll(true);
        });

        const advancedSearchButton = panel.querySelector('[id^="advanced-search-button"]');
        const actionColumn = advancedSearchButton?.closest('[class*="col-"]');
        if (actionColumn) {
            const clearRow = document.createElement('div');
            clearRow.className = 'w-100 mt-2';
            clearRow.appendChild(clearButton);
            actionColumn.appendChild(clearRow);
        }
        else {
            const controls = document.createElement('div');
            controls.className = 'd-flex justify-content-end mt-3';
            controls.appendChild(clearButton);
            panel.appendChild(controls);
        }
    },

    prepareModalSelects: function (modal) {
        if (!modal?.id || typeof window.mdb?.Select === 'undefined') return;

        const containerSelector = `#${modal.id}`;
        modal.querySelectorAll('select.select').forEach(select => {
            const currentContainer = select.getAttribute('data-mdb-container');
            if (currentContainer === containerSelector) return;

            select.setAttribute('data-mdb-container', containerSelector);
            mdb.Select.getInstance(select)?.dispose();
            new mdb.Select(select);
        });
    },

    setupAdvancedPanel: function (control) {
        const activeScope = new URLSearchParams(window.location.search).get('listTab');
        const form = ConSyst.ListSearch.getIndexForm(activeScope);
        if (!form) return null;

        ConSyst.ListSearch.updateAdvancedFilterCount(form);

        form.dataset.cstAdvancedMode = 'modal';
        if (form.dataset.cstAdvancedMode === 'modal') {
            let modal = form.querySelector('[data-cst-advanced-modal-content]');
            if (!modal) {
                const scope = form.dataset.cstListScope || 'default';
                const safeScope = scope.replace(/[^a-zA-Z0-9_-]/g, '-');
                modal = document.createElement('div');
                modal.className = 'modal fade';
                modal.id = `${ConSyst.controllerName}-${safeScope}-advanced-filters-modal`;
                modal.tabIndex = -1;
                modal.setAttribute('aria-hidden', 'true');
                modal.dataset.cstAdvancedModalContent = 'true';

                const dialog = document.createElement('div');
                dialog.className = 'modal-dialog modal-xl modal-dialog-scrollable';
                const content = document.createElement('div');
                content.className = 'modal-content';
                const header = document.createElement('div');
                header.className = 'modal-header';
                const title = document.createElement('h5');
                title.className = 'modal-title';
                const titleIcon = document.createElement('i');
                titleIcon.className = 'fa fa-sliders me-2';
                titleIcon.setAttribute('aria-hidden', 'true');
                title.append(titleIcon, document.createTextNode(ConSyst.localizeText('Advanced Filters')));
                const close = document.createElement('button');
                close.type = 'button';
                close.className = 'btn-close';
                close.setAttribute('data-mdb-dismiss', 'modal');
                close.setAttribute('aria-label', ConSyst.localizeText('Close'));
                header.append(title, close);

                const body = document.createElement('div');
                body.className = 'modal-body';
                const movable = Array.from(form.children).filter(child => {
                    if (child.matches('input[type="hidden"], [data-cst-list-quick-search], .first-row, .modal')) return false;
                    return true;
                });
                movable.forEach(child => body.appendChild(child));

                content.append(header, body);
                dialog.appendChild(content);
                modal.appendChild(dialog);
                form.appendChild(modal);
            }

            const syncModalToggle = () => {
                const isOpen = modal.classList.contains('show');
                ConSyst.ListSearch.setAdvancedToggleState(control, isOpen);
            };
            $(modal)
                .off('show.mdb.modal.cstAdvanced hidden.mdb.modal.cstAdvanced')
                .on('show.mdb.modal.cstAdvanced shown.mdb.modal.cstAdvanced', () => ConSyst.ListSearch.setAdvancedToggleState(control, true))
                .on('hidden.mdb.modal.cstAdvanced', syncModalToggle);
            ConSyst.ListSearch.ensureAdvancedClearButton(modal);
            form.querySelectorAll('[id^="clear-search-button"]').forEach(button => {
                button.hidden = true;
                button.setAttribute('aria-hidden', 'true');
            });
            ConSyst.ListSearch.prepareModalSelects(modal);
            syncModalToggle();
            control.setAttribute('aria-controls', modal.id);
            return modal;
        }

        let panel = form.querySelector('[data-cst-generated-advanced-panel]');
        const isNewPanel = !panel;
        if (!panel) {
            panel = document.createElement('div');
            panel.className = 'collapse cst-list-advanced-filter-panel';
            const scope = form.dataset.cstListScope;
            panel.id = scope
                ? `${ConSyst.controllerName}-${scope}-advanced-filters`
                : (control.dataset.cstAdvancedPanelId || `${ConSyst.controllerName}-advanced-filters`);
            panel.dataset.cstGeneratedAdvancedPanel = 'true';

            const movable = Array.from(form.children).filter(child => {
                if (child.matches('input[type="hidden"]')) return false;
                if (child.matches('[data-cst-list-quick-search]')) return false;
                if (child.classList.contains('first-row')) return false;
                return true;
            });
            movable.forEach(child => panel.appendChild(child));
            form.appendChild(panel);
        }

        if (isNewPanel) {
            const shouldOpen = ConSyst.ListSearch.hasAdvancedValues(form);
            panel.classList.toggle('show', shouldOpen);
        }
        ConSyst.ListSearch.setAdvancedToggleState(control, panel.classList.contains('show'));
        ConSyst.ListSearch.ensureAdvancedClearButton(panel);
        form.querySelectorAll('[id^="clear-search-button"]').forEach(button => {
            button.hidden = true;
            button.setAttribute('aria-hidden', 'true');
        });
        control.setAttribute('aria-controls', panel.id);
        return panel;
    },

    initialize: function () {
        ConSyst.ListSearch.attachReturnUrlToForms();
        const quickControl = document.querySelector('[data-cst-list-quick-search]');
        if (!quickControl) return;
        if (quickControl.dataset.cstListSearchInitialized === 'true') return;

        const clearStateKey = ConSyst.getListStateKey(`${ConSyst.controllerName}.Index`) + '.clear';
        const shouldClear = window.sessionStorage?.getItem(clearStateKey) === 'true';
        if (shouldClear) {
            window.sessionStorage.removeItem(clearStateKey);
        }

        const initialScope = new URLSearchParams(window.location.search).get('listTab');
        if (initialScope && window.location.search) {
            ConSyst.ListSearch.rememberScopedState(initialScope, ConSyst.getCurrentRelativeUrl(), ConSyst.getPageTitleText());
        }

        const quickInput = quickControl.querySelector('[data-cst-quick-search-input]');
        const submitButton = quickControl.querySelector('[data-cst-quick-search-submit]');
        const clearButton = quickControl.querySelector('[data-cst-list-clear]');
        const toggleButton = quickControl.querySelector('[data-cst-advanced-toggle]');
        const panel = ConSyst.ListSearch.setupAdvancedPanel(toggleButton || quickControl);

        const form = ConSyst.ListSearch.getIndexForm(new URLSearchParams(window.location.search).get('listTab'));
        ConSyst.ListSearch.updateAdvancedFilterCount(form);
        ConSyst.ListTable?.initializeColumnVisibilityModal?.({
            modalSelector: '#listColumnVisibilityModal',
            getTables: () => {
                const queryScope = new URLSearchParams(window.location.search).get('listTab');
                const activeTab = queryScope
                    ? Array.from(document.querySelectorAll('[data-cst-list-tab-scope]'))
                        .find(tab => tab.dataset.cstListTabScope === queryScope)
                    : document.querySelector('[data-cst-list-tab-scope].active');
                const scope = queryScope || activeTab?.dataset.cstListTabScope;
                const tables = Array.from(document.querySelectorAll('[data-cst-list-table]'));
                if (!scope) {
                    return tables;
                }

                const activeForm = ConSyst.ListSearch.getIndexForm(scope);
                const activePaneId = activeTab?.getAttribute('href')?.replace(/^#/, '');
                const activePane = activePaneId ? document.getElementById(activePaneId) : null;
                return tables.filter(table =>
                    (activeForm && table.closest('form') === activeForm)
                    || (activePane && activePane.contains(table))
                    || table.dataset.cstListTableScope === scope);
            }
        });
        const multilineControllers = new Set(['Acceptance', 'Booking', 'Estimate', 'GateEvent', 'Invoice', 'Inspection', 'PreGateEvent', 'Repair', 'Unit']);
        if (multilineControllers.has(ConSyst.controllerName)) {
            document.querySelectorAll('form[data-cst-list-scope] input:not([type="hidden"])[name="id"], form[data-cst-list-scope] input:not([type="hidden"])[name="code"], form[data-cst-list-scope] input:not([type="hidden"])[name="unitId"], form[data-cst-list-scope] input:not([type="hidden"])[name="unit"], form[data-cst-list-scope] input:not([type="hidden"])[name="invoiceIds"], form[data-cst-list-scope] input:not([type="hidden"])[name="estimateIds"]').forEach(input => {
            const textarea = document.createElement('textarea');
            Array.from(input.attributes).forEach(attribute => {
                if (attribute.name !== 'type') textarea.setAttribute(attribute.name, attribute.value);
            });
            textarea.value = input.value;
            textarea.rows = 2;
            textarea.dataset.cstMultilineIdentifier = 'true';
            input.replaceWith(textarea);
            });
        }

        const listForms = new Set(document.querySelectorAll('form[data-cst-list-scope]'));
        if (form) listForms.add(form);
        listForms.forEach(listForm => {
            listForm.querySelectorAll('input, select, textarea').forEach(field => {
                const markChanged = () => { field.dataset.cstUserChanged = 'true'; };
                field.addEventListener('input', markChanged);
                if (field.tagName !== 'SELECT') {
                    $(field).off('change.cstListSearch').on('change.cstListSearch', markChanged);
                }
            });
            ConSyst.ListSearch.rememberInitialFormValues(listForm);
        });

        document.querySelectorAll('[data-cst-list-tab-scope]').forEach(tab => {
            tab.addEventListener('click', () => {
                const scope = tab.dataset.cstListTabScope;
                if (!scope) return;

                const currentUrl = new URL(window.location.href);
                const currentQuickSearch = ConSyst.ListSearch.getQuickInput()?.value?.trim()
                    || currentUrl.searchParams.get('quickSearch')
                    || '';
                const rememberedState = ConSyst.ListSearch.getScopedState(scope);
                const nextUrl = rememberedState?.url
                    ? new URL(rememberedState.url, window.location.origin)
                    : new URL(currentUrl.pathname, window.location.origin);
                nextUrl.searchParams.set('listTab', scope);
                nextUrl.searchParams.set('firstLoad', 'False');
                if (currentQuickSearch) {
                    nextUrl.searchParams.set('quickSearch', currentQuickSearch);
                }
                else {
                    nextUrl.searchParams.delete('quickSearch');
                }
                window.history.replaceState({ cstListTab: scope }, '', `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`);
                const handler = ConSyst.ListSearch.scopedTabHandlers[scope];
                if (handler) handler(nextUrl);
                ConSyst.ListSearch.setupAdvancedPanel(toggleButton || quickControl);
                ConSyst.ListSearch.updateAdvancedFilterCount(ConSyst.ListSearch.getIndexForm(scope));
            });
        });

        const activeTabScope = new URLSearchParams(window.location.search).get('listTab');
        if (activeTabScope) {
            const activeTab = document.querySelector(`[data-cst-list-tab-scope="${activeTabScope}"]`);
            if (activeTab && !activeTab.classList.contains('active')) activeTab.click();
        }

        if (form && form.dataset.cstListSearchBound !== 'true') {
            form.dataset.cstListSearchBound = 'true';
            form.addEventListener('submit', event => {
                event.preventDefault();
                ConSyst.ListSearch.submit('advanced');
            });
        }

        submitButton?.addEventListener('click', () => ConSyst.ListSearch.submit('quick'));
        clearButton?.addEventListener('click', event => {
            event.preventDefault();
            ConSyst.ListSearch.clearAll();
        });
        quickInput?.addEventListener('keydown', event => {
            if (event.key === 'Enter') {
                event.preventDefault();
                ConSyst.ListSearch.submit('quick');
            }
        });
        toggleButton?.addEventListener('click', () => {
            if (quickControl.dataset.cstAdvancedModal === 'true') {
                const target = ConSyst.ListSearch.setupAdvancedPanel(toggleButton || quickControl);
                if (target && window.mdb?.Modal) {
                    mdb.Modal.getOrCreateInstance(target).show();
                }
                return;
            }
            const target = ConSyst.ListSearch.setupAdvancedPanel(toggleButton || quickControl) || panel;
            if (!target) return;
            const isOpen = !target.classList.contains('show');
            target.classList.toggle('show', isOpen);
            ConSyst.ListSearch.setAdvancedToggleState(toggleButton, isOpen);
        });

        document.querySelectorAll('#SearchInput, #SearchInputEst, #SearchInputTemp, #SearchInputInv, #SearchInputAut, #SearchInputIn, #SearchInputOut').forEach(input => {
            input.dataset.cstLegacyPageSearch = 'true';
            input.disabled = true;
            input.closest('.row')?.setAttribute('hidden', 'hidden');
        });

        document.querySelectorAll('#clear-search-button').forEach(button => {
            button.addEventListener('click', event => {
                event.preventDefault();
                const form = ConSyst.ListSearch.getIndexForm(new URLSearchParams(window.location.search).get('listTab'));
                const action = form?.getAttribute('action') || window.location.pathname;
                const clearStateKey = ConSyst.getListStateKey(`${ConSyst.controllerName}.Index`) + '.clear';
                const listStateKey = ConSyst.getListStateKey(`${ConSyst.controllerName}.Index`);
                if (listStateKey) window.sessionStorage?.removeItem(listStateKey);
                window.sessionStorage?.setItem(clearStateKey, 'true');
                window.location.assign(new URL(action, window.location.origin).toString());
            });
        });

        document.addEventListener('click', event => {
            const link = event.target.closest?.('.datatable a[href], [data-cst-list-action-link][href]');
            if (!link || link.dataset.cstReturnUrlDecorated === 'true') return;

            const returnUrl = ConSyst.ListSearch.buildRowActionUrl(link.href);
            if (!returnUrl) return;

            link.href = returnUrl;
            link.dataset.cstReturnUrlDecorated = 'true';
        }, true);

        quickControl.dataset.cstListSearchInitialized = 'true';
    }
};

ConSyst.getReturnUrl = function (controllerName = null) {
    const toLocalRelativeUrl = function (value) {
        if (!value) return null;

        try {
            const normalizedValue = value.startsWith('~/') ? value.substring(1) : value;
            const parsedUrl = new URL(normalizedValue, window.location.origin);
            if (parsedUrl.origin !== window.location.origin) return null;
            return `${parsedUrl.pathname}${parsedUrl.search}`;
        }
        catch {
            return null;
        }
    };

    const urlParams = new URLSearchParams(window.location.search);
    const returnUrlParam = urlParams.get('returnUrl');
    const explicitReturnUrl = toLocalRelativeUrl(returnUrlParam);
    if (explicitReturnUrl) return explicitReturnUrl;

    const referrerParam = urlParams.get('referrer');
    const explicitReferrer = toLocalRelativeUrl(referrerParam);
    if (explicitReferrer) return explicitReferrer;

    const targetController = controllerName || ConSyst.controllerName;
    if (document.referrer) {
        try {
            if (ConSyst.isSameOrigin(document.referrer)) {
                const parsedReferrer = new URL(document.referrer, window.location.origin);
                const referrerParts = parsedReferrer.pathname.split('/').filter(Boolean);
                const referrerController = referrerParts[0] || '';
                const referrerAction = referrerParts[1] || 'Index';

                if (targetController
                    && referrerController.toLowerCase() === targetController.toLowerCase()
                    && referrerAction.toLowerCase() !== 'index') {
                    return null;
                }

                return `${parsedReferrer.pathname}${parsedReferrer.search}`;
            }
        }
        catch {
            return null;
        }
    }

    return null;
}

ConSyst.renderPageBreadcrumbs = function () {
    const trail = document.getElementById('pageBreadcrumbTrail');
    if (!trail) {
        return;
    }

    const navigationTrail = ConSyst.getNavigationTrail();
    if (navigationTrail.length === 0) {
        ConSyst.captureCurrentNavigationStep(true);
    }

    const trailEntries = ConSyst.getNavigationTrail();
    const crumbs = trailEntries.length > 0
        ? trailEntries.map((entry, index, allEntries) => ({
            label: entry.title || entry.controller || entry.url,
            url: entry.url,
            current: index === allEntries.length - 1
        }))
        : [{
            label: ConSyst.getPageTitleText(),
            url: ConSyst.getCurrentRelativeUrl(),
            current: true
        }];

    trail.innerHTML = crumbs.map(crumb => {
        if (crumb.current) {
            return `<li class="breadcrumb-item cst-breadcrumb-item active" aria-current="page"><span class="cst-breadcrumb-pill">${crumb.label}</span></li>`;
        }

        return `<li class="breadcrumb-item cst-breadcrumb-item"><a href="${crumb.url}" class="cst-breadcrumb-pill">${crumb.label}</a></li>`;
    }).join('');
}

ConSyst.navigateBack = function () {
    const returnUrl = ConSyst.getReturnUrl();
    if (returnUrl) {
        window.location.href = returnUrl;
    } else {
        window.history.back();
    }
}

ConSyst.setDateInputsBlur = function (selector = null) {
    if (selector == null) {
        selector = 'input[type="date"], input[type="datetime-local"]';
    }
    $(selector).off('blur');
    // Inputs desired behaviours
    $(selector).on('blur', function () {
        var inputValue = $(this).val();
        if (!inputValue) return;

        var isDateTimeLocal = $(this).attr("type") === "datetime-local";
        var datePart, timePart = "";

        if (isDateTimeLocal) {
            // Split datetime-local format: YYYY-MM-DDTHH:mm or YYYY-MM-DDTHH:mm:ss
            var parts = inputValue.split('T');
            if (parts.length === 2) {
                datePart = parts[0];
                timePart = parts[1];
            } else {
                datePart = inputValue;
            }
        } else {
            datePart = inputValue;
        }

        var dateParts = datePart.split('-');

        if (dateParts.length === 3 && (dateParts[0].length === 2 || dateParts[0].startsWith("0"))) {
            var fullYear;
            var yearStr = dateParts[0];

            // Handle years starting with "00" (e.g., "0026" -> "2026")
            if (yearStr.startsWith("00") && yearStr.length > 2) {
                var yearDigits = yearStr.substring(2);
                var yearInt = parseInt(yearDigits, 10);
                if (yearInt >= 60 && yearInt <= 99) {
                    fullYear = '19' + (yearDigits.length === 1 ? '0' + yearDigits : yearDigits);
                } else {
                    fullYear = '20' + (yearDigits.length === 1 ? '0' + yearDigits : yearDigits);
                }
            }
            else {
                // Handle 2-digit or 3-digit years starting with 0: 00-59 -> 2000-2059, 60-99 -> 1960-1999
                var yearInt = parseInt(yearStr, 10);
                if (yearInt >= 0 && yearInt <= 99) {
                    var paddedYear = yearInt < 10 ? '0' + yearInt : yearInt.toString();
                    if (yearInt >= 60 && yearInt <= 99) {
                        fullYear = '19' + paddedYear;
                    } else {
                        fullYear = '20' + paddedYear;
                    }
                } else {
                    fullYear = yearStr; // Already 4 digits or invalid
                }
            }

            var newDateValue = fullYear + '-' + dateParts[1] + '-' + dateParts[2];

            if (isDateTimeLocal && timePart) {
                newDateValue = newDateValue + 'T' + timePart;
            }

            $(this).val(newDateValue);
        }
    });
}

ConSyst.navigationKeyFunction = function (ev, current, keyCode, shiftKey) {
    // Get the index of the current input field in the inputs collection
    var index = ConSyst.inputs.index(current);
    // Saves move idx positions
    var move = 0;
    // Declare a variable to store the next or previous input field
    var next = null;
    // Check which arrow key was pressed
    switch (keyCode) {
        case 9:
            if (shiftKey) {
                move = -1;
            }
            else {
                move = 1;
            }
            next = ConSyst.inputs.eq(index + move);
            break;

        case 13:
            if (ConSyst.isMobile()) {
                if (shiftKey) {
                    move = -1;
                }
                else {
                    move = 1;
                }
                next = ConSyst.inputs.eq(index + move);
                break;
            }
            break;
        case 38: // Up arrow key
            // Get the closest row element
            var row = current.closest(".row");
            // Get the previous row element, if any
            var prevRow = row.prev(".row");
            // Get the column index of the current input field
            var colIndex = current.parent().index();
            // Get the input field in the same column of the previous row, if any
            next = prevRow.find(".col:eq(" + colIndex + ")").find("input:not([readonly])");
            break;
        case 40: // Down arrow key
            // Get the closest row element
            var row = current.closest(".row");
            // Get the next row element, if any
            var nextRow = row.next(".row");
            // Get the column index of the current input field
            var colIndex = current.parent().index();
            // Get the input field in the same column of the next row, if any
            next = nextRow.find(".col:eq(" + colIndex + ")").find("input:not([readonly])");
            break;
        default:
            return;
    }

    // Check if there is a next or previous input field
    if (next != null && next.length > 0) {
        var newMove = move;
        while (!next.is(selector) || next.is(":hidden")) {
            newMove = newMove + move;
            next = ConSyst.inputs.eq((index + newMove) % ConSyst.inputs.length);
        }
        // Move the focus to the next or previous input field
        //current.blur();
        if (current.hasClass("autocomplete-input")) {
            //autocomplete = mdb.Autocomplete.getInstance(current.parent()[0]);
            //autocomplete.close();
            //ev.preventDefault();
        }
        else {
            ev.preventDefault();
            next.focus();
        }
    }

}


ConSyst.keyDownFunction = function (ev) {
    // Get the current input field
    var current = $(this);

    switch (ev.which ?? ev.keyCode) {
        case 9:
        case 13:
            if (ConSyst.isMobile()) {
                ConSyst.navigationKeyFunction(ev, current, ev.which ?? ev.keyCode, ev.shiftKey);
            }
            break;
        case 38: // Up arrow key
        case 40: // Down arrow key
            ConSyst.navigationKeyFunction(ev, current, ev.which ?? ev.keyCode, ev.shiftKey);
            break;
        case 46: // Delete key
            // Check if the current input field is a textarea
            if (current.is('textarea')) break;
            current.val(null);
            current.trigger("input");
            if (current.hasClass("select-input")) {
                ConSyst.setSelectValue(current.parent().parent().find("select")[0], null)
            }
            break;
        default:
            if (current.hasClass("select-input")) {
                // For select input validations
                var closestParent = $(this).closest('.select-wrapper');
                var letter = ev.key.toUpperCase();
                var optionsJq = closestParent.find('select option');
                var firstMatch = optionsJq.filter(function () {
                    return $(this).text().toUpperCase().startsWith(letter);
                });
                if (firstMatch.length > 1) {
                    // Try filtering more with value
                    var secondMatch = firstMatch.filter(function () {
                        return $(this).attr("value").toUpperCase().startsWith(letter);
                    });
                    // Return to previous if nothing matches code
                    var firstMatch = secondMatch.length > 0 ? secondMatch : firstMatch;
                }
                else if (firstMatch.length == 0) {
                    firstMatch = optionsJq.filter(function () {
                        return $(this).attr("value").toUpperCase().startsWith(letter);
                    });
                }
                // Check result
                if (firstMatch.length > 1) {
                    var select = mdb.Select.getInstance(closestParent.find(".select")[0]);
                    select?.open();
                    $(".select-filter-input").val(letter);
                    $(".select-filter-input").trigger("input");
                }
                else if (firstMatch.length == 1) {
                    closestParent.find(".select").val(firstMatch.attr("value"));
                    ConSyst.setSelectValue(closestParent.find(".select"), firstMatch.attr("value"), true, true);
                }
            }
            else if (!ev.altKey && (current.attr("type") == 'date' || current.attr("type") == 'datetime-local')) {
                // For date inputs
                var letter = ev.key?.toUpperCase();
                const isOnlyDate = current.attr("type") == 'date';
                //next = inputs.eq(index + 1);
                switch (letter) {
                    case "T": // Today
                        var today = new Date();
                        var formattedDate = ConSyst.formatDateTimeString(today, isOnlyDate);
                        current.val(formattedDate);
                        break;
                    case "Y": // Yesterday for past and year for future
                        if (current.hasClass("past-date")) {
                            var yesterday = new Date();
                            yesterday.setDate(yesterday.getDate() - 1);
                            var formattedDate = ConSyst.formatDateTimeString(yesterday, isOnlyDate);
                            current.val(formattedDate);
                        }
                        else {
                            var date = new Date();
                            date.setFullYear(date.getFullYear() + 1);
                            var formattedDate = ConSyst.formatDateTimeString(date, isOnlyDate);
                            current.val(formattedDate);
                        }
                        break;
                    case "M": // 1 Month after for future and 1 month before for past
                        var monthChg = current.hasClass("past-date") ? -1 : 1;
                        var date = new Date();
                        var newMonth = date.getMonth() + monthChg;
                        if (newMonth > 12) {
                            var newYear = date.getFullYear() + 1;
                            newMonth = 1;
                        }
                        else if (newMonth < 1) {
                            var newYear = date.getFullYear() - 1;
                            newMonth = 12;
                        }
                        else {
                            var newYear = date.getFullYear()
                        }
                        date.setFullYear(newYear, newMonth);
                        var formattedDate = ConSyst.formatDateTimeString(date, isOnlyDate);
                        current.val(formattedDate);
                        break;
                    case "E": // End of month
                        var date = new Date();
                        date.setMonth(date.getMonth() + 1);
                        date.setDate(0);
                        var formattedDate = ConSyst.formatDateTimeString(date, isOnlyDate);
                        current.val(formattedDate);
                        var date = new Date();
                        break;
                    case "B": // Begin of month
                        var date = new Date();
                        date.setDate(1);
                        var formattedDate = ConSyst.formatDateTimeString(date, isOnlyDate);
                        current.val(formattedDate);
                        break;
                    case "S": // TEC 6, 2 months after for future and 2 months before for past
                        var monthChg = current.hasClass("past-date") ? -2 : 2;
                        var date = new Date();
                        var newMonth = date.getMonth() + monthChg;
                        if (newMonth > 12) {
                            var newYear = date.getFullYear() + 1;
                            newMonth = 1;
                        }
                        else if (newMonth < 1) {
                            var newYear = date.getFullYear() - 1;
                            newMonth = 12;
                        }
                        else {
                            var newYear = date.getFullYear()
                        }
                        date.setFullYear(newYear, newMonth);
                        var formattedDate = ConSyst.formatDateTimeString(date, isOnlyDate);
                        current.val(formattedDate);
                        break;
                    default:
                        break;

                }
            }
            return;
    }

}

ConSyst.formatDateTimeString = function(date, isOnlyDate){
    if (isOnlyDate) {
        return date.toISOString().split('T')[0];
    }

    const pad = value => String(value).padStart(2, '0');
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}

// Default input keys behaviour
ConSyst.setInputsAutoCaps = function () {

    // If useAutoCaps, set onchange event to input fields
    if (ConSyst.useAutoCaps) {
        $('input[type="text"], textarea')
            .not('.allow-lower-case')
            // remove any existing autoCaps handler…
            .off('change.autoCaps')
            // …then attach exactly one
            .on('change.autoCaps', function () {
                this.value = this.value.toUpperCase();
            });
    }


}

// Default input keys behaviour
ConSyst.setInputsKeys = function (selector = null) {


    // Skip readonly inputs with tab key
    $('input[readonly]').not(".select-input").attr('tabindex', '-1');
    if (selector == null) {
        selector = 'input:not(:disabled):not(:hidden):not([readonly]):not([type="hidden"]):not(.wysiwyg input), input.select-input:not([type="hidden"]):not([disabled]), textarea:not(:disabled):not([readonly]):not(.wysiwyg-textarea), button:visible:not(:disabled):not(.wysiwyg button):not(.trailing button), a.btn:visible:not(:disabled), .wysiwyg-content, .modal input:not(:disabled):not([readonly]):not([type="hidden"]):not(.wysiwyg input)'
    }
    // Get all the input fields that are not readonly
    var nextInputs = $(selector);
    var previousInputs = ConSyst.inputs;
    if (previousInputs != null && ConSyst.inputsSelector === selector && previousInputs.length === nextInputs.length) {
        var hasChanged = false;
        previousInputs.each(function (index, element) {
            if (nextInputs[index] !== element) {
                hasChanged = true;
                return false;
            }
        });
        if (!hasChanged) {
            return;
        }
    }

    ConSyst.inputsSelector = selector;
    ConSyst.inputs = nextInputs;
    // Replace previous keydowns on this same function for new one
    previousInputs?.off('keydown', ConSyst.keyDownFunction);
    // Add a keydown event handler to each input field
    ConSyst.inputs.keydown(ConSyst.keyDownFunction);


}

ConSyst.scheduleInputsKeysRefresh = function () {
    if (ConSyst.inputsRefreshScheduled) {
        return;
    }

    ConSyst.inputsRefreshScheduled = true;
    var scheduleTask = window.queueMicrotask || function (callback) {
        window.setTimeout(callback, 0);
    };
    scheduleTask(function () {
        ConSyst.inputsRefreshScheduled = false;
        ConSyst.setInputsKeys();
    });
};

// Function to toggle scrollbar
ConSyst.toggleScrollbar = function () {
    var style = document.getElementById('hideScrollbarCss');
    if (style) {
        // Show scrollbar
        style.remove();
    }
    else {
        // Hide scrollbar
        var style = document.createElement('style');
        style.id = 'hideScrollbarCss';
        style.innerHTML = `::-webkit-scrollbar {display: none;}`;
        document.head.appendChild(style);
    }
}

ConSyst.getQueryParams = function () {
    const params = {};
    const queryParams = new URLSearchParams(window.location.search);

    queryParams.forEach((value, key) => {
        // Split comma-separated lists into arrays
        const values = value.includes(',')
            ? value.split(',').map(v => v.trim()).filter(Boolean)
            : [value];

        if (params[key])
        {
            if (Array.isArray(params[key]))
            {
                params[key].push(...values);
            }
            else
            {
                params[key] = [params[key], ...values];
            }
        }
        else
        {
            params[key] = values.length > 1 ? values : values[0];
        }
    });

    return params;
};

ConSyst.openOptions = function (btn) {
    ConSyst.toggleScrollbar();
    var sideNavJq = $("#sidenav-right");
    const sidenav = mdb.Sidenav.getInstance(sideNavJq[0]);
    sidenav.toggle();
    ConSyst.toggleScrollbar();
}

ConSyst.showDeleteModal = function (id = null, code = null, extras = null) {
    var deleteActionName = ConSyst.deleteActionName ?? "Delete";
    var idPath = id != null ? `/${id}` : "";
    var codePath = code != null ? `?code=${code}` : "";
    if (extras != null) {
        const queryParams = $.param(extras, true);
        if (codePath.length > 0) {
            codePath += "&";
        }
        else {
            codePath += "?";
        }
        codePath += `${queryParams}`;
    }
    $('#deleteForm').attr('action', `/${ConSyst.controllerName}/${deleteActionName}${idPath}${codePath}`);
    $('#deleteForm input[data-cst-list-return-url]').val(`${window.location.pathname}${window.location.search}`);
    $('#deleteIdLabel').text(id ?? code ?? Object.values(extras)[0]);
    var modalJq = $('#deleteModal');
    var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
    modal.show();
}

/// ConSyst.showcompletedModal
ConSyst.showcompletedModal = function (id = null, code = null, extras = null) {
    var completedActionName = ConSyst.completedActionName ?? "completed";
    var idPath = id != null ? `/${id}` : "";
    var codePath = code != null ? `?code=${code}` : "";
    if (extras != null) {
        const queryParams = $.param(extras, true);
        if (codePath.length > 0) {
            codePath += "&";
        }
        else {
            codePath += "?";
        }
        codePath += `${queryParams}`;
    }
    $('#completedForm').attr('action', `/${ConSyst.controllerName}/${completedActionName}${idPath}${codePath}`);
    $('#completedIdLabel').text(id ?? code ?? Object.values(extras)[0]);
    var modalJq = $('#completedModal');
    var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
    modal.show();
}



ConSyst.showCancelModal = function (id = null, code = null, extras = null) {
    var cancelActionName = ConSyst.cancelActionName ?? "Cancel";
    var idPath = id != null ? `/${id}` : "";
    var codePath = code != null ? `?code=${code}` : "";
    if (extras != null) {
        const queryParams = $.param(extras, true);
        if (codePath.length > 0) {
            codePath += "&";
        }
        else {
            codePath += "?";
        }
        codePath += `${queryParams}`;
    }
    $('#cancelForm').attr('action', `/${ConSyst.controllerName}/${cancelActionName}${idPath}${codePath}`);
    $('#cancelIdLabel').text(id ?? code ?? Object.values(extras)[0]);
    var modalJq = $('#cancelModal');
    var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
    modal.show();
}

ConSyst.loadStandardCodeAutocomplete = function (div, type, codeInput, nameInput, codes) {
    const autocompleteDivJq = $(div);
    const codesFilterFunction = (value) => {
        return codes.filter((item) => {
            return (type==null || item.CType==type) && (item.SDescription.toLowerCase().includes(value.toLowerCase()) || item.SId.toString().toLowerCase().includes(value.toLowerCase()) || item.SAltCode.toLowerCase().includes(value.toLowerCase()));
        });
    };
    new mdb.Autocomplete(autocompleteDivJq[0], {
        filter: codesFilterFunction,
        displayValue: (value) => value.SCode,
        autoSelect: true,
        itemContent: (value) => {
            return `
                <div class="autocomplete-custom-item-content">
                    <span><b>${value.SId}</b> - ${value.SDescription} - ${value.SAltCode}</span>
                </div>`;
        },
    });
    var codeInputJq = $(codeInput);
    autocompleteDivJq.on('itemSelect.mdb.autocomplete', (event) => {
        codeInputJq.val(event.value.SCode);
        ConSyst.changeStandardCode(codeInput, nameInput, codes, event.value.SId, event.value.SDescription);
    });
    codeInputJq.on('change', (event) => {
        ConSyst.changeStandardCode(codeInput, nameInput, codes, null, null);
    });
}

ConSyst.changeStandardCode = function (codeInput, nameInput, codes, code = null, name = null) {
    var codeInputJq = $(codeInput);
    var codeObj = codes.find((c) => c.SId == codeInputJq.val()?.toUpperCase());
    var codeName = "";
    var codeId = "";
    if (codeObj == null) {
        if (name == null) {
            codeName = "";
        }
        else {
            codeName = name;
        }
        if (code == null) {
            codeId = "";
        }
        else {
            codeId = code;
        }
    }
    else {
        codeName = codeObj.SDescription;
        codeId = codeObj.SId;
    }
    var nameInputJq = $(nameInput);
    nameInputJq.val(codeName);
    nameInputJq.addClass("active");
    codeInputJq.val(codeId);
    codeInputJq.addClass("active");
}

ConSyst.loadCustomerAutocomplete = function (
    div,
    codeInput,
    nameInput,
    idInput,
    gradeSelect,
    labelsSelect,
    customers,
    emptyString = "N/A",
    subCustomerSelect = null,
    relationsSelect = null,
    isLessee = false,
    poolsSelect = null,
    unitTypeInput = null) {
    const autocompleteDivJq = $(div);
    const customerFilterFunction = (value) => {
        var localCusts = customers;
        if (localCusts == null) {
            localCusts = isLessee ? ConSyst.lessees : ConSyst.customers;
        }
        const searchValue = String(value || "").trim().toLowerCase();
        const isGateEventMobile = document.body.classList.contains("gateevent-mobile-layout");
        if (!searchValue) {
            return isGateEventMobile ? [] : (localCusts || []);
        }
        const matches = (localCusts || []).filter((item) => {
            const searchText = item._cstSearchText || `${item.SFullName || ""} ${item.IId || ""} ${item.SCode || ""}`.toLowerCase();
            item._cstSearchText = searchText;
            return searchText.includes(searchValue);
        });

        return isGateEventMobile ? matches.slice(0, 40) : matches;
    };

    var existAutocomplete = mdb.Autocomplete.getInstance(autocompleteDivJq[0]);
    if (existAutocomplete != null) {
        existAutocomplete.dispose();
    }

    new mdb.Autocomplete(autocompleteDivJq[0], {
        filter: customerFilterFunction,
        displayValue: (value) => value.SCode,
        autoSelect: true,
        itemContent: (value) => {
            return `
                <div class="autocomplete-custom-item-content">
                    <span><b>${value.SCode}</b> - ${value.SFullName} - ${value.IId}</span>
                </div>`;
        },
    });

    var codeInputJq = $(codeInput);
    var suppressChangeForValue = null;
    var notifyCustomerChanged = function () {
        codeInputJq.trigger("cstCustomerChanged", {
            customerId: idInput != null ? $(idInput).first().val() : null,
            customerCode: codeInputJq.val()
        });
    };
    autocompleteDivJq.off('itemSelect.mdb.autocomplete');
    codeInputJq.off('change.customerAutocomplete');
    autocompleteDivJq.on('itemSelect.mdb.autocomplete', (event) => {
        suppressChangeForValue = String(event.value.SCode || "").trim().toUpperCase();
        codeInputJq.val(event.value.SCode);
        ConSyst.changeCustomer(codeInput, nameInput, customers, idInput, gradeSelect, labelsSelect, event.value.SCode, event.value.SFullName, emptyString, subCustomerSelect, relationsSelect, isLessee, poolsSelect, unitTypeInput);
        notifyCustomerChanged();
    });

    codeInputJq.on('change.customerAutocomplete', (event) => {
        var currentValue = String(codeInputJq.val() || "").trim().toUpperCase();
        if (suppressChangeForValue != null && suppressChangeForValue === currentValue) {
            suppressChangeForValue = null;
            return;
        }
        suppressChangeForValue = null;
        ConSyst.changeCustomer(codeInput, nameInput, customers, idInput, gradeSelect, labelsSelect, null, null, emptyString, subCustomerSelect, relationsSelect, isLessee, poolsSelect, unitTypeInput);
        notifyCustomerChanged();
    });
};

ConSyst.loadLesseeAutocomplete = function (div, codeInput, nameInput, idInput, lessees) {
    ConSyst.loadCustomerAutocomplete(div, codeInput, nameInput, idInput, null, null, lessees,null,null,null,true)
}

ConSyst.loadCustomerRelationAutocomplete = function (div, codeInput, nameInput, idInput, customerIdInput) {
    const autocompleteDivJq = $(div);
    const codeInputJq = $(codeInput);
    const nameInputJq = $(nameInput);
    const idInputJq = $(idInput);
    let requestNumber = 0;
    let selectedCode = codeInputJq.val()?.toString().toUpperCase() || "";

    var existingAutocomplete = mdb.Autocomplete.getInstance(autocompleteDivJq[0]);
    if (existingAutocomplete != null) {
        existingAutocomplete.dispose();
    }

    const clearSelection = () => {
        selectedCode = "";
        nameInputJq.val("").removeClass("active");
        idInputJq.val("");
    };

    autocompleteDivJq.off("itemSelect.gateEventLesseeAutocomplete");
    codeInputJq.off("input.gateEventLesseeAutocomplete");

    new mdb.Autocomplete(autocompleteDivJq[0], {
        filter: async (query) => {
            const search = query?.trim() ?? "";
            if (!search) {
                return [{
                    label: ConSyst.localizeText("Start typing to see results"),
                    original: null,
                    disabled: true
                }];
            }

            const customerId = parseInt($(customerIdInput).val());
            if (!customerId || isNaN(customerId)) {
                return [{
                    label: ConSyst.localizeText("Select a customer to search"),
                    original: null,
                    disabled: true
                }];
            }

            const currentRequest = ++requestNumber;
            const relations = await ConSyst.callService(
                "Customer",
                "GET",
                "GetRelations",
                { customerId: customerId, query: search },
                {},
                "json"
            );

            if (currentRequest !== requestNumber || parseInt($(customerIdInput).val()) !== customerId) {
                return [];
            }

            if (!relations || relations.length === 0) {
                return [{
                    label: ConSyst.localizeText("No results found"),
                    original: null,
                    disabled: true
                }];
            }

            return relations.map(relation => ({
                original: relation,
                label: `${relation.SCode} - ${relation.SFullName}`
            }));
        },
        displayValue: (value) => value?.original?.SCode || "",
        itemContent: (value) => `
            <div class="autocomplete-custom-item-content">
                <span><b>${value.label}</b></span>
            </div>`,
        autoSelect: true,
        noResultsText: "",
        threshold: 2
    });

    codeInputJq.on("input.gateEventLesseeAutocomplete", function () {
        const currentCode = codeInputJq.val()?.toString().trim().toUpperCase() || "";
        if (currentCode !== selectedCode) {
            clearSelection();
        }
    });

    autocompleteDivJq.on("itemSelect.gateEventLesseeAutocomplete", (event) => {
        const selected = event.value?.original;
        if (!selected) return;

        selectedCode = selected.SCode?.toString().toUpperCase() || "";
        codeInputJq.val(selected.SCode).addClass("active");
        codeInputJq.siblings(".form-label").addClass("active");
        nameInputJq.val(selected.SFullName).addClass("active");
        nameInputJq.siblings(".form-label").addClass("active");
        idInputJq.val(selected.IId).trigger("change");
    });
};

ConSyst.changeCustomer = function (
    codeInput,
    nameInput,
    customers = null,
    idInput = null,
    customerGradeSelect = null,
    customerLabelsSelect = null,
    code = null,
    name = null,
    emptyItemStr = "N/A",
    subCustomerSelect = null,
    relationsSelect = null,
    isLessee = false,
    poolsSelect = null,
    unitTypeInput = null
) {
    var codeInputJq = $(codeInput);
    var idInputJq = idInput != null ? $(idInput) : $();
    var customerChangeState = codeInputJq.data("cst-customer-change-state");
    if (customerChangeState == null) {
        customerChangeState = { number: 0, requests: [] };
        codeInputJq.data("cst-customer-change-state", customerChangeState);
    }
    customerChangeState.requests.forEach(function (request) {
        if (request != null && request.readyState !== 4) {
            request.abort();
        }
    });
    customerChangeState.requests = [];
    var changeNumber = ++customerChangeState.number;
    var isCurrentCustomerChange = function (customerId) {
        if (customerChangeState.number !== changeNumber) {
            return false;
        }
        if (idInputJq.length === 0) {
            return true;
        }
        return String(idInputJq.first().val() || "") === String(customerId || "");
    };
    var getCustomerJson = function (url, data) {
        var request = $.getJSON(url, data);
        customerChangeState.requests.push(request);
        request.always(function () {
            customerChangeState.requests = customerChangeState.requests.filter(function (activeRequest) {
                return activeRequest !== request;
            });
        });
        return request;
    };
    var previousCustomerValues = idInputJq.length > 0
        ? idInputJq.map(function () { return String($(this).val() || ""); }).get()
        : [];
    var applyCustomerId = function (customerId) {
        if (idInputJq.length === 0) {
            return false;
        }
        var customerValue = String(customerId || "");
        var customerIdChanged = previousCustomerValues.some(function (value) {
            return value !== customerValue;
        });
        idInputJq.val(customerId);
        if (customerIdChanged) {
            idInputJq.trigger("change");
        }
        return customerIdChanged;
    };
    var inputVal = String(codeInputJq.val() || "").trim().toUpperCase();

    // If the user has not entered anything, clear and exit
    if (inputVal.trim() === "") {
        $(nameInput).val("").removeClass("active");
        codeInputJq.val("").removeClass("active");
        applyCustomerId(null);
        if (subCustomerSelect != null) {
            ConSyst.setSelectValue(subCustomerSelect, null);
        }
        return;
    }

    if (customers == null) {
        customers = isLessee ? ConSyst.lessees : ConSyst.customers;
    }
    customers = Array.isArray(customers) ? customers : [];


    // Filter clients by exact code match first
    var filteredCustomers = customers.filter((c) =>
        (c.SCode && c.SCode.toUpperCase() == inputVal)
    );

    if (filteredCustomers.length == 0) {
        // Filter clients by code, id or name (case insensitive)
        filteredCustomers = customers.filter((c) =>
            (c.SCode && c.SCode.toUpperCase().includes(inputVal)) ||
            (c.IId && c.IId.toString().toUpperCase().includes(inputVal)) ||
            (c.SFullName && c.SFullName.toUpperCase().includes(inputVal))
        );
    }

    // If there are matches, select the first one
    var customer = null;
    if (filteredCustomers.length > 0) {
        customer = filteredCustomers[0];
    }

    var customerId = customer?.IId ?? null;
    var customerName = customer?.SFullName || "";
    var customerCode = customer?.SCode || "";

    if (customer == null || customerId == null) {
        $(nameInput).val("").removeClass("active");
        codeInputJq.val("").removeClass("active");
        applyCustomerId(null);
        if (subCustomerSelect != null) {
            ConSyst.setSelectValue(subCustomerSelect, null);
        }
        return;
    }

    var nameInputJq = $(nameInput);
    nameInputJq.val(customerName);
    nameInputJq.addClass("active");
    codeInputJq.val(customerCode);
    codeInputJq.addClass("active");

    var customerIdChanged = applyCustomerId(customerId);
    if (subCustomerSelect != null && customerIdChanged) {
        ConSyst.setSelectValue(subCustomerSelect, null);
    }

    if (customerGradeSelect != null) {
        // Get the unit type from the input field
        var unitType = unitTypeInput ? $(unitTypeInput).val() : null;
        var selectedGradeIds = [];
        $(customerGradeSelect).each(function () {
            var selected = $(this).val();
            if (Array.isArray(selected)) {
                selectedGradeIds.push(...selected.filter((value) => value));
            } else if (selected) {
                selectedGradeIds.push(selected);
            }
        });
        var gradesUrl = '/Customer/GetGrades?' + $.param({ customerId: customerId, unitType: unitType, selectedGradeIds: selectedGradeIds }, true);
        getCustomerJson(gradesUrl).done(function (gradesObj) {
            if (!isCurrentCustomerChange(customerId)) {
                return;
            }
            var grades = Array.isArray(gradesObj.LGrades) ? gradesObj.LGrades : [];
            var areGradesCustomer = gradesObj.BAreGradesCustomer;
            if (grades.length > 0) {
                ConSyst.customerGrades = grades;
                ConSyst.areGradesCustomer = areGradesCustomer;
            }
            $("#BIsGradeCustomer").val(areGradesCustomer);
            var gradeSelectJq = $(customerGradeSelect);
            $.each(gradeSelectJq, function (selIdx, select) {
                var oldVal = $(select).val();
                if (oldVal == "" || oldVal == null || oldVal == undefined) {
                    oldVal = $(select).data("cst-old-value");
                }
                var selectedValues = Array.isArray(oldVal) ? oldVal : (oldVal ? [oldVal] : []);
                $(select).empty();
                var gradesToAdd = grades;
                if ($(select).hasClass("customer-criteria")) {
                    gradesToAdd = grades.filter((gr) => gr.BIsCriteria == true);
                }
                if (emptyItemStr != null && $(select).attr('multiple') == undefined) {
                    $(select).append($('<option/>', {
                        value: '',
                        text: emptyItemStr
                    }));
                }

                if ($(select).prop("multiple"))
                {
                    $.each(gradesToAdd, function (gradeIdx, grade) {
                        $(select).append($('<option/>', {
                            value: grade.SCode,
                            text: grade.SName,
                            selected: selectedValues.includes(grade.SCode)
                        }));
                    });
                }
                else
                {
                    $.each(gradesToAdd, function (gradeIdx, grade) {
                        $(select).append($('<option/>', {
                            value: grade.SCode,
                            text: grade.SName,
                            selected: selectedValues.includes(grade.SCode)
                        }));
                    });
                }

            });
        });
    }
    if (customerLabelsSelect != null) {
        getCustomerJson('/Customer/GetLabels', { customerId: customerId, includeMeta: true }).done(function (result) {
            if (!isCurrentCustomerChange(customerId)) {
                return;
            }
            // Support both the old array and the new extended payload
            const labels = Array.isArray(result) ? result : (result.LLabels || []);

            // Keep legacy cache
            if (labels.length > 0) {
                ConSyst.customerLabels = labels;
            }

            // Populate the select(s)
            var labelSelectsJq = $(customerLabelsSelect);
            $.each(labelSelectsJq, function (index, select) {
                $(select).empty();
                $.each(labels, function (i, label) {
                    $(select).append($('<option/>', {
                        value: label.SCode,
                        text: label.SName
                    }));
                });
            });

            // If extended payload, hydrate globals so _CustomerLabelsControl.jsx can build inputs
            if (!Array.isArray(result)) {
                window.customerLabelsFull = result.customerLabelsFull || [];
                window.possibleFieldTypes = result.possibleFieldTypes || [];
            }
            /// onclick in the customerLabelsSelect to refresh the _CustomerLabelsControl.jsx
            $(customerLabelsSelect).trigger('change');

        });
    }
    if (subCustomerSelect != null) {
        getCustomerJson('/Customer/GetSubCustomers', { customerId: customerId }).done(function (subCustomers) {
            if (!isCurrentCustomerChange(customerId)) {
                return;
            }
            var subCust = Array.isArray(subCustomers) ? subCustomers : [];
            if (subCust.length > 0) {
                ConSyst.subCustomers = subCust;
            }
            var subCustomerSelectJq = $(subCustomerSelect);
            $.each(subCustomerSelectJq, function (index, select) {
                var selectedSubCustomer = $(select).val();
                $(select).empty();
                if (emptyItemStr != null && $(select).attr('multiple') == undefined) {
                    $(select).append($('<option/>', {
                        value: '',
                        text: emptyItemStr
                    }));
                }
                $.each(subCust, function (index, subCustomer) {
                    $(select).append($('<option/>', {
                        value: subCustomer.ICode,
                        text: subCustomer.SName,
                        selected: String(selectedSubCustomer || "") === String(subCustomer.ICode)
                    }));
                });
                var selectedSubCustomerExists = subCust.some((subCustomer) => String(selectedSubCustomer || "") === String(subCustomer.ICode));
                ConSyst.setSelectValue(select, selectedSubCustomerExists ? selectedSubCustomer : null);
            });
        });
    }
    if (relationsSelect != null) {
        getCustomerJson('/Customer/GetRelations', { customerId: customerId }).done(function (relations) {
            if (!isCurrentCustomerChange(customerId)) {
                return;
            }
            ConSyst.lessees = relations;
            var relationsSelectJq = $(relationsSelect);
            if (relationsSelectJq.is("select")) {
                relationsSelectJq.empty();
                if (emptyItemStr != null && relationsSelectJq.attr('multiple') === undefined) {
                    relationsSelectJq.append($('<option/>', {
                        value: '',
                        text: emptyItemStr
                    }));
                }
                $.each(relations, function (index, relation) {
                    relationsSelectJq.append($('<option/>', {
                        value: relation.ICode,
                        text: relation.SName
                    }));
                });
            } else {
                ConSyst.refreshAutocompleteSearch(relationsSelectJq.parent()[0]);
            }
        });
    }
    if (poolsSelect != null) {
        getCustomerJson('/Customer/GetPools', { customerId: customerId }).done(function (pools) {
            if (!isCurrentCustomerChange(customerId)) {
                return;
            }
            ConSyst.pools = pools;
            var poolsSelectJq = $(poolsSelect);
            if (poolsSelectJq.is("select")) {
                poolsSelectJq.empty();
                if (emptyItemStr != null && poolsSelectJq.attr('multiple') === undefined) {
                    poolsSelectJq.append($('<option/>', {
                        value: '',
                        text: emptyItemStr
                    }));
                }
                $.each(pools, function (index, pool) {
                    poolsSelectJq.append($('<option/>', {
                        value: pool.ICode,
                        text: pool.SName
                    }));
                });
            } else {
                ConSyst.refreshAutocompleteSearch(poolsSelectJq.parent()[0]);
            }
        });
    }
};


ConSyst.refreshAutocompleteSearch = function (element) {
    const autocompleteInstance = mdb.Autocomplete.getInstance(element);
    if (!autocompleteInstance) return;
    var autocompleteVal = $(element).find('input').val();
    autocompleteInstance.filter(autocompleteVal);
}


ConSyst.changeLessee = function (codeInput, nameInput, lessees, idInput = null, code = null, name = null) {
    ConSyst.changeCustomer(codeInput, nameInput, lessees, idInput, null, null, code, name, null, null, null, true)
}

ConSyst.changeCountry = function (countryCodeSelect, stateCodeSelect, countyCodeSelect, districtCodeSelect, emptyItem = null) {
    var svSelectedCountry = $(countryCodeSelect).val();
    if (svSelectedCountry != null && svSelectedCountry != '') {
        $.getJSON('/Country/GetStates', { countryCode: svSelectedCountry }, function (lpStates) {
            var ovStateSelect = $(stateCodeSelect);
            ovStateSelect.empty();
            if (emptyItem != null) {
                ovStateSelect.append($('<option/>', {
                    value: 0,
                    text: emptyItem
                }));
            }
            $.each(lpStates, function (index, opState) {
                ovStateSelect.append($('<option/>', {
                    value: opState.ICode,
                    text: opState.SName
                }));
            });
            ovStateSelect[0].selectedIndex = 0;
            var svSelectedState = ovStateSelect.val();
            if (lpStates.length == 0) {
                ovStateSelect.siblings('.form-outline').find('.form-label').text("");
                if (countyCodeSelect != null) {
                    var ovCountySelect = $(countyCodeSelect);
                    ovCountySelect.empty();
                    if (emptyItem != null) {
                        ovCountySelect.append($('<option/>', {
                            value: 0,
                            text: emptyItem
                        }));
                    }
                    ovCountySelect.siblings('.form-outline').find('.form-label').text("");
                    if (districtCodeSelect != null) {
                        var ovDistrictSelect = $(districtCodeSelect);
                        ovDistrictSelect.empty();
                        if (emptyItem != null) {
                            ovDistrictSelect.append($('<option/>', {
                                value: 0,
                                text: emptyItem
                            }));
                        }
                        ovDistrictSelect.siblings('.form-outline').find('.form-label').text("");
                    }
                }
            }
            if (svSelectedState != null && svSelectedState != '' && countyCodeSelect != null) {
                $.getJSON('/Country/GetCounties', { countryCode: svSelectedCountry, stateCode: svSelectedState }, function (lpCounties) {
                    var ovCountySelect = $(countyCodeSelect);
                    ovCountySelect.empty();
                    if (emptyItem != null) {
                        ovCountySelect.append($('<option/>', {
                            value: 0,
                            text: emptyItem
                        }));
                    }
                    $.each(lpCounties, function (index, opCounty) {
                        ovCountySelect.append($('<option/>', {
                            value: opCounty.ICode,
                            text: opCounty.SName
                        }));
                    });
                    ovCountySelect[0].selectedIndex = 0;
                    var svSelectedCounty = ovCountySelect.val();
                    if (districtCodeSelect != null) {
                        $.getJSON('/Country/GetDistricts', { countryCode: svSelectedCountry, stateCode: svSelectedState, cityCode: svSelectedCounty }, function (lpDistricts) {
                            var ovDistrictSelect = $(districtCodeSelect);
                            ovDistrictSelect.empty();
                            if (emptyItem != null) {
                                ovDistrictSelect.append($('<option/>', {
                                    value: 0,
                                    text: emptyItem
                                }));
                            }
                            $.each(lpDistricts, function (index, opDistrict) {
                                ovDistrictSelect.append($('<option/>', {
                                    value: opDistrict.ICode,
                                    text: opDistrict.SName
                                }));
                            });
                            ovDistrictSelect[0].selectedIndex = 0;
                        });
                    }
                });
            }
        });
    }
}

ConSyst.changeState = function (countryCodeSelect, stateCodeSelect, countyCodeSelect, districtCodeSelect, emptyItem=null) {
    var svSelectedCountry = $(countryCodeSelect).val();
    var svSelectedState = $(stateCodeSelect).val();
    if (svSelectedCountry != null && svSelectedCountry != '' && svSelectedState != null && svSelectedState != '') {
        $.getJSON('/Country/GetCounties', { countryCode: svSelectedCountry, stateCode: svSelectedState }, function (lpCounties) {
            var ovCountySelect = $(countyCodeSelect);
            ovCountySelect.empty();
            if (emptyItem != null) {
                ovCountySelect.append($('<option/>', {
                    value: 0,
                    text: emptyItem
                }));
            }
            $.each(lpCounties, function (index, opCounty) {
                ovCountySelect.append($('<option/>', {
                    value: opCounty.ICode,
                    text: opCounty.SName
                }));
            });
            ovCountySelect[0].selectedIndex = 0;
            var svSelectedCounty = ovCountySelect.val();
            if (svSelectedCounty != null && svSelectedCounty != '' && districtCodeSelect!=null) {
                $.getJSON('/Country/GetDistricts', { countryCode: svSelectedCountry, stateCode: svSelectedState, cityCode: svSelectedCounty }, function (lpDistricts) {
                    var ovDistrictSelect = $(districtCodeSelect);
                    ovDistrictSelect.empty();
                    if (emptyItem != null) {
                        ovDistrictSelect.append($('<option/>', {
                            value: 0,
                            text: emptyItem
                        }));
                    }
                    $.each(lpDistricts, function (index, opDistrict) {
                        ovDistrictSelect.append($('<option/>', {
                            value: opDistrict.ICode,
                            text: opDistrict.SName
                        }));
                    });
                    ovDistrictSelect[0].selectedIndex = 0;
                });
            }
        });
    }
}

ConSyst.changeCounty = function (countryCodeSelect, stateCodeSelect, countyCodeSelect, districtCodeSelect, emptyItem = null) {
    var svSelectedCountry = $(countryCodeSelect).val();
    var svSelectedState = $(stateCodeSelect).val();
    var svSelectedCounty = $(countyCodeSelect).val();
    if (svSelectedCountry != null && svSelectedCountry != '' && svSelectedCounty != null && svSelectedCounty != '' && svSelectedState != null && svSelectedState != '') {
        $.getJSON('/Country/GetDistricts', { countryCode: svSelectedCountry, stateCode: svSelectedState, cityCode: svSelectedCounty }, function (districts) {
            var ovDistrictSelect = $(districtCodeSelect);
            ovDistrictSelect.empty();
            if (emptyItem != null) {
                ovDistrictSelect.append($('<option/>', {
                    value: 0,
                    text: emptyItem
                }));
            }
            $.each(districts, function (index, opDistrict) {
                ovDistrictSelect.append($('<option/>', {
                    value: opDistrict.ICode,
                    text: opDistrict.SName
                }));
            });
            ovDistrictSelect[0].selectedIndex = 0;
        });
    }
}

ConSyst.serviceResults = {
    Success: 83,
    Error: 69,
    Warning: 87,
    Info: 73
};

ConSyst.callService = function (urlOrController, method, actionName = null, data = null, headers = {}, contentType="json") {
    // Construct URL if controller and action name are provided
    var url = actionName ? `/${urlOrController}/${actionName}` : urlOrController;

    if (method === 'GET' && data) {
        const queryParams = $.param(data,true);
        url += `?${queryParams}`;
    }

    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            type: method,
            data: method === 'GET' ? null : JSON.stringify(data),
            headers: headers,
            contentType: `application/${contentType}; charset=utf-8`,
            //dataType: contentType,
            success: function (response) {

                resolve(response);
            },
            error: function (xhr, status, error) {
                var errorContent = xhr.responseText;
                if (errorContent != null && errorContent.trim() != "") {
                    if (contentType == 'json') {
                        var sysResult = JSON.parse(errorContent);
                        if (sysResult != null) {
                            ConSyst.showMessage(sysResult.SName, "danger");
                        }
                    }
                }
                reject(new Error(`Status: ${status}, Error: ${error}, Response: ${xhr.responseText}`));

            }
        });
    });
}

ConSyst.waitModalTime = 500; // Wait half second for modal animation to finish before opening a new one.
ConSyst.confirmModalQueue = Promise.resolve();

ConSyst.waitForModalHidden = function (modalJq, modal) {
    return new Promise((resolve) => {
        if (!modal._isShown && !modal._isTransitioning) {
            resolve();
            return;
        }

        modalJq.off('hidden.mdb.modal.cstAwaitHidden').one('hidden.mdb.modal.cstAwaitHidden', resolve);
        modal.hide();
    });
}

ConSyst.showResultModal = async function (title = null, message = null, color = null) {
    await ConSyst.confirmModalQueue;
    var modalJq = $('#genericWaitResultModal');
    modalJq.find('.modal-footer button').attr("disabled",true);
    var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
    if (modal._isShown || modal._isTransitioning) {
        await ConSyst.sleep(ConSyst.waitModalTime);
    }
    if (title != null) {
        modalJq.find('.modal-title-txt').text(title);
    }
    if (message != null) {
        modalJq.find('.modal-message').html(`<span class="spinner-grow spinner-grow-sm" role="status"></span> ${message}`);
    }
    if (color != null) {
        modalJq.find('.modal-header').removeClass("bg-success").removeClass("bg-warning").removeClass("bg-danger").removeClass("bg-primary").removeClass("bg-secondary").addClass(`bg-${color}`);
        modalJq.find('.modal-footer button').removeClass("bg-success").removeClass("bg-warning").removeClass("bg-danger").removeClass("bg-primary").removeClass("bg-secondary").addClass(`bg-${color}`);
    }
    //Ensure that the loading modal appears on top of all other modals.
    setTimeout(() => {
        ConSyst.ensureModalOnTop(modalJq[0]);
    }, 10);

    modal.show();
    return [modalJq, modal];
}

ConSyst.waitResultModal = async function (action, title = null, message = null, color = null) {
    if (document.body?.classList.contains('cst-page-loading')) {
        return await Promise.resolve(action());
    }

    const [modalJq, modal] = await ConSyst.showResultModal(title, message, color);

    //Ensure correct z-index after displaying the modal
    setTimeout(() => {
        ConSyst.ensureModalOnTop(modalJq[0]);
    }, 50);
    try {
        const result = await Promise.resolve(action());

        if (modal._isTransitioning) {
            await ConSyst.sleep(100);
        }
        modalJq.data("should-hide", true);
        modal.hide();
        return result;
    } catch (error) {

        if (modal._isTransitioning) {
            await ConSyst.sleep(100);
        }
        modalJq.data("should-hide", true);
        modal.hide();
        throw error;
    }
}

ConSyst.defaultColorForIcon = { "success": "fa-check-circle", "danger": "fa-times-circle", "warning": "fa-exclamation-triangle", "info": "fa-info-circle", "primary": "fa-chevron-circle-right", "secondary": "fa-chevron-circle-right" }

ConSyst.showConfirmModal = function (title, message, color, icon=null, confirmBtnMsg="Confirm", cancelBtnMsg="Cancel") {
    if (icon == null) {
        icon = ConSyst.defaultColorForIcon[color]
    }

    var showConfirmModal = async function () {
        try {
            var modalJq = $('#genericConfirmationModal');
            var modal = mdb.Modal.getOrCreateInstance(modalJq[0]);
            if (modal._isShown || modal._isTransitioning) {
                await ConSyst.sleep(ConSyst.waitModalTime);
            }
            modalJq.find('.modal-title-txt').text(title);
            modalJq.find('.modal-title-ico').removeClass().addClass(`modal-title-ico icon fas ${icon}`);
            modalJq.find('.modal-message').html(message);
            modalJq.find('.modal-header').removeClass('bg-success').removeClass('bg-warning').removeClass('bg-danger').removeClass('bg-primary').removeClass('bg-secondary').addClass(`bg-${color}`);
            var confirmIconJq = modalJq.find('.btn-confirm .icon');
            confirmIconJq.removeClass().addClass(`icon fas ${icon}`);
            modalJq.find('.btn-confirm').removeClass('bg-success').removeClass('bg-warning').removeClass('bg-danger').removeClass('bg-primary').removeClass('bg-secondary').addClass(`bg-${color}`).text(` ${ConSyst.localizeText(confirmBtnMsg)}`).prepend(confirmIconJq);

            var result = await new Promise((resolve) => {
                var settled = false;
                var finish = function (value) {
                    if (settled) {
                        return;
                    }
                    settled = true;
                    ConSyst.waitForModalHidden(modalJq, modal).then(() => resolve(value));
                };

                modalJq.find('.btn-confirm').removeAttr("onclick");
                modalJq.find('.btn-confirm').off('click.cstConfirmModal').on('click.cstConfirmModal', function () {
                    finish(1);
                });

                var cancelIconJq = modalJq.find('.btn-cancel .icon');
                modalJq.find('.btn-cancel').removeAttr("onclick");
                modalJq.find('.btn-cancel').off('click.cstConfirmModal').text(` ${ConSyst.localizeText(cancelBtnMsg)}`).prepend(cancelIconJq);
                modalJq.find('.btn-cancel').on('click.cstConfirmModal', function () {
                    finish(0);
                });
                modalJq.find('.btn-close').off('click.cstConfirmModal').on('click.cstConfirmModal', function () {
                    finish(-1);
                });

                // Show the modal
                modal.show();

                // After a brief delay, ensure modal is on top level
                // (useful when there are multiple modals or elements with high z-index)
                setTimeout(() => {
                    ConSyst.ensureModalOnTop(modalJq[0]);
                }, 10);
            });

            return result;
        } catch (error) {
            throw new Error(`Error: ${error.message}`);
        }
    };

    var queuedConfirmModal = ConSyst.confirmModalQueue.then(showConfirmModal, showConfirmModal);
    ConSyst.confirmModalQueue = queuedConfirmModal.catch(() => { });
    return queuedConfirmModal;
}

/** ===== NEW HELPER FUNCTION FOR Z-INDEX MANAGEMENT =====
 * Ensures a modal is displayed on top of all other modals by managing z-index values
 * This function finds the highest z-index among currently open modals and sets
 * the target modal to a higher z-index to guarantee it appears on top
 * @param {HTMLElement} modalElement - The modal DOM element to bring to front
 * @returns {number} The new z-index value assigned to the modal
 */
ConSyst.ensureModalOnTop = function (modalElement) {
    // Convert DOM element to jQuery object for easier manipulation
    var $modal = $(modalElement);

    // Set initial baseline z-index (Bootstrap's default modal z-index is 1050)
    var highestZIndex = 1050;

    // Find the highest z-index among currently open modals
    // Iterate through all modals that have the 'show' class (currently visible)
    $('.modal.show').each(function () {
        // Get the current modal's z-index, default to 1050 if not set or invalid
        var currentZIndex = parseInt($(this).css('z-index')) || 1050;

        // Update highest z-index if current modal has a higher value
        if (currentZIndex > highestZIndex) {
            highestZIndex = currentZIndex;
        }
    });

    // Calculate new z-index with sufficient gap to avoid conflicts
    // Adding 20 ensures clear separation from other modals
    var newZIndex = highestZIndex + 20;

    // Apply the new z-index to the target modal
    $modal.css('z-index', newZIndex);

    // Handle modal backdrop z-index adjustment
    // The backdrop should be below the modal but above other elements
    var modal = mdb.Modal.getInstance(modalElement);
    if (modal && modal._backdrop && modal._backdrop._element) {
        // Set backdrop z-index to 10 units below the modal
        // This ensures proper layering: backdrop behind modal, but above other content
        $(modal._backdrop._element).css('z-index', newZIndex - 10);
    }

    // Log the operation for debugging purposes
    // Helps track which modal was moved and to what z-index value
    console.log(`Modal ${$modal.attr('id')} z-index set to: ${newZIndex}`);

    // Return the assigned z-index value for potential use by calling code
    return newZIndex;
};

/* ===== OPTIONAL DEBUGGING FUNCTION =====
 * Debug utility function that logs detailed information about all modals in the DOM
 * This function helps developers troubleshoot modal layering issues by providing
 * a complete overview of all modal states, z-index values, and visibility status
 * Useful for debugging complex modal interactions and z-index conflicts
 */
ConSyst.debugModalStack = function () {
    // Print debug session header for easy identification in console
    console.log('=== Start Modal Stack Debug ===');

    // Iterate through ALL modal elements in the DOM (not just visible ones)
    $('.modal').each(function () {
        // Get jQuery reference to current modal for easier property access
        var $modal = $(this);

        // Extract modal identification - ID attribute for unique identification
        var id = $modal.attr('id');

        // Get current z-index CSS property value
        // This shows the actual computed z-index, whether set by CSS or JavaScript
        var zIndex = $modal.css('z-index');

        // Check visibility status by looking for Bootstrap's 'show' class
        // 'show' class indicates the modal is currently displayed to the user
        var isShown = $modal.hasClass('show');

        // Log comprehensive modal information in a readable format
        // Format: Modal ID, current z-index, and visibility status
        console.log(`Modal: ${id}, Z-Index: ${zIndex}, Shown: ${isShown}`);
    });

    // Print debug session footer to clearly mark the end of the output
    console.log('=== End Modal Stack Debug ===');
};

ConSyst.showMessageHtml = function (alertHtml) {
    alertJq = $(alertHtml);
    alertHtml = alertJq.prop('outerHTML');
    alertJq.remove();
    messagesDiv = $("#messagesDiv");
    messagesDiv.append(alertHtml);
    alertId = alertJq.attr('id');
    animation = mdb.Animate.getInstance(document.getElementById(alertId));
    if (animation != null) {
        animation.stopAnimation();
        animation.dispose();
    }
    animation = mdb.Animate.getOrCreateInstance(document.getElementById(alertId));
    animation.changeAnimationType('fade-in-right');
    animation.startAnimation();
    olAlert = new mdb.Alert(document.getElementById(alertId), {position:"top-right"});
    olAlert.show();
}

ConSyst.dismissAlert = async function (btn, sleep = null) {
    if (sleep != null) {
        await ConSyst.sleep(sleep);
    }
    $(btn).trigger("click");
}

ConSyst.dismissAlertAnimation = async function (btn) {
    var alertElem = document.querySelector($(btn).attr('data-mdb-animation-target'))
    if (alertElem != null) {
        var alert = mdb.Alert.getOrCreateInstance(alertElem);
        alert.hide();
        var animation = mdb.Animate.getInstance(alertElem);
        if (animation != null) {
            animation.stopAnimation();
            animation.dispose();
        }
        animation = mdb.Animate.getOrCreateInstance(alertElem);
        animation.changeAnimationType('fade-out-right');
        animation.startAnimation();
    }
}

ConSyst.showMessage = function (message, color, icon = null, alertId = null, inputName=null, formSel=null, position=null) {
    if (typeof message === 'string') {
        text = message;
    }
    else {
        text = message.SMessage;
        color = message.SColor;
        icon = message.SIcon;
        alertId = `alert-js-${message.IId}`
    }
    if (position == null) {
        position = "bottom";
    }
    alertId = alertId ?? 'alert-default';
    color = color ?? 'primary';
    $(`#${alertId} button`).trigger("click");
    $(`#${alertId}`).remove();
    messagesDiv = $("#messagesDiv");
    //Do something
    var messageHtml = `
        <div class="alert alert-dismissible fade" id="${alertId}" role="alert"
             data-mdb-color="${color}"
             data-mdb-position="${position}-right"
             data-mdb-stacking="true"
             data-mdb-append-to-body="true"
             data-mdb-hidden="true"
             data-mdb-autohide="${(color!="danger")?"true":"false"}"
             data-mdb-animation="fade-in-right"
             data-mdb-animation-start="onLoad">

            <i class="fas ${icon} me-3"></i> <span></span>
            <button type="button" class="btn-close" onclick="ConSyst.dismissAlertAnimation(this);" data-mdb-dismiss="alert" aria-label="Close" data-mdb-animation-target="#${alertId}"></button>
        </div>
        `;
    messagesDiv.append(messageHtml);
    animation = mdb.Animate.getInstance(document.getElementById(alertId));
    if (animation != null) {
        animation.stopAnimation();
        animation.dispose();
    }
    animation = mdb.Animate.getOrCreateInstance(document.getElementById(alertId));
    animation.changeAnimationType('fade-in-right');
    animation.startAnimation();
    var alert = new mdb.Alert(document.getElementById(alertId));
    alert.update({
        color: color
    });
    $(`#${alertId} i`).remove();
    $(`#${alertId} span`).remove();
    if (icon == null) {
        icon = ConSyst.defaultColorForIcon[color];
    }
    $(`#${alertId}`).prepend(`<i class="fas ${icon} me-3"></i> <span>${text}</span>`);
    alert.show();
    if (inputName != null) {
        var errors = {};
        errors[inputName] = text;
        if (formSel == null) {
            formSel = "#form";
        }
        var formJq = $(formSel);
        var inputJq = formJq.find(`input[name="${inputName}"]`)
        if (inputJq.length>0) {
            formJq.validate().showErrors(errors);
            $(`#${inputName}`).trigger("focus");
        }
    }
    if (color == "success" || color=="info") {

        ConSyst.dismissAlert($(`#${alertId} button`)[0],5000);
    }
}

ConSyst.showMessages = function (messages, inputName = null) {
    // If an inputName is provided, find the first error message to associate with it.
    let firstError = null;
    if (inputName && messages?.length > 0) {
        // Find the first message of type 'Error'.
        firstError = messages.find(m => m.EType === ConSyst.serviceResults.Error);
    }

    // Display all messages as toasts.
    for (var i = 0; i < messages.length; i++) {
        const message = messages[i];
        let currentInputName = null;

        // If we found an error, only associate the first one with the input.
        if (firstError) {
            if (message === firstError) {
                currentInputName = inputName;
            }
        } else if (i === 0) {
            // If no errors, associate the first message with the input.
            currentInputName = inputName;
        }

        ConSyst.showMessage(message, null, null, null, currentInputName);
    }
}
ConSyst.validateUnitNumber = async function (unitInputOrId, unitType, customerId, eventType = null, eventKey = null, otherUnits = null, preclearCode = null, showWaitModal = true, returnMessages = null, isUnit = false, isChassis = false, isGenset = false, dateTime=null, hasPools = null, grades = null, transactionType = null, subCustomerCode = null) {
    var unitInputJq = $(unitInputOrId);
    var unitInputName = unitInputJq.attr("name");
    var unitId = unitInputJq?.val()
    if (unitInputJq.length == 0) {
        unitId = unitInputOrId; //Is id if can't find input
    }
    unitId = unitId.toUpperCase().replaceAll("_", "").replaceAll(" ", "");
    if ((unitId.length >= 2) && (unitId.length <= 11) || (!isNaN(unitId.charAt(0)) && (unitId.length >= 1) && (unitId.length <= 11))) {
        var useProvidedSubCustomerCode = false;
        if (eventType != null && eventType[0] === 'B') {
            const bookingSubCustomerInput = $("#ISubCustomer");
            if (bookingSubCustomerInput.length > 0) {
                useProvidedSubCustomerCode = true;
                subCustomerCode = bookingSubCustomerInput.val();
            }
        }
        var isUnitValidCode = 0; //0 is invalid, 1 is valid, 10 is new and invalid, 11 is new and valid and any negative is error
        try {
            data = {
                unitId: unitId,
                unitType: unitType,
                customerId: customerId,
                eventType: eventType != null ? eventType[0] : null,
                eventKey: eventKey,
                preclearCode: preclearCode,
                isUnit: isUnit,
                isChassis: isChassis,
                isGenset: isGenset,
                tDocTime: dateTime,
                hasPools: hasPools,
                transactionType: transactionType,
            }
            if (subCustomerCode != null && subCustomerCode !== '') {
                data.subCustomerCode = subCustomerCode;
            }
            if (useProvidedSubCustomerCode) {
                data.useProvidedSubCustomerCode = true;
            }
            //Assign lists only if valid ones are present
            if (otherUnits != null) {
                data.otherUnits = otherUnits;
            }
            if (grades != null) {
                data.grades = grades;
            }

            var result = {};
            if (showWaitModal) {
                result = await ConSyst.waitResultModal(() => {
                    return ConSyst.callService("Unit", "GET", "GetUnitValidation", data);
                }, ConSyst.localizeText("Validating..."), ConSyst.localizeText("Please wait while your unit gets validated..."));
            }
            else {
                result = await ConSyst.callService("Unit", "GET", "GetUnitValidation", data);
            }
            var messages = JSON.parse(result.SValue);
            switch (result.EType) {
                case ConSyst.serviceResults.Success:
                case ConSyst.serviceResults.Info: // Success or info only show every message received adn return true
                    if (returnMessages == null) {
                        ConSyst.showMessages(messages);
                    }
                    else {
                        returnMessages.push(...messages);
                    }
                    isUnitValidCode = 1;
                    break;
                case ConSyst.serviceResults.Error: // Error only show whatever message received and return false
                    if (result.SCode < 0) {
                        ConSyst.showMessage(result.SName, "danger", null, null, unitInputName);
                        isUnitValidCode = result.SCode;
                    }
                    else {
                        if (returnMessages == null) { // Show messages, not returning them
                            //Get only error messages
                            var errorMessages = messages.filter((m) => m.EType == ConSyst.serviceResults.Error);
                            ConSyst.showMessages(errorMessages, unitInputName);
                            var otherMessages = messages.filter((m) => m.EType != ConSyst.serviceResults.Error);
                            ConSyst.showMessages(otherMessages);
                        }
                        else { // Want them returned, not shown
                            returnMessages.push(...messages);
                        }
                        isUnitValidCode = 0;
                    }
                    break;
                case ConSyst.serviceResults.Warning:
                default: // Warning is default behaviour of checking each message and asking for confirmation if there's a warning only
                    var infoMessages = []
                    var infoCount = 0;
                    allConfirm = true;
                    for (var i = 0; i < messages.length; i++) {
                        var message = messages[i];
                        if (message.EType == ConSyst.serviceResults.Warning) {
                            var messConfirm = ConSyst.overrideWarnings ? await ConSyst.showConfirmModal(ConSyst.localizeText("Warning"), `${message.SMessage}. ${ConSyst.localizeText("Are you sure you want to use it?")}`, message.SColor, message.SIcon) : 0;
                            if (messConfirm != 1) {
                                if (returnMessages == null) {
                                    ConSyst.showMessage(message, null, null, null, unitInputName);
                                }
                                else {
                                    returnMessages.push(message);
                                }
                            }
                            allConfirm = allConfirm && messConfirm
                        }
                        else {
                            infoCount++;
                            message.IId = infoCount;
                            infoMessages.push(message);
                        }
                    }
                    if (allConfirm) { // Show info messages for reference if confirmed warnings
                        if (returnMessages == null) {
                            ConSyst.showMessages(infoMessages);
                        }
                        else {
                            returnMessages.push(...infoMessages);
                        }
                        isUnitValidCode = 1;
                    }

            }
            if (isUnitValidCode) {
                unitId = result.SName;
                if (unitInputJq.length > 0) {
                    // Validation went ok
                    unitInputJq.val(unitId);
                    unitInputJq.attr("value", unitId);
                    var inputMask = unitInputJq.attr("data-mdb-input-mask");
                    if (inputMask != null && inputMask != "") {
                        ConSyst.loadInputMask(unitInputOrId, unitId);
                    }
                    unitInputJq.data("cst-validated", true);
                    unitInputJq.attr("data-cst-validated", true);
                    $(`#${unitInputJq.attr("id")}-error`).remove();
                }
            }
            if (result.SCode >= 0) { //is new unit
                //10-19 are for new units, 20-29 are for re entries
                isUnitValidCode += (10 * result.SCode);
            }
            return isUnitValidCode;
        } catch (error) {
            console.error("Error calling service:", error);
            return -1;
        }
        //if (unitId.length != 11) {
        //    var confirm = await ConSyst.showConfirmModal(ConSyst.localizeText("Warning"),ConSyst.localizeText("Unit number is not 11 characters long, check digit can't be validated. Are you sure you want to use it?"), "danger","fa-triangle-exclamation" );
        //}
    }
    else {
        if (isNaN(unitId.charAt(0))) {
            ConSyst.showMessage(ConSyst.localizeText(`Unit ${unitId} must be 2-11 characters (with prefix)`), "danger", null, null, unitInputName);
        }
        else {
            ConSyst.showMessage(ConSyst.localizeText(`Unit ${unitId} must be 1-11 characters (without prefix)`), "danger", null, null, unitInputName);
        }
        return 0;
    }
}

ConSyst.loadSelects = function (selector, values=null) {
    var selectJq = $(selector);
    selectJq.each(function (index, select) {
        var oldValue = null;
        if (values != null) {
            selectId = $(select).attr("id")
            if (selectId in values) {
                sentVal = values[selectId];
            }
            else{
                sentVal = null;
            }
            var oldValue = sentVal;
        }
        if (oldValue == null) {
            oldValue = $(select).val();
        }
        //var parent = $(select).parent();
        //if (parent.hasClass("select-wrapper")) {
        //    $(select).parent().parent().append(select);
        //    parent.remove();
        //}
        ConSyst.setSelectValue(select, oldValue);
    });
}

ConSyst.setSelectValue = function (select, value, triggerOnChange = false, setFocus = false, forceRebuild = false) {
    var selectJq = $(select);
    if (selectJq.length === 0) {
        return;
    }

    var selectElement = selectJq[0];
    var select = mdb.Select.getInstance(selectElement);
    var isDisposed = false;
    var values = Array.isArray(value)
        ? value.filter(item => item != null && item !== '').map(item => `${item}`)
        : (value == null || value === '' ? [] : [`${value}`]);
    var isMultiple = selectJq.prop('multiple');
    var selectedValues = isMultiple
        ? values
        : (values.length > 0 ? values : (selectJq.find('option[value=""]').length > 0 ? [''] : []));
    var opts = selectJq.find('option');
    var optionSignature = Array.from(selectElement.options || [])
        .map(option => `${option.value}|${option.text}|${option.disabled ? '1' : '0'}`)
        .join('\u001f');
    var previousOptionSignature = selectJq.data('cst-select-option-signature');
    var currentValues = isMultiple
        ? ($(selectElement).val() || []).map(item => `${item}`)
        : ($(selectElement).val() == null || $(selectElement).val() === '' ? [] : [`${$(selectElement).val()}`]);
    var valuesMatch = currentValues.length === selectedValues.length
        && currentValues.every((item, index) => item === selectedValues[index]);

    if (select != null && !forceRebuild && valuesMatch && (previousOptionSignature == null || previousOptionSignature === optionSignature)) {
        selectJq.data('cst-select-option-signature', optionSignature);
        selectJq.attr('value', selectedValues.join(','));
        if (setFocus) {
            selectJq.parent().find("input").trigger("focus");
        }
        return;
    }

    if (select != null) {
        select.dispose();
        isDisposed = true;
    }

    opts.each(function () {
        var isSelected = selectedValues.includes(`${this.value}`);
        this.selected = isSelected;
        $(this).prop('selected', isSelected);
        if (isSelected) {
            $(this).attr('selected', 'selected');
        }
        else {
            $(this).removeAttr('selected');
        }
    });
    select = new mdb.Select(selectElement);
    select.setValue(isMultiple ? selectedValues : (selectedValues[0] ?? ''));
    if (triggerOnChange) {
        selectJq.trigger('change');
    }
    // Dispose might get it lost on the input key order, need to set it again
    if (isDisposed) {
        ConSyst.scheduleInputsKeysRefresh();
    }
    if (setFocus) {
        selectJq.parent().find("input").trigger("focus");
    }
    if (selectJq.hasClass("no-arrow")) {
        selectJq.parent().find(".select-arrow").hide();
    }
    selectJq.data('cst-select-option-signature', optionSignature);
    selectJq.attr("value", selectedValues.join(','));
    //var svSelTxt = $(`${select} option:selected`).text();
    //var ivIptSelect = ovSelect.siblings('.form-outline').find('.form-label.select-fake-value.active');
    //ivIptSelect.text(svSelTxt);
    //var ivIptSelect = ovSelect.siblings('.form-outline').find('.select-input');
    //ivIptSelect.val(svSelTxt);
}

ConSyst.getUnitIdCategoryChars = function (unitCategory, fallback) {
    var category = unitCategory;
    if (ConSyst.containerCategoryCodes.includes(unitCategory)) {
        category = "CON";
    }
    else if (ConSyst.chassisCategoryCodes.includes(unitCategory)) {
        category = "CHZ";
    }
    else if (ConSyst.gensetCategoryCodes.includes(unitCategory) || ConSyst.miscellaneousCategoryCodes.includes(unitCategory)) {
        category = "GEN";
    }

    var chars = ConSyst.unitCategoryChars?.[category];
    if (!Array.isArray(chars) || chars.length === 0) {
        return fallback;
    }

    var variants = chars
        .filter(value => value != null && `${value}`.trim() !== "")
        .map(value => `${value}`.trim().charAt(0).toUpperCase())
        .filter((value, index, values) => values.indexOf(value) === index)
        .map(value => `${value}${value.toLowerCase()}`)
        .join("");
    return variants || fallback;
};

ConSyst.loadUnitIdInputMask = function (selector, unitCategory = null) {
    if (unitCategory == null) {

        // Set input mask depending on combo type (any type for Stacked/Multiple)
        $(selector).attr("data-mdb-custom-validator", "[a-zA-Z ],[aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ ]");
        $(selector).attr("data-mdb-custom-mask", "A,T");
        $(selector).attr("data-mdb-input-mask", "AAAT9999999");
        ConSyst.loadInputMask(selector, null, true);
        $(selector).addClass("active");
    }
    else if (ConSyst.containerCategoryCodes.includes(unitCategory)) {

        // Set input mask depending on combo type (only container units for Standard)
        var containerPrefixChars = ConSyst.getUnitIdCategoryChars(unitCategory, "uUxXbBoOaA");
        $(selector).attr("data-mdb-custom-validator", `[a-zA-Z],[${containerPrefixChars} ]`);
        $(selector).attr("data-mdb-custom-mask", "A,U");
        $(selector).attr("data-mdb-input-mask", "AAAU9999999");
        ConSyst.loadInputMask(selector, null, true);
        $(selector).addClass("active");
    }
    else if (ConSyst.chassisCategoryCodes.includes(unitCategory)) {

        // Set input mask depending on combo type (only container units for Standard)
        var chassisPrefixChars = ConSyst.getUnitIdCategoryChars(unitCategory, "zZcCqQxXtTsSuUfFmMlLjJwWhHaApPeEnNrRiI");
        $(selector).attr("data-mdb-custom-validator", `[a-zA-Z ],[${chassisPrefixChars} ]`);
        $(selector).attr("data-mdb-custom-mask", "A,Z");
        $(selector).attr("data-mdb-input-mask", "AAAZ9999999");
        ConSyst.loadInputMask(selector, null, true);
        $(selector).addClass("active");
    }
    else if (ConSyst.gensetCategoryCodes.includes(unitCategory) || ConSyst.miscellaneousCategoryCodes.includes(unitCategory)) {

        // Set input mask depending on combo type (only container units for Standard)
        var gensetPrefixChars = ConSyst.getUnitIdCategoryChars(unitCategory, "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ");
        $(selector).attr("data-mdb-custom-validator", `[a-zA-Z ],[${gensetPrefixChars} ]`);
        $(selector).attr("data-mdb-custom-mask", "A,J");
        $(selector).attr("data-mdb-input-mask", "AAAJ9999999");
        ConSyst.loadInputMask(selector, null, true);
        $(selector).addClass("active");
    }
}

ConSyst.loadInputMask = function (selector, setValue = null, disposeExisting = false) {
    $(selector).each(function (index, element) {
        var value = setValue;
        if (value == null) {
            value = $(element).val();
        }
        var placeholder = $(element).attr('data-mdb-char-placeholder');
        var maskStr = $(element).attr('data-mdb-input-mask');
        var inputMask = Inputmask.getInstance(element);
        if (inputMask == null) {
            inputMask = new Inputmask(element);
        }
        else {
            if (disposeExisting) {
                inputMask.dispose();
                inputMask = new Inputmask(element);
            }
        }
        if (inputMask != null) {
            inputMask._previousValue = value;
            inputMask._value = value;
            inputMask._initialValue = value;
            inputMask._isEmpty = (value == "") || (value == null);
            inputMask._futureCaretPosition = value.length;
            if (placeholder != null && placeholder != "") {
                inputMask._inputPlaceholder = placeholder.repeat(maskStr.length);
            }
        }

        if (placeholder != null && placeholder != "") {
            //Place cursor on current value or beginning of input if there's placeholder
            $(element).on("focus", async function () {
                await ConSyst.sleep(100);
                var inputJq = $(this);
                var val = inputJq.val();
                inputJq.val(val); // Trigger input re-evaluation by setting its value to itself
                var val = val.replaceAll(placeholder, "");
                this.setSelectionRange(val.length, val.length); // Set cursor position
            });
        }
    });
}

ConSyst.removeUnitIdInputMask = function (selector, { clearValue = false } = {}) {
    $(selector).each(function (_, element) {
        const inst = Inputmask.getInstance?.(element);
        if (inst && typeof inst.dispose === 'function') {
            inst.dispose();
        } else if (typeof Inputmask?.remove === 'function') {
            Inputmask.remove(element);
        }
        element.removeAttribute('data-mdb-input-mask');
        element.removeAttribute('data-mdb-custom-mask');
        element.removeAttribute('data-mdb-custom-validator');
        element.removeAttribute('data-mdb-char-placeholder');
        element.removeAttribute('placeholder');
    });
};

ConSyst.preserveCaret = function (selector) {
    const input = document.querySelector(selector);
    if (!input) return;

    var selectionStartIndex = 0;
    var selectionEndIndex = 0;
    var previousValueLength = input.value.length;
    var lastKeyPressed = "";
    var lastInputType = "";

    // Capture caret + value length before any mutation
    var snapshotState = (e) => {
        lastKeyPressed = e?.key || "";
        selectionStartIndex = input.selectionStart ?? 0;
        selectionEndIndex = input.selectionEnd ?? selectionStartIndex;
        previousValueLength = input.value.length;
    };

    var on = (type, handler) => input.addEventListener(type, handler, true);
    on("beforeinput", (e) => { lastInputType = e.inputType || ""; snapshotState(e); });
    on("keydown", snapshotState);
    on("click", snapshotState);
    on("input", () => {
        var newValueLength = input.value.length;
        var selectedLength = selectionEndIndex - selectionStartIndex;

        var isDeletion =
            lastKeyPressed === "Delete" ||
            lastKeyPressed === "Backspace" ||
            lastInputType?.startsWith("delete");

        var isBackwardDeletion =
            lastKeyPressed === "Backspace" ||
            lastInputType?.endsWith("Backward");

        // How many characters were effectively inserted
        var insertedCount = Math.max(newValueLength - previousValueLength + selectedLength, 0);

        var caretPos = isDeletion
            ? (selectedLength
                ? selectionStartIndex
                : (isBackwardDeletion ? Math.max(selectionStartIndex - 1, 0) : selectionStartIndex))
            : selectionStartIndex + insertedCount;

        requestAnimationFrame(() => { try { input.setSelectionRange(caretPos, caretPos); } catch { } });
        lastKeyPressed = lastInputType = "";
    });
}


ConSyst.getSorted = function (selector, spAttrName, bpDesc=false) {
    return $($(selector).toArray().sort(function (a, b) {
        var aVal = parseInt(a.getAttribute(spAttrName)),
            bVal = parseInt(b.getAttribute(spAttrName));
        if (bpDesc) {
            return bVal - aVal;
        }
        else {
            return aVal - bVal;
        }
    }));
}

ConSyst.langChange = function (btn) {
    $("#cultureCode").val($(btn).attr('data-lang'));
}

ConSyst.menuChange = function (btn) {
    $("#spMenu").val($(btn).attr('data-menu'));
    $("#controllerName").val($(btn).attr('data-controller'));
    $("#actionName").val($(btn).attr('data-action'));
}

ConSyst.localizeText = function (text) {
    formatString = ConSyst.localizer != null ? (text in ConSyst.localizer ? ConSyst.localizer[text] : text) : text;
    if (arguments.length > 1) {
        const placeholders = Array.from({ length: arguments.length - 1 }, (_, index) => `{${index}}`);
        var formattedString = placeholders.reduce(
            (result, placeholder, index) => result.replace(placeholder, arguments[index + 1]),
            formatString
        );
    }
    else {
        var formattedString = formatString;
    }
    return formattedString;
}

ConSyst.initTextEditor = function () {
    var wysiwygElement = document.getElementsByClassName('wysiwyg')[0];
    var wysiwygInstance = new WYSIWYG(wysiwygElement, {
        wysiwygTranslations: {
            paragraph: ConSyst.localizeText("Paragraph"),
            textStyle: ConSyst.localizeText("Text Style"),
            heading: ConSyst.localizeText("Heading"),
            preformatted: ConSyst.localizeText('Pre-formatted'),
            bold: ConSyst.localizeText('Bold'),
            italic: ConSyst.localizeText('Italic'),
            strikethrough: ConSyst.localizeText('Strike Through'),
            underline: ConSyst.localizeText('Underline'),
            textcolor: ConSyst.localizeText('Text Color'),
            textBackgroundColor: ConSyst.localizeText('Text Background Color'),
            alignLeft: ConSyst.localizeText('Align Left'),
            alignCenter: ConSyst.localizeText('Align Center'),
            alignRight: ConSyst.localizeText('Align Right'),
            alignJustify: ConSyst.localizeText('Align Justify'),
            insertLink: ConSyst.localizeText('Insert Link'),
            insertPicture: ConSyst.localizeText('Insert Picture'),
            unorderedList: ConSyst.localizeText('Unordered List'),
            orderedList: ConSyst.localizeText('Ordered List'),
            increaseIndent: ConSyst.localizeText('Increase Indent'),
            decreaseIndent: ConSyst.localizeText('Decrease Indent'),
            insertHorizontalRule: 'Wstaw linię poziomą',
            showHTML: 'Pokaż kod HTML',
            undo: 'Cofnij',
            redo: 'Przywróć',
            addLinkHead: 'Dodaj Link',
            addImageHead: 'Dodaj Obraz',
            linkUrlLabel: 'Wpisz adres:',
            linkDescription: 'Wpisz opis',
            imageUrlLabel: 'Wpisz adres obrazka:',
            okButton: 'OK',
            cancelButton: 'Anuluj',
            moreOptions: 'Pokaż więcej opcji',
        }
    });
}

ConSyst.replaceURLParam = function (url, paramName, paramValue) {
    if (paramValue == null) {
        paramValue = '';
    }
    var pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)');
    if (url.search(pattern) >= 0) {
        return url.replace(pattern, '$1' + paramValue + '$2');
    }
    url = url.replace(/[?#]$/, '');
    return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue;
}

ConSyst.removeURLParam = function (url, paramName) {
    //prefer to use l.search if you have a location/link object
    var urlparts = url.split('?');
    if (urlparts.length >= 2) {

        var prefix = encodeURIComponent(paramName) + '=';
        var pars = urlparts[1].split(/[&;]/g);

        //reverse iteration as may be destructive
        for (var i = pars.length; i-- > 0;) {
            //idiom for string.startsWith
            if (pars[i].lastIndexOf(prefix, 0) !== -1) {
                pars.splice(i, 1);
            }
        }

        return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : '');
    }
    return url;
}

ConSyst.globalTabCount = 1;

ConSyst.redirectToPage = function (url) {
    if (ConSyst.globalTabCount >= 5) {
        void (window.open(url), 'child_window');
    }
    else {
        void (window.open(url));
    }
    return false;
}

ConSyst.parseFloat = function (value, defVal = null) {
    const result = parseFloat(value);
    if (defVal == null) {
        return result;
    }
    else {
        return isNaN(result) ? defVal : result;
    }
}

ConSyst.supportCreate = function (url) {

    var newUrl = '/Support/Create?callerUrl=' + encodeURIComponent(url);

    if (ConSyst.globalTabCount >= 5) {
        void (window.open(newUrl, 'child_window'));
    } else {
        void (window.open(newUrl));
    }
    return false;
}

ConSyst.updateGradesForType = async function (customerId, unitType, gradesSelect, emptyString="N/A") {

    // Validate required parameters
    if (!customerId || !unitType) {
        console.warn("updateGradesForType: Invalid parameters", { customerId, unitType });
        return [];
    }

    if (gradesSelect == null || gradesSelect == '') {
        gradesSelect = '.customer-grade';
    }

    try {
        var selectedGradeIds = [];
        $(gradesSelect).each(function () {
            var selected = $(this).val();
            if (Array.isArray(selected)) {
                selectedGradeIds.push(...selected.filter((value) => value));
            } else if (selected) {
                selectedGradeIds.push(selected);
            }
        });
        // Call the GetGrades endpoint to fetch grades filtered by customer ID and unit type
        const result = await ConSyst.callService("Customer", "GET", "GetGrades", {
            customerId: customerId,
            unitType: unitType,
            criteriaOnly: false,
            selectedGradeIds: selectedGradeIds
        });

        if (result && Array.isArray(result.LGrades) && result.LGrades.length >= 0) {

            const grades = result.LGrades || [];
            const areGradesCustomer = result.BAreGradesCustomer || false;

            // Update the global variables for later use
            if (grades.length > 0) {
                ConSyst.customerGrades = grades;
                ConSyst.areGradesCustomer = areGradesCustomer;
            }

            // Update the select element if provided

            if (gradesSelect) {
                const $select = $(gradesSelect);

                for (var s = 0; s < $select.length; s++) {

                    var selectJq = $($select[s]);
                    var prevVal = selectJq.val();
                    if (prevVal == "" || prevVal == null || prevVal == undefined) {
                        prevVal = selectJq.data("cst-old-value");
                    }
                    var selectedValues = Array.isArray(prevVal) ? prevVal : (prevVal ? [prevVal] : []);
                    selectJq.empty();

                    var gradesToAdd = grades;
                    if (selectJq.hasClass("customer-criteria")) {
                        gradesToAdd = grades.filter((gr) => {
                            return gr.BIsCriteria == true;
                        });
                    }

                    if (emptyString != null && emptyString != "" && selectJq.attr('multiple') == undefined) {
                        // Add empty option
                        selectJq.append($('<option/>', {
                            value: '',
                            text: emptyString
                        }));
                    }

                    // Add grade options
                    $.each(gradesToAdd, function (index, grade) {
                        selectJq.append($('<option/>', {
                            value: grade.SCode,
                            text: grade.SName,
                            selected: selectedValues.includes(grade.SCode)
                        }));

                    });

                    // Update Select UI component if necessary
                    if (selectJq.hasClass("select")) {
                        ConSyst.setSelectValue(selectJq[0], prevVal);
                    }
                }
            }

            // Update BIsGradeCustomer hidden input
            $("#BIsGradeCustomer").val(areGradesCustomer);

            return grades;

        } else {
            const errorMessage = result?.SName || ConSyst.localizeText("Unknown error in grade service");
            //ConSyst.showMessage(errorMessage, "danger");
            return [];
        }
    } catch (error) {
        console.error("Error fetching grades for unit type:", error);
        //ConSyst.showMessage(ConSyst.localizeText("Error fetching grade criteria"), "danger");
        return [];
    }
};

ConSyst.updateSortIcons = function (preferredSort = null, preferredSortOrder = null) {
    // Get the current sort parameters from the URL
    const urlParams = new URLSearchParams(window.location.search);
    const tableState = $('.datatable[data-cst-sort-field]').first();
    const serverState = document.querySelector('[data-cst-server-sort]');
    const sort = preferredSort || urlParams.get('sort') || tableState.attr('data-cst-sort-field') || serverState?.dataset.cstServerSort;
    const sortOrder = preferredSortOrder || urlParams.get('sortOrder') || tableState.attr('data-cst-sort-order') || serverState?.dataset.cstServerSortOrder;

    const applySortIcons = function () {
        // Reset all icons first
        $('.datatable-sort-icon').removeClass('active').css('transform', '');
        if (!sort) {
            return;
        }

        const $sortIcon = $(`i[data-mdb-sort="${sort}"]`);
        $sortIcon.addClass('active');
        // If we have a sort parameter and it's descending order
        if (sortOrder === "desc" && $sortIcon.length) {
            // Rotate the icon 180 degrees to indicate descending order
            $sortIcon.css('transform', 'rotate(180deg)');
        }
    };

    applySortIcons();
    $(document)
        .off('render.mdb.datatable.sort-icons', '.datatable')
        .on('render.mdb.datatable.sort-icons', '.datatable', applySortIcons);
}
