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 {ResizeObserverMock} from "../../../util/resize-observer.mjs";

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

const global = getGlobal();


// language=html
let html1 = `
    <monster-tabs id="mytabs">
        <div data-monster-button-label="TAB1">
            <div>
                Das ist tab 1
            </div>

        </div>
        <div data-monster-button-label="TAB2">
            Das ist tab 2
        </div>
        <div>
            Das ist tab 3 ohne button
        </div>
        <div></div>
    </monster-tabs>
`;

// language=html
let htmlActiveSecond = `
    <monster-tabs id="mytabs">
        <div data-monster-button-label="TAB1">
            Das ist tab 1
        </div>
        <div data-monster-button-label="TAB2" class="active">
            Das ist tab 2
        </div>
        <div data-monster-button-label="TAB3">
            Das ist tab 3
        </div>
    </monster-tabs>
`;

// language=html
let htmlOverflow = `
    <monster-tabs id="overflow-tabs">
        <div data-monster-button-label="Profil">Profil</div>
        <div data-monster-button-label="Adressen">Adressen</div>
        <div data-monster-button-label="Kommunikation">Kommunikation</div>
        <div data-monster-button-label="Beziehungen">Beziehungen</div>
        <div data-monster-button-label="Anstellungen">Anstellungen</div>
        <div data-monster-button-label="Einwilligungen">Einwilligungen</div>
        <div data-monster-button-label="Einwilligungsstatus">Einwilligungsstatus</div>
        <div data-monster-button-label="Berechtigungen">Berechtigungen</div>
        <div data-monster-button-label="Dateien">Dateien</div>
        <div data-monster-button-label="Kommentare">Kommentare</div>
        <div data-monster-button-label="Wiedervorlagen">Wiedervorlagen</div>
    </monster-tabs>
`;

// language=html
let htmlAvailability = `
    <monster-tabs id="availability-tabs">
        <div id="overview-panel" data-monster-name="overview" data-monster-button-label="Overview">
            Overview content
        </div>
        <div id="vacation-panel" data-monster-name="vacation" data-monster-button-label="Vacation" data-monster-tab-available="false">
            Vacation content
        </div>
        <div id="details-panel" data-monster-name="details" data-monster-button-label="Details">
            Details content
        </div>
    </monster-tabs>
`;

// language=html
let htmlActiveUnavailable = `
    <monster-tabs id="fallback-tabs">
        <div id="first-panel" data-monster-name="first" data-monster-button-label="First">
            First content
        </div>
        <div id="second-panel" data-monster-name="second" data-monster-button-label="Second" class="active">
            Second content
        </div>
        <div id="third-panel" data-monster-name="third" data-monster-button-label="Third">
            Third content
        </div>
    </monster-tabs>
`;

// language=html
let htmlRemoveFallback = `
    <monster-tabs id="remove-fallback-tabs" data-monster-options='{"features":{"openFirst":false}}'>
        <div id="remove-first-panel" data-monster-name="first" data-monster-button-label="First">
            First content
        </div>
        <div id="remove-second-panel" data-monster-name="second" data-monster-button-label="Second" class="active">
            Second content
        </div>
        <div id="remove-third-panel" data-monster-name="third" data-monster-button-label="Third">
            Third content
        </div>
    </monster-tabs>
`;

// language=html
let htmlDisabled = `
    <monster-tabs id="disabled-tabs">
        <div id="enabled-panel" data-monster-name="enabled" data-monster-button-label="Enabled">
            Enabled content
        </div>
        <div id="disabled-panel"
             data-monster-name="disabled"
             data-monster-button-label="Disabled"
             data-monster-tab-disabled="true"
             data-monster-tab-disabled-reason="Only available for employees">
            Disabled content
        </div>
    </monster-tabs>
`;

// language=html
let htmlMetadata = `
    <monster-tabs id="metadata-tabs">
        <div id="metadata-panel"
             data-monster-name="metadata"
             data-monster-button-label="Metadata"
             data-monster-tab-kind="profile"
             data-monster-tab-priority="10"
             data-monster-tab-group="main">
            Metadata content
        </div>
    </monster-tabs>
`;

// language=html
let htmlNoDerivedLabel = `
    <monster-tabs id="label-tabs" data-monster-options='{"features":{"deriveLabelFromContent":false}}'>
        <div id="content-panel">
            Overview about your profile and vacation configuration
        </div>
    </monster-tabs>
`;

let Tabs;
let restoreBoundingClientRect = null;
let restoreResizeObserver = null;

function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
	const startedAt = Date.now();

	return new Promise((resolve, reject) => {
		const tick = () => {
			try {
				if (check() === true) {
					resolve();
					return;
				}
			} catch (e) {
				reject(e);
				return;
			}

			if (Date.now() - startedAt > timeout) {
				reject(new Error('condition timed out'));
				return;
			}

			setTimeout(tick, interval);
		};

		tick();
	});
}

describe('Tabs', function () {

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

            import("element-internals-polyfill").catch(e => done(e));
            
            let promises = []

            if (!global['crypto']) {
                promises.push(import("@peculiar/webcrypto").then((m) => {
                    const Crypto = m['Crypto'];
                    global['crypto'] = new Crypto();
                }));
            }

            promises.push(import("../../../../source/components/layout/tabs.mjs").then((m) => {
                Tabs = m['Tabs'];
            }))

            Promise.all(promises).then(()=>{
                done();
            }).catch(e => done(e))

        });
    })

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

        afterEach(() => {
			if (restoreBoundingClientRect instanceof Function) {
				restoreBoundingClientRect();
				restoreBoundingClientRect = null;
			}
			if (restoreResizeObserver instanceof Function) {
				restoreResizeObserver();
				restoreResizeObserver = null;
			}
            let mocks = document.getElementById('mocks');
            mocks.innerHTML = "";
        })

        it('should have buttons and tabs', function (done) {

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

            setTimeout(() => {
                try {
                    const tabs = document.getElementById('mytabs')
                    expect(tabs).is.instanceof(Tabs);

                    setTimeout(() => {
                        let nav = tabs.shadowRoot.querySelector('nav');
                        const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
                        expect(buttons[0]).is.instanceof(HTMLButtonElement);
                        expect(nav.hasChildNodes()).to.be.true;
                        expect(buttons.length).to.be.equal(4);
                        done();
                    }, 100)

                } catch (e) {
                    return done(e);
                }

            }, 0)


        });

		it('should shorten label from content when no explicit label is provided', function (done) {

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

			setTimeout(() => {
				try {
					const tabs = document.getElementById('mytabs');
					expect(tabs).is.instanceof(Tabs);

					setTimeout(() => {
						const thirdTab = tabs.children[2];
						expect(thirdTab).to.not.equal(undefined);
						const reference = thirdTab.getAttribute('id');
						expect(reference).to.not.equal(null);
						const button = tabs.shadowRoot.querySelector(
							`button[part=button][data-monster-tab-reference="${reference}"]`,
						);
						expect(button).to.not.equal(null);
						const labelSpan = button.querySelector('span[data-monster-replace]');
						expect(labelSpan).to.not.equal(null);
						expect(labelSpan.textContent.trim()).to.equal('Das ist tab…');
						done();
					}, 100);
				} catch (e) {
					return done(e);
                }
            }, 0);
        });

		it('should open the first tab by default when no tab is predefined as active', function (done) {

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

			setTimeout(() => {
				try {
					const tabs = document.getElementById('mytabs');
					expect(tabs).is.instanceof(Tabs);

					setTimeout(() => {
						expect(tabs.children[0].classList.contains('active')).to.equal(true);
						expect(tabs.children[1].classList.contains('active')).to.equal(false);
						expect(tabs.getActiveTab()).to.equal(tabs.children[0].getAttribute('id'));
						done();
					}, 100);
				} catch (e) {
					return done(e);
                }
            }, 0);
        });

		it('should keep a predefined active tab instead of opening the first tab', function (done) {

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

			setTimeout(() => {
				try {
					const tabs = document.getElementById('mytabs');
					expect(tabs).is.instanceof(Tabs);

					setTimeout(() => {
						expect(tabs.children[0].classList.contains('active')).to.equal(false);
						expect(tabs.children[1].classList.contains('active')).to.equal(true);
						expect(tabs.getActiveTab()).to.equal(tabs.children[1].getAttribute('id'));
						done();
					}, 100);
				} catch (e) {
					return done(e);
                }
            }, 0);
        });

		it('should configure flip and shift middleware for the overflow popper', function (done) {

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

			setTimeout(() => {
				try {
					const tabs = document.getElementById('mytabs');
					expect(tabs).is.instanceof(Tabs);

					const modifiers = tabs.getOption('popper.modifiers');
					expect(modifiers.some((entry) => entry?.name === 'flip')).to.equal(true);
					expect(modifiers.some((entry) => entry?.name === 'shift')).to.equal(true);
					done();
				} catch (e) {
					return done(e);
                }
            }, 0);
        });

		it('should move overflowing tabs into the popper without losing them', function (done) {
			this.timeout(5000);

            let mocks = document.getElementById('mocks');
			const OriginalResizeObserver = window.ResizeObserver;
			const originalGlobalResizeObserver = global.ResizeObserver;
			class TrackingResizeObserver extends ResizeObserverMock {
				static instances = [];
				static callbackCount = 0;

				constructor(callback) {
					super((entries, observer) => {
						TrackingResizeObserver.callbackCount++;
						callback(entries, observer);
					});
					TrackingResizeObserver.instances.push(this);
				}
			}
			window.ResizeObserver = TrackingResizeObserver;
			global.ResizeObserver = TrackingResizeObserver;
			restoreResizeObserver = () => {
				window.ResizeObserver = OriginalResizeObserver;
				global.ResizeObserver = originalGlobalResizeObserver;
			};
			const originalGetBoundingClientRect =
				global.HTMLElement.prototype.getBoundingClientRect;
			restoreBoundingClientRect = () => {
				global.HTMLElement.prototype.getBoundingClientRect =
					originalGetBoundingClientRect;
			};
			global.HTMLElement.prototype.getBoundingClientRect = function () {
				const role = this.getAttribute?.('data-monster-role');
				const label = this.textContent?.trim() || '';

				if (role === 'nav') {
					return {
						width: 480,
						height: 40,
						top: 0,
						left: 0,
						right: 480,
						bottom: 40,
						x: 0,
						y: 0,
					};
				}

				if (role === 'button') {
					return {
						width: Math.max(70, label.length * 11),
						height: 40,
						top: 0,
						left: 0,
						right: 0,
						bottom: 40,
						x: 0,
						y: 0,
					};
				}

				return originalGetBoundingClientRect.call(this);
			};

            mocks.innerHTML = htmlOverflow;

			waitForCondition(() => {
				const tabs = document.getElementById('overflow-tabs');
				const switchButton = tabs?.shadowRoot?.querySelector(
					'[data-monster-role="switch"]',
				);
				const standardButtons = tabs?.getOption?.('buttons.standard') || [];
				const popperButtons = tabs?.getOption?.('buttons.popper') || [];

				return (
					tabs instanceof Tabs &&
					switchButton !== null &&
					standardButtons.length > 0 &&
					popperButtons.length > 0 &&
					standardButtons.length + popperButtons.length === 11 &&
					switchButton.classList.contains('hidden') === false
				);
			}).then(() => {
				try {
					const tabs = document.getElementById('overflow-tabs');
					expect(tabs).is.instanceof(Tabs);
					const switchButton = tabs.shadowRoot.querySelector(
						'[data-monster-role="switch"]',
					);
					let standardButtons = tabs.getOption('buttons.standard');
					let popperButtons = tabs.getOption('buttons.popper');

					expect(switchButton).to.not.equal(null);
					expect(standardButtons.length).to.be.greaterThan(0);
					expect(popperButtons.length).to.be.greaterThan(0);
					expect(standardButtons.length + popperButtons.length).to.equal(11);
					expect(switchButton.classList.contains('hidden')).to.equal(false);

					const resizeObserver = TrackingResizeObserver.instances.find(
						(observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')),
					);
					expect(resizeObserver).to.not.equal(undefined);
					resizeObserver.triggerResize([]);

					return waitForCondition(() => {
						return (
							TrackingResizeObserver.callbackCount > 0 &&
							tabs.getOption('buttons.standard').length === 11 &&
							tabs.getOption('buttons.popper').length === 0
						);
					}).then(() => {
						return waitForCondition(() => {
							standardButtons = tabs.getOption('buttons.standard');
							popperButtons = tabs.getOption('buttons.popper');
							return (
								standardButtons.length > 0 &&
								popperButtons.length > 0 &&
								standardButtons.length + popperButtons.length === 11 &&
								new Set(
									standardButtons
										.concat(popperButtons)
										.map((button) => button.reference),
								).size === 11
							);
						});
					}).then(() => {
						restoreBoundingClientRect();
						restoreBoundingClientRect = null;
						restoreResizeObserver();
						restoreResizeObserver = null;
						done();
					});
				} catch (e) {
					restoreBoundingClientRect();
					restoreBoundingClientRect = null;
					restoreResizeObserver();
					restoreResizeObserver = null;
					return done(e);
                }
			}).catch((e) => {
				if (restoreBoundingClientRect instanceof Function) {
					restoreBoundingClientRect();
					restoreBoundingClientRect = null;
				}
				if (restoreResizeObserver instanceof Function) {
					restoreResizeObserver();
					restoreResizeObserver = null;
				}
				done(e);
			});
        });

		it('should ignore unavailable panels when creating tabs and buttons', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlAvailability;

			waitForCondition(() => {
				const tabs = document.getElementById('availability-tabs');
				return (
					tabs instanceof Tabs &&
					tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2 &&
					tabs.getActiveTab() === 'overview'
				);
			}).then(() => {
				try {
					const tabs = document.getElementById('availability-tabs');
					const vacation = document.getElementById('vacation-panel');
					const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');

					expect(tabs.getTabs().length).to.equal(2);
					expect(tabs.getTabs().includes(vacation)).to.equal(false);
					expect(tabs.shadowRoot.querySelector(
						`button[part=button][data-monster-tab-reference="${vacation.getAttribute('id')}"]`,
					)).to.equal(null);
					expect(buttons[0].textContent.trim()).to.equal('Overview');
					expect(buttons[1].textContent.trim()).to.equal('Details');
					expect(tabs.getActiveTab()).to.equal('overview');
					done();
				} catch (e) {
					done(e);
				}
			}).catch(done);
		});

		it('should ignore unavailable tabs in activeTab and rebuild when they become available', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlAvailability;

			waitForCondition(() => {
				const tabs = document.getElementById('availability-tabs');
				return tabs instanceof Tabs && tabs.getActiveTab() === 'overview';
			}).then(() => {
				const tabs = document.getElementById('availability-tabs');
				const vacation = document.getElementById('vacation-panel');
				let availabilityEvent = null;
				tabs.addEventListener('monster-tab-availability-changed', (event) => {
					availabilityEvent = event;
				});

				tabs.activeTab('vacation');
				expect(tabs.getActiveTab()).to.equal('overview');

				vacation.setAttribute('data-monster-tab-available', 'true');

				return waitForCondition(() => {
					return tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3;
				}).then(() => {
					try {
						expect(availabilityEvent).to.not.equal(null);
						expect(availabilityEvent.detail.name).to.equal('vacation');
						expect(availabilityEvent.detail.available).to.equal(true);

						tabs.activeTab('vacation');
						expect(tabs.getActiveTab()).to.equal('vacation');
						done();
					} catch (e) {
						done(e);
					}
				}, 100);
			}).catch(done);
		});

		it('should fallback when the active tab becomes unavailable', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlActiveUnavailable;

			waitForCondition(() => {
				const tabs = document.getElementById('fallback-tabs');
				return tabs instanceof Tabs && tabs.getActiveTab() === 'second';
			}).then(() => {
				const tabs = document.getElementById('fallback-tabs');
				const activePanel = document.getElementById('second-panel');
				activePanel.setAttribute('data-monster-tab-available', 'false');

				return waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
					try {
						expect(activePanel.classList.contains('active')).to.equal(false);
						expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
						done();
					} catch (e) {
						done(e);
					}
				});
			}).catch(done);
		});

		it('should fallback when the active tab is removed directly', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlRemoveFallback;

			waitForCondition(() => {
				const tabs = document.getElementById('remove-fallback-tabs');
				return (
					tabs instanceof Tabs &&
					tabs.getActiveTab() === 'second' &&
					tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3
				);
			}).then(() => {
				const tabs = document.getElementById('remove-fallback-tabs');
				setTimeout(() => {
					document.getElementById('remove-second-panel').remove();

					waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
						try {
							expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
							done();
						} catch (e) {
							done(e);
						}
					}).catch(done);
				});
			}).catch(done);
		});

		it('should keep disabled tabs visible but not activatable', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlDisabled;

			waitForCondition(() => {
				const tabs = document.getElementById('disabled-tabs');
				const disabledPanel = document.getElementById('disabled-panel');
				const disabledButton = tabs?.shadowRoot?.querySelector(
					`button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute('id')}"]`,
				);
				return (
					tabs instanceof Tabs &&
					disabledButton instanceof HTMLButtonElement &&
					tabs.getActiveTab() === 'enabled'
				);
			}).then(() => {
				try {
					const tabs = document.getElementById('disabled-tabs');
					const disabledPanel = document.getElementById('disabled-panel');
					const disabledButton = tabs.shadowRoot.querySelector(
						`button[part=button][data-monster-tab-reference="${disabledPanel.getAttribute('id')}"]`,
					);

					expect(disabledButton.disabled).to.equal(true);
					expect(disabledButton.getAttribute('title')).to.equal('Only available for employees');
					tabs.activeTab('disabled');
					expect(tabs.getActiveTab()).to.equal('enabled');
					done();
				} catch (e) {
					done(e);
				}
			}).catch(done);
		});

		it('should expose refresh, sync, hide and show APIs', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlAvailability;

			waitForCondition(() => {
				const tabs = document.getElementById('availability-tabs');
				return tabs instanceof Tabs && tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2;
			}).then(() => {
				const tabs = document.getElementById('availability-tabs');

				tabs.showTab('vacation');
				return tabs.refreshTabs().then((result) => {
					expect(result).to.equal(tabs);
					expect(tabs.getTabs().length).to.equal(3);

					tabs.hideTab('vacation');
					return tabs.syncTabs();
				}).then((result) => {
					try {
						expect(result).to.equal(tabs);
						expect(tabs.getTabs().length).to.equal(2);
						expect(document.getElementById('vacation-panel').getAttribute('data-monster-tab-available')).to.equal('false');
						done();
					} catch (e) {
						done(e);
					}
				});
			}).catch(done);
		});

		it('should not derive labels from content when disabled by option', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlNoDerivedLabel;

			waitForCondition(() => {
				const tabs = document.getElementById('label-tabs');
				return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
			}).then(() => {
				try {
					const tabs = document.getElementById('label-tabs');
					const button = tabs.shadowRoot.querySelector('button[part=button]');
					expect(button.textContent.trim()).to.equal('New Tab');
					expect(document.getElementById('content-panel').hasAttribute('data-monster-button-label')).to.equal(false);
					done();
				} catch (e) {
					done(e);
				}
			}).catch(done);
		});

		it('should include stable names and metadata in tab events and buttons', function (done) {
			let mocks = document.getElementById('mocks');
			mocks.innerHTML = htmlMetadata;

			waitForCondition(() => {
				const tabs = document.getElementById('metadata-tabs');
				return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
			}).then(() => {
				try {
					const tabs = document.getElementById('metadata-tabs');
					const button = tabs.shadowRoot.querySelector('button[part=button]');
					let changedEvent = null;
					tabs.addEventListener('monster-tab-changed', (event) => {
						changedEvent = event;
					});

					expect(button.getAttribute('data-monster-tab-name')).to.equal('metadata');
					expect(button.getAttribute('data-monster-tab-kind')).to.equal('profile');
					expect(button.getAttribute('data-monster-tab-priority')).to.equal('10');
					expect(button.getAttribute('data-monster-tab-group')).to.equal('main');

					tabs.activeTab('metadata');
					expect(changedEvent).to.not.equal(null);
					expect(changedEvent.detail.name).to.equal('metadata');
					expect(changedEvent.detail.tab).to.equal('metadata');
					expect(changedEvent.detail.metadata.kind).to.equal('profile');
					done();
				} catch (e) {
					done(e);
				}
			}).catch(done);
		});

    });


});
