import CookieHandler from "./CookieHandler"

export default class GDPRHandler {
    constructor(config) {
        let defaultCb = () => {};
        this.defaults = {
            sections: {
                functional: {
                    enable: defaultCb,
                    disable: defaultCb,
                }
            }
        };

        this.config = {};
        this.config = mergeDeep(this.config, this.defaults);
        this.config = mergeDeep(this.config, config);

        this.interfaceQuickAccessBtn = document.querySelector('.gdpr-quick-access');
        this.interfaceBanner = document.querySelector('.gdpr-banner');
        this.interfaceBackdrop = document.querySelector('.gdpr-backdrop');

        this.iframeObserver = null;

        // setup default section callback if none provided
        Object.keys(this.config.sections).forEach((section) => {
            if (typeof this.config.sections[section].enable === 'undefined') {
                this.config.sections[section].enable = defaultCb;
            }
            if (typeof this.config.sections[section].disable === 'undefined') {
                this.config.sections[section].disable = defaultCb;
            }
        });
    }

    init() {
        const versionChanged = CookieHandler.clearCookiesIfVersionChanged();

        this._initGTMConsentDefaults();
    
        // add event listeners
        this._getSectionControls().forEach((control) => {
            control.addEventListener('change', () => {
                this.onControlChange(control, control.checked === true);
            })
        });
        [...document.querySelectorAll('.gdpr-save, .gdpr-close')].forEach((control) => {
            control.addEventListener('click', () => {
                this.onSave();
            });
        });
        [...document.querySelectorAll('.gdpr-accept-all')].forEach((control) => {
            control.addEventListener('click', () => {
                this.onGrantAll();
            });
        });
        [...document.querySelectorAll('.gdpr-deny-all')].forEach((control) => {
            control.addEventListener('click', () => {
                this.onDenyAll();
            });
        });
        document.querySelector('.gdpr-banner .gdpr-configure')?.addEventListener('click', () => {
            this._hideEl(this.interfaceBanner);
        });

        let preInitialized = CookieHandler.hasConsentCookie() && !versionChanged;
        if (preInitialized) {
            this._showEl(this.interfaceQuickAccessBtn);
        } else {
            this._showEl(this.interfaceBanner);
        }

        // retrieve state
        this.syncStates();

        this._updateIframesProtection();
        this._startIframeObserver();

        // call callbacks after sync on initialization
        this._getSectionControls().forEach((control) => {
            let section = control.getAttribute('data-gdpr-section');
            this.config.sections[section][control.checked ? 'enable' : 'disable']();
        });
    }

    onGrantAll() {
        this._setControlsState(true);
        this._getSectionControls().forEach((el) => {
            let section = el.getAttribute('data-gdpr-section');
            this.config.sections[section].enable();
        });
        this.config.sections.functional.enable();
        this.persistStates();
        this._closeInterfaces();
    }

    onDenyAll() {
        this._setControlsState(false);
        this._getSectionControls().forEach((el) => {
            let section = el.getAttribute('data-gdpr-section');
            this.config.sections[section].disable();
        });
        this.config.sections.functional.disable();
        this.persistStates();
        this._closeInterfaces();
    }

    onSave() {
        this.persistStates();
        this._closeInterfaces();
    }

    onControlChange(control, state) {
        let section = control.getAttribute('data-gdpr-section');
        if (typeof this.config.sections[section] === 'undefined') {
            console.warn('gdpr section "' + section + '" is not declared');
        }
        this.config.sections[section][state ? 'enable' : 'disable']();
        this.persistStates();
    }

    /**
     * check cookie values and init controls
     */
    syncStates() {
        const consentData = CookieHandler.getConsentCookie();
        if (!consentData) {
            return;
        }

        const analyticsControl = this._getSectionControl('analytics');
        if (analyticsControl) {
            analyticsControl.checked = consentData.analytics_storage === "granted";
        }

        const marketingControl = this._getSectionControl('marketing');
        if (marketingControl) {
            marketingControl.checked = consentData.ad_storage === "granted" &&
                consentData.personalization_storage === "granted";
        }
    }

    /**
     * check controls values and set cookie values
     */
    persistStates() {
        let consentData = CookieHandler.getConsentCookie() || {};

        this._getSectionControls().forEach((control) => {
            let section = control.getAttribute('data-gdpr-section');
            if (section !== 'functional') {
                consentData = CookieHandler.updateConsentCookie(section, control.checked === true, false);
            }
        });
    
        consentData = CookieHandler.updateConsentCookie('functional', true, false);
        CookieHandler.updateGTMConsentState(consentData);

        this._updateIframesProtection();
    }

    _getSectionControls() {
        return [...document.querySelectorAll('.gdpr-control:not([data-gdpr-section="functional"])')]
    }

    _getSectionControl(section) {
        return document.querySelector('.gdpr-control[data-gdpr-section="' + section + '"]')
    }

    _setControlsState(state) {
        this._getSectionControls().forEach((control) => {
            let section = control.getAttribute('data-gdpr-section');
            if (section !== 'functional') {
                control.checked = state;
            } else {
                control.checked = true;
            }
        });
    }

    _closeInterfaces() {
        let modal = document.querySelector('#gdpr-modal');
        modal.classList.remove('show');
        modal.removeAttribute('aria-modal');
        modal.removeAttribute('role');
        modal.setAttribute('aria-hidden', 'true');
        modal.setAttribute('style', 'display: none');

        this._hideEl(this.interfaceBanner);
        this._showEl(this.interfaceQuickAccessBtn);
    }

    _showEl(el) {
        el.classList.add('shown');
        if (el === this.interfaceBanner) {
            this.interfaceBackdrop?.classList.add('shown');
        }
    }

    _hideEl(el) {
        el.classList.remove('shown');
        if (el === this.interfaceBanner) {
            this.interfaceBackdrop?.classList.remove('shown');
        }
    }

    _updateIframesProtection() {
        if (typeof document === 'undefined') {
            return;
        }

        const frames = document.querySelectorAll('iframe');
        if (!frames || frames.length === 0) {
            return;
        }

        frames.forEach((frame) => {
            this._processFrame(frame);
        });
    }

    _processFrame(frame) {
        if (!frame || frame.tagName !== 'IFRAME') {
            return;
        }

        let src = frame.getAttribute('src') || frame.getAttribute('data-src') || '';
        src = src === 'about:blank' ? frame.getAttribute('data-src') : src;
        if (!src || src === 'about:blank' || !this._isExternalDomain(src)) {
            return;
        }

        const sections = ['marketing', 'analytics'];
        const granted = sections.every((s) => this._isConsentGranted(s));

        const existingPlaceholder = frame.nextElementSibling && frame.nextElementSibling.classList
            ? frame.nextElementSibling.classList.contains('gdpr-iframe-placeholder') ? frame.nextElementSibling : null
            : null;

        if (!granted) {
            if (!frame.dataset.src || frame.dataset.src === '') {
                const currentSrc = frame.getAttribute('src');
                if (currentSrc && currentSrc !== 'about:blank') {
                    frame.setAttribute('data-src', currentSrc);
                }
            }
            if (frame.getAttribute('src') !== 'about:blank') {
                frame.setAttribute('src', 'about:blank');
            }
            frame.setAttribute('data-gdpr-blocked', 'true');

            frame.style.display = 'none';

            if (!existingPlaceholder) {
                const ph = document.createElement('div');
                ph.className = 'gdpr-iframe-placeholder';
                ph.setAttribute('tabindex', '0');

                const rect = typeof frame.getBoundingClientRect === 'function' ? frame.getBoundingClientRect() : { width: 0, height: 0 };
                const hAttr = parseInt(frame.getAttribute('height') || '0', 10);
                const wAttr = parseInt(frame.getAttribute('width') || '0', 10);
                const phHeight = (rect && rect.height) ? rect.height : (hAttr || 200);
                const phWidth = (rect && rect.width) ? rect.width : (wAttr || 0);

                ph.style.minHeight = phHeight + 'px';
                if (phWidth) {
                    ph.style.width = phWidth + 'px';
                }

                const title = staticTranslations['gdpr.iframe.blocked.title'];
                const description = staticTranslations['gdpr.iframe.blocked.description'];
                const button = staticTranslations['gdpr.configure'];

                ph.innerHTML = `
                    <div>
                        <div>${title}</div>
                        <div>${description}</div>
                        <button class="gdpr-configure btn btn-transparent"
                            data-bs-toggle="modal"
                            data-bs-target="#gdpr-modal"
                        >${button}</button>
                    </div>
                `;

                if (frame.parentNode) {
                    frame.parentNode.insertBefore(ph, frame.nextSibling);
                }
            }
        } else {
            const wasBlocked = frame.getAttribute('data-gdpr-blocked') === 'true';
            const savedSrc = frame.getAttribute('data-src');

            if (wasBlocked && savedSrc) {
                frame.setAttribute('src', savedSrc);
            }
            frame.removeAttribute('data-gdpr-blocked');
            frame.style.display = '';

            if (existingPlaceholder && existingPlaceholder.parentNode) {
                existingPlaceholder.parentNode.removeChild(existingPlaceholder);
            }
        }
    }

    _isExternalDomain(src) {
        try {
            const srcUrl = new URL(src, window.location.origin);
            const currentDomain = window.location.hostname;

            if (srcUrl.hostname === currentDomain || srcUrl.hostname === '') {
                return false;
            }

            if (!srcUrl.protocol.startsWith('http')) {
                return false;
            }

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

    _isConsentGranted(section) {
        if (section === 'functional') {
            return true;
        }
        const consentData = CookieHandler.getConsentCookie();
        if (!consentData) {
            return false;
        }
        switch (section) {
            case 'analytics':
                return consentData.analytics_storage === 'granted';
            case 'marketing':
                return consentData.ad_storage === 'granted' && consentData.personalization_storage === 'granted';
            default:
                return false;
        }
    }

    _startIframeObserver() {
        if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') {
            return;
        }

        if (this.iframeObserver) {
            this.iframeObserver.disconnect();
        }

        this.iframeObserver = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName === 'IFRAME') {
                                setTimeout(() => this._processFrame(node), 100);
                            }

                            const childIframes = node.querySelectorAll ? node.querySelectorAll('iframe') : [];
                            if (childIframes.length > 0) {
                                setTimeout(() => {
                                    childIframes.forEach((iframe) => this._processFrame(iframe));
                                }, 100);
                            }
                        }
                    });
                }
            });
        });

        this.iframeObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    _initGTMConsentDefaults() {
        if (typeof window === 'undefined') {
            return;
        }

        window.gtag = window.gtag || function() {
            (window.dataLayer = window.dataLayer || []).push(arguments);
        };

        const consentData = CookieHandler.getConsentCookie();
        if (consentData) {
            CookieHandler.updateGTMConsentState(consentData);
            return;
        }

        gtag('consent', 'default', {
            'analytics_storage': 'denied',
            'ad_user_data': 'denied',
            'ad_storage': 'denied',
            'personalization_storage': 'denied',
            'ad_personalization': 'denied',
            'functionality_storage': 'granted',
            'security_storage': 'granted',
            'wait_for_update': 500
        });
    }
};

function isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param sources
 */
function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if (isObject(target) && isObject(source)) {
        for (const key in source) {
            if (isObject(source[key])) {
                if (!target[key]) Object.assign(target, {[key]: {}});
                mergeDeep(target[key], source[key]);
            } else {
                Object.assign(target, {[key]: source[key]});
            }
        }
    }

    return mergeDeep(target, ...sources);
}
