/* eslint-env browser */
/* eslint-disable no-alert, no-console */

/**
 * @file apx.js (previously apxnew.js)
 * @description Modern, class-based implementation to manage data and interactions with an apxtri instance from a webpage.
 * @version 2.1
 * @author support@ndda.fr
 */

// Establish the global namespace
window.apx = window.apx || {};

/**
 * @class ApxManager
 * Manages the core application state and lifecycle.
 */
class ApxManager {
    constructor() {
        this.data = {};
        this.pageContext = { search: {}, hash: {} };
        this.afterUpdateCallbacks = [];
        this.wcoProxies = {};
        // Capture the observer flag immediately from the initial global config.
        this.useWcoObserver = window.apxtri?.wcoobserver || false;
    }

    ready(callback) {
        if (typeof callback !== 'function') {
            alert("Apx.ready(callback) requires a valid function.");
            return;
        }
        if (document.readyState !== 'loading') {
            callback();
        } else {
            document.addEventListener('DOMContentLoaded', callback);
        }
    }

    registerUpdateCallback(callback) {
        this.afterUpdateCallbacks.push(callback);
    }

    lazyLoad() {
        const { xalias, xuuid, xtrkversion, xlang } = this.data.headers;
        const consentCookie = localStorage.getItem('consentcookie');

        document.querySelectorAll('img[data-lazysrc]').forEach(img => {
            let src = img.dataset.lazysrc;
            if (img.dataset.trksrckey) {
                src = `/trk/${src}?alias=${xalias}&uuid=${xuuid}&srckey=${img.dataset.trksrckey}&version=${xtrkversion}&consentcookie=${consentCookie}&lg=${xlang}`;
            }
            img.setAttribute('src', src);
            img.removeAttribute('data-lazysrc');
        });

        document.querySelectorAll('[data-lazybgsrc]').forEach(el => {
            el.style.backgroundImage = `url(${el.dataset.lazybgsrc})`;
            el.removeAttribute('data-lazybgsrc');
        });

        document.querySelectorAll('a[data-trksrckey]').forEach(a => {
            let urlDest = a.getAttribute('href');
            if (!urlDest.startsWith('http') && !urlDest.startsWith('/')) {
                urlDest = `/${urlDest}`;
            }
            const trackedHref = `/trk/redirect?alias=${xalias}&uuid=${xuuid}&srckey=${a.dataset.trksrckey}&version=${xtrkversion}&consentcookie=${consentCookie}&lg=${xlang}&url=${encodeURIComponent(urlDest)}`;
            a.setAttribute('href', trackedHref);
            a.removeAttribute('data-trksrckey');
        });
    }

    notify(selector, responseData, clearBefore = false) {
        const el = document.querySelector(selector);
        if (!el) {
            console.warn(`Notification selector not found: ${selector}`);
            return;
        }
        if (clearBefore) {
            el.innerHTML = '';
        }

        const messages = responseData.multimsg || [responseData];
        messages.forEach(info => {
            const template = this.data.ref?.[info.ref]?.[info.msg];
            if (!template) {
                console.warn(`Notification template not found for ref: ${info.ref}, msg: ${info.msg}`);
                return;
            }
            el.innerHTML += Mustache.render(template, info.data);
        });

        if (responseData.status === 200) {
            el.classList.remove('text-red');
            el.classList.add('text-green');
        } else {
            el.classList.add('text-red');
            el.classList.remove('text-green');
        }
    }

    listenWcoData() {
        if (!this.data.wco || Object.keys(this.data.wco).length === 0) return;

        const updateElement = (element, value) => {
            if (value.innerHTML !== undefined) element.innerHTML = value.innerHTML;
            if (value.textContent !== undefined) element.textContent = value.textContent;
            ['src', 'alt', 'placeholder', 'class', 'href'].forEach(attr => {
                if (value[attr] !== undefined) element.setAttribute(attr, value[attr]);
            });
        };

        for (const prop in this.data.wco) {
            if (Object.hasOwnProperty.call(this.data.wco, prop) && !this.wcoProxies[prop]) {
                const elements = document.querySelectorAll(`[data-wco='${prop}']`);
                elements.forEach(el => updateElement(el, this.data.wco[prop]));

                this.wcoProxies[prop] = new Proxy(this.data.wco[prop], {
                    set: (target, key, value) => {
                        target[key] = value;
                        elements.forEach(el => updateElement(el, target));
                        return true;
                    },
                });
            }
        }
        this.wco = new Proxy(this.data.wco, {
            set: (target, prop, value) => {
                target[prop] = value;
                this.listenWcoData();
                return true;
            }
        });
    }

    startWcoObserver() {
        console.log('[APX] Starting WCO observer and triggering initial load...');
        
        const processElement = (element) => {
            if (element.nodeType !== 1 || !element.hasAttribute('wco-name') || !element.id) {
                return;
            }

            const wcoName = element.getAttribute('wco-name');
            const wcoModule = window.apx?.[wcoName];

            if (wcoModule && typeof wcoModule.loadwco === 'function') {
                console.log(`[APX] Observer is processing component: ${wcoName} on #${element.id}`);
                const ctx = {};
                for (const attr of element.attributes) {
                    if (attr.name.startsWith('wco-')) {
                        ctx[attr.name.slice(4)] = attr.value;
                    }
                }
                wcoModule.loadwco(element.id, ctx);
            } else {
                console.warn(`[APX] WCO handler not found for component: apx.${wcoName}`);
            }
        };

        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(processElement);
                }
                if (mutation.type === 'attributes' && mutation.attributeName.startsWith('wco-')) {
                    processElement(mutation.target);
                }
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
        });

        // This is the key part from your original logic:
        // Manually trigger the observer for components already in the DOM.
        document.querySelectorAll('div[wco-name]').forEach((element) => {
            const wcoName = element.getAttribute('wco-name');
            console.log(`[APX] Manually triggering initial load for: ${wcoName}`);
            element.setAttribute('wco-name', wcoName);
        });
    }

    saveState() {
        localStorage.setItem(this.data.headers.xapp, JSON.stringify(this.data));
    }

    parseUrl() {
        const parse = (str) => str.slice(1).split('&').reduce((acc, kv) => {
            if (kv) {
                const [key, value] = kv.split('=');
                acc[key] = decodeURIComponent(value);
            }
            return acc;
        }, {});

        this.pageContext.search = parse(window.location.search);
        this.pageContext.hash = parse(window.location.hash);
    }

    loadState() {
        if (typeof apxtri === 'undefined') {
            throw new Error('Global `apxtri` configuration object is missing.');
        }

        const storedState = localStorage.getItem(apxtri.headers.xapp);
        if (storedState) {
            this.data = JSON.parse(storedState);
            // Invalidate cache if key configuration has changed
            if (
                this.data.headers.xtribe !== apxtri.headers.xtribe ||
                this.data.headers.xlang !== apxtri.headers.xlang ||
                this.data.headers.xtrkversion !== apxtri.headers.xtrkversion
            ) {
                localStorage.removeItem(apxtri.headers.xapp);
                // Create a deep copy to prevent modifying the global apxtri object
                this.data = JSON.parse(JSON.stringify(apxtri));
            }
        } else {
            // Create a deep copy to prevent modifying the global apxtri object
            this.data = JSON.parse(JSON.stringify(apxtri));
        }
        // Always update with current page info from the original config
        this.data.pagename = apxtri.pagename;
        if (apxtri.pageauth) this.data.pageauth = apxtri.pageauth;
    }

    handleAuthRedirect() {
        const { xhash, xdays, xprofils, xtribe, url } = this.pageContext.hash;
        if (xhash && xdays && xprofils && xtribe && dayjs(xdays).isValid() && dayjs(xdays).diff(dayjs(), 'hours') < 25) {
            const headerKeys = ['xalias', 'xhash', 'xdays', 'xprofils', 'xtribe', 'xlang'];
            const newHeaders = {};
            let isValid = true;

            headerKeys.forEach(key => {
                if (this.pageContext.hash[key]) {
                    newHeaders[key] = (key === 'xprofils') ? this.pageContext.hash[key].split(',') : this.pageContext.hash[key];
                } else {
                    isValid = false;
                }
            });

            if (isValid) {
                this.data.headers = { ...this.data.headers, ...newHeaders };
                this.saveState();
                if (url) {
                    window.location.href = url;
                    return true;
                }
            }
        }
        return false;
    }

    checkAccess() {
        const { allowedprofils, pagename, pageauth, headers } = this.data;
        if (allowedprofils && !allowedprofils.includes('anonymous') && pagename !== pageauth) {
            const hasAccess = allowedprofils.some(p => headers.xprofils?.includes(p));

            if (!hasAccess) {
                alert(this.data.ref?.Middlewares?.notallowtoaccess || 'Access denied.');
                return false;
            }
            if (dayjs().valueOf() - headers.xdays > 86400000) {
                const redirectUrl = `/${pageauth}_${headers.xlang}.html#url=${pagename}_${headers.xlang}.html`;
                document.location.href = redirectUrl;
                return false;
            }
        }
        return true;
    }

    async fetchAndUpdateDataModel() {
        this.data.version = 0;

        const { xalias, xtribe, xapp } = this.data.headers;
        const ano = (xalias === 'anonymous') ? 'anonymous' : '';
        const url = `/api/apxtri/wwws/updatelocaldb${ano}/${xtribe}/${xapp}/${this.data.pagename}/${this.data.version}`;

        try {
            const response = await axios.get(url, { headers: this.data.headers, timeout: 2000 });

            if (response.data.msg === 'datamodelupdate') {
                Object.keys(response.data.data).forEach(key => {
                    if (key !== 'headers') {
                        this.data[key] = response.data.data[key];
                    }
                });
                this.saveState();
                console.log('Local data model updated.');
            } else if (response.data.msg === 'forbidenaccess') {
                alert(this.data.ref?.Middlewares?.notallowtoaccess || 'Access denied by API.');
                return false;
            }
        } catch (error) {
            console.error('API is unavailable. Retrying in 30 seconds.', error);
            setTimeout(() => this.init(), 30000);
            return false;
        }
        return true;
    }

    async init() {
        this.loadState();
        this.parseUrl();

        if (this.handleAuthRedirect()) return;
        if (!this.checkAccess()) return;

        if (await this.fetchAndUpdateDataModel()) {
            this.listenWcoData();
            
            // This is the correct place to check and start the observer.
            if (window.apxtri?.wcoobserver) {
                this.startWcoObserver();
            } else {
                console.log('[APX] wcoobserver is not enabled.');
            }

            this.afterUpdateCallbacks.forEach(cb => cb());
            this.lazyLoad();
            this.saveState();
        }
    }
}

// Instantiate the manager and attach it to the global namespace
apx.main = new ApxManager();

// Start the application lifecycle
apx.main.ready(() => apx.main.init());