/**
 * Kosmelita_AiChat — standalone widget embed for headless storefronts
 * (ScandiPWA / PWA Studio / any SPA served from the Magento domain).
 *
 * Framework-free: no jQuery, no RequireJS, no Magento frontend libraries.
 * Embed with a single tag (the module serves this file at a stable URL):
 *
 *     <script src="https://www.eurokos.lt/aichat/widget/script" defer></script>
 *
 * It bootstraps from /aichat/widget/config (which also establishes the
 * session + form key), injects the widget stylesheet, and talks to the same
 * aichat/chat/* endpoints as the Magento-layout widget.
 *
 * Add-to-cart on product cards works on ScandiPWA-style storefronts: the
 * embed reads (or creates) the SAME GraphQL cart the PWA uses — the masked
 * cart id ScandiPWA keeps in localStorage under "cart_id" (per website code,
 * BrowserDatabase envelope) — and calls the standard addProductsToCart
 * mutation. The PWA's header badge only refetches on a full page load; the
 * embed dispatches an "aichat:cart-changed" DOM event so a one-line theme
 * hook can refresh it instantly (see INSTALL.md §8), and the "view cart"
 * link performs a full-load navigation so the cart page is always correct.
 * Remaining difference from the layout widget: no page-context awareness.
 */
(function () {
    'use strict';

    if (window.__kosmelitaAichat) { return; }
    window.__kosmelitaAichat = true;

    var OPEN_KEY = 'aichat_open';
    var TRANSCRIPT_KEY = 'aichat_transcript';
    var TEASER_KEY = 'aichat_teaser_seen';
    var ACTIVITY_KEY = 'aichat_last_activity';
    var CONSENT_KEY = 'aichat_photo_consent';
    var UNREAD_KEY = 'aichat_unread';
    var MAX_STORED = 60;

    var SVG_RESET = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>';
    var SVG_CLOSE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
    var SVG_ATTACH = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8.5" cy="8.5" r="1.6"/><path d="M21 15.5l-5-5-10 10"/></svg>';
    var SVG_CAMERA = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h3l2-2.5h6L17 7h3a1.5 1.5 0 0 1 1.5 1.5v10A1.5 1.5 0 0 1 20 20H4a1.5 1.5 0 0 1-1.5-1.5v-10A1.5 1.5 0 0 1 4 7z"/><circle cx="12" cy="13" r="3.4"/></svg>';
    var SVG_INFO = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9.5"/><line x1="12" y1="11" x2="12" y2="16"/><circle cx="12" cy="7.4" r="0.7" fill="currentColor" stroke="none"/></svg>';
    var SVG_CART = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="20" r="1.3"/><circle cx="18" cy="20" r="1.3"/><path d="M2 3h3l2.2 11.2a1.6 1.6 0 0 0 1.6 1.3h8.4a1.6 1.6 0 0 0 1.6-1.3L21.5 7H6"/></svg>';
    var SVG_CHECK = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l4 4 10-10"/></svg>';
    var SVG_UP = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v11"/><path d="M4 21h13.3a2 2 0 0 0 2-1.7l1.3-8A2 2 0 0 0 18 9h-5l1-4.5A2 2 0 0 0 12.2 2L7 10"/></svg>';
    var SVG_DOWN = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 14V3"/><path d="M20 3H6.7a2 2 0 0 0-2 1.7l-1.3 8A2 2 0 0 0 5.3 15h5l-1 4.5A2 2 0 0 0 11.8 22L17 14"/></svg>';
    var SVG_SHIELD = '<svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5l-8-3Zm-1 14-4-4 1.4-1.4L11 13.2l4.6-4.6L17 10l-6 6Z"/></svg>';

    function lsGet(k) { try { return window.localStorage.getItem(k); } catch (e) { return null; } }
    function lsSet(k, v) { try { window.localStorage.setItem(k, v); } catch (e) { /* ignore */ } }
    function ssGet(k) { try { return window.sessionStorage.getItem(k); } catch (e) { return null; } }
    function ssSet(k, v) { try { window.sessionStorage.setItem(k, v); } catch (e) { /* ignore */ } }

    function el(tag, className, attrs) {
        var node = document.createElement(tag);
        if (className) { node.className = className; }
        if (attrs) {
            Object.keys(attrs).forEach(function (k) {
                if (k === 'text') { node.textContent = attrs[k]; }
                else if (k === 'html') { node.innerHTML = attrs[k]; }
                else { node.setAttribute(k, attrs[k]); }
            });
        }
        return node;
    }

    function escapeHtml(text) {
        var d = document.createElement('div');
        d.textContent = text == null ? '' : text;
        return d.innerHTML;
    }

    /** Escape, then render a small safe subset: links, **bold**, *italic*, newlines. */
    function renderMessage(text) {
        var html = escapeHtml(text);
        html = html.replace(/\[([^\]]+)]\((https?:\/\/[^\s)]+)\)/g, function (m, label, url) {
            return '<a href="' + url + '" target="_blank" rel="noopener nofollow">' + label + '</a>';
        });
        html = html.replace(/(^|[^"=>])(https?:\/\/[^\s<]+)/g, function (m, pre, url) {
            return pre + '<a href="' + url + '" target="_blank" rel="noopener nofollow">' + url + '</a>';
        });
        html = html.replace(/\*\*([^*\n]+?)\*\*/g, '<strong>$1</strong>');
        html = html.replace(/\*([^*\n]+?)\*/g, '<em>$1</em>');
        return html.replace(/\n/g, '<br>');
    }

    function timeNow() {
        var d = new Date(), h = d.getHours(), m = d.getMinutes();
        return ((h % 12) || 12) + ':' + (m < 10 ? '0' + m : m) + ' ' + (h < 12 ? 'AM' : 'PM');
    }

    function post(url, data) {
        var body = new URLSearchParams();
        Object.keys(data).forEach(function (k) { body.append(k, data[k] == null ? '' : data[k]); });
        return fetch(url, {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' },
            body: body.toString()
        }).then(function (r) { return r.json(); });
    }

    /* ---------------- bootstrap ---------------- */

    fetch('/aichat/widget/config', { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
        .then(function (r) { return r.text(); })
        .then(function (raw) {
            // Resilience: tolerate PHP notices/warnings printed before the
            // JSON (e.g. stale generated code right after a deployment) —
            // parse from the first brace instead of dying silently.
            var start = raw.indexOf('{');
            if (start === -1) {
                throw new Error('non-JSON response: ' + raw.slice(0, 200));
            }
            var cfg = JSON.parse(raw.slice(start));
            if (!cfg || !cfg.enabled) { return; }
            var link = el('link', '', { rel: 'stylesheet', href: cfg.urls.css });
            link.addEventListener('load', function () { init(cfg); });
            link.addEventListener('error', function () { init(cfg); });
            document.head.appendChild(link);
        })
        .catch(function (e) {
            // Loud failure beats an invisibly missing widget.
            console.error('[aichat] widget bootstrap failed:', e);
        });

    /* ---------------- widget ---------------- */

    function init(cfg) {
        var t = function (key, fallback) { return (cfg.text && cfg.text[key]) || fallback; };
        var transcript = [];
        try {
            var stored = JSON.parse(lsGet(TRANSCRIPT_KEY) || '[]');
            if (Array.isArray(stored)) { transcript = stored; }
        } catch (e) { /* fresh start */ }
        var busy = false;
        var pendingImage = null;
        var pendingThumb = null;
        var consentPendingFile = null;

        // Session timeout: wipe a stale local transcript (server side expires independently).
        var timeoutMinutes = parseInt(cfg.sessionTimeoutMinutes, 10) || 0;
        if (timeoutMinutes > 0 && transcript.length) {
            var lastActivity = parseInt(lsGet(ACTIVITY_KEY), 10) || 0;
            if (lastActivity && (Date.now() - lastActivity) > timeoutMinutes * 60000) {
                transcript = [];
                lsSet(TRANSCRIPT_KEY, '[]');
                lsSet(OPEN_KEY, '0');
            }
        }

        /* ----- DOM ----- */
        var root = el('div', 'aichat-widget');
        root.setAttribute('style', cfg.widgetStyle || '');

        var launcher = el('button', 'aichat-launcher' + (cfg.logoUrl ? ' has-logo' : ''), {
            type: 'button', 'aria-label': t('openChat', 'Open chat assistant')
        });
        launcher.innerHTML = cfg.logoUrl
            ? '<img src="' + escapeHtml(cfg.logoUrl) + '" alt=""/>'
            : '<span class="aichat-spark">✨</span>';
        var unread = el('span', 'aichat-unread');
        unread.style.display = 'none';
        launcher.appendChild(unread);
        root.appendChild(launcher);

        var teaser = null;
        if (cfg.welcomeMessage) {
            teaser = el('div', 'aichat-teaser');
            teaser.style.display = 'none';
            var teaserClose = el('button', 'aichat-teaser-close', { type: 'button', 'aria-label': t('close', 'Close'), html: '&times;' });
            var teaserText = el('div', 'aichat-teaser-text', { text: cfg.welcomeMessage });
            teaser.appendChild(teaserClose);
            teaser.appendChild(teaserText);
            root.appendChild(teaser);
        }

        var panel = el('div', 'aichat-panel');

        var header = el('div', 'aichat-header');
        var id = el('div', 'aichat-id');
        var avatarBig = avatar();
        var idText = el('span', 'aichat-id-text');
        var titleRow = el('span', 'aichat-titlerow');
        titleRow.appendChild(el('span', 'aichat-title', { text: cfg.assistantName || '' }));
        titleRow.appendChild(el('span', 'aichat-badge', { text: t('ai', 'AI') }));
        idText.appendChild(titleRow);
        if (cfg.headerSubtitle) { idText.appendChild(el('span', 'aichat-subtitle', { text: cfg.headerSubtitle })); }
        id.appendChild(avatarBig);
        id.appendChild(idText);
        header.appendChild(id);

        var actions = el('span', 'aichat-actions');
        var resetBtn = el('button', 'aichat-reset', {
            type: 'button', title: t('startOver', 'Start over'), 'aria-label': t('startOver', 'Start over'), html: SVG_RESET
        });
        var closeBtn = el('button', 'aichat-close', {
            type: 'button', 'aria-label': t('closeChat', 'Close chat'), html: SVG_CLOSE
        });
        actions.appendChild(resetBtn);
        actions.appendChild(closeBtn);
        header.appendChild(actions);
        panel.appendChild(header);

        var messagesWrap = el('div', 'aichat-messages-wrap');
        var messages = el('div', 'aichat-messages', { role: 'log', 'aria-live': 'polite' });
        var scrollHint = el('div', 'aichat-scroll-more', { 'aria-hidden': 'true', html: '<span>▾</span>' });
        messagesWrap.appendChild(messages);
        messagesWrap.appendChild(scrollHint);
        panel.appendChild(messagesWrap);

        var consentBox = null, preview = null, fileInput = null, cameraInput = null;
        if (cfg.visionEnabled) {
            consentBox = el('div', 'aichat-photo-consent');
            consentBox.style.display = 'none';
            consentBox.appendChild(el('p', '', { text: cfg.visionConsentText || '' }));
            var consentActions = el('div', 'aichat-photo-consent-actions');
            var consentAccept = el('button', 'aichat-consent-accept', { type: 'button', text: t('agree', 'I agree') });
            var consentCancel = el('button', 'aichat-consent-cancel', { type: 'button', text: t('cancel', 'Cancel') });
            consentActions.appendChild(consentAccept);
            consentActions.appendChild(consentCancel);
            consentBox.appendChild(consentActions);
            panel.appendChild(consentBox);

            preview = el('div', 'aichat-attach-preview');
            preview.style.display = 'none';
            preview.appendChild(el('img', '', { alt: '', src: '' }));
            var previewRemove = el('button', '', { type: 'button', 'aria-label': t('removePhoto', 'Remove photo'), html: '&times;' });
            preview.appendChild(previewRemove);
            panel.appendChild(preview);
        }

        var form = el('form', 'aichat-input');
        if (cfg.visionEnabled) {
            fileInput = el('input', '', { type: 'file', accept: 'image/*', hidden: 'hidden' });
            cameraInput = el('input', '', { type: 'file', accept: 'image/*', capture: 'environment', hidden: 'hidden' });
            var attachBtn = el('button', 'aichat-attach', { type: 'button', title: t('attach', 'Attach a photo'), 'aria-label': t('attach', 'Attach a photo'), html: SVG_ATTACH });
            var cameraBtn = el('button', 'aichat-attach', { type: 'button', title: t('takePhoto', 'Take a photo'), 'aria-label': t('takePhoto', 'Take a photo'), html: SVG_CAMERA });
            form.appendChild(fileInput);
            form.appendChild(cameraInput);
            form.appendChild(attachBtn);
            form.appendChild(cameraBtn);
            attachBtn.addEventListener('click', function () { fileInput.click(); });
            cameraBtn.addEventListener('click', function () { cameraInput.click(); });
        }
        var textarea = el('textarea', '', { rows: '1', placeholder: cfg.inputPlaceholder || '', 'aria-label': t('message', 'Message') });
        var sendBtn = el('button', '', { type: 'submit', 'aria-label': t('send', 'Send'), html: '➤' });
        sendBtn.setAttribute('data-aichat-send', '');
        form.appendChild(textarea);
        form.appendChild(sendBtn);
        panel.appendChild(form);

        if (cfg.disclaimer) {
            var disc = el('div', 'aichat-disclaimer', { html: SVG_SHIELD });
            disc.appendChild(el('span', '', { text: cfg.disclaimer }));
            panel.appendChild(disc);
        }

        root.appendChild(panel);
        document.body.appendChild(root);

        /* ----- excluded URLs on SPA route changes ----- */
        function pathExcluded() {
            var path = ('/' + window.location.pathname.replace(/^\/+|\/+$/g, '')).toLowerCase();
            return (cfg.excludedUrls || []).some(function (pattern) {
                pattern = String(pattern).toLowerCase();
                if (pattern.indexOf('://') !== -1) {
                    try { pattern = new URL(pattern).pathname; } catch (e) { /* keep as-is */ }
                }
                pattern = pattern.replace(/\/+$/, '');
                if (!pattern) { return false; }
                if (pattern.indexOf('*') !== -1) {
                    var rx = new RegExp(pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'));
                    return rx.test(path);
                }
                return path.indexOf(pattern) !== -1;
            });
        }
        function applyExclusion() {
            root.style.display = pathExcluded() ? 'none' : '';
        }
        ['pushState', 'replaceState'].forEach(function (fn) {
            var orig = window.history[fn];
            window.history[fn] = function () {
                var out = orig.apply(this, arguments);
                window.dispatchEvent(new Event('aichat:nav'));
                return out;
            };
        });
        window.addEventListener('popstate', applyExclusion);
        window.addEventListener('aichat:nav', applyExclusion);
        applyExclusion();

        /* ----- state helpers ----- */
        function touchActivity() { lsSet(ACTIVITY_KEY, String(Date.now())); }
        function isOpen() { return root.classList.contains('aichat-open'); }

        function updateUnreadBadge() {
            var count = parseInt(lsGet(UNREAD_KEY), 10) || 0;
            if (count > 0 && !isOpen()) {
                unread.textContent = count > 9 ? '9+' : String(count);
                unread.style.display = '';
            } else {
                unread.style.display = 'none';
            }
        }
        function markUnread() {
            lsSet(UNREAD_KEY, String((parseInt(lsGet(UNREAD_KEY), 10) || 0) + 1));
            updateUnreadBadge();
        }

        function dismissTeaser() {
            ssSet(TEASER_KEY, '1');
            if (teaser) { teaser.style.display = 'none'; }
        }
        function setOpen(open) {
            root.classList.toggle('aichat-open', open);
            lsSet(OPEN_KEY, open ? '1' : '0');
            if (open) {
                dismissTeaser();
                lsSet(UNREAD_KEY, '0');
                updateUnreadBadge();
                textarea.focus();
                scrollDown();
            } else {
                updateUnreadBadge();
            }
        }

        function scrollDown() { messages.scrollTop = messages.scrollHeight; }
        function nearBottom() {
            return messages.scrollHeight - messages.scrollTop - messages.clientHeight < 40;
        }
        function updateScrollHint() {
            scrollHint.classList.toggle('is-visible', !nearBottom());
        }
        function scrollToRow(row) {
            messages.scrollTop = Math.max(0, row.offsetTop - 6);
        }

        function record(entry) {
            transcript.push(entry);
            if (transcript.length > MAX_STORED) { transcript = transcript.slice(-MAX_STORED); }
            lsSet(TRANSCRIPT_KEY, JSON.stringify(transcript));
        }

        /* ----- PWA cart (shared GraphQL cart) ----- */

        // ScandiPWA's BrowserDatabase envelope: {data, expiration, createdAt}.
        function bdGet(key) {
            try {
                var o = JSON.parse(window.localStorage.getItem(key) || '');
                if (o.expiration && Date.now() - o.createdAt > o.expiration * 1000) { return null; }
                return o.data;
            } catch (e) { return null; }
        }
        function bdSet(key, data) {
            try {
                window.localStorage.setItem(key, JSON.stringify({ data: data, createdAt: Date.now() }));
            } catch (e) { /* ignore */ }
        }
        function pwaWebsiteCode() { return window.website_code || cfg.websiteCode || 'base'; }
        function pwaAuthToken() { return bdGet('auth_token') || null; }

        // The PWA's cart id: localStorage "cart_id" keyed by website code.
        // A customer-token cart id is only valid while the customer token
        // exists — mirror ScandiPWA's own getCartId() rules exactly.
        function pwaGetCartId() {
            var tokens = bdGet('cart_id') || {};
            var entry = tokens[pwaWebsiteCode()];
            if (!entry) { return null; }
            if (entry.isCustomerToken && !pwaAuthToken()) { return null; }
            return entry.token || null;
        }
        function pwaSetCartId(token) {
            var tokens = bdGet('cart_id') || {};
            tokens[pwaWebsiteCode()] = { token: token, isCustomerToken: !!pwaAuthToken() };
            bdSet('cart_id', tokens);
        }

        function gql(query) {
            var headers = { 'Content-Type': 'application/json', Store: cfg.storeCode || 'default' };
            var token = pwaAuthToken();
            if (token) { headers.Authorization = 'Bearer ' + token; }
            return fetch(cfg.graphqlUrl || '/graphql', {
                method: 'POST',
                credentials: 'same-origin',
                headers: headers,
                body: JSON.stringify({ query: query })
            }).then(function (r) { return r.json(); });
        }

        // Resolve the cart to add into: the PWA's stored cart, else the
        // signed-in customer's cart, else a fresh guest cart — which is then
        // stored back in ScandiPWA's own format so the PWA adopts it too.
        var ensureCartPending = null;
        function ensureCartId() {
            var id = pwaGetCartId();
            if (id) { return Promise.resolve(id); }
            if (ensureCartPending) { return ensureCartPending; }
            var q = pwaAuthToken() ? '{ customerCart { id } }' : 'mutation { createEmptyCart }';
            ensureCartPending = gql(q).then(function (r) {
                var created = r && r.data && (r.data.createEmptyCart || (r.data.customerCart && r.data.customerCart.id));
                if (created) { pwaSetCartId(created); }
                ensureCartPending = null;
                return created || null;
            }, function (e) { ensureCartPending = null; throw e; });
            return ensureCartPending;
        }

        function addToPwaCart(sku, qty) {
            return ensureCartId().then(function (cartId) {
                if (!cartId) { throw new Error('no cart'); }
                var q = 'mutation { addProductsToCart(cartId: ' + JSON.stringify(cartId)
                    + ', cartItems: [{sku: ' + JSON.stringify(sku)
                    + ', quantity: ' + (parseInt(qty, 10) > 0 ? parseInt(qty, 10) : 1)
                    + '}]) { cart { total_quantity } user_errors { message } } }';
                return gql(q).then(function (r) {
                    var d = r && r.data && r.data.addProductsToCart;
                    var userErrors = (d && d.user_errors) || [];
                    if (!d || userErrors.length) {
                        throw new Error(userErrors.length ? userErrors[0].message : 'add failed');
                    }
                    // Let the PWA refresh its cart state instantly when the
                    // theme installed the one-line hook (INSTALL.md §8);
                    // without it the badge catches up on the next full load.
                    try {
                        document.dispatchEvent(new CustomEvent('aichat:cart-changed', { detail: { sku: sku } }));
                        if (typeof window.__aichatCartSync === 'function') { window.__aichatCartSync(); }
                    } catch (e) { /* ignore */ }
                    return d.cart && d.cart.total_quantity;
                });
            });
        }

        /* ----- renderers ----- */
        function avatar() {
            var a = el('span', 'aichat-avatar' + (cfg.logoUrl ? ' has-logo' : ''));
            a.innerHTML = cfg.logoUrl
                ? '<img src="' + escapeHtml(cfg.logoUrl) + '" alt=""/>'
                : '<span class="aichat-spark">✨</span>';
            return a;
        }

        function linkTarget() { return cfg.linkNewTab === false ? '_self' : '_blank'; }

        /* Invalidated when the conversation is reset mid-flight, so a pending
           reply can never leak into a fresh conversation. */
        var sendGen = 0;

        function formatPrice(value, currency) {
            return Number(value).toFixed(2) + (currency ? ' ' + currency : '');
        }

        function cardsEl(products) {
            var wrap = el('div', 'aichat-cards');
            products.forEach(function (p) {
                var card = el('div', 'aichat-card');
                var main = el('a', 'aichat-card-main', { target: linkTarget(), rel: 'noopener', href: p.url || '#' });
                var img = el('span', 'aichat-card-img' + (p.image ? '' : ' aichat-card-noimg'));
                if (p.image) { img.style.backgroundImage = 'url("' + p.image + '")'; }
                main.appendChild(img);
                main.appendChild(el('span', 'aichat-card-name', { text: p.name || p.sku }));
                if (typeof p.price === 'number' && p.price > 0) {
                    var priceEl = el('span', 'aichat-card-price');
                    priceEl.appendChild(el('span', '', { text: formatPrice(p.price, p.currency) }));
                    // Discounts sell: strike the old price and badge the cut.
                    if (typeof p.old_price === 'number' && p.old_price > p.price) {
                        priceEl.appendChild(el('del', 'aichat-card-oldprice', { text: formatPrice(p.old_price, p.currency) }));
                        if (p.discount_percent) {
                            priceEl.appendChild(el('span', 'aichat-card-discount', { text: '-' + p.discount_percent + '%' }));
                        }
                    }
                    main.appendChild(priceEl);
                }
                if (p.rating && p.rating.stars) {
                    var full = Math.round(p.rating.stars);
                    main.appendChild(el('span', 'aichat-card-rating', {
                        text: '★★★★★'.slice(0, full) + '☆☆☆☆☆'.slice(0, 5 - full)
                            + ' ' + p.rating.stars + ' (' + p.rating.count + ' ' + t('reviewsSuffix', 'reviews') + ')'
                    }));
                }
                if (p.label) { main.appendChild(el('span', 'aichat-card-tag', { text: p.label })); }
                card.appendChild(main);

                if (p.sku) {
                    var acts = el('div', 'aichat-card-actions');
                    // Add-to-cart on the PWA's own shared GraphQL cart. Only
                    // when the server says the product is directly addable
                    // (simple/virtual + in stock) — configurables deep-link.
                    if (cfg.cartButtons && p.add_to_cart) {
                        var cart = el('button', 'aichat-card-btn aichat-card-cart', {
                            type: 'button',
                            title: t('addToCart', 'Add to cart'),
                            'aria-label': t('addToCart', 'Add to cart'),
                            html: '<span class="aichat-plus">+</span>' + SVG_CART
                        });
                        cart.setAttribute('data-sku', p.sku);
                        acts.appendChild(cart);
                    }
                    var info = el('button', 'aichat-card-btn aichat-card-info', {
                        type: 'button', title: t('moreInfo', 'More info'), 'aria-label': t('moreInfo', 'More info'), html: SVG_INFO
                    });
                    info.setAttribute('data-sku', p.sku);
                    info.setAttribute('data-name', p.name || p.sku);
                    acts.appendChild(info);
                    card.appendChild(acts);
                }
                wrap.appendChild(card);
            });
            return wrap;
        }

        function chipsEl(items, starter) {
            var wrap = el('div', 'aichat-chips' + (starter ? ' aichat-starters' : ''));
            items.forEach(function (c) {
                var chip = el('button', 'aichat-chip', { type: 'button', text: c.label });
                chip.setAttribute('data-send', starter ? c.prompt : c.message);
                // Failswitch: the customer's bubble shows the label they
                // tapped, never the internal message behind it.
                chip.setAttribute('data-display', c.label);
                wrap.appendChild(chip);
            });
            return wrap;
        }

        function compareEl(items) {
            var table = el('table', 'aichat-compare-table');
            function addRow(cls, builder) {
                var tr = el('tr', 'aichat-cmp-' + cls);
                items.forEach(function (p) {
                    var td = el('td');
                    td.appendChild(builder(p));
                    tr.appendChild(td);
                });
                table.appendChild(tr);
            }
            addRow('img', function (p) {
                var a = el('a', '', { target: linkTarget(), rel: 'noopener', href: p.url || '#' });
                var img = el('span', 'aichat-cmp-img');
                if (p.image) { img.style.backgroundImage = 'url("' + p.image + '")'; }
                a.appendChild(img);
                return a;
            });
            addRow('name', function (p) {
                return el('a', '', { target: linkTarget(), rel: 'noopener', href: p.url || '#', text: p.name || p.sku });
            });
            addRow('price', function (p) {
                return el('span', '', { text: typeof p.price === 'number' ? formatPrice(p.price, p.currency) : '' });
            });
            addRow('rating', function (p) {
                return el('span', '', { text: p.rating_stars ? '★ ' + p.rating_stars + ' (' + p.rating_count + ')' : '—' });
            });
            addRow('stock', function (p) {
                return el('span', p.in_stock ? 'is-in' : 'is-out', {
                    text: p.in_stock ? t('inStock', 'In stock') : t('outOfStock', 'Out of stock')
                });
            });
            addRow('desc', function (p) { return el('span', '', { text: p.description || '' }); });
            var wrap = el('div', 'aichat-compare');
            wrap.appendChild(table);
            return wrap;
        }

        function feedbackEl(entry) {
            var fb = el('div', 'aichat-feedback');
            fb.setAttribute('data-mid', entry.message_id);
            var up = el('button', 'aichat-fb aichat-fb-up' + (entry.feedback === 1 ? ' is-active' : ''), {
                type: 'button', 'aria-label': t('helpful', 'Helpful'), html: SVG_UP
            });
            var down = el('button', 'aichat-fb aichat-fb-down' + (entry.feedback === -1 ? ' is-active' : ''), {
                type: 'button', 'aria-label': t('notHelpful', 'Not helpful'), html: SVG_DOWN
            });
            fb.appendChild(up);
            fb.appendChild(down);
            return fb;
        }

        function typeInto(bubble, text, finalHtml) {
            var parts = String(text).split(/(\s+)/), i = 0, shown = '';
            (function step() {
                if (i >= parts.length) {
                    bubble.innerHTML = finalHtml;
                    if (nearBottom()) { scrollDown(); }
                    updateScrollHint();
                    return;
                }
                shown += parts[i++];
                bubble.textContent = shown;
                if (nearBottom()) { scrollDown(); }
                updateScrollHint();
                setTimeout(step, 16);
            })();
        }

        function renderEntry(entry, typing, anchorTop) {
            var isUser = entry.role === 'user';
            var row = el('div', 'aichat-row aichat-row-' + (isUser ? 'user' : 'assistant'));
            if (!isUser) { row.appendChild(avatar()); }
            var col = el('div', 'aichat-col');

            var hasText = isUser || entry.role === 'error' || (entry.content && entry.content.trim() !== '');
            if (hasText) {
                var bubble = el('div', 'aichat-msg aichat-msg-' + (isUser ? 'user' : 'assistant')
                    + (entry.role === 'error' ? ' aichat-error' : ''));
                if (typing && !isUser && entry.content) {
                    col.appendChild(bubble);
                    typeInto(bubble, entry.content, renderMessage(entry.content));
                } else {
                    bubble.innerHTML = isUser ? escapeHtml(entry.content) : renderMessage(entry.content);
                    if (isUser && entry.imageThumb) {
                        var photo = el('img', 'aichat-msg-photo', { alt: '', src: entry.imageThumb });
                        bubble.insertBefore(photo, bubble.firstChild);
                    }
                    col.appendChild(bubble);
                }
            }
            if (entry.products && entry.products.length) { col.appendChild(cardsEl(entry.products)); }
            if (entry.compare && entry.compare.length > 1) { col.appendChild(compareEl(entry.compare)); }
            if (entry.chips && entry.chips.length) { col.appendChild(chipsEl(entry.chips, false)); }
            if (entry.ts) {
                var meta = el('div', 'aichat-meta');
                meta.innerHTML = escapeHtml(entry.ts) + (isUser ? ' <span class="aichat-ticks">✓✓</span>' : '');
                col.appendChild(meta);
            }
            if (!isUser && entry.role === 'assistant' && cfg.feedbackEnabled && entry.message_id) {
                col.appendChild(feedbackEl(entry));
            }
            row.appendChild(col);
            messages.appendChild(row);
            if (anchorTop) { scrollToRow(row); } else { scrollDown(); }
            updateScrollHint();
            return row;
        }

        function renderAll() {
            messages.innerHTML = '';
            if (transcript.length) {
                transcript.forEach(function (e) { renderEntry(e); });
            } else {
                if (cfg.welcomeMessage) {
                    renderEntry({ role: 'assistant', content: cfg.welcomeMessage });
                }
                if (cfg.starterPrompts && cfg.starterPrompts.length) {
                    messages.appendChild(starterBlock(cfg.starterPrompts));
                }
            }
            scrollDown();
        }

        /* Starter chips + (when vision is on) a photo hint chip that opens the
           picker directly — the feature is invisible unless something says it
           exists. */
        function starterBlock(chips) {
            var wrap = chipsEl(chips, true);
            if (cfg.visionEnabled && fileInput) {
                var photo = el('button', 'aichat-chip aichat-chip-photo', {
                    type: 'button', text: t('photoChip', '📷 Send a photo — I will find the product')
                });
                photo.setAttribute('data-action', 'photo');
                wrap.appendChild(photo);
            }
            return wrap;
        }

        /* Context-aware starters: ask the server what page the customer is on
           (via the SPA path) and swap the generic chips for specific ones.
           Best-effort — the generic chips are already rendered and stay if
           this fails or returns nothing new. */
        function refreshStarters() {
            if (transcript.length || !cfg.urls.starters) { return; }
            fetch(cfg.urls.starters + '?path=' + encodeURIComponent(window.location.pathname.replace(/^\//, '')), {
                credentials: 'same-origin',
                headers: { 'X-Requested-With': 'XMLHttpRequest' }
            }).then(function (r) { return r.json(); }).then(function (res) {
                if (!res || !res.chips || !res.chips.length || transcript.length) { return; }
                var current = messages.querySelector('.aichat-starters');
                if (current) {
                    current.parentNode.replaceChild(starterBlock(res.chips), current);
                }
            }).catch(function () { /* generic starters remain */ });
        }

        /* ----- photos ----- */
        function resizeImage(file, maxDim, quality, cb) {
            var img = new Image();
            img.onload = function () {
                try {
                    var scale = Math.min(1, maxDim / Math.max(img.width, img.height));
                    var canvas = document.createElement('canvas');
                    canvas.width = Math.max(1, Math.round(img.width * scale));
                    canvas.height = Math.max(1, Math.round(img.height * scale));
                    canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
                    cb(canvas.toDataURL('image/jpeg', quality));
                } catch (e) { cb(null); }
                URL.revokeObjectURL(img.src);
            };
            img.onerror = function () { URL.revokeObjectURL(img.src); cb(null); };
            img.src = URL.createObjectURL(file);
        }

        function setPendingImage(file) {
            if (!file || !/^image\//.test(file.type)) { return; }
            resizeImage(file, 1280, 0.85, function (dataUrl) {
                if (!dataUrl) { return; }
                pendingImage = dataUrl;
                resizeImage(file, 96, 0.7, function (thumb) {
                    pendingThumb = thumb;
                    if (preview) {
                        preview.querySelector('img').setAttribute('src', thumb || dataUrl);
                        preview.style.display = '';
                    }
                });
            });
        }

        function clearPendingImage() {
            pendingImage = null;
            pendingThumb = null;
            if (preview) {
                preview.style.display = 'none';
                preview.querySelector('img').setAttribute('src', '');
            }
            if (fileInput) { fileInput.value = ''; }
            if (cameraInput) { cameraInput.value = ''; }
        }

        function offerImage(file) {
            var needsConsent = cfg.visionConsentText && lsGet(CONSENT_KEY) !== '1';
            if (!needsConsent) {
                setPendingImage(file);
                return;
            }
            consentPendingFile = file;
            if (consentBox) { consentBox.style.display = ''; }
        }

        /* ----- send ----- */
        function send(message, display) {
            if (busy || (!message && !pendingImage)) { return; }
            busy = true;
            touchActivity();
            var starters = messages.querySelector('.aichat-starters');
            if (starters) { starters.remove(); }

            var image = pendingImage;
            var userEntry = { role: 'user', content: display || message, ts: timeNow() };
            if (pendingThumb) { userEntry.imageThumb = pendingThumb; }
            clearPendingImage();
            record(userEntry);
            renderEntry(userEntry);

            var typingRow = el('div', 'aichat-row aichat-row-assistant');
            typingRow.appendChild(avatar());
            var typingCol = el('div', 'aichat-col', {
                html: '<div class="aichat-msg aichat-msg-assistant aichat-typing"><span></span><span></span><span></span></div>'
            });
            typingRow.appendChild(typingCol);
            messages.appendChild(typingRow);
            scrollDown();

            // Staged waiting notes: a product answer takes two model passes
            // plus catalog work — silence reads as "broken", a narrated wait
            // reads as "working". Texts are translatable via widget config.
            var waitNote = el('div', 'aichat-wait-note');
            var waitTimers = [
                setTimeout(function () {
                    waitNote.textContent = t('waitSearching', 'Searching the catalog…');
                    typingCol.appendChild(waitNote);
                    scrollDown();
                }, 3500),
                setTimeout(function () {
                    waitNote.textContent = t('waitAlmost', 'One moment — comparing the options…');
                }, 12000)
            ];
            function clearWait() { waitTimers.forEach(clearTimeout); }

            var myGen = ++sendGen;
            function firePost() {
                return post(cfg.urls.send, {
                    message: message,
                    image: image || '',
                    form_key: cfg.formKey,
                    client: 'pwa',
                    pwa_cart_id: pwaGetCartId() || ''
                });
            }
            var formKeyRetried = false;
            firePost().then(function handleRes(res) {
                // Session expired mid-conversation: silently refresh the form
                // key from the config endpoint and retry ONCE.
                if (res && res.code === 'invalid_form_key' && !formKeyRetried) {
                    formKeyRetried = true;
                    return fetch(cfg.urls ? '/aichat/widget/config' : '/aichat/widget/config', {
                        credentials: 'same-origin',
                        headers: { 'X-Requested-With': 'XMLHttpRequest' }
                    }).then(function (r) { return r.json(); }).then(function (fresh) {
                        if (fresh && fresh.formKey) { cfg.formKey = fresh.formKey; }
                        return firePost().then(handleRes);
                    });
                }
                if (myGen !== sendGen) { return; } // conversation was reset
                clearWait();
                typingRow.remove();
                touchActivity();
                if (res && res.cart_adds && res.cart_adds.length) {
                    executeCartAdds(res.cart_adds);
                }
                var hasProducts = res && res.products && res.products.length;
                var hasChips = res && res.chips && res.chips.length;
                var hasCompare = res && res.compare && res.compare.length > 1;
                if (res && (res.reply || hasProducts || hasChips || hasCompare)) {
                    var entry = {
                        role: 'assistant', content: res.reply || '', ts: timeNow(),
                        products: res.products || [], chips: res.chips || [],
                        compare: res.compare || [],
                        message_id: res.message_id || null
                    };
                    record(entry);
                    renderEntry(entry, !!cfg.streamEnabled, true);
                    if (!isOpen()) { markUnread(); }
                } else {
                    var err = { role: 'error', content: (res && res.error) || t('genericError', 'Something went wrong.'), ts: timeNow() };
                    record(err);
                    renderEntry(err);
                }
            }).catch(function () {
                clearWait();
                if (myGen !== sendGen) { return; }
                typingRow.remove();
                var err = { role: 'error', content: t('networkError', 'Network error. Please try again.'), ts: timeNow() };
                record(err);
                renderEntry(err);
            }).then(function () { if (myGen === sendGen) { busy = false; } });
        }

        /* Conversational add-to-cart on the PWA: the server VALIDATED the add
           (stock, options) and handed it to us — we perform it against the
           PWA's own GraphQL cart and confirm inline. */
        function executeCartAdds(adds) {
            // Sequential: parallel adds raced guest-cart creation.
            adds.reduce(function (chain, add) {
                return chain.then(function () {
                    return addToPwaCart(add.sku, add.qty || 1).then(function () {
                    var note = el('div', 'aichat-cart-toast');
                    note.appendChild(el('span', '', {
                        text: '✓ ' + t('cartAddedToast', 'Added to your cart:') + ' ' + (add.name || add.sku)
                    }));
                    note.appendChild(el('a', 'aichat-card-viewcart', {
                        href: cfg.cartUrl || '/cart', text: t('viewCart', 'View cart')
                    }));
                    messages.appendChild(note);
                    scrollDown();
                    }).catch(function () {
                        var err = { role: 'error', content: t('addFailed', 'Could not add to cart. Please try from the product page.'), ts: timeNow() };
                        record(err);
                        renderEntry(err);
                    });
                });
            }, Promise.resolve());
        }

        /* ----- events ----- */
        launcher.addEventListener('click', function () { setOpen(true); refreshStarters(); });
        closeBtn.addEventListener('click', function () { setOpen(false); });
        if (teaser) {
            teaser.querySelector('.aichat-teaser-close').addEventListener('click', function (e) {
                e.stopPropagation();
                dismissTeaser();
            });
            teaser.querySelector('.aichat-teaser-text').addEventListener('click', function () { setOpen(true); });
        }

        resetBtn.addEventListener('click', function () {
            sendGen++;           // orphan any in-flight reply
            busy = false;        // and unlock the composer
            transcript = [];
            lsSet(TRANSCRIPT_KEY, '[]');
            renderAll();
            post(cfg.urls.reset, { form_key: cfg.formKey });
        });

        form.addEventListener('submit', function (e) {
            e.preventDefault();
            var message = textarea.value.trim();
            if (message || pendingImage) {
                textarea.value = '';
                send(message);
            }
        });
        textarea.addEventListener('keydown', function (e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                form.dispatchEvent(new Event('submit', { cancelable: true }));
            }
        });

        messages.addEventListener('scroll', updateScrollHint);
        scrollHint.querySelector('span').addEventListener('click', scrollDown);

        // Delegated clicks inside the message area (chips, info buttons, feedback).
        messages.addEventListener('click', function (e) {
            var chip = e.target.closest('.aichat-chip');
            if (chip) {
                if (chip.getAttribute('data-action') === 'photo') {
                    if (fileInput) { fileInput.click(); }
                    return;
                }
                var m = chip.getAttribute('data-send');
                if (m) { send(m, chip.getAttribute('data-display')); }
                return;
            }
            var cartBtn = e.target.closest('.aichat-card-cart');
            if (cartBtn) {
                e.preventDefault();
                if (cartBtn.disabled || cartBtn.classList.contains('is-added')) { return; }
                var cartSku = cartBtn.getAttribute('data-sku');
                if (!cartSku) { return; }
                cartBtn.disabled = true;
                addToPwaCart(cartSku).then(function () {
                    cartBtn.disabled = false;
                    cartBtn.classList.add('is-added');
                    cartBtn.innerHTML = SVG_CHECK;
                    cartBtn.title = t('added', 'Added to cart');
                    // "View cart" is a PLAIN link (not the SPA router), so it
                    // full-loads the app and the PWA cart is always correct
                    // even without the theme sync hook.
                    var actsWrap = cartBtn.closest('.aichat-card');
                    if (actsWrap && !actsWrap.querySelector('.aichat-card-viewcart')) {
                        var view = el('a', 'aichat-card-viewcart', {
                            href: cfg.cartUrl || '/cart', text: t('viewCart', 'View cart')
                        });
                        actsWrap.appendChild(view);
                    }
                }).catch(function () {
                    cartBtn.disabled = false;
                    cartBtn.title = t('addFailed', 'Could not add to cart. Please try from the product page.');
                    cartBtn.classList.add('is-error');
                    setTimeout(function () { cartBtn.classList.remove('is-error'); }, 2500);
                });
                return;
            }
            var info = e.target.closest('.aichat-card-info');
            if (info) {
                e.preventDefault();
                var sku = info.getAttribute('data-sku'), name = info.getAttribute('data-name');
                if (sku) {
                    var tmpl = cfg.infoPrompt || 'Tell me more about {name} (SKU {sku})';
                    send(tmpl.replace(/\{name\}/g, name || '').replace(/\{sku\}/g, sku).trim());
                }
                return;
            }
            var fb = e.target.closest('.aichat-fb');
            if (fb) {
                var wrap = fb.closest('.aichat-feedback');
                var mid = parseInt(wrap.getAttribute('data-mid'), 10);
                if (!mid) { return; }
                var value = fb.classList.contains('is-active') ? 0 : (fb.classList.contains('aichat-fb-up') ? 1 : -1);
                wrap.querySelectorAll('.aichat-fb').forEach(function (b) { b.classList.remove('is-active'); });
                if (value === 1) { wrap.querySelector('.aichat-fb-up').classList.add('is-active'); }
                else if (value === -1) { wrap.querySelector('.aichat-fb-down').classList.add('is-active'); }
                transcript.forEach(function (en) { if (en.message_id === mid) { en.feedback = value || null; } });
                lsSet(TRANSCRIPT_KEY, JSON.stringify(transcript));
                post(cfg.urls.feedback, { message_id: mid, value: value, form_key: cfg.formKey });

                var parent = wrap.parentNode;
                parent.querySelectorAll('.aichat-fb-comment, .aichat-fb-thanks').forEach(function (n) { n.remove(); });
                if (value === -1) {
                    var cf = el('form', 'aichat-fb-comment');
                    cf.setAttribute('data-mid', mid);
                    var inp = el('input', '', { type: 'text', maxlength: '500', placeholder: t('fbCommentPlaceholder', 'What went wrong? (optional)') });
                    cf.appendChild(inp);
                    cf.appendChild(el('button', '', { type: 'submit', text: t('fbCommentSend', 'Send') }));
                    wrap.parentNode.insertBefore(cf, wrap.nextSibling);
                    cf.addEventListener('submit', function (ev) {
                        ev.preventDefault();
                        var comment = inp.value.trim();
                        if (comment) {
                            post(cfg.urls.feedback, { message_id: mid, value: -1, comment: comment, form_key: cfg.formKey });
                        }
                        var thanks = el('div', 'aichat-fb-thanks', { text: t('fbThanks', 'Thank you for the feedback!') });
                        cf.parentNode.replaceChild(thanks, cf);
                    });
                    inp.focus();
                    scrollDown();
                }
            }
        });

        if (cfg.visionEnabled) {
            [fileInput, cameraInput].forEach(function (input) {
                input.addEventListener('change', function () {
                    if (this.files && this.files[0]) { offerImage(this.files[0]); }
                });
            });
            preview.querySelector('button').addEventListener('click', clearPendingImage);
            consentBox.querySelector('.aichat-consent-accept').addEventListener('click', function () {
                lsSet(CONSENT_KEY, '1');
                var file = consentPendingFile;
                consentPendingFile = null;
                consentBox.style.display = 'none';
                if (file) { setPendingImage(file); }
            });
            consentBox.querySelector('.aichat-consent-cancel').addEventListener('click', function () {
                consentPendingFile = null;
                consentBox.style.display = 'none';
                if (fileInput) { fileInput.value = ''; }
                if (cameraInput) { cameraInput.value = ''; }
            });
        }

        // Click outside closes the panel (mousedown decides inside/outside — a
        // clicked chip may already be removed from the DOM by click time).
        var pointerDownInside = false;
        document.addEventListener('mousedown', function (e) {
            pointerDownInside = root.contains(e.target);
        });
        document.addEventListener('click', function () {
            if (isOpen() && !pointerDownInside) { setOpen(false); }
        });

        /* ----- go ----- */
        renderAll();
        if (lsGet(OPEN_KEY) === '1') { setOpen(true); refreshStarters(); }
        if (teaser && ssGet(TEASER_KEY) !== '1' && !isOpen() && !transcript.length) {
            setTimeout(function () {
                if (ssGet(TEASER_KEY) !== '1' && !isOpen()) {
                    teaser.style.display = '';
                    teaser.classList.add('is-in');
                }
            }, 1200);
        }
        updateUnreadBadge();
    }
})();