import { slideFadeDown, slideFadeUp } from '@site/js/utils/dom.js';

/**
 * Legacy reset: when a tab/accordion changes, reveal all FAQ headings and
 * collapse every answer except the first. Preserved verbatim from the
 * jQuery plugin — only the call sites have been deduplicated.
 */
function resetFaqDisplay() {
    document.querySelectorAll('ul.faqs li h4').forEach((h4) => {
        h4.style.display = '';
    });
    document.querySelectorAll('ul.faqs li .answer').forEach((answer, i) => {
        if (i !== 0) answer.style.display = 'none';
    });
}

/**
 * Responsive tabs (vanilla front-end copy).
 *
 * Mirrors the previous mtcTabs plugin API:
 *
 *   import Tabs from '@core/js/plugins/mtc_tabs.js';
 *   const instance = new Tabs(element, { mode: 'accordion', ... });
 *
 *   // or via the legacy window helper used during the transition:
 *   window.mtcTabs(selectorOrNodeList, { ... });
 *
 * Modes:
 *   - 'tabs'      (default) — horizontal nav of links, panel below.
 *   - 'accordion' — heading + panel pairs stacked; click toggles each.
 *   - 'select'    — <select> of titles, choosing one switches the panel.
 *
 * Animations:
 *   - 'fade'  (default) — hide previous, fade in next.
 *   - 'slide' — slide previous out, then slide next in.
 *
 * Responsive: pass an array of `{ breakpoint, settings }` and the smallest
 * matching breakpoint's settings override the defaults on resize.
 *
 * Front-end only. The admin's legacy copy stays put until the admin spec.
 */

const DEFAULTS = {
    active: 1,
    animation_type: 'fade',
    content_selector: '.tabContent',
    content_wrap_selector: '.tabsContentWrap',
    easing: 'linear',
    mode: 'tabs',
    responsive: [],
    speed: 400,
    afterChange: '',
    beforeChange: '',
    onChange: '',
    onDestroy: '',
    onInit: '',
};

function deepMerge(target, source) {
    const out = { ...target };
    for (const key of Object.keys(source)) {
        const sv = source[key];
        const tv = out[key];
        if (sv && typeof sv === 'object' && !Array.isArray(sv) && tv && typeof tv === 'object' && !Array.isArray(tv)) {
            out[key] = deepMerge(tv, sv);
        } else {
            out[key] = sv;
        }
    }
    return out;
}

function wait(ms) {
    return new Promise((resolve) => window.setTimeout(resolve, ms));
}

export default class Tabs {

    constructor(element, options = {}) {
        if (!element) {
            return;
        }
        this.element = element;
        this.allSettings = deepMerge(DEFAULTS, options);
        this.settings = { ...this.allSettings };
        this.activeIndex = this.settings.active - 1;

        this.readResponsive();
        this.checkResponsive();
        this.init();
    }

    // ---- responsive helpers ----

    readResponsive() {
        let prev = this.allSettings;
        if (Array.isArray(this.allSettings.responsive)) {
            this.allSettings.responsive.forEach((entry) => {
                entry.settings = deepMerge(prev, entry.settings || {});
                delete entry.settings.responsive;
                prev = entry.settings;
            });
        }
    }

    checkResponsive() {
        this.settings = { ...this.allSettings };
        if (!Array.isArray(this.allSettings.responsive)) {
            return;
        }
        this.allSettings.responsive.forEach((entry) => {
            if (window.matchMedia(`(max-width: ${entry.breakpoint}px)`).matches) {
                this.settings = entry.settings;
            }
        });
    }

    // ---- init ----

    init() {
        this.element.classList.add('mtcTabsInitialised');

        // Wrap inner content
        const wrapClass = this.settings.content_wrap_selector.replace('.', '');
        const wrap = document.createElement('div');
        wrap.className = wrapClass;
        while (this.element.firstChild) {
            wrap.appendChild(this.element.firstChild);
        }
        this.element.appendChild(wrap);
        this._contentWrap = wrap;

        this.activeIndex = this.settings.active - 1;
        this.setOpen();
        this.buildNavigation();
        this.runCallback('onInit');

        this._onResize = window.debouncer(() => this.resize());
        window.addEventListener('resize', this._onResize);
    }

    resize() {
        const cachedMode = this.settings.mode;
        this.checkResponsive();

        if (cachedMode !== this.settings.mode) {
            if (cachedMode === 'accordion' || cachedMode === 'select') {
                this.destroyMode(cachedMode);
            }
            this.destroyNav();
            this.buildNavigation();
        }
    }

    setOpen() {
        if (this.settings.active <= 0) {
            return;
        }
        const panels = this.contentPanels();
        const target = panels[this.activeIndex];
        if (!target) {
            return;
        }
        target.classList.add('open');
        target.style.display = '';
        // CSS often hides `.tabContent` by default; if it does, force this
        // panel visible so the initial render actually shows it.
        if (getComputedStyle(target).display === 'none') {
            target.style.display = 'block';
        }
    }

    contentPanels() {
        return this.element.querySelectorAll(this.settings.content_selector);
    }

    buildNavigation() {
        switch (this.settings.mode) {
            case 'accordion': this.accordionNav(); break;
            case 'select': this.selectNav(); break;
            default: this.tabNav(); break;
        }

        this.contentPanels().forEach((panel, i) => {
            panel.dataset.tabId = `#tab_${i}`;
        });

        this.switchTrigger();
    }

    // ---- tab mode ----

    tabNav() {
        const nav = document.createElement('nav');
        nav.className = 'tabsNav';
        const ul = document.createElement('ul');
        nav.appendChild(ul);
        this.element.insertBefore(nav, this.element.firstChild);

        this.contentPanels().forEach((panel, i) => {
            const titleEl = panel.querySelector('.tabTitle');
            const li = document.createElement('li');
            const a = document.createElement('a');
            a.href = `#tab_${i}`;
            a.innerHTML = titleEl ? titleEl.innerHTML : '';
            li.appendChild(a);
            ul.appendChild(li);
        });

        // Mark the active one.
        const active = ul.children[this.getActive()];
        active?.classList.add('active');
    }

    tabTrigger(nav) {
        nav.querySelectorAll('a').forEach((a) => {
            a.addEventListener('click', (event) => {
                event.preventDefault();
                const elementId = a.getAttribute('href');
                const wrap = nav.nextElementSibling;
                const li = a.parentElement;
                if (li.classList.contains('active')) {
                    return;
                }
                li.classList.add('active');
                [...li.parentElement.children].forEach((sib) => {
                    if (sib !== li) sib.classList.remove('active');
                });
                this.runCallback('beforeChange');
                this.switchAnimationType(elementId, wrap);
                resetFaqDisplay();
            });
        });
    }

    // ---- accordion mode ----

    accordionNav() {
        this.contentPanels().forEach((panel, i) => {
            const titleEl = panel.querySelector('.tabTitle');

            // Wrap panel in .accordionWrap.
            const wrap = document.createElement('div');
            wrap.className = 'accordionWrap';
            panel.parentNode.insertBefore(wrap, panel);
            wrap.appendChild(panel);

            // Insert <nav class="tabsNav"><a>title</a></nav> before the panel.
            const nav = document.createElement('nav');
            nav.className = 'tabsNav';
            const a = document.createElement('a');
            a.href = `#tab_${i}`;
            a.innerHTML = titleEl ? titleEl.innerHTML : '';
            nav.appendChild(a);
            wrap.insertBefore(nav, panel);

            if (i === this.getActive()) {
                nav.classList.add('active');
            }
        });
    }

    accordionTrigger() {
        // Each accordion has its own .tabsNav next to the panel. Delegate via
        // the main content wrap.
        this._contentWrap?.querySelectorAll('.accordionWrap > .tabsNav a').forEach((a) => {
            a.addEventListener('click', (event) => {
                event.preventDefault();
                const elementId = a.getAttribute('href');
                const accordionWrap = a.closest('.accordionWrap');
                const contentContainer = accordionWrap?.parentElement;
                const navEl = a.parentElement;
                if (navEl.classList.contains('active')) {
                    return;
                }
                contentContainer?.querySelectorAll('.tabsNav').forEach((n) => n.classList.remove('active'));
                navEl.classList.add('active');
                this.runCallback('beforeChange');
                this.switchAnimationType(elementId, contentContainer);
                resetFaqDisplay();
            });
        });
    }

    // ---- select mode ----

    selectNav() {
        const nav = document.createElement('nav');
        nav.className = 'tabsNav';
        const select = document.createElement('select');
        nav.appendChild(select);
        this.element.insertBefore(nav, this.element.firstChild);

        this.contentPanels().forEach((panel, i) => {
            const titleEl = panel.querySelector('.title');
            const opt = document.createElement('option');
            opt.value = `#tab_${i}`;
            opt.textContent = titleEl ? titleEl.textContent : '';
            select.appendChild(opt);
        });
        const active = select.children[this.getActive()];
        if (active) active.selected = true;
    }

    selectTrigger(nav) {
        const select = nav.querySelector('select');
        select?.addEventListener('change', (event) => {
            event.preventDefault();
            const elementId = select.value;
            const wrap = nav.nextElementSibling;
            this.runCallback('beforeChange');
            this.switchAnimationType(elementId, wrap);
        });
    }

    // ---- shared ----

    switchTrigger() {
        const nav = this.element.querySelector('.tabsNav');
        if (!nav) return;
        switch (this.settings.mode) {
            case 'accordion': this.accordionTrigger(); break;
            case 'select': this.selectTrigger(nav); break;
            default: this.tabTrigger(nav); break;
        }
    }

    switchAnimationType(elementId, contentContainer) {
        // Accordion mode forces slide for this invocation only — keep the
        // configured this.settings.animation_type untouched so we don't
        // permanently change the instance after the first accordion.
        let animationType = this.settings.animation_type;
        if (contentContainer?.querySelector('.accordionWrap')) {
            animationType = 'slide';
        }
        if (animationType === 'slide') {
            this.slide(elementId, contentContainer);
        } else {
            this.fade(elementId, contentContainer);
        }
    }

    async slide(elementId, contentContainer) {
        if (!contentContainer) return;
        const open = contentContainer.querySelector('.open');
        if (open) {
            // Lock visibility inline BEFORE removing `.open`. If CSS uses the
            // class to drive `display`, removing it would immediately collapse
            // the element to display:none — slideFadeUp would then measure
            // offsetHeight=0, animate 0→0, never fire transitionend, and leak
            // its cleanup listener (which later corrupts the next panel's
            // slide-down end state).
            open.style.display = 'block';
            open.classList.remove('open');
            slideFadeUp(open, this.settings.speed);
        }
        // Wait for the previous slide-up to finish before sliding the new one
        // in — preserves the sequential motion of the legacy plugin.
        await wait(this.settings.speed + 20);

        const next = contentContainer.querySelector(`[data-tab-id="${elementId}"]`);
        if (next) {
            slideFadeDown(next, this.settings.speed);
            next.classList.add('open');
        }

        this.runCallback('afterChange', elementId);
        this.runCallback('onChange');
    }

    fade(elementId, contentContainer) {
        if (!contentContainer) return;
        const open = contentContainer.querySelector('.open');
        if (open) {
            open.classList.remove('open');
            open.style.display = 'none';
        }

        const next = contentContainer.querySelector(`[data-tab-id="${elementId}"]`);
        if (!next) return;

        next.style.opacity = '0';
        next.style.display = '';
        next.style.transition = `opacity ${this.settings.speed}ms ease`;
        // Force the browser to paint the start frame so the transition runs.
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                next.style.opacity = '1';
            });
        });
        next.classList.add('open');

        window.setTimeout(() => {
            next.style.transition = '';
            next.style.opacity = '';
            this.runCallback('afterChange', elementId);
            // Tell other modules (sticky bits, etc.) that the layout changed.
            window.setTimeout(() => window.dispatchEvent(new Event('resize')), 10);
        }, this.settings.speed);

        this.runCallback('onChange');
    }

    getActive() {
        if (this.settings.mode === 'accordion') {
            const open = this.element.querySelector('.open');
            const accordionWrap = open?.closest('.accordionWrap');
            const parent = accordionWrap?.parentElement;
            const siblings = parent ? [...parent.children].filter((c) => c.classList.contains('accordionWrap')) : [];
            this.activeIndex = accordionWrap ? siblings.indexOf(accordionWrap) : 0;
        } else {
            const open = this.element.querySelector('.open');
            const parent = open?.parentElement;
            const siblings = parent ? [...parent.children] : [];
            this.activeIndex = open ? siblings.indexOf(open) : 0;
        }
        return this.activeIndex;
    }

    runCallback(name, elementId) {
        const fn = this.settings[name];
        if (typeof fn === 'function') {
            fn(elementId);
        }
    }

    // ---- destroy ----

    destroyNav() {
        this.element.querySelectorAll('.tabsNav').forEach((nav) => nav.remove());
    }

    destroyMode(mode) {
        if (mode === 'accordion') {
            this.element.querySelectorAll('.accordionWrap').forEach((wrap) => {
                while (wrap.firstChild) {
                    wrap.parentNode.insertBefore(wrap.firstChild, wrap);
                }
                wrap.remove();
            });
        }
    }

    destroy() {
        const panels = this.contentPanels();
        const currentMode = this.settings.mode;

        panels.forEach((p) => {
            p.removeAttribute('data-tab-id');
            p.removeAttribute('style');
        });

        this.destroyNav();
        if (currentMode !== 'tabs') {
            this.destroyMode(currentMode);
        }

        // Unwrap the content wrapper we created in init().
        if (this._contentWrap && this._contentWrap.parentNode === this.element) {
            while (this._contentWrap.firstChild) {
                this.element.appendChild(this._contentWrap.firstChild);
            }
            this._contentWrap.remove();
        }

        this.element.classList.remove('mtcTabsInitialised');
        if (this._onResize) {
            window.removeEventListener('resize', this._onResize);
        }
        this.runCallback('onDestroy');
    }
}

// Legacy global helper for the transitional period — accepts a selector
// string OR a NodeList / array of elements, and applies Tabs to each.
// Drop once every caller has been migrated to direct construction.
window.mtcTabs = function mtcTabs(target, options) {
    const nodes = typeof target === 'string'
        ? document.querySelectorAll(target)
        : (target && 'length' in target ? target : [target]);
    const instances = [];
    [...nodes].forEach((el) => {
        if (!el) return;
        instances.push(new Tabs(el, options));
    });
    return instances;
};
