/**
 * Image zoom (vanilla front-end copy).
 *
 * Replaces the previous mtcZoom plugin on `$.fn`. Same public surface:
 *
 *   import Zoom from '@core/js/plugins/mtc_zoom.js';
 *   new Zoom(el, {
 *       hideMessageOnLaunch: true,
 *       paddingOffset: 0,
 *       message: '<i class="far fa-search-plus"></i>',
 *   });
 *
 *   // or via the legacy window helper used by the unconverted callers:
 *   window.mtcZoom('.enableZoom', { … });
 *
 * Targets a wrapping element (e.g. `.largeImage.enableZoom`) containing
 * a single `<img>`. On hover or touch the plugin:
 *
 *   - Wraps the image in `.zoomContainer > .zoom`.
 *   - Builds a `.portal` div sized to the image and positioned over it.
 *   - Loads the high-res image from `<img data-zoom="...">` and pans it
 *     inside the portal based on cursor / touch position.
 *
 * Optional gallery support: any `<a>.mtcZoomGallery` inside a
 * `.thumbs[data-zoom-gallery]` element with the matching id will swap
 * the displayed image (and trigger a portal rebuild) on click.
 *
 * Front-end only. Used solely by `sites/default/js/site_scripts/mtc_item.js`
 * (the product page).
 */

const DEFAULTS = {
    zoomHeight: 'auto',
    zoomWidth: 'auto',
    displayLoading: true,
    enableGallery: true,
    hideMessageOnLaunch: false,
    paddingOffset: 30,
    message: '<i class="fa fa-search-plus"></i> Tap or mouseover to activate zoom',
};

function debounce(fn, wait = 300) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = window.setTimeout(() => fn(...args), wait);
    };
}

export default class Zoom {

    constructor(element, options = {}) {
        if (!element) return;
        this.element = element;
        this.settings = { ...DEFAULTS, ...options };
        this.state = {
            imageLoaded: false,
            portalLoaded: true,
            zoomEnabled: false,
        };
        this.portal = null;
        this.zoomedImage = null;
        this.init();
    }

    // ---- lifecycle ----

    init() {
        const el = this.element;

        if (this.settings.displayLoading) {
            this.showTapClickToLoad();
        }

        if (this.settings.enableGallery) {
            this.setUpGallery();
        }

        // Wrap inner content in .zoomContainer > .zoom (so the portal
        // overlays the image cleanly).
        const zoomContainer = document.createElement('div');
        zoomContainer.className = 'zoomContainer';
        const zoom = document.createElement('div');
        zoom.className = 'zoom';
        zoomContainer.appendChild(zoom);
        while (el.firstChild) {
            zoom.appendChild(el.firstChild);
        }
        el.appendChild(zoomContainer);
        this.zoomDiv = zoom;

        // Bind kill on mouse-leave / touch-end.
        this._onLeave = () => {
            if (this.settings.hideMessageOnLaunch && !el.classList.contains('disableThisItemZooming')) {
                const help = el.querySelector('.clickToLoad');
                if (help) {
                    help.style.opacity = '1';
                    help.style.display = '';
                }
            }
            this.kill();
        };
        el.addEventListener('mouseleave', this._onLeave);
        el.addEventListener('touchend', this._onLeave);

        // Debounced resize → rebuild portal.
        this._onResize = debounce(() => this.onResize(), 300);
        window.addEventListener('resize', this._onResize);

        // Build the portal once at init so dimensions are ready.
        this.buildPortal();

        // Bind start-pan on mouseenter / touchstart. Only touchstart needs
        // preventDefault (to suppress mobile scroll while panning).
        this._onEnter = () => {
            this.zoomInit();
        };
        this._onTouchStart = (event) => {
            event.preventDefault();
            this.zoomInit();
        };
        el.addEventListener('mouseenter', this._onEnter);
        el.addEventListener('touchstart', this._onTouchStart, { passive: false });

        this.loaded = true;
    }

    onResize() {
        if (!this.loaded) return;
        this.state.imageLoaded = false;
        this.state.portalLoaded = false;
        this.buildPortal();
    }

    destroy() {
        const el = this.element;
        this.state.imageLoaded = false;
        this.state.portalLoaded = false;
        this.state.zoomEnabled = false;

        el.querySelectorAll('.portal, .clickToLoad').forEach((n) => n.remove());
        el.removeAttribute('style');
        el.removeEventListener('mouseenter', this._onEnter);
        el.removeEventListener('mouseleave', this._onLeave);
        el.removeEventListener('touchstart', this._onTouchStart);
        el.removeEventListener('touchend', this._onLeave);
        if (this._onMove) {
            el.removeEventListener('mousemove', this._onMove);
            el.removeEventListener('touchmove', this._onMove);
        }
        window.removeEventListener('resize', this._onResize);

        // Unwrap .zoomContainer (move children back out, remove wrapper).
        const container = el.querySelector('.zoomContainer');
        const zoom = container?.querySelector('.zoom');
        if (zoom) {
            while (zoom.firstChild) {
                container.parentNode.insertBefore(zoom.firstChild, container);
            }
        }
        container?.remove();
    }

    // ---- portal / image loading ----

    buildPortal() {
        const el = this.element;
        const zoom = this.zoomDiv;
        if (!zoom) return;

        this.state.portalLoaded = true;
        zoom.style.padding = '';

        const img = el.querySelector('img');
        if (!img) return;

        const probe = new Image();
        probe.onload = () => {
            const portalHeight = this.settings.zoomHeight === 'auto'
                ? el.offsetHeight
                : this.settings.zoomHeight;
            const portalWidth = this.settings.zoomWidth === 'auto'
                ? el.offsetWidth
                : this.settings.zoomWidth;

            this.portalWidth = portalWidth;
            this.portalHeight = portalHeight;

            let portal = el.querySelector('.portal');
            if (!portal) {
                portal = document.createElement('div');
                portal.className = 'portal';
            }
            portal.innerHTML = '';
            Object.assign(portal.style, {
                height: typeof portalHeight === 'number' ? `${portalHeight}px` : portalHeight,
                maxWidth: typeof portalWidth === 'number' ? `${portalWidth}px` : portalWidth,
                width: '100%',
                position: 'absolute',
                top: '0',
                left: '0',
                overflow: 'hidden',
            });
            zoom.appendChild(portal);
            this.portal = portal;

            zoom.style.paddingBottom = `${this.settings.paddingOffset}px`;
        };
        probe.src = img.src;
    }

    loadImage() {
        const el = this.element;
        if (!el.classList.contains('disableThisItemZooming')) {
            this.loadingMessage(true);
        }

        const img = el.querySelector('img');
        if (!img) return;

        const zoomedImage = document.createElement('img');
        Object.assign(zoomedImage.style, { position: 'absolute', top: '0', left: '0' });

        zoomedImage.onload = () => {
            this.loadingMessage(false);
            this.state.imageLoaded = true;
            this.portal?.appendChild(zoomedImage);
            zoomedImage.style.display = 'none';

            const wrapper = img.closest('.largeImage');
            if (wrapper?.classList.contains('disableThisItemZooming')) {
                const portalImg = this.portal?.querySelector('img');
                if (portalImg) portalImg.style.display = 'none';
            } else if (img.naturalWidth < zoomedImage.naturalWidth && img.naturalHeight < zoomedImage.naturalHeight) {
                this.state.zoomEnabled = true;
                const portalImg = this.portal?.querySelector('img');
                if (portalImg) {
                    portalImg.style.display = 'none';
                    // Fade in.
                    requestAnimationFrame(() => {
                        portalImg.style.transition = 'opacity 200ms';
                        portalImg.style.opacity = '0';
                        portalImg.style.display = '';
                        requestAnimationFrame(() => { portalImg.style.opacity = '1'; });
                    });
                }
            } else {
                const portalImg = this.portal?.querySelector('img');
                if (portalImg) portalImg.style.display = 'none';
            }
        };

        zoomedImage.src = img.dataset.zoom || '';
        this.zoomedImage = zoomedImage;
    }

    // ---- interaction ----

    zoomInit() {
        const el = this.element;

        if (!this.state.portalLoaded) {
            this.buildPortal();
        } else if (!this.state.imageLoaded) {
            this.state.zoomEnabled = false;
            this.loadImage();
        }

        if (this.settings.hideMessageOnLaunch) {
            const help = el.querySelector('.clickToLoad');
            if (help) help.style.display = 'none';
        }

        el.classList.add('zooming');

        // Mouse / touch move handler. We bind once per zoomInit so multiple
        // enters don't stack listeners — kill() removes it.
        this._onMove = (event) => {
            if (!this.zoomedImage) return;
            const portal = this.portal;
            const portalImg = portal?.querySelector('img');
            if (!portal || !portalImg) return;

            if (this.state.imageLoaded === false || this.state.zoomEnabled !== true) {
                return;
            }

            // Allow default touchmove behaviour to be cancelled where supported.
            if (event.cancelable) {
                event.preventDefault();
            }

            const touch = event.touches?.[0] || event.changedTouches?.[0] || event;
            const rect = el.getBoundingClientRect();
            const x = (touch.clientX || touch.pageX) - rect.left;
            const y = (touch.clientY || touch.pageY) - rect.top;

            const zoomedWidth = this.zoomedImage.naturalWidth;
            const zoomedHeight = this.zoomedImage.naturalHeight;
            let zWidth = zoomedWidth - this.portalWidth;
            let zHeight = zoomedHeight - this.portalHeight;
            const zWidthSign = zWidth;
            const zHeightSign = zHeight;
            if (zWidth < 0) zWidth = -zWidth;
            if (zHeight < 0) zHeight = -zHeight;

            let nX = x * (zWidth / this.portalWidth);
            let nY = y * (zHeight / this.portalHeight);

            // Reveal the panned image.
            portalImg.style.top = '0';
            portalImg.style.left = '0';
            portalImg.style.display = '';

            if (x < this.portalWidth && x > 0 && -nX < 0) {
                if (zWidthSign < 0) nX = -nX;
                portalImg.style.left = `${-nX}px`;
            }

            if (y < this.portalHeight && y > 0 && -nY < 0) {
                if (zHeightSign < 0) nY = -nY;
                portalImg.style.top = `${-nY}px`;
            }

            event.stopPropagation?.();
        };

        el.addEventListener('mousemove', this._onMove);
        el.addEventListener('touchmove', this._onMove, { passive: false });
    }

    kill() {
        const portalImg = this.portal?.querySelector('img');
        if (portalImg) {
            portalImg.style.display = 'none';
        }
        this.element.classList.remove('zooming');

        if (this._onMove) {
            this.element.removeEventListener('mousemove', this._onMove);
            this.element.removeEventListener('touchmove', this._onMove);
            this._onMove = null;
        }

        if (this.settings.displayLoading && !this.element.classList.contains('disableThisItemZooming')) {
            const help = this.element.querySelector('.clickToLoad');
            if (help) help.style.display = '';
        }
    }

    // ---- gallery ----

    setUpGallery() {
        const el = this.element;
        const dataAttr = el.getAttribute('data-zoom-gallery');
        if (!dataAttr) return;

        const thumbList = document.querySelector(`.thumbs[data-zoom-gallery="${dataAttr}"]`);
        if (!thumbList) return;
        thumbList.classList.add('active');

        thumbList.querySelectorAll('.mtcZoomGallery').forEach((thumb) => {
            const li = thumb.closest('li');
            const currentImg = el.querySelector('img');
            if (thumb.getAttribute('data-large') === currentImg?.getAttribute('data-zoom')) {
                li?.classList.add('active');
            }

            thumb.addEventListener('click', (event) => {
                event.preventDefault();
                const href = thumb.getAttribute('href');
                const large = thumb.dataset.large;

                // Update active markers.
                li?.parentElement?.querySelectorAll(':scope > li').forEach((sibling) => {
                    sibling.classList.remove('active');
                });
                li?.classList.add('active');

                const imgReplace = el.querySelector('img');
                if (!imgReplace) return;

                imgReplace.dataset.zoom = large || '';
                imgReplace.src = href || '';

                const largeImage = imgReplace.closest('.largeImage');
                if (thumb.classList.contains('disableThisItemZooming')) {
                    largeImage?.classList.add('disableThisItemZooming');
                    const help = largeImage?.querySelector('.clickToLoad');
                    if (help) help.style.display = 'none';
                } else {
                    largeImage?.classList.remove('disableThisItemZooming');
                    const help = largeImage?.querySelector('.clickToLoad');
                    if (help) help.style.display = '';
                }

                this.state.imageLoaded = false;
                this.state.portalLoaded = false;
                this.state.zoomEnabled = false;

                // Rebuild the portal once the new image has loaded.
                const onNewLoad = () => {
                    this.buildPortal();
                    imgReplace.removeEventListener('load', onNewLoad);
                };
                imgReplace.addEventListener('load', onNewLoad);
            });
        });
    }

    // ---- loading / help overlays ----

    loadingMessage(show) {
        const portal = this.portal;
        if (!portal) return;
        if (show) {
            const site = document.body.dataset.site || 'default';
            const loader = document.createElement('div');
            loader.className = 'loader';
            loader.innerHTML = `<img src="/sites/${site}/images/loader.gif" alt="Loading" />`;
            portal.appendChild(loader);
        } else {
            portal.querySelector('.loader')?.remove();
        }
    }

    showTapClickToLoad() {
        const el = this.element;
        const tip = document.createElement('div');
        tip.className = 'clickToLoad';
        tip.innerHTML = this.settings.message;
        el.style.display = 'block';
        el.appendChild(tip);
        if (el.classList.contains('disableThisItemZooming')) {
            tip.style.display = 'none';
        }
    }
}

// Legacy global — used by sites/default/js/site_scripts/mtc_item.js until
// that file is converted in the next Phase 3 batch.
window.mtcZoom = function mtcZoom(target, options) {
    const nodes = typeof target === 'string'
        ? document.querySelectorAll(target)
        : (target && 'length' in target ? target : [target]);
    const instances = [];
    [...nodes].forEach((el) => {
        if (!el) return;
        if (el._zoomInstance) return; // already initialised
        const instance = new Zoom(el, options);
        el._zoomInstance = instance;
        instances.push(instance);
    });
    return instances;
};
