/**
 * Live Chat Widget v2.0
 *
 * Embed on any website:
 *   <script src="https://your-domain.com/widget/v1.js" data-workspace="YOUR_TOKEN" data-color="#4F46E5"></script>
 *
 * The bundled "[Chat]" console prefix is intentionally neutral so a
 * customer's site visitor inspecting DevTools never sees the underlying
 * platform name. Brand text, "Powered by" credit, and all UI strings
 * are server-driven and follow the workspace's white-label settings.
 */
(function() {
    'use strict';

    const WIDGET_SCRIPT = document.currentScript;
    const TOKEN = WIDGET_SCRIPT?.getAttribute('data-workspace') || WIDGET_SCRIPT?.getAttribute('data-widget-id') || '';
    const PRIMARY = WIDGET_SCRIPT?.getAttribute('data-color') || '#4F46E5';
    const POSITION = WIDGET_SCRIPT?.getAttribute('data-position') || '';
    // BASE_URL is derived by stripping `/widget/v1.js` from the script
    // src. The earlier `new URL(src).origin` approach returned just the
    // protocol+host, which silently discarded any subdirectory path —
    // breaking every customer on a cPanel-style install like
    // `https://example.com/mailtrixy/public/widget/v1.js`. Those calls
    // were going to `https://example.com/api/widget/status` (the bare
    // domain), which is NOT the Laravel install — it's whatever lives
    // at the domain root, usually a 404 from Apache with no CORS
    // headers. From the visitor's browser POV that looked like a CORS
    // bug, but the real cause was the URL never reaching PHP.
    //
    // Stripping just `/widget/v1.js` (plus an optional ?query string)
    // preserves any preceding path so requests land on the correct
    // install for both root and subdirectory deployments.
    // Strips the script's filename suffix to leave just the install root.
    // Handles BOTH supported embed URLs:
    //   /widget.js          → recommended dynamic route (auto cache control)
    //   /widget/v1.js       → legacy static path (still works for old embeds)
    // Trailing `?query` strings (e.g. `?v=2` cache-busts) are stripped too.
    const BASE_URL = WIDGET_SCRIPT?.src
        ? WIDGET_SCRIPT.src.replace(/\/widget(?:\/v1)?\.js(\?.*)?$/, '')
        : '';

    if (!TOKEN || !BASE_URL) { console.error('[Chat] Missing data-workspace or invalid script URL.'); return; }
    if (window.__chatWidgetMounted) return;
    window.__chatWidgetMounted = true;

    // Color utilities
    function hexToRgb(hex) { const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16); return `${r},${g},${b}`; }
    function darken(hex, pct) { const n = parseInt(hex.slice(1),16); const r = Math.max(0,(n>>16)-Math.round(2.55*pct)); const g = Math.max(0,((n>>8)&0xFF)-Math.round(2.55*pct)); const b = Math.max(0,(n&0xFF)-Math.round(2.55*pct)); return `#${(r<<16|g<<8|b).toString(16).padStart(6,'0')}`; }
    const PRIMARY_RGB = hexToRgb(PRIMARY);
    const PRIMARY_DARK = darken(PRIMARY, 12);

    // API — returns the parsed JSON body (kept stable so all other
    // callers in this file don't have to be rewritten).
    async function api(method, path, body) {
        const opts = { method, headers: { 'Content-Type':'application/json', 'Accept':'application/json', 'Authorization':'Bearer '+TOKEN } };
        if (body && method !== 'GET') opts.body = JSON.stringify(body);
        const url = method === 'GET' && body ? BASE_URL+path+'?'+new URLSearchParams(body) : BASE_URL+path;
        const resp = await fetch(url, opts);
        return resp.json();
    }
    function unwrap(r) { return r?.data ?? r ?? {}; }

    async function loadConfig() {
        // We don't go through api() here so we can inspect the raw
        // HTTP status — most "widget doesn't load at all" reports are
        // really endpoint-missing (404 / 503), bad token (403), or CORS
        // rejections. The previous loadConfig() called .json()
        // unconditionally and lost all of that diagnostic information.
        const url = BASE_URL + '/api/widget/status';
        try {
            const resp = await fetch(url, {
                method: 'GET',
                headers: { 'Accept': 'application/json', 'Authorization': 'Bearer ' + TOKEN },
            });
            if (!resp.ok) {
                console.error('[Chat] Config endpoint returned HTTP ' + resp.status, 'url=' + url);
                return null;
            }
            const body = await resp.json().catch(() => null);
            if (!body) {
                console.error('[Chat] Config endpoint returned a non-JSON response.');
                return null;
            }
            if (body.success === false) {
                console.error('[Chat] Config endpoint rejected the request:', body.message || body);
                return null;
            }
            return body;
        } catch (e) {
            // Most often this is a CORS rejection (the embed origin is
            // not allowed by the platform) or a complete network
            // failure. Log the URL we attempted so the customer can
            // verify from DevTools' Network tab.
            console.error('[Chat] Config load failed:', e && e.message ? e.message : e, 'url=' + url);
            return null;
        }
    }

    // Styles
    function injectStyles() {
        const s = document.createElement('style');
        s.textContent = `
@keyframes mb-slide-up { from { opacity:0; transform:translateY(16px) scale(0.96); } to { opacity:1; transform:translateY(0) scale(1); } }
@keyframes mb-fade-in { from { opacity:0; } to { opacity:1; } }
@keyframes mb-bounce-in { 0% { transform:scale(0); } 50% { transform:scale(1.12); } 100% { transform:scale(1); } }
@keyframes mb-pulse-ring { 0% { transform:scale(1); opacity:0.6; } 100% { transform:scale(2.2); opacity:0; } }
@keyframes mb-typing-dot { 0%,60%,100% { transform:translateY(0); } 30% { transform:translateY(-4px); } }
@keyframes mb-msg-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }

#mb-chat-widget * { box-sizing:border-box; margin:0; padding:0; }
#mb-chat-widget { position:fixed; bottom:24px; right:24px; z-index:2147483647; font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; font-size:14px; line-height:1.5; }

/* Toggle button */
#mb-chat-toggle {
    width:64px; height:64px; border-radius:50%; border:none; cursor:pointer;
    background: linear-gradient(135deg, ${PRIMARY}, ${PRIMARY_DARK});
    color:#fff; display:flex; align-items:center; justify-content:center;
    box-shadow: 0 6px 20px rgba(${PRIMARY_RGB},0.4), 0 2px 8px rgba(0,0,0,0.08);
    transition: all 0.3s cubic-bezier(0.16,1,0.3,1); position:relative; overflow:visible;
}
#mb-chat-toggle:hover { transform:scale(1.08); box-shadow: 0 8px 28px rgba(${PRIMARY_RGB},0.5), 0 4px 12px rgba(0,0,0,0.1); }
#mb-chat-toggle:active { transform:scale(0.95); }
#mb-chat-toggle svg { width:28px; height:28px; transition: transform 0.3s; }
#mb-chat-toggle.mb-active svg { transform:rotate(90deg) scale(0.8); }

/* Pulse ring */
#mb-chat-toggle::before {
    content:''; position:absolute; inset:-4px; border-radius:50%;
    border:2px solid rgba(${PRIMARY_RGB},0.5); animation: mb-pulse-ring 2.5s ease-out infinite;
    pointer-events:none;
}
#mb-chat-toggle.mb-active::before { display:none; }

/* Unread badge */
.mb-unread-badge {
    position:absolute; top:-2px; right:-2px; min-width:20px; height:20px; border-radius:10px;
    background:#ef4444; color:#fff; font-size:11px; font-weight:700; display:flex; align-items:center;
    justify-content:center; padding:0 5px; border:2px solid #fff;
    animation: mb-bounce-in 0.4s cubic-bezier(0.16,1,0.3,1);
}

/* Chat window */
#mb-chat-window {
    display:none; position:absolute; bottom:80px; right:0; width:400px; height:580px;
    background:#fff; border-radius:20px; overflow:hidden; flex-direction:column;
    box-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 8px 20px rgba(0,0,0,0.06), 0 0 0 1px rgba(0,0,0,0.04);
    animation: mb-slide-up 0.35s cubic-bezier(0.16,1,0.3,1);
}
#mb-chat-window.mb-open { display:flex; }

/* Header */
#mb-chat-header {
    background: linear-gradient(135deg, ${PRIMARY}, ${PRIMARY_DARK});
    color:#fff; padding:20px 24px; position:relative; overflow:hidden;
}
#mb-chat-header::after {
    content:''; position:absolute; top:-50%; right:-30%; width:200px; height:200px;
    background:radial-gradient(circle, rgba(255,255,255,0.08) 0%, transparent 70%);
    border-radius:50%; pointer-events:none;
}
.mb-header-top { display:flex; align-items:center; justify-content:space-between; position:relative; z-index:1; }
.mb-header-info { display:flex; align-items:center; gap:12px; }
.mb-header-avatar {
    width:40px; height:40px; border-radius:12px; background:rgba(255,255,255,0.2);
    display:flex; align-items:center; justify-content:center; backdrop-filter:blur(8px);
    border:1px solid rgba(255,255,255,0.15);
}
.mb-header-avatar svg { width:22px; height:22px; }
.mb-header-title { font-size:16px; font-weight:700; letter-spacing:-0.01em; }
.mb-header-subtitle { font-size:12px; opacity:0.8; margin-top:1px; display:flex; align-items:center; gap:6px; }
.mb-online-dot { width:6px; height:6px; border-radius:50%; background:#34d399; display:inline-block; }
#mb-chat-close {
    width:32px; height:32px; border-radius:10px; background:rgba(255,255,255,0.12);
    border:1px solid rgba(255,255,255,0.1); color:#fff; cursor:pointer;
    display:flex; align-items:center; justify-content:center; transition:all 0.2s;
    backdrop-filter:blur(8px);
}
#mb-chat-close:hover { background:rgba(255,255,255,0.25); }
#mb-chat-close svg { width:16px; height:16px; }

/* Messages area */
#mb-chat-messages {
    flex:1; overflow-y:auto; padding:20px; display:flex; flex-direction:column; gap:6px;
    background:#fafbfc; scroll-behavior:smooth;
}
#mb-chat-messages::-webkit-scrollbar { width:4px; }
#mb-chat-messages::-webkit-scrollbar-thumb { background:#d1d5db; border-radius:4px; }
#mb-chat-messages::-webkit-scrollbar-track { background:transparent; }

/* Messages */
.mb-msg {
    max-width:82%; padding:10px 16px; font-size:14px; line-height:1.55; word-wrap:break-word;
    animation: mb-msg-in 0.3s cubic-bezier(0.16,1,0.3,1);
}
.mb-msg-bot {
    align-self:flex-start; background:#fff; color:#1f2937;
    border-radius:4px 16px 16px 16px;
    box-shadow:0 1px 3px rgba(0,0,0,0.06), 0 0 0 1px rgba(0,0,0,0.04);
}
.mb-msg-user {
    align-self:flex-end; color:#fff;
    background: linear-gradient(135deg, ${PRIMARY}, ${PRIMARY_DARK});
    border-radius:16px 4px 16px 16px;
    box-shadow:0 2px 8px rgba(${PRIMARY_RGB},0.25);
}
.mb-msg-time {
    font-size:10px; color:#9ca3af; margin-top:2px; padding:0 4px;
}
.mb-msg-time.mb-time-right { text-align:right; }

/* AI Knowledge Agent — citation pills + thumbs feedback */
.mb-ai-sources {
    display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; padding:0 4px;
    max-width:100%;
}
.mb-ai-source {
    font-size:10px; color:#4b5563; background:#f3f4f6; border:1px solid #e5e7eb;
    border-radius:999px; padding:2px 8px; text-decoration:none;
    display:inline-flex; align-items:center; gap:4px; max-width:200px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.mb-ai-source:hover { background:#e5e7eb; }
.mb-ai-source-num {
    background:${PRIMARY}; color:#fff; border-radius:999px;
    padding:0 5px; font-weight:600; font-size:9px;
}
.mb-ai-thumbs {
    display:flex; align-items:center; gap:6px; margin-top:6px; padding:0 4px;
    font-size:11px; color:#6b7280;
}
.mb-ai-thumb {
    background:#f9fafb; border:1px solid #e5e7eb; border-radius:8px;
    width:24px; height:24px; cursor:pointer; display:inline-flex;
    align-items:center; justify-content:center; font-size:13px; line-height:1;
    transition:all 0.15s ease;
}
.mb-ai-thumb:hover { background:#f3f4f6; }
.mb-ai-thumb.mb-thumbed { background:${PRIMARY}; color:#fff; border-color:${PRIMARY}; cursor:default; }
.mb-ai-thumbs-label { font-weight:500; }

/* Typing indicator */
.mb-typing { display:flex; gap:4px; padding:12px 16px; align-self:flex-start; }
.mb-typing span {
    width:6px; height:6px; background:#9ca3af; border-radius:50%; display:inline-block;
    animation: mb-typing-dot 1.2s ease-in-out infinite;
}
.mb-typing span:nth-child(2) { animation-delay:0.15s; }
.mb-typing span:nth-child(3) { animation-delay:0.3s; }

/* Input area */
#mb-chat-input-wrap {
    padding:16px 20px; border-top:1px solid #f3f4f6; display:flex; gap:10px; align-items:flex-end;
    background:#fff;
}
#mb-chat-input {
    flex:1; border:1.5px solid #e5e7eb; border-radius:14px; padding:10px 16px; font-size:14px;
    outline:none; resize:none; font-family:inherit; line-height:1.5; max-height:100px;
    transition: border-color 0.2s, box-shadow 0.2s; background:#fafbfc;
}
#mb-chat-input:focus { border-color:${PRIMARY}; box-shadow:0 0 0 3px rgba(${PRIMARY_RGB},0.1); background:#fff; }
#mb-chat-input::placeholder { color:#9ca3af; }
#mb-chat-send {
    width:40px; height:40px; border-radius:12px; border:none; cursor:pointer;
    background: linear-gradient(135deg, ${PRIMARY}, ${PRIMARY_DARK});
    color:#fff; display:flex; align-items:center; justify-content:center; flex-shrink:0;
    transition: all 0.2s; box-shadow:0 2px 8px rgba(${PRIMARY_RGB},0.3);
}
#mb-chat-send:hover { transform:scale(1.05); box-shadow:0 4px 12px rgba(${PRIMARY_RGB},0.4); }
#mb-chat-send:active { transform:scale(0.95); }
#mb-chat-send:disabled { opacity:0.4; cursor:not-allowed; transform:none; box-shadow:none; }
#mb-chat-send svg { width:18px; height:18px; }

/* Pre-chat form */
#mb-pre-chat {
    flex:1; padding:32px 24px; display:flex; flex-direction:column; justify-content:center;
    background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
}
.mb-pre-chat-icon {
    width:56px; height:56px; border-radius:16px; margin:0 auto 16px;
    background: linear-gradient(135deg, rgba(${PRIMARY_RGB},0.1), rgba(${PRIMARY_RGB},0.05));
    display:flex; align-items:center; justify-content:center;
    border:1px solid rgba(${PRIMARY_RGB},0.1);
}
.mb-pre-chat-icon svg { width:28px; height:28px; color:${PRIMARY}; }
.mb-pre-chat-title { text-align:center; font-size:18px; font-weight:700; color:#111827; margin-bottom:4px; }
.mb-pre-chat-desc { text-align:center; font-size:13px; color:#6b7280; margin-bottom:24px; line-height:1.5; }
#mb-pre-chat input {
    width:100%; padding:12px 16px; border:1.5px solid #e5e7eb; border-radius:12px;
    font-size:14px; margin-bottom:10px; outline:none; font-family:inherit;
    transition: border-color 0.2s, box-shadow 0.2s; background:#fff;
}
#mb-pre-chat input:focus { border-color:${PRIMARY}; box-shadow:0 0 0 3px rgba(${PRIMARY_RGB},0.1); }
#mb-pre-chat input::placeholder { color:#9ca3af; }
#mb-pre-start {
    width:100%; padding:13px; border:none; border-radius:14px; font-size:15px; font-weight:600;
    cursor:pointer; font-family:inherit; letter-spacing:-0.01em; color:#fff; margin-top:4px;
    background: linear-gradient(135deg, ${PRIMARY}, ${PRIMARY_DARK});
    box-shadow:0 4px 14px rgba(${PRIMARY_RGB},0.35);
    transition: all 0.2s;
}
#mb-pre-start:hover { box-shadow:0 6px 20px rgba(${PRIMARY_RGB},0.45); transform:translateY(-1px); }
#mb-pre-start:active { transform:translateY(0); }

/* Powered by */
.mb-powered {
    padding:8px; text-align:center; font-size:10px; color:#9ca3af; background:#fafbfc;
    border-top:1px solid #f3f4f6; letter-spacing:0.02em;
}
.mb-powered a { color:#6b7280; text-decoration:none; font-weight:600; }
.mb-powered a:hover { color:${PRIMARY}; }

/* Left position */
#mb-chat-widget.mb-left { right:auto; left:24px; }
#mb-chat-widget.mb-left #mb-chat-window { right:auto; left:0; }

/* Mobile */
@media (max-width: 480px) {
    #mb-chat-widget { bottom:16px; right:16px; }
    #mb-chat-widget.mb-left { right:auto; left:16px; }
    #mb-chat-window { width:calc(100vw - 32px); right:-8px; height:70vh; bottom:76px; border-radius:16px; }
    #mb-chat-widget.mb-left #mb-chat-window { right:auto; left:-8px; }
    #mb-chat-toggle { width:56px; height:56px; }
    #mb-chat-toggle svg { width:24px; height:24px; }
}
`;
        document.head.appendChild(s);
    }

    // English fallback strings. The server endpoint /api/widget/status
    // sends translated copies in `config.texts` based on the workspace's
    // chosen widget language (Settings → Channels → Live Chat → Widget
    // Language). If the server didn't send a key, we fall back to the
    // English string here so the widget never renders an empty button.
    const T_DEFAULT = {
        online_now:         'Online now',
        reply_fast:         'We typically reply fast',
        start_conversation: 'Start a conversation',
        pre_chat_intro:     "We're here to help. Send us a message and we'll respond as soon as we can.",
        your_name:          'Your name',
        your_email:         'Your email',
        start_chat:         'Start Chat',
        connecting:         'Connecting...',
        type_message:       'Type your message...',
        error_generic:      'Sorry, something went wrong. Please try again.',
        powered_by:         'Powered by',
        // New for visible-validation feedback. Customers were reporting
        // "Start Chat button does nothing" — the click handler was just
        // painting the input borders red and returning silently when the
        // name or email was empty. That looks identical to a dead button
        // from the visitor's POV. These strings drive the error banner
        // we now show beneath the form.
        validation_required: 'Please enter your name and email to start chatting.',
        validation_invalid_email: 'That email address doesn\'t look valid.',
    };

    function buildWidget(config) {
        const d = unwrap(config);
        const name = d?.company_name || d?.name || 'Chat with us';
        const welcome = d?.welcome_message || 'Hi there! How can we help you today?';
        const online = d?.online !== false;
        const pos = POSITION || d?.position || 'bottom-right';
        const isLeft = pos === 'bottom-left';
        // Merge server-sent translations over English defaults — missing
        // keys gracefully fall back to English so partial translations
        // never produce empty buttons.
        const T = Object.assign({}, T_DEFAULT, (d && d.texts) || {});

        const c = document.createElement('div');
        c.id = 'mb-chat-widget';
        if (isLeft) c.classList.add('mb-left');
        c.setAttribute('role','complementary');
        c.setAttribute('aria-label','Live chat widget');
        c.innerHTML = `
<div id="mb-chat-window" role="dialog" aria-label="Chat window">
    <div id="mb-chat-header">
        <div class="mb-header-top">
            <div class="mb-header-info">
                <div class="mb-header-avatar">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"/></svg>
                </div>
                <div>
                    <div class="mb-header-title">${name}</div>
                    <div class="mb-header-subtitle">${online ? `<span class="mb-online-dot"></span> ${T.online_now}` : T.reply_fast}</div>
                </div>
            </div>
            <button id="mb-chat-close" aria-label="Close chat"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg></button>
        </div>
    </div>
    <div id="mb-pre-chat">
        <div class="mb-pre-chat-icon">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"/></svg>
        </div>
        <div class="mb-pre-chat-title">${T.start_conversation}</div>
        <div class="mb-pre-chat-desc">${T.pre_chat_intro}</div>
        <input type="text" id="mb-pre-name" placeholder="${T.your_name}" aria-label="${T.your_name}">
        <input type="email" id="mb-pre-email" placeholder="${T.your_email}" aria-label="${T.your_email}">
        <div id="mb-pre-error" role="alert" aria-live="polite" style="display:none; margin-bottom:8px; padding:8px 12px; background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.25); border-radius:8px; color:#dc2626; font-size:12px; line-height:1.4;"></div>
        <button id="mb-pre-start">${T.start_chat}</button>
    </div>
    <div id="mb-chat-messages" style="display:none;" aria-live="polite"></div>
    <div id="mb-chat-input-wrap" style="display:none;">
        <input type="text" id="mb-chat-input" placeholder="${T.type_message}" aria-label="${T.type_message}" autocomplete="off">
        <button id="mb-chat-send" aria-label="Send message">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"/></svg>
        </button>
    </div>
    ${(config && config.show_branding !== false && config.brand_name) ? `<div class="mb-powered">${T.powered_by} <a href="${BASE_URL}" target="_blank" rel="noopener">${config.brand_name}</a></div>` : ''}
</div>
<button id="mb-chat-toggle" aria-label="Open live chat">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.363.3.023.575.175.74.44l2.44 3.96 2.44-3.96c.165-.265.44-.417.74-.44a47.518 47.518 0 003.238-.363c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"/></svg>
</button>`;
        document.body.appendChild(c);
        return { welcome, online };
    }

    function formatTime() {
        const now = new Date();
        return now.toLocaleTimeString([], { hour:'2-digit', minute:'2-digit' });
    }

    function initChat(welcome) {
        const toggle = document.getElementById('mb-chat-toggle');
        const chatWindow = document.getElementById('mb-chat-window');
        const close = document.getElementById('mb-chat-close');
        const preChat = document.getElementById('mb-pre-chat');
        const messages = document.getElementById('mb-chat-messages');
        const inputWrap = document.getElementById('mb-chat-input-wrap');
        const input = document.getElementById('mb-chat-input');
        const sendBtn = document.getElementById('mb-chat-send');
        const preStart = document.getElementById('mb-pre-start');
        const preName = document.getElementById('mb-pre-name');
        const preEmail = document.getElementById('mb-pre-email');

        let sessionId = localStorage.getItem('mb_session_' + TOKEN);
        let isOpen = false;
        let lastMessageCount = 0;

        // ID dedupe for the /history poll. Seeded with the visitor's own
        // message id when sendMessage() succeeds — without that, the poll 4s
        // later would re-render the visitor's row as a fresh message (the
        // sender_type filter catches most of them, but the id dedupe is a
        // belt-and-braces guard against legacy rows or future schema drift).
        const seenIds = new Set();

        // Timestamp (ms) of the most recent message we've rendered.
        // Persisted to localStorage so a page reload doesn't replay the
        // entire chat history into the widget. The 4s poll only renders
        // messages strictly newer than this.
        const lastSeenKey = 'mb_last_seen_' + TOKEN;
        let lastSeenAt = parseInt(localStorage.getItem(lastSeenKey) || '0', 10) || 0;
        function bumpLastSeen(m) {
            const ts = Date.parse(m.created_at || m.sent_at || m.timestamp || '');
            if (!isNaN(ts) && ts > lastSeenAt) {
                lastSeenAt = ts;
                localStorage.setItem(lastSeenKey, String(lastSeenAt));
            }
        }

        function openWindow() {
            isOpen = true;
            chatWindow.classList.add('mb-open');
            toggle.classList.add('mb-active');
            toggle.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>';
            if (sessionId) {
                preChat.style.display = 'none';
                messages.style.display = 'flex';
                inputWrap.style.display = 'flex';
                input.focus();
            }
        }

        function closeWindow() {
            isOpen = false;
            chatWindow.classList.remove('mb-open');
            toggle.classList.remove('mb-active');
            toggle.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.068.157 2.148.279 3.238.363.3.023.575.175.74.44l2.44 3.96 2.44-3.96c.165-.265.44-.417.74-.44a47.518 47.518 0 003.238-.363c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z"/></svg>';
        }

        toggle.addEventListener('click', () => { isOpen ? closeWindow() : openWindow(); });
        close.addEventListener('click', closeWindow);

        function addMessage(text, type, aiMeta) {
            // Remove typing indicator if present
            const typing = messages.querySelector('.mb-typing');
            if (typing) typing.remove();

            const wrap = document.createElement('div');
            wrap.style.cssText = type === 'user' ? 'display:flex;flex-direction:column;align-items:flex-end;' : 'display:flex;flex-direction:column;align-items:flex-start;';

            const div = document.createElement('div');
            div.className = 'mb-msg mb-msg-' + type;
            div.textContent = text;
            wrap.appendChild(div);

            // Knowledge Agent extras — citation pills + thumbs UI under
            // bot messages. Only renders when the server attached the
            // structured ai metadata (i.e. real KB-grounded answers; not
            // fallback messages, agent typing, or visitor echoes).
            if (type === 'bot' && aiMeta && !aiMeta.is_fallback) {
                const sources = Array.isArray(aiMeta.cited_sources) ? aiMeta.cited_sources : [];
                // Only show sources actually referenced inline as [N].
                const refs = (text.match(/\[(\d+)\]/g) || []).map(s => parseInt(s.slice(1, -1), 10));
                const seen = {};
                const used = refs.filter(n => {
                    if (seen[n]) return false;
                    seen[n] = true;
                    return sources.some(s => Number(s.index) === n);
                });
                if (used.length > 0) {
                    const sourcesEl = document.createElement('div');
                    sourcesEl.className = 'mb-ai-sources';
                    used.forEach(n => {
                        const src = sources.find(s => Number(s.index) === n);
                        if (!src) return;
                        const el = src.url
                            ? document.createElement('a')
                            : document.createElement('span');
                        el.className = 'mb-ai-source';
                        if (src.url) { el.href = src.url; el.target = '_blank'; el.rel = 'noopener'; }
                        const numEl = document.createElement('span');
                        numEl.className = 'mb-ai-source-num';
                        numEl.textContent = String(n);
                        el.appendChild(numEl);
                        el.appendChild(document.createTextNode(' ' + (src.title || 'Source')));
                        sourcesEl.appendChild(el);
                    });
                    wrap.appendChild(sourcesEl);
                }

                // Thumbs feedback — server hides links when the row has no
                // token (playground / test calls), so just gate on URL
                // presence. Clicking fires-and-forgets to the public
                // signed endpoint; we update local state immediately so
                // the visitor sees feedback regardless of network outcome.
                if (aiMeta.feedback_url_up && aiMeta.feedback_url_down) {
                    const thumbsEl = document.createElement('div');
                    thumbsEl.className = 'mb-ai-thumbs';
                    const label = document.createElement('span');
                    label.className = 'mb-ai-thumbs-label';
                    label.textContent = 'Was this helpful?';
                    thumbsEl.appendChild(label);

                    const mkBtn = (emoji, url) => {
                        const b = document.createElement('button');
                        b.className = 'mb-ai-thumb';
                        b.type = 'button';
                        b.textContent = emoji;
                        b.addEventListener('click', () => {
                            if (b.classList.contains('mb-thumbed')) return;
                            // Mark both thumbs spent so the visitor can't
                            // contradict themselves and so we don't double-fire.
                            thumbsEl.querySelectorAll('.mb-ai-thumb').forEach(el => {
                                el.classList.remove('mb-thumbed');
                                el.disabled = true;
                            });
                            b.classList.add('mb-thumbed');
                            // fire-and-forget; backend is idempotent
                            try { fetch(url, { method: 'GET', mode: 'no-cors', keepalive: true }); } catch (e) {}
                            label.textContent = 'Thanks for the feedback!';
                        });
                        return b;
                    };
                    thumbsEl.appendChild(mkBtn('👍', aiMeta.feedback_url_up));
                    thumbsEl.appendChild(mkBtn('👎', aiMeta.feedback_url_down));
                    wrap.appendChild(thumbsEl);
                }
            }

            const time = document.createElement('div');
            time.className = 'mb-msg-time' + (type === 'user' ? ' mb-time-right' : '');
            time.textContent = formatTime();
            wrap.appendChild(time);

            messages.appendChild(wrap);
            messages.scrollTop = messages.scrollHeight;
        }

        function showTyping() {
            if (messages.querySelector('.mb-typing')) return;
            const t = document.createElement('div');
            t.className = 'mb-typing';
            t.innerHTML = '<span></span><span></span><span></span>';
            messages.appendChild(t);
            messages.scrollTop = messages.scrollHeight;
        }

        // Pre-chat error region helper. Shows a visible message above the
        // Start button instead of relying on input border colour alone
        // (which made the button look "dead" to visitors who skipped
        // filling in the form).
        const preError = document.getElementById('mb-pre-error');
        function showPreError(msg) {
            preError.textContent = msg;
            preError.style.display = 'block';
        }
        function hidePreError() {
            preError.style.display = 'none';
            preError.textContent = '';
        }

        // Minimal client-side email format check. The server still
        // validates server-side; this is just to catch obvious typos
        // (no @, no domain) before the round trip.
        function looksLikeEmail(v) {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
        }

        // Pre-chat form
        preStart.addEventListener('click', async () => {
            const name = preName.value.trim();
            const email = preEmail.value.trim();

            // Required fields — show visible error AND red border.
            // Previously this branch returned silently after only
            // setting borders red, which made the Start button look
            // completely broken on every install where the visitor
            // forgot a field. That was the #1 "Start Chat does
            // nothing" support complaint.
            if (!name || !email) {
                if (!name) preName.style.borderColor = '#ef4444';
                if (!email) preEmail.style.borderColor = '#ef4444';
                showPreError(T.validation_required);
                return;
            }

            // Format check — same UX as the required-field path.
            if (!looksLikeEmail(email)) {
                preEmail.style.borderColor = '#ef4444';
                showPreError(T.validation_invalid_email);
                return;
            }

            hidePreError();
            preStart.textContent = T.connecting;
            preStart.disabled = true;
            try {
                const raw = await api('POST', '/api/widget/start-chat', { name, email });
                const data = unwrap(raw);
                if (data.session_id) {
                    sessionId = data.session_id;
                    localStorage.setItem('mb_session_' + TOKEN, sessionId);
                    preChat.style.display = 'none';
                    messages.style.display = 'flex';
                    inputWrap.style.display = 'flex';
                    addMessage(welcome, 'bot');
                    input.focus();
                } else {
                    // Server responded but didn't return a session_id —
                    // surface whatever message the server sent (validation
                    // errors etc.) instead of failing silently.
                    preStart.textContent = T.start_chat;
                    preStart.disabled = false;
                    const serverMsg = (raw && (raw.message || raw.error)) || (data && data.message)
                        || T.error_generic;
                    showPreError(serverMsg);
                    console.error('[Chat] No session_id', raw);
                }
            } catch (e) {
                // Network failure / CORS rejection / endpoint 404. Show
                // the visitor a generic error AND log enough context to
                // debug from the customer's browser DevTools — without
                // exposing internal stack traces in the UI.
                preStart.textContent = T.start_chat;
                preStart.disabled = false;
                showPreError(T.error_generic);
                console.error('[Chat] Start failed:', e && e.message ? e.message : e);
            }
        });

        // Reset border + clear error on input focus so the form recovers
        // gracefully when the visitor fixes their input.
        preName.addEventListener('focus',  () => { preName.style.borderColor = '';  hidePreError(); });
        preEmail.addEventListener('focus', () => { preEmail.style.borderColor = ''; hidePreError(); });

        // Send message
        async function sendMessage() {
            const text = input.value.trim();
            if (!text || !sessionId) return;
            input.value = '';
            sendBtn.disabled = true;
            addMessage(text, 'user');
            showTyping();

            try {
                const raw = await api('POST', '/api/widget/send-message', { session_id:sessionId, message:text });
                const data = unwrap(raw);

                // Server returns the stored message envelope: { id, body,
                // sender_type, sender_name, created_at }. Seed seenIds with
                // its UUID and bump lastSeenAt to its timestamp so the 4-second
                // /history poll doesn't try to repaint the visitor's own row.
                const ownId = data.id || raw.id || null;
                if (ownId != null) seenIds.add(ownId);

                const ownTs = Date.parse(data.created_at || raw.created_at || '');
                if (!isNaN(ownTs) && ownTs > lastSeenAt) {
                    lastSeenAt = ownTs;
                    localStorage.setItem(lastSeenKey, String(lastSeenAt));
                }
            } catch(e) {
                addMessage(T.error_generic, 'bot');
            }
            sendBtn.disabled = false;
            input.focus();
        }

        sendBtn.addEventListener('click', sendMessage);
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
        });

        // Poll /history every 4s for new agent replies. Two guards:
        //
        //  - seenIds: per-session UUID dedupe so a slow poll arriving after
        //    a faster one can't repaint a row we already rendered.
        //
        //  - lastSeenAt: persisted to localStorage. On reload, history would
        //    otherwise dump the entire transcript back into the empty widget
        //    on the first poll — this skips anything older than the most
        //    recent row we'd previously rendered.
        setInterval(async () => {
            if (!sessionId || !isOpen) return;
            try {
                const raw  = await api('GET', '/api/widget/history', { session_id: sessionId });
                const data = unwrap(raw);
                const msgs = data.messages || raw.messages || [];

                msgs.forEach(m => {
                    const id = m.id || m.uuid;
                    if (id != null) {
                        if (seenIds.has(id)) return;
                        seenIds.add(id);
                    }

                    // Skip anything older than the most recent message we
                    // already rendered — keeps reload-with-active-session
                    // from re-painting the whole history.
                    const ts = Date.parse(m.created_at || m.sent_at || m.timestamp || '');
                    if (!isNaN(ts) && ts <= lastSeenAt) return;

                    // Skip anything sent BY the visitor — these are already
                    // shown locally in sendMessage(). Server stores them as
                    // sender_type='contact'; some legacy rows may say
                    // 'visitor'; direction='inbound' is the canonical signal.
                    const isVisitor =
                        m.direction === 'inbound' ||
                        m.sender_type === 'contact' ||
                        m.sender_type === 'visitor';
                    if (isVisitor) {
                        // Still bump lastSeenAt so we don't replay visitor
                        // rows after reload either.
                        bumpLastSeen(m);
                        return;
                    }

                    addMessage(m.body || m.text || m.message, 'bot', m.ai);
                    bumpLastSeen(m);
                });

                lastMessageCount = msgs.length;
            } catch (e) {}
        }, 4000);
    }

    // Boot
    loadConfig().then(config => {
        if (!config || config.success === false) { console.error('[Chat] Widget disabled or not found.'); return; }
        injectStyles();
        const { welcome } = buildWidget(config);
        initChat(welcome);
    });
})();
