import {getGlobal} from "../../../../source/types/global.mjs";
import * as chai from 'chai';
import {chaiDom} from "../../../util/chai-dom.mjs";
import {initJSDOM} from "../../../util/jsdom.mjs";
import {setupIntersectionObserverMock} from "../../../util/intersection-mock.mjs";
import {ResizeObserverMock} from "../../../util/resize-observer.mjs";

let expect = chai.expect;
chai.use(chaiDom);

const global = getGlobal();

let html1 = `
    <div id="test1">
    </div>
`;

let html2 = `
    <div id="test2">
    <monster-select
                        tabindex="0"
                        data-monster-bind="path:values.checkbox"
                        data-monster-options='{
  "url": "mock-data.json",
  "mapping": {
    "selector": "*",
    "valueTemplate": "\${first_name} \${last_name}",
    "labelTemplate": "id"
  },
      "type":"checkbox"

}'></monster-select>
    </div>
`;

function createJsonResponse(data, status = 200) {
    let headers = new Map();
    headers.set('content-type', 'application/json');

    return Promise.resolve({
        ok: status >= 200 && status < 300,
        status,
        headers,
        text: function () {
            return Promise.resolve(JSON.stringify(data));
        }
    });
}

function createDeferred() {
    let resolve;
    let reject;

    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });

    return {promise, resolve, reject};
}

function waitForCondition(check, {timeout = 4000, interval = 25} = {}) {
    return new Promise((resolve, reject) => {
        const start = Date.now();

        const poll = () => {
            try {
                if (check()) {
                    resolve();
                    return;
                }
            } catch (e) {
                reject(e);
                return;
            }

            if (Date.now() - start >= timeout) {
                reject(new Error('Timed out while waiting for test condition.'));
                return;
            }

            setTimeout(poll, interval);
        };

        poll();
    });
}

let Select,
    SelectStyleSheet,
    getDefaultSelectPopperPositionProfile,
    resolveSelectListDimension,
    resolveSelectPopperWidthConstraints,
    resolveSelectVisibleRect,
    resolveSelectViewportMetrics,
    fetchReference,
    FLOATING_LAYOUT_REASON,
    cancelFloatingLayout,
    enqueueFloatingLayout,
    flushFloatingLayoutQueueForTests;

describe('Select', function () {

    before(function (done) {
        initJSDOM().then(() => {

            import("element-internals-polyfill").catch(e => done(e));

            fetchReference = global['fetch'];

            if (!global.ResizeObserver) {
                global.ResizeObserver = ResizeObserverMock;
            }

            import("../../../../source/components/host/host.mjs").then(() => {
                return import("../../../../source/components/form/popper-button.mjs");
            }).then(() => {
                return import("../../../../source/components/form/control-bar.mjs");
            }).then(() => {
                return import("../../../../source/components/form/select.mjs");
            }).then((m) => {
                Select = m['Select'];
                getDefaultSelectPopperPositionProfile = m['getDefaultSelectPopperPositionProfile'];
                resolveSelectListDimension = m['resolveSelectListDimension'];
                resolveSelectPopperWidthConstraints = m['resolveSelectPopperWidthConstraints'];
                resolveSelectVisibleRect = m['resolveSelectVisibleRect'];
                resolveSelectViewportMetrics = m['resolveSelectViewportMetrics'];
                return import("../../../../source/components/form/stylesheet/select.mjs");
            }).then((m) => {
                SelectStyleSheet = m['SelectStyleSheet'];
                return import("../../../../source/components/form/util/floating-layout-queue.mjs");
            }).then((m) => {
                FLOATING_LAYOUT_REASON = m['FLOATING_LAYOUT_REASON'];
                cancelFloatingLayout = m['cancelFloatingLayout'];
                enqueueFloatingLayout = m['enqueueFloatingLayout'];
                flushFloatingLayoutQueueForTests = m['flushFloatingLayoutQueueForTests'];
                done()
            }).catch(e => done(e))


        });
    })

    it('should expose compact layout variables for constrained control contexts', function () {
        const cssText = SelectStyleSheet
            .cssRules
            ? Array.from(SelectStyleSheet.cssRules).map((rule) => rule.cssText).join("\n")
            : "";

        expect(cssText).to.contain("--monster-select-container-overflow");
        expect(cssText).to.contain("--monster-select-container-align-self");
        expect(cssText).to.contain("--monster-select-container-block-size");
        expect(cssText).to.contain("--monster-select-control-padding");
        expect(cssText).to.contain("--monster-select-selection-margin");
        expect(cssText).to.contain("--monster-select-selection-flex-wrap");
        expect(cssText.replace(/\s+/g, '')).to.contain('z-index:var(--monster-z-index-popover)');
    });

    it('should deduplicate floating layout queue jobs by popper element', async function () {
        const popper = document.createElement('div');
        const reasons = [];
        let mutateCount = 0;

        enqueueFloatingLayout({
            popperElement: popper,
            reason: FLOATING_LAYOUT_REASON.RESIZE,
            mutate: (measurement, reason) => {
                mutateCount += 1;
                reasons.push(reason);
            }
        });
        enqueueFloatingLayout({
            popperElement: popper,
            reason: FLOATING_LAYOUT_REASON.POSITION,
            mutate: (measurement, reason) => {
                mutateCount += 1;
                reasons.push(reason);
            }
        });

        await flushFloatingLayoutQueueForTests();

        expect(mutateCount).to.equal(1);
        expect(reasons[0] & FLOATING_LAYOUT_REASON.RESIZE).to.equal(FLOATING_LAYOUT_REASON.RESIZE);
        expect(reasons[0] & FLOATING_LAYOUT_REASON.POSITION).to.equal(FLOATING_LAYOUT_REASON.POSITION);
    });

    it('should keep independent floating layout queue jobs for different poppers', async function () {
        const firstPopper = document.createElement('div');
        const secondPopper = document.createElement('div');
        const flushed = [];

        enqueueFloatingLayout({
            popperElement: firstPopper,
            reason: FLOATING_LAYOUT_REASON.RESIZE,
            mutate: () => {
                flushed.push('first');
            }
        });
        enqueueFloatingLayout({
            popperElement: secondPopper,
            reason: FLOATING_LAYOUT_REASON.POSITION,
            mutate: () => {
                flushed.push('second');
            }
        });

        await flushFloatingLayoutQueueForTests();

        expect(flushed).to.deep.equal(['first', 'second']);
    });

    it('should merge position-only floating layout events without dropping pending mutation', async function () {
        const popper = document.createElement('div');
        const flushed = [];

        enqueueFloatingLayout({
            popperElement: popper,
            reason: FLOATING_LAYOUT_REASON.CONTENT,
            mutate: () => {
                flushed.push('mutate');
            }
        });
        enqueueFloatingLayout({
            popperElement: popper,
            reason: FLOATING_LAYOUT_REASON.POSITION,
            position: () => {
                flushed.push('position');
            }
        });

        await flushFloatingLayoutQueueForTests();

        expect(flushed).to.deep.equal(['mutate', 'position']);
    });

    it('should defer reentrant floating layout queue jobs to a later flush', async function () {
        const popper = document.createElement('div');
        let mutateCount = 0;

        enqueueFloatingLayout({
            popperElement: popper,
            reason: FLOATING_LAYOUT_REASON.RESIZE,
            mutate: () => {
                mutateCount += 1;
                enqueueFloatingLayout({
                    popperElement: popper,
                    reason: FLOATING_LAYOUT_REASON.SETTLE,
                    mutate: () => {
                        mutateCount += 1;
                    }
                });
            }
        });

        await flushFloatingLayoutQueueForTests();
        expect(mutateCount).to.equal(1);

        await flushFloatingLayoutQueueForTests();
        expect(mutateCount).to.equal(2);
    });

    it('should flush floating layout queue jobs when requestAnimationFrame stalls', async function () {
        const originalRequestAnimationFrame = global.requestAnimationFrame;
        const originalCancelAnimationFrame = global.cancelAnimationFrame;
        const popper = document.createElement('div');
        let mutateCount = 0;

        try {
            global.requestAnimationFrame = () => 1;
            global.cancelAnimationFrame = () => {};

            const promise = enqueueFloatingLayout({
                popperElement: popper,
                reason: FLOATING_LAYOUT_REASON.POSITION,
                mutate: () => {
                    mutateCount += 1;
                }
            });

            await waitForCondition(() => mutateCount === 1);
            await promise;

            expect(mutateCount).to.equal(1);
        } finally {
            global.requestAnimationFrame = originalRequestAnimationFrame;
            global.cancelAnimationFrame = originalCancelAnimationFrame;
            await flushFloatingLayoutQueueForTests();
        }
    });

    it('should cancel stalled floating layout queue jobs before the watchdog flushes', async function () {
        const originalRequestAnimationFrame = global.requestAnimationFrame;
        const originalCancelAnimationFrame = global.cancelAnimationFrame;
        const popper = document.createElement('div');
        let mutateCount = 0;

        try {
            global.requestAnimationFrame = () => 1;
            global.cancelAnimationFrame = () => {};

            const promise = enqueueFloatingLayout({
                popperElement: popper,
                reason: FLOATING_LAYOUT_REASON.POSITION,
                mutate: () => {
                    mutateCount += 1;
                }
            });

            cancelFloatingLayout(popper);
            await promise;
            await new Promise((resolve) => setTimeout(resolve, 80));

            expect(mutateCount).to.equal(0);
        } finally {
            global.requestAnimationFrame = originalRequestAnimationFrame;
            global.cancelAnimationFrame = originalCancelAnimationFrame;
            await flushFloatingLayoutQueueForTests();
        }
    });

    it('should flush reentrant floating layout queue jobs through the watchdog', async function () {
        const originalRequestAnimationFrame = global.requestAnimationFrame;
        const originalCancelAnimationFrame = global.cancelAnimationFrame;
        const popper = document.createElement('div');
        let mutateCount = 0;

        try {
            global.requestAnimationFrame = () => 1;
            global.cancelAnimationFrame = () => {};

            enqueueFloatingLayout({
                popperElement: popper,
                reason: FLOATING_LAYOUT_REASON.RESIZE,
                mutate: () => {
                    mutateCount += 1;
                    if (mutateCount === 1) {
                        enqueueFloatingLayout({
                            popperElement: popper,
                            reason: FLOATING_LAYOUT_REASON.SETTLE,
                            mutate: () => {
                                mutateCount += 1;
                            }
                        });
                    }
                }
            });

            await waitForCondition(() => mutateCount === 2);

            expect(mutateCount).to.equal(2);
        } finally {
            global.requestAnimationFrame = originalRequestAnimationFrame;
            global.cancelAnimationFrame = originalCancelAnimationFrame;
            await flushFloatingLayoutQueueForTests();
        }
    });

    it('should flush many independent floating layout queue jobs in one stalled frame', async function () {
        const originalRequestAnimationFrame = global.requestAnimationFrame;
        const originalCancelAnimationFrame = global.cancelAnimationFrame;
        const poppers = Array.from({length: 8}, () => document.createElement('div'));
        const flushed = [];

        try {
            global.requestAnimationFrame = () => 1;
            global.cancelAnimationFrame = () => {};

            for (const [index, popper] of poppers.entries()) {
                enqueueFloatingLayout({
                    popperElement: popper,
                    reason: FLOATING_LAYOUT_REASON.POSITION,
                    mutate: () => {
                        flushed.push(index);
                    }
                });
            }

            await waitForCondition(() => flushed.length === poppers.length);

            expect(flushed).to.deep.equal([0, 1, 2, 3, 4, 5, 6, 7]);
        } finally {
            global.requestAnimationFrame = originalRequestAnimationFrame;
            global.cancelAnimationFrame = originalCancelAnimationFrame;
            await flushFloatingLayoutQueueForTests();
        }
    });

    describe('With fetch', function () {

        beforeEach((done) => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = html1;

            global['fetch'] = function (url, options) {
                let headers = new Map;
                headers.set('content-type', 'application/json');
                return new Promise((resolve, reject) => {
                    resolve({
                        ok:true,
                        status:200,
                        headers: headers,
                        text: function () {



                            return new Promise((resolve2, reject2) => {

                                let json = JSON.parse(`[
                                          {
                                            "id": 1,
                                            "first_name": "Alexia",
                                            "last_name": "Oughtright",
                                            "email": "aoughtright0@exblog.jp",
                                            "gender": "Agender",
                                            "country": "mn" 
                                          },
                                          {
                                            "id": 2,
                                            "first_name": "Beth",
                                            "last_name": "Boddington",
                                            "email": "bboddington1@statcounter.com",
                                            "gender": "Non-binary",
                                            "country": "sy"
                                          },
                                          {
                                            "id": 3,
                                            "first_name": "Shelli",
                                            "last_name": "A'Barrow",
                                            "email": "sabarrow2@google.co.uk",
                                            "gender": "Polygender",
                                            "country": "no"
                                          } ]`);

                                resolve2(JSON.stringify(json))


                            })


                        }
                    });
                })

            };
            done()

        })

        afterEach(() => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
            global['fetch'] = fetchReference;
        })

        describe('create from template', function () {
            beforeEach(() => {
                let mocks = document.getElementById('mocks');
                mocks.innerHTML = html2;
            });

            describe('create from template', function () {
                it('should contains monster-select', function () {
                    expect(document.getElementById('test2')).contain.html('<monster-select');
                });
            });

        });

        describe('document.createElement', function () {
            it('should instance of select', function () {
                const select = document.createElement('monster-select');
                expect(select).is.instanceof(Select);
            });

            it('should have options', function (done) {

                this.timeout(5000)

                let mocks = document.getElementById('mocks');
                const select = document.createElement('monster-select');
                select.setOption('url', 'https://monsterjs.org/assets/examples/countries.json') // url is not used in this test, see fetch mock
                select.setOption('mapping.selector', '*')
                select.setOption('mapping.labelTemplate', '${id}')
                select.setOption('mapping.valueTemplate', '${id}')

                select.addEventListener('monster-options-set', (e) => {
                    setTimeout(() => {

                        try {
                            const options = select.shadowRoot.querySelectorAll('[data-monster-role=option]');
                            expect(options.length).is.equal(3);

                            const optionHtml = select.shadowRoot.querySelector('[data-monster-role=options]');
                            expect(optionHtml).contain.html('data-monster-insert-reference="options-0"');
                            expect(optionHtml).contain.html('data-monster-insert-reference="options-1"');
                            expect(optionHtml).contain.html('data-monster-insert-reference="options-2"');
                            expect(optionHtml).contain.not.html('data-monster-insert-reference="options-3"');
                            done();

                        } catch (e) {
                            done(e)

                        }
                    }, 100)
                })
                
                mocks.appendChild(select);


            });

        });

    });

    describe('Popper sizing', function () {
        this.timeout(5000);

        afterEach(() => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
        });

        it('should render the popper outside of the internal control clipping container', function () {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            expect(control).to.exist;
            expect(popper).to.exist;
            expect(control.contains(popper)).to.equal(false);
            expect(popper.parentNode).to.equal(shadowRoot);
        });

        it('should open in a constrained filter bar without a control overflow override', function (done) {
            const mocks = document.getElementById('mocks');
            const filterBar = document.createElement('div');
            const select = document.createElement('monster-select');

            filterBar.style.display = 'grid';
            filterBar.style.gridTemplateColumns = 'minmax(0, 1fr)';
            filterBar.style.height = '44px';
            filterBar.style.overflow = 'hidden';

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            filterBar.appendChild(select);
            mocks.appendChild(filterBar);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            control.style.overflow = 'hidden';
            control.getBoundingClientRect = () => ({
                width: 180,
                height: 40,
                top: 100,
                left: 40,
                right: 220,
                bottom: 140,
                x: 40,
                y: 100
            });

            setTimeout(() => {
                try {
                    shadowRoot.querySelector('[data-monster-role=container]').click();
                    setTimeout(() => {
                        try {
                            expect(control.style.overflow).to.equal('hidden');
                            expect(control.contains(popper)).to.equal(false);
                            expect(popper.style.display).to.equal('block');
                            done();
                        } catch (e) {
                            done(e);
                        }
                    }, 80);
                } catch (e) {
                    done(e);
                }
            }, 20);
        });

        it('should preserve an immediate open intent while configured options render', async function () {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            mocks.appendChild(select);
            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            const shadowRoot = select.shadowRoot;
            const container = shadowRoot.querySelector('[data-monster-role=container]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            container.click();

            await waitForCondition(() => {
                return shadowRoot.querySelectorAll('[data-monster-role=option]').length === 2;
            });
            await waitForCondition(() => {
                return popper.style.display === 'block';
            });

            expect(popper.style.display).to.equal('block');
        });

        it('should keep an empty select without popper filter closed', async function () {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const container = shadowRoot.querySelector('[data-monster-role=container]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            container.click();

            await new Promise((resolve) => setTimeout(resolve, 160));

            expect(popper.style.display).to.not.equal('block');
        });

        it('should still open an empty select with a popper filter', async function () {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            select.setOption('filter.mode', 'options');
            select.setOption('filter.position', 'popper');
            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const container = shadowRoot.querySelector('[data-monster-role=container]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            container.click();

            await waitForCondition(() => {
                return popper.style.display === 'block';
            });

            expect(popper.style.display).to.equal('block');
        });

        it('should allow the popper to become wider than a narrow control', function (done) {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            control.getBoundingClientRect = () => ({
                width: 120,
                height: 36,
                top: 100,
                left: 40,
                right: 160,
                bottom: 136,
                x: 40,
                y: 100
            });

            setTimeout(() => {
                try {
                    shadowRoot.querySelector('[data-monster-role=container]').click();
                    setTimeout(() => {
                        try {
                            expect(popper.style.minWidth).to.equal('240px');
                            expect(popper.dataset.monsterWidthBehavior).to.equal('preferred');
                            expect(popper.dataset.monsterPreferredWidth).to.equal('240');
                            done();
                        } catch (e) {
                            done(e);
                        }
                    }, 80);
                } catch (e) {
                    done(e);
                }
            }, 20);
        });

        it('should use fixed positioning inside a control bar', function (done) {
            const mocks = document.getElementById('mocks');
            mocks.innerHTML = '<monster-control-bar id="select-bar"></monster-control-bar>';

            const bar = document.getElementById('select-bar');
            const select = document.createElement('monster-select');

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            bar.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            control.getBoundingClientRect = () => ({
                width: 120,
                height: 40,
                top: 100,
                left: 40,
                right: 160,
                bottom: 140,
                x: 40,
                y: 100
            });

            setTimeout(() => {
                try {
                    shadowRoot.querySelector('[data-monster-role=container]').click();
                    setTimeout(() => {
                        try {
                            expect(popper.style.position).to.equal('fixed');
                            done();
                        } catch (e) {
                            done(e);
                        }
                    }, 80);
                } catch (e) {
                    done(e);
                }
            }, 100);
        });

        it('should keep the control bar popper width stable while layout observers settle', function (done) {
            const mocks = document.getElementById('mocks');
            mocks.innerHTML = '<monster-control-bar id="stable-select-bar"></monster-control-bar>';

            const bar = document.getElementById('stable-select-bar');
            const select = document.createElement('monster-select');

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            bar.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const container = shadowRoot.querySelector('[data-monster-role=container]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');
            let measuredWidth = 528;

            const getRect = () => ({
                width: measuredWidth,
                height: 40,
                top: 100,
                left: 40,
                right: 40 + measuredWidth,
                bottom: 140,
                x: 40,
                y: 100
            });

            control.getBoundingClientRect = getRect;
            select.getBoundingClientRect = getRect;

            setTimeout(() => {
                try {
                    container.click();
                    setTimeout(() => {
                        try {
                            expect(popper.style.display).to.equal('block');
                            expect(popper.dataset.monsterPreferredWidth).to.equal('528');
                            expect(popper.style.width).to.equal('528px');
                            expect(select.style.flex).to.equal('0 0 528px');

                            measuredWidth = 279;
                            select.calcAndSetOptionsDimension();

                            expect(popper.dataset.monsterPreferredWidth).to.equal('528');
                            expect(popper.style.width).to.equal('528px');

                            container.click();

                            setTimeout(() => {
                                try {
                                    expect(popper.style.display).to.equal('none');
                                    expect(select.style.flex).to.equal('');
                                    expect(select.style.inlineSize).to.equal('');
                                    expect(select.style.minInlineSize).to.equal('');
                                    done();
                                } catch (e) {
                                    done(e);
                                }
                            }, 80);
                        } catch (e) {
                            done(e);
                        }
                    }, 80);
                } catch (e) {
                    done(e);
                }
            }, 100);
        });

        it('should use the same sibling popper structure for derived selects', function () {
            const tagName = 'issue-460-derived-select';
            const mocks = document.getElementById('mocks');

            if (!customElements.get(tagName)) {
                customElements.define(tagName, class Issue460DerivedSelect extends Select {
                    static getTag() {
                        return tagName;
                    }
                });
            }

            const select = document.createElement(tagName);
            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');

            expect(control).to.exist;
            expect(popper).to.exist;
            expect(control.contains(popper)).to.equal(false);
            expect(popper.parentNode).to.equal(shadowRoot);
        });

        it('should keep the option list height tight for short lists', function () {
            const result = resolveSelectListDimension({
                visibleOptionHeights: [28, 28],
                maxVisibleOptions: 20,
                availableHeight: 500,
                padding: 0,
                margin: 0
            });

            expect(result.desiredHeight).to.equal(56);
            expect(result.maxHeight).to.equal(500);
            expect(result.overflowY).to.equal('hidden');
        });

        it('should keep option list scroll layout stable while overflow toggles', function () {
            const cssText = Array.from(SelectStyleSheet.cssRules)
                .map((rule) => rule.cssText)
                .join('\n')
                .replace(/\s+/g, '');

            expect(cssText).to.contain('scrollbar-gutter:stable');
            expect(cssText).to.not.contain('transition:height');
        });

        it('should refresh the content max height when the available popper height grows again', function (done) {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            select.setOption('options', Array.from({length: 24}, (_, index) => ({
                label: `Option ${index + 1}`,
                value: `${index + 1}`
            })));

            mocks.appendChild(select);

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');
            const content = shadowRoot.querySelector('[part=content]');
            const options = shadowRoot.querySelector('[data-monster-role=options]');

            control.getBoundingClientRect = () => ({
                width: 200,
                height: 36,
                top: 100,
                left: 40,
                right: 240,
                bottom: 136,
                x: 40,
                y: 100
            });

            setTimeout(() => {
                try {
                    shadowRoot.querySelector('[data-monster-role=container]').click();
                    setTimeout(() => {
                        try {
                            content.style.maxHeight = '20px';
                            options.style.height = '20px';
                            options.style.maxHeight = '20px';

                            select.calcAndSetOptionsDimension();

                            expect(parseFloat(content.style.maxHeight)).to.be.greaterThan(20);
                            expect(content.style.maxHeight).to.equal(options.style.maxHeight);
                            expect(parseFloat(popper.style.maxHeight)).to.be.at.least(parseFloat(content.style.maxHeight));
                            done();
                        } catch (e) {
                            done(e);
                        }
                    }, 80);
                } catch (e) {
                    done(e);
                }
            }, 20);
        });

        it('should reserve popper chrome height before sizing the option list', async function () {
            const mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');

            select.setOption('showMaxOptions', 10);
            select.setOption('filter.position', 'popper');
            select.setOption('options', Array.from({length: 10}, (_, index) => ({
                label: `Option ${index + 1}`,
                value: `${index + 1}`
            })));

            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });
            await waitForCondition(() => {
                return select.shadowRoot.querySelectorAll('[data-monster-role=option]').length === 10;
            });

            const shadowRoot = select.shadowRoot;
            const control = shadowRoot.querySelector('[data-monster-role=control]');
            const popper = shadowRoot.querySelector('[data-monster-role=popper]');
            const content = shadowRoot.querySelector('[part=content]');
            const options = shadowRoot.querySelector('[data-monster-role=options]');
            const filterControl = shadowRoot.querySelector('[part=popper-filter-control]');
            const pagination = shadowRoot.querySelector('[data-monster-role=pagination]');
            const remoteInfo = shadowRoot.querySelector('[data-monster-role=remote-info]');

            control.getBoundingClientRect = () => ({
                width: 300,
                height: 50,
                top: 100,
                left: 40,
                right: 340,
                bottom: 150,
                x: 40,
                y: 100
            });
            popper.getBoundingClientRect = () => ({
                width: 300,
                height: 500,
                top: 150,
                left: 40,
                right: 340,
                bottom: 650,
                x: 40,
                y: 150
            });
            filterControl.getBoundingClientRect = () => ({
                width: 260,
                height: 40,
                top: 0,
                left: 0,
                right: 260,
                bottom: 40,
                x: 0,
                y: 0
            });
            pagination.getBoundingClientRect = () => ({
                width: 260,
                height: 34,
                top: 0,
                left: 0,
                right: 260,
                bottom: 34,
                x: 0,
                y: 0
            });
            remoteInfo.getBoundingClientRect = () => ({
                width: 260,
                height: 33,
                top: 0,
                left: 0,
                right: 260,
                bottom: 33,
                x: 0,
                y: 0
            });

            for (const option of shadowRoot.querySelectorAll('[data-monster-role=option]')) {
                option.getBoundingClientRect = () => ({
                    width: 260,
                    height: 50,
                    top: 0,
                    left: 0,
                    right: 260,
                    bottom: 50,
                    x: 0,
                    y: 0
                });
            }

            select.calcAndSetOptionsDimension();

            const optionHeight = parseFloat(options.style.height);

            expect(optionHeight).to.be.greaterThan(0);
            expect(optionHeight).to.be.lessThan(500);
            expect(options.style.maxHeight).to.equal(options.style.height);
            expect(options.style.overflowY).to.equal('auto');
            expect(content.style.maxHeight).to.equal(options.style.height);
            expect(parseFloat(popper.style.maxHeight)).to.be.greaterThan(optionHeight);
        });

        it('should clamp preferred width to the visible viewport width', function () {
            const result = resolveSelectPopperWidthConstraints({
                controlWidth: 120,
                availableWidth: 176
            });

            expect(result.preferredWidth).to.equal(240);
            expect(result.maxWidth).to.equal(176);
        });

        it('should keep start placement while enabling cross-axis shifting', function () {
            const result = getDefaultSelectPopperPositionProfile();

            expect(result.placement).to.equal('bottom-start');
            expect(result.middleware).to.deep.equal([
                'flip',
                'offset:4',
                'shift:crossAxis',
                'size'
            ]);
        });

        it('should prefer the larger live viewport metrics after a resize', function () {
            const result = resolveSelectViewportMetrics({
                layoutWidth: 1400,
                layoutHeight: 900,
                visualWidth: 1024,
                visualHeight: 700,
                offsetLeft: 20,
                offsetTop: 30,
                padding: 12
            });

            expect(result.width).to.equal(1400);
            expect(result.height).to.equal(900);
            expect(result.left).to.equal(20);
            expect(result.top).to.equal(30);
            expect(result.padding).to.equal(12);
        });

        it('should intersect viewport and split container for the visible rect', function () {
            const result = resolveSelectVisibleRect({
                viewportMetrics: {
                    width: 1400,
                    height: 900,
                    left: 0,
                    top: 0,
                    padding: 12
                },
                boundaryRect: {
                    left: 100,
                    top: 80,
                    right: 620,
                    bottom: 700
                }
            });

            expect(result.left).to.equal(100);
            expect(result.top).to.equal(80);
            expect(result.right).to.equal(620);
            expect(result.bottom).to.equal(700);
            expect(result.width).to.equal(520);
            expect(result.height).to.equal(620);
        });

        it('should keep the popper closed when a queued resize update settles after an outside click', function (done) {
            this.timeout(4000);

            const mocks = document.getElementById('mocks');
            const originalResizeObserver = global.ResizeObserver;
            const resizeObservers = [];

            global.ResizeObserver = class extends ResizeObserverMock {
                constructor(callback) {
                    super(callback);
                    resizeObservers.push(this);
                }
            };

            const restoreResizeObserver = () => {
                global.ResizeObserver = originalResizeObserver;
            };

            const fail = (error) => {
                restoreResizeObserver();
                done(error);
            };

            const finish = () => {
                restoreResizeObserver();
                done();
            };

            const select = document.createElement('monster-select');
            select.setOption('type', 'checkbox');
            select.setOption('features.clear', true);
            select.setOption('features.clearAll', true);
            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);

            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const shadowRoot = select.shadowRoot;
                    const container = shadowRoot.querySelector('[data-monster-role=container]');
                    const control = shadowRoot.querySelector('[data-monster-role=control]');
                    const popper = shadowRoot.querySelector('[data-monster-role=popper]');
                    const statusBadge = shadowRoot.querySelector('[data-monster-role=status-or-remove-badges]');

                    container.click();

                    setTimeout(() => {
                        try {
                            const optionControl = shadowRoot.querySelector('[data-monster-role=option-control]');
                            expect(optionControl).to.exist;
                            expect(resizeObservers.length).to.be.at.least(1);

                            optionControl.checked = true;
                            optionControl.dispatchEvent(new Event('input', {
                                bubbles: true,
                                composed: true
                            }));
                            optionControl.dispatchEvent(new Event('change', {
                                bubbles: true,
                                composed: true
                            }));

                            setTimeout(() => {
                                try {
                                    resizeObservers[0].triggerResize([]);
                                    document.body.dispatchEvent(new window.MouseEvent('click', {
                                        bubbles: true,
                                        composed: true
                                    }));

                                    setTimeout(() => {
                                        try {
                                            expect(control.className).to.equal('');
                                            expect(statusBadge.className).to.equal('clear');
                                            expect(popper.style.display).to.equal('none');
                                        } catch (e) {
                                            return fail(e);
                                        }

                                        finish();
                                    }, 50);
                                } catch (e) {
                                    fail(e);
                                }
                            }, 20);
                        } catch (e) {
                            fail(e);
                        }
                    }, 20);
                } catch (e) {
                    fail(e);
                }
            }, 20);
        });
    });

    describe('Host dismissal', function () {
        afterEach(() => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
        });

        it('should close only the nested select popper on host outside-pointer dismissal', function (done) {
            this.timeout(4000);

            const mocks = document.getElementById('mocks');
            const host = document.createElement('monster-host');
            const button = document.createElement('monster-popper-button');
            const select = document.createElement('monster-select');
            const outside = document.createElement('div');
            const dismissEventType =
                typeof global.PointerEvent === 'function'
                    ? 'pointerdown'
                    : 'mousedown';

            outside.textContent = 'outside';

            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'},
                {label: 'Beta', value: 'beta'}
            ]);
            select.setOption('filter.position', 'popper');
            select.setOption('features.closeOnSelect', true);

            button.setOption('content', select);
            host.appendChild(button);
            host.appendChild(outside);
            mocks.appendChild(host);

            setTimeout(() => {
                try {
                    button.showDialog();

                    setTimeout(() => {
                        try {
                            const buttonShadowRoot = button.shadowRoot;
                            const selectShadowRoot = select.shadowRoot;
                            const selectContainer = selectShadowRoot.querySelector(
                                '[data-monster-role=container]',
                            );

                            expect(buttonShadowRoot).to.exist;
                            expect(selectShadowRoot).to.exist;
                            expect(selectContainer).to.exist;

                            selectContainer.click();

                            setTimeout(() => {
                                try {
                                    outside.dispatchEvent(
                                        new window.MouseEvent(dismissEventType, {
                                            bubbles: true,
                                            composed: true
                                        }),
                                    );

                                    setTimeout(() => {
                                        try {
                                            expect(
                                                selectShadowRoot.querySelector(
                                                    '[data-monster-role=control]',
                                                ).className,
                                            ).to.equal('');
                                            expect(
                                                selectShadowRoot.querySelector(
                                                    '[data-monster-role=popper]',
                                                ).style.display,
                                            ).to.equal('none');
                                            expect(
                                                buttonShadowRoot.querySelector(
                                                    '[data-monster-role=control]',
                                                ).className,
                                            ).to.equal('open');
                                            expect(
                                                buttonShadowRoot.querySelector(
                                                    '[data-monster-role=popper]',
                                                ).style.display,
                                            ).to.equal('block');
                                        } catch (e) {
                                            return done(e);
                                        }

                                        done();
                                    }, 50);
                                } catch (e) {
                                    done(e);
                                }
                            }, 50);
                        } catch (e) {
                            done(e);
                        }
                    }, 50);
                } catch (e) {
                    done(e);
                }
            }, 50);
        });
    });

    describe('Remote filter pagination', function () {
        let requestCount = 0;

        beforeEach((done) => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = html1;

            requestCount = 0;
            global['fetch'] = function () {
                requestCount += 1;
                let headers = new Map;
                headers.set('content-type', 'application/json');

                if (requestCount === 1) {
                    return Promise.resolve({
                        ok: true,
                        status: 200,
                        headers: headers,
                        text: function () {
                            return Promise.resolve(JSON.stringify({
                                items: [
                                    {id: 1, name: "Alpha"}
                                ],
                                pagination: {
                                    total: 2,
                                    page: 1,
                                    perPage: 1
                                }
                            }));
                        }
                    });
                }

                return Promise.resolve({
                    ok: false,
                    status: 500,
                    headers: headers,
                    text: function () {
                        return Promise.resolve(JSON.stringify({}));
                    }
                });
            };

            done();
        });

        afterEach(() => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
            global['fetch'] = fetchReference;
        });

        it('should reset pagination and clear options on fetch error', function (done) {
            this.timeout(5000);

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            mocks.appendChild(select);

            const pagination = () => select.shadowRoot.querySelector('[data-monster-role=pagination]');

            const fetchRemotePage = (value) => {
                return select.fetch(`https://example.com/items?filter=${encodeURIComponent(value)}&page=1`);
            };

            setTimeout(() => {
                fetchRemotePage('a')
                    .then(() => {
                        const options = select.getOption('options');
                        const pager = pagination();

                        expect(options.length).to.equal(1);
                        expect(pager.getOption('currentPage')).to.equal(1);
                        expect(pager.getOption('pages')).to.equal(2);
                        expect(pager.getOption('objectsPerPage')).to.equal(1);

                        return fetchRemotePage('b')
                            .then(() => Promise.reject(new Error('Expected remote fetch to fail')))
                            .catch((e) => {
                                if (e.message === 'Expected remote fetch to fail') {
                                    throw e;
                                }

                                const optionsAfterError = select.getOption('options');
                                const pager = pagination();

                                expect(optionsAfterError.length).to.equal(0);
                                expect(pager.getOption('currentPage')).to.equal(null);
                                expect(pager.getOption('pages')).to.equal(null);
                                expect(pager.getOption('objectsPerPage')).to.equal(null);
                                expect(select.getOption('total')).to.equal(null);
                                expect(select.getOption('messages.total')).to.equal("");
                            });
                    })
                    .then(() => done())
                    .catch((e) => done(e));
            }, 50);
        });
    });

    describe('document.createElement()', function () {

        afterEach(() => {
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
        })

        it('should have no options', function (done) {

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const options = select.shadowRoot.querySelector('[data-monster-role=options]');

                    expect(options).is.instanceof(HTMLDivElement);

                    const a = options.parentNode.outerHTML;
                    const b = options.childNodes.length;
                    expect(options.hasChildNodes()).to.be.false;
                } catch (e) {
                    return done(e);
                }

                done();
            }, 0)


        });

        it('should expose styling parts for container, filter messages and pagination', function (done) {

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('filter.mode', 'remote');
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const container = select.shadowRoot.querySelector('[data-monster-role=container]');
                    const selectionMessages = select.shadowRoot.querySelector('[data-monster-role=selection-messages]');
                    const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');

                    expect(container.getAttribute('part')).to.equal('container');
                    expect(selectionMessages.getAttribute('part')).to.equal('selection-messages');
                    expect(pagination.getAttribute('part')).to.equal('pagination');
                } catch (e) {
                    return done(e);
                }

                done();
            }, 0)
        });

        it('should normalize options without throwing', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('options', [
                {label: 'Alpha'},
                {value: 'Beta'},
                {},
                {label: '', value: 'empty-label'},
                {label: '   ', value: 'blank-label'}
            ]);
            mocks.appendChild(select);

            const startedAt = Date.now();
            const poll = () => {
                try {
                    const options = select.getOption('options');
                    if (options?.[0]?.value !== 'Alpha') {
                        if (Date.now() - startedAt < 1500) {
                            return setTimeout(poll, 50);
                        }
                    }

                    expect(options[0].value).to.equal('Alpha');
                    expect(options[0].label).to.equal('Alpha');
                    expect(options[0].visibility).to.equal('visible');
                    expect(options[1].value).to.equal('Beta');
                    expect(options[1].label).to.equal('Beta');
                    expect(options[1].visibility).to.equal('visible');
                    expect(options[2].value).to.be.a('string');
                    expect(options[2].label).to.equal(options[2].value);
                    expect(options[2].visibility).to.equal('visible');
                    expect(options[3].label).to.equal('empty-label');
                    expect(options[4].label).to.equal('blank-label');
                } catch (e) {
                    return done(e);
                }

                done();
            };

            setTimeout(poll, 50);
        });

        it('should fall back to the option value when imported labels resolve empty', function () {
            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('mapping.selector', '*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            mocks.appendChild(select);

            select.importOptions([
                {id: 'company-1', name: ''},
                {id: 'company-2', name: '   '},
                {id: 'company-3', name: 'Visible label'}
            ]);

            const options = select.getOption('options');

            expect(options[0].label).to.equal('company-1');
            expect(options[1].label).to.equal('company-2');
            expect(options[2].label).to.equal('Visible label');
        });

        it('should not parse options arrays with multiple string entries', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('options', ['One', 'Two']);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const options = select.getOption('options');
                    const error = select.getAttribute('data-monster-error') ?? '';
                    expect(error).to.not.contain('Unexpected token');
                    expect(options.length).to.equal(2);
                    expect(options[0]).to.equal('One');
                    expect(options[1]).to.equal('Two');
                } catch (e) {
                    return done(e);
                }

                done();
            }, 350);
        });

        it('should treat null value as empty selection', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('options', [{label: 'One', value: '1'}]);
            mocks.appendChild(select);

            setTimeout(() => {
                select.value = null;

                setTimeout(() => {
                    try {
                        const selection = select.getOption('selection');
                        const error = select.getAttribute('data-monster-error') ?? '';
                        expect(Array.isArray(selection)).to.equal(true);
                        expect(selection.length).to.equal(0);
                        expect(select.value).to.equal('');
                        expect(error).to.not.contain('unsupported type');
                    } catch (e) {
                        return done(e);
                    }

                    done();
                }, 50);
            }, 50);
        });

        it('should remove the last badge without applyRemoval errors', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const select = document.createElement('monster-select');
            select.setOption('type', 'checkbox');
            select.setOption('features.clear', true);
            select.setOption('options', [{label: 'One', value: '1'}]);
            mocks.appendChild(select);

            select.addEventListener('monster-selection-removed', (event) => {
                try {
                    expect(event.detail.value).to.equal('1');
                    expect(select.getOption('selection')).to.deep.equal([]);
                } catch (e) {
                    return done(e);
                }

                done();
            }, {once: true});

            setTimeout(() => {
                try {
                    select.value = ['1'];
                } catch (e) {
                    return done(e);
                }

                setTimeout(() => {
                    try {
                        const removeBadge = select.shadowRoot.querySelector('[data-monster-role=remove-badge]');
                        expect(removeBadge).to.be.instanceof(HTMLDivElement);

                        removeBadge.dispatchEvent(new Event('click', {
                            bubbles: true,
                            composed: true
                        }));
                    } catch (e) {
                        return done(e);
                    }
                }, 50);
            }, 50);
        });

        it('should skip lookup for empty equivalent selection values', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const mockIntersectionObserver = setupIntersectionObserverMock();
            let lookupCount = 0;
            global['fetch'] = function () {
                lookupCount += 1;
                let headers = new Map;
                headers.set('content-type', 'application/json');
                return Promise.resolve({
                    ok: true,
                    status: 200,
                    headers: headers,
                    text: function () {
                        return Promise.resolve(JSON.stringify({
                            items: [],
                            pagination: {
                                total: 0,
                                page: 1,
                                perPage: 1
                            }
                        }));
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            select.setOption('empty.equivalents', ['EMPTY']);
            select.setOption('selection', [{value: 'EMPTY'}]);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    expect(lookupCount).to.equal(0);
                } catch (e) {
                    mockIntersectionObserver.restore();
                    return done(e);
                }

                mockIntersectionObserver.restore();
                done();
            }, 150);
        });

        it('should not eagerly fetch remote info when showRemoteInfo is disabled', function (done) {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');
            const requests = [];
            const previousFetch = global['fetch'];

            global['fetch'] = function (url) {
                requests.push(String(url));

                return createJsonResponse({
                    items: [
                        {id: 'alpha', name: 'Alpha'}
                    ],
                    pagination: {
                        total: 3,
                        page: 2,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            select.setOption('remoteInfo.url', 'https://example.com/remote-info');
            select.setOption('features.showRemoteInfo', false);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    expect(requests).to.deep.equal([]);
                } catch (e) {
                    global['fetch'] = previousFetch;
                    return done(e);
                }

                select.fetch('https://example.com/items?filter=*&page=2')
                    .then(() => {
                        try {
                            const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');

                            expect(requests).to.deep.equal([
                                'https://example.com/items?filter=*&page=2'
                            ]);
                            expect(select.getOption('total')).to.equal(3);
                            expect(pagination.getOption('currentPage')).to.equal(2);
                            expect(pagination.getOption('pages')).to.equal(3);
                            expect(pagination.getOption('objectsPerPage')).to.equal(1);
                        } catch (e) {
                            return done(e);
                        } finally {
                            global['fetch'] = previousFetch;
                        }

                        done();
                    })
                    .catch((e) => {
                        global['fetch'] = previousFetch;
                        done(e);
                    });
            }, 150);
        });

        it('should defer remote info fetching until the dropdown is opened', async function () {
            this.timeout(4000);

            let mocks = document.getElementById('mocks');
            const requests = [];
            const remoteInfoUrl = 'https://example.com/remote-info';

            global['fetch'] = function (url) {
                requests.push(String(url));

                return createJsonResponse({
                    pagination: {
                        total: 5
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.position', 'popper');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('remoteInfo.url', remoteInfoUrl);
            select.setOption('options', [
                {label: 'Alpha', value: 'alpha'}
            ]);
            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });

            expect(requests).to.deep.equal([]);

            const container = select.shadowRoot.querySelector('[data-monster-role=container]');

            container.click();
            await waitForCondition(() => requests.includes(remoteInfoUrl));
            expect(requests.filter((url) => url === remoteInfoUrl)).to.have.length(1);
        });

        it('should keep total-only remote totals for loaded options', async function () {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');

            global['fetch'] = function () {
                return createJsonResponse({
                    items: [
                        {id: 'alpha', name: 'Alpha'}
                    ],
                    pagination: {
                        total: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });

            await select.fetch('https://example.com/items?filter=alpha&page=1');

            expect(select.getOption('total')).to.equal(1);
            expect(select.getOption('messages.total')).to.contain('No additional entries are available');
        });

        it('should keep the remote-info footer height stable while a remote page request is pending', async function () {
            this.timeout(4000);

            let mocks = document.getElementById('mocks');
            const firstRequest = createDeferred();
            const secondRequest = createDeferred();
            let requestCount = 0;

            global['fetch'] = function () {
                requestCount += 1;

                if (requestCount === 1) {
                    return firstRequest.promise;
                }

                if (requestCount === 2) {
                    return secondRequest.promise;
                }

                return Promise.reject(new Error('unexpected fetch request'));
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.position', 'popper');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });

            const firstFetch = select.fetch('https://example.com/items?filter=*&page=1');
            firstRequest.resolve(await createJsonResponse({
                items: [
                    {id: 'alpha', name: 'Alpha'}
                ],
                pagination: {
                    total: 12,
                    page: 1,
                    perPage: 1
                }
            }));
            await firstFetch;

            await waitForCondition(() => {
                return select.getOption('messages.total').includes('additional entries are available');
            });

            const container = select.shadowRoot.querySelector('[data-monster-role=container]');
            const remoteInfoElement = select.shadowRoot.querySelector('[data-monster-role=remote-info]');

            container.click();

            await waitForCondition(() => {
                return select.shadowRoot
                    .querySelector('[data-monster-role=control]')
                    .classList
                    .contains('open');
            });

            expect(remoteInfoElement.style.visibility).to.equal('');

            const secondFetch = select.fetch('https://example.com/items?filter=*&page=2');

            await waitForCondition(() => {
                return remoteInfoElement.style.visibility === 'hidden';
            });

            expect(select.getOption('messages.total')).to.contain('additional entries are available');

            secondRequest.resolve(await createJsonResponse({
                items: [
                    {id: 'beta', name: 'Beta'}
                ],
                pagination: {
                    total: 12,
                    page: 2,
                    perPage: 1
                }
            }));

            await secondFetch;

            await waitForCondition(() => {
                return remoteInfoElement.style.visibility === '';
            });

            expect(select.getOption('messages.total')).to.contain('additional entries are available');
        });

        it('should clear preserved remote-info text after a later empty remote result settles', async function () {
            this.timeout(4000);

            let mocks = document.getElementById('mocks');
            const firstRequest = createDeferred();
            const secondRequest = createDeferred();
            let requestCount = 0;

            global['fetch'] = function () {
                requestCount += 1;

                if (requestCount === 1) {
                    return firstRequest.promise;
                }

                if (requestCount === 2) {
                    return secondRequest.promise;
                }

                return Promise.reject(new Error('unexpected fetch request'));
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.position', 'popper');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });

            const firstFetch = select.fetch('https://example.com/items?filter=*&page=1');
            firstRequest.resolve(await createJsonResponse({
                items: [
                    {id: 'alpha', name: 'Alpha'}
                ],
                pagination: {
                    total: 12,
                    page: 1,
                    perPage: 1
                }
            }));
            await firstFetch;

            await waitForCondition(() => {
                return select.getOption('messages.total').includes('additional entries are available');
            });

            const container = select.shadowRoot.querySelector('[data-monster-role=container]');
            const remoteInfoElement = select.shadowRoot.querySelector('[data-monster-role=remote-info]');

            container.click();

            await waitForCondition(() => {
                return select.shadowRoot
                    .querySelector('[data-monster-role=control]')
                    .classList
                    .contains('open');
            });

            const secondFetch = select.fetch('https://example.com/items?filter=leer&page=1');

            await waitForCondition(() => {
                return remoteInfoElement.style.visibility === 'hidden';
            });

            secondRequest.resolve(await createJsonResponse({
                items: [],
                pagination: {
                    total: 0,
                    page: 1,
                    perPage: 1
                }
            }));

            await secondFetch;

            await waitForCondition(() => {
                return select.getOption('messages.total') === '';
            });

            expect(remoteInfoElement.style.visibility).to.equal('');
            expect(select.getOption('messages.total')).to.equal('');
        });

        it('should avoid duplicate remote-info badges for empty remote filter results', async function () {
            this.timeout(4000);

            let mocks = document.getElementById('mocks');
            const previousFetch = global['fetch'];

            global['fetch'] = function () {
                return createJsonResponse({
                    items: [],
                    pagination: {
                        total: 0
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.position', 'popper');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            mocks.appendChild(select);

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=filter][name=\"popper-filter\"]') instanceof HTMLInputElement;
            });

            const container = select.shadowRoot.querySelector('[data-monster-role=container]');
            const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
            filterInput.value = 'alpha';

            try {
                container.click();
                filterInput.dispatchEvent(new Event('input', {
                    bubbles: true,
                    composed: true
                }));

                await waitForCondition(() => {
                    return select.getOption('total') === 0;
                });

                expect(select.getOption('total')).to.equal(0);
                expect(select.getOption('messages.total')).to.equal('');
                await waitForCondition(() => {
                    return select.getOption('messages.emptyOptions').includes('Please consider modifying the filter');
                });
                expect(select.getOption('messages.emptyOptions')).to.contain('Please consider modifying the filter');
            } finally {
                global['fetch'] = previousFetch;
            }
        });

        it('should not show the select placeholder while a value sync is still pending', async function () {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');
            const savedGlobalQueueMicrotask = global.queueMicrotask;
            const savedWindowQueueMicrotask = window.queueMicrotask;
            const delayedQueueMicrotask = (callback) => setTimeout(callback, 50);

            global.queueMicrotask = delayedQueueMicrotask;
            window.queueMicrotask = delayedQueueMicrotask;

            try {
                const select = document.createElement('monster-select');
                select.setOption('options', [
                    {label: 'Alpha', value: 'a'}
                ]);
                select.setAttribute('value', 'a');
                mocks.appendChild(select);

                await new Promise((resolve) => setTimeout(resolve, 20));

                expect(select.getOption('messages.control')).to.not.equal(
                    select.getOption('labels.select-an-option')
                );

                await waitForCondition(() => {
                    return Array.isArray(select.getOption('selection')) &&
                        select.getOption('selection').length === 1;
                });

                expect(select.value).to.equal('a');
                expect(select.getOption('messages.control')).to.equal('');
            } finally {
                global.queueMicrotask = savedGlobalQueueMicrotask;
                window.queueMicrotask = savedWindowQueueMicrotask;
            }
        });

        it('should keep empty equivalent matching type-safe for numeric zero', async function () {
            let mocks = document.getElementById('mocks');
            const stringEquivalentSelect = document.createElement('monster-select');
            stringEquivalentSelect.setOption('empty.equivalents', ['0']);
            mocks.appendChild(stringEquivalentSelect);

            const numericEquivalentSelect = document.createElement('monster-select');
            numericEquivalentSelect.setOption('empty.equivalents', [0]);
            mocks.appendChild(numericEquivalentSelect);

            stringEquivalentSelect.value = 0;
            numericEquivalentSelect.value = 0;

            await waitForCondition(() => {
                return stringEquivalentSelect.value === '0' &&
                    Array.isArray(stringEquivalentSelect.getOption('selection')) &&
                    stringEquivalentSelect.getOption('selection').length === 1 &&
                    numericEquivalentSelect.value === '' &&
                    Array.isArray(numericEquivalentSelect.getOption('selection')) &&
                    numericEquivalentSelect.getOption('selection').length === 0;
            });

            expect(stringEquivalentSelect.getOption('selection')).to.deep.equal([
                {
                    label: '0',
                    value: 0,
                    class: 'monster-badge-primary',
                    unresolved: false
                }
            ]);
            expect(numericEquivalentSelect.getOption('selection')).to.deep.equal([]);
        });

        it('should not refetch after selecting an already loaded remote option', function (done) {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');
            const requests = [];

            global['fetch'] = function (url) {
                requests.push(String(url));
                return createJsonResponse({
                    items: [
                        {id: 'alpha', name: 'Alpha'}
                    ],
                    pagination: {
                        total: 1,
                        page: 1,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.defaultValue', '*');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            mocks.appendChild(select);

            setTimeout(() => {
                select.fetch('https://example.com/items?filter=*&page=1')
                    .then(() => {
                        try {
                            expect(requests).to.deep.equal([
                                'https://example.com/items?filter=*&page=1'
                            ]);

                            select.value = 'alpha';
                        } catch (e) {
                            return done(e);
                        }

                        setTimeout(() => {
                            try {
                                expect(requests).to.deep.equal([
                                    'https://example.com/items?filter=*&page=1'
                                ]);
                            } catch (e) {
                                return done(e);
                            }

                            done();
                        }, 200);
                    })
                    .catch((e) => done(e));
            }, 50);
        });

        it('should reuse defaultOptionsUrl after clearing the filter and reopening', async function () {
            this.timeout(5000);

            let mocks = document.getElementById('mocks');
            const requests = [];

            global['fetch'] = function (url) {
                const requestUrl = String(url);
                requests.push(requestUrl);

                if (requestUrl.indexOf('/defaults') !== -1) {
                    return createJsonResponse({
                        items: [
                            {id: 'seed', name: 'Seed'}
                        ],
                        pagination: {
                            total: 1,
                            page: 1,
                            perPage: 1
                        }
                    });
                }

                return createJsonResponse({
                    items: [
                        {id: 'match', name: 'Match'}
                    ],
                    pagination: {
                        total: 1,
                        page: 1,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('filter.position', 'popper');
            select.setOption('filter.defaultOptionsUrl', 'https://example.com/defaults?page={page}');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            mocks.appendChild(select);

            const dispatchFilterKey = (input, code, key) => {
                input.dispatchEvent(new KeyboardEvent('keydown', {
                    bubbles: true,
                    composed: true,
                    code,
                    key
                }));
            };

            await waitForCondition(() => {
                return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
            });

            const container = select.shadowRoot.querySelector('[data-monster-role=container]');
            const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');

            container.click();
            await waitForCondition(() => requests.length >= 1);
            expect(requests[0]).to.equal('https://example.com/defaults?page=1');

            filterInput.value = 'alpha';
            dispatchFilterKey(filterInput, 'KeyA', 'a');
            await waitForCondition(() => requests.length >= 2);
            expect(requests[1]).to.equal('https://example.com/items?filter=alpha&page=1');

            filterInput.value = '';
            dispatchFilterKey(filterInput, 'Backspace', 'Backspace');
            await waitForCondition(() => requests.length >= 3);
            expect(requests[2]).to.equal('https://example.com/defaults?page=1');

            container.click();
            await waitForCondition(() => select.shadowRoot.querySelector('[data-monster-role=popper]').style.display === 'none');

            container.click();
            await waitForCondition(() => requests.length >= 4);
            expect(requests[3]).to.equal('https://example.com/defaults?page=1');
        });

        it('should keep unresolved lookup values visible and mark their badge', function (done) {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');
            global['fetch'] = function () {
                return createJsonResponse({
                    items: [],
                    pagination: {
                        total: 0,
                        page: 1,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('lookup.url', 'https://example.com/items?filter={filter}');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('selection', [{value: 'missing-key'}]);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const badge = select.shadowRoot.querySelector('[data-monster-role=badge]');
                    const badgeLabel = select.shadowRoot.querySelector('[data-monster-role=badge-label]');

                    expect(badge).to.be.instanceof(HTMLDivElement);
                    expect(badgeLabel).to.be.instanceof(HTMLDivElement);
                    expect(badgeLabel.textContent.trim()).to.equal('missing-key');
                    expect(badge.className).to.contain('monster-badge-warning');
                    expect(badge.className).to.not.contain('monster-badge-primary');
                    expect(badge.getAttribute('data-monster-unresolved')).to.equal('true');
                    expect(badgeLabel.textContent).to.not.contain(select.getOption('labels.cannot-be-loaded'));
                } catch (e) {
                    return done(e);
                }

                done();
            }, 250);
        });

        it('should keep unresolved object lookup values visible by key and mark their badge', function (done) {
            this.timeout(3000);

            let mocks = document.getElementById('mocks');
            const requests = [];
            global['fetch'] = function (url) {
                requests.push(url.toString());
                return createJsonResponse({
                    items: [],
                    pagination: {
                        total: 0,
                        page: 1,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('lookup.url', 'https://example.com/items?filter={filter}');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('selection', [{value: {id: 'missing-key'}}]);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    const badge = select.shadowRoot.querySelector('[data-monster-role=badge]');
                    const badgeLabel = select.shadowRoot.querySelector('[data-monster-role=badge-label]');

                    expect(requests).to.include('https://example.com/items?filter=missing-key');
                    expect(badge).to.be.instanceof(HTMLDivElement);
                    expect(badgeLabel).to.be.instanceof(HTMLDivElement);
                    expect(badgeLabel.textContent.trim()).to.equal('missing-key');
                    expect(badge.className).to.contain('monster-badge-warning');
                    expect(badge.className).to.not.contain('monster-badge-primary');
                    expect(badge.getAttribute('data-monster-unresolved')).to.equal('true');
                    expect(badgeLabel.textContent).to.not.contain(select.getOption('labels.cannot-be-loaded'));
                } catch (e) {
                    return done(e);
                }

                done();
            }, 250);
        });

        it('should clear the unresolved badge state once a local option can resolve the value', function (done) {
            this.timeout(4000);

            let mocks = document.getElementById('mocks');
            global['fetch'] = function () {
                return createJsonResponse({
                    items: [],
                    pagination: {
                        total: 0,
                        page: 1,
                        perPage: 1
                    }
                });
            };

            const select = document.createElement('monster-select');
            select.setOption('lookup.url', 'https://example.com/items?filter={filter}');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('selection', [{value: 'missing-key'}]);
            mocks.appendChild(select);

            setTimeout(() => {
                select.setOption('options', [{label: 'Resolved Label', value: 'missing-key'}]);
                select.value = 'missing-key';

                setTimeout(() => {
                    try {
                        const badge = select.shadowRoot.querySelector('[data-monster-role=badge]');
                        const badgeLabel = select.shadowRoot.querySelector('[data-monster-role=badge-label]');

                        expect(badge).to.be.instanceof(HTMLDivElement);
                        expect(badgeLabel.textContent.trim()).to.equal('Resolved Label');
                        expect(badge.className).to.contain('monster-badge-primary');
                        expect(badge.className).to.not.contain('monster-badge-warning');
                        expect(badge.getAttribute('data-monster-unresolved')).to.not.equal('true');
                    } catch (e) {
                        return done(e);
                    }

                    done();
                }, 150);
            }, 250);
        });

        it('should not throw when IntersectionObserver is unavailable', function (done) {
            this.timeout(2000);

            let mocks = document.getElementById('mocks');
            const savedIntersectionObserver = global.IntersectionObserver;
            global.IntersectionObserver = undefined;
            window.IntersectionObserver = undefined;
            const failures = [];
            const onError = (event) => {
                failures.push(event?.error || event);
            };
            window.addEventListener('error', onError);

            const select = document.createElement('monster-select');
            select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
            select.setOption('filter.mode', 'remote');
            select.setOption('mapping.selector', 'items.*');
            select.setOption('mapping.labelTemplate', '${name}');
            select.setOption('mapping.valueTemplate', '${id}');
            select.setOption('mapping.total', 'pagination.total');
            select.setOption('mapping.currentPage', 'pagination.page');
            select.setOption('mapping.objectsPerPage', 'pagination.perPage');
            select.setOption('selection', [{value: 'alpha'}]);
            mocks.appendChild(select);

            setTimeout(() => {
                try {
                    expect(failures).to.have.length(0);
                } catch (e) {
                    return done(e);
                } finally {
                    window.removeEventListener('error', onError);
                    global.IntersectionObserver = savedIntersectionObserver;
                    window.IntersectionObserver = savedIntersectionObserver;
                }

                done();
            }, 250);
        });

    });


});
