/**
 * SiteStaffr Fixed Position Widget - UI Wrapper
 *
 * Thin UI wrapper for the fixed floating widget (bottom-right corner).
 * All core voice logic (WebRTC, WebSocket, audio) is in sitestaffr-core.js
 * Text chat logic is in sitestaffr-text-chat.js
 *
 * @package    SiteStaffr
 * @since      0.11.93
 */

(function() {
	'use strict';

	// Cross-widget panel coordination — only one panel open at a time
	let floatingWidgetInstance = null;

	document.addEventListener('sitestaffr:panel-opening', () => {
		if (floatingWidgetInstance) {
			// Close own panels if open
			if (floatingWidgetInstance.chatPanelOpen) {
				floatingWidgetInstance.closeChatPanel();
			} else if (floatingWidgetInstance.landingCardOpen) {
				floatingWidgetInstance.closeLandingCard();
			}
			// Hide widget when another widget's panel opens
			if (!floatingWidgetInstance._isOpeningOwnPanel) {
				floatingWidgetInstance.widget.style.display = 'none';
			}
		}
	});

	document.addEventListener('sitestaffr:panel-closed', () => {
		if (floatingWidgetInstance) {
			floatingWidgetInstance.widget.style.display = '';
		}
	});

	// Escape key dismisses floating widget panel
	document.addEventListener('keydown', (e) => {
		if (e.key === 'Escape' && floatingWidgetInstance) {
			if (floatingWidgetInstance.chatPanelOpen) {
				floatingWidgetInstance.closeChatPanel();
			} else if (floatingWidgetInstance.landingCardOpen) {
				floatingWidgetInstance.closeLandingCard();
			}
		}
	});

	function showSiteStaffrGlobalNotice(message, timeoutMs = 6000) {
		if (!message) return;

		let notice = document.getElementById('sitestaffr-global-notice');
		if (!notice) {
			notice = document.createElement('div');
			notice.id = 'sitestaffr-global-notice';
			notice.setAttribute('role', 'status');
			notice.setAttribute('aria-live', 'polite');
			notice.style.position = 'fixed';
			notice.style.left = '50%';
			notice.style.bottom = '24px';
			notice.style.transform = 'translateX(-50%)';
			notice.style.background = 'rgba(185, 28, 28, 0.96)';
			notice.style.color = '#fff';
			notice.style.padding = '10px 14px';
			notice.style.borderRadius = '8px';
			notice.style.boxShadow = '0 10px 30px rgba(0,0,0,0.22)';
			notice.style.fontSize = '13px';
			notice.style.lineHeight = '1.4';
			notice.style.maxWidth = 'min(92vw, 560px)';
			notice.style.zIndex = '999999';
			notice.style.opacity = '0';
			notice.style.transition = 'opacity .18s ease';
			notice.style.pointerEvents = 'none';
			document.body.appendChild(notice);
		}

		notice.textContent = message;
		notice.style.opacity = '1';

		if (window.sitestaffrNoticeTimer) {
			clearTimeout(window.sitestaffrNoticeTimer);
		}
		window.sitestaffrNoticeTimer = setTimeout(() => {
			notice.style.opacity = '0';
		}, timeoutMs);
	}

	/**
	 * SiteStaffr Fixed Widget UI
	 */
	class SiteStaffrWidget {
		constructor(element) {
			this.widget = element;
			this.button = element.querySelector('.sitestaffr-widget-button');
			this.tooltip = element.querySelector('.sitestaffr-tooltip-text');
			this.idleText = element.dataset.idleText || '';

			// Mode configuration
			this.mode = this.widget.dataset.mode || sitestaffr_widget_data.mode || 'voice';
			this.soundsEnabled = sitestaffr_widget_data.sounds_enabled !== false;
			this.position = this.widget.dataset.position || 'bottom-right';

			// Chat state
			this.chatInstance = null;
			this.panelEl = null;
			this.landingCardEl = null;
			this.chatPanelOpen = false;
			this.landingCardOpen = false;
			this._streamingBubble = null;
			this._isOpeningOwnPanel = false;

			// Get configuration from data attributes
			const config = {
				siteId: this.widget.dataset.siteId,
				ajaxUrl: sitestaffr_widget_data.ajax_url || '',
				middlewareUrl: sitestaffr_widget_data.middleware_url || '',
				pluginUrl: sitestaffr_widget_data.plugin_url || '',
				channelSource: 'widget',
				persona: this.widget.dataset.persona || '',
			};

			this.config = config;

			// Only require browser voice support for voice modes
			const needsVoice = this.mode === 'voice' || this.mode === 'both';
			if (needsVoice && !this.checkBrowserSupport()) {
				// Fallback to text-only if browser doesn't support voice
				if (this.mode === 'both') {
					this.mode = 'text';
				} else {
					this.showStaticError(sitestaffr_widget_data.strings.permission || 'Browser not supported');
					return;
				}
			}

			// Initialize voice core for voice-capable modes
			if (needsVoice && this.checkBrowserSupport()) {
				this.core = new SiteStaffrCore(config, {
					onStateChange: (state, oldState) => this.handleStateChange(state, oldState),
					onError: (message) => this.handleError(message),
					onConversationStart: () => this.handleConversationStart(),
					onConversationEnd: (reason) => this.handleConversationEnd(reason),
					onSearchStart: (query) => this.showSearchIndicator(query),
					onSearchEnd: () => this.hideSearchIndicator(),
					onMicDenied: () => this.handleMicDenied(),
					onMinutesExhausted: () => this.handleMinutesExhausted(),
				});
			} else {
				this.core = null;
			}

			// Event listeners
			this.button.addEventListener('click', () => this.handleButtonClick());

			// beforeunload for chat save
			window.addEventListener('beforeunload', () => {
				if (this.chatInstance && !this.chatInstance.ended) {
					this.chatInstance.saveViaBeacon();
				}
			});

			// Current state for UI
			this.currentState = 'idle';
			this.inConversation = false;

			this.initTooltipAutoDismiss();

			floatingWidgetInstance = this;
		}

			initTooltipAutoDismiss() {
			if (sitestaffr_widget_data.tooltipBehavior !== 'auto') return;
			if (!this.widget.classList.contains('sitestaffr-tooltip--auto-dismiss')) return;

			var tooltipEl = this.widget.querySelector('.sitestaffr-widget-tooltip');
			if (tooltipEl) {
				tooltipEl.addEventListener('animationend', () => {
					this.widget.classList.remove('sitestaffr-tooltip--auto-dismiss');
				}, { once: true });
			}

			if (this.button) {
				this.button.addEventListener('animationend', (e) => {
					if (e.animationName === 'sitestaffr-bounce-nudge') {
						this.button.style.animation = '';
					}
				}, { once: true });
			}
		}

		/**
		 * Check browser voice support
		 */
		checkBrowserSupport() {
			return !!(
				navigator.mediaDevices &&
				navigator.mediaDevices.getUserMedia &&
				window.MediaRecorder &&
				window.WebSocket
			);
		}

		/**
		 * Handle button click — mode-aware routing
		 */
		async handleButtonClick() {
			// Inactive plan — always show contact form
			if (sitestaffr_widget_data.entitlement === 'inactive') {
				if (this.chatPanelOpen) {
					this.closeChatPanel();
				} else {
					this.openChatPanel();
				}
				return;
			}

			// GTM dataLayer: widget opened
			(window.dataLayer = window.dataLayer || []).push(Object.assign({
				event: 'sitestaffr_widget_open',
				sitestaffr_mode: this.mode,
				sitestaffr_widget_type: 'floating',
			}, window._sitestaffrGTM.pageContext()));

			if (this.mode === 'voice') {
				await this.core.toggleConversation();
			} else if (this.mode === 'text') {
				this.toggleChatPanel();
			} else if (this.mode === 'both') {
				// If chat or voice is already active, handle accordingly
				if (this.chatPanelOpen) {
					this.closeChatPanel();
				} else if (this.inConversation) {
					await this.core.toggleConversation();
				} else if (this.landingCardOpen) {
					this.closeLandingCard();
				} else {
					this.openLandingCard();
				}
			}
		}

		/* =============================================
		   LANDING CARD (both mode)
		   ============================================= */

		createLandingCard() {
			if (this.landingCardEl) return this.landingCardEl;

			const card = document.createElement('div');
			card.className = `sitestaffr-landing-card position-${this.position}`;
			card.innerHTML = `
				<button type="button" class="sitestaffr-landing-card-close" aria-label="Close">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
				</button>
				<div class="sitestaffr-landing-card-header">
					<h3>How would you like to connect?</h3>
					<p>Choose your preferred way to chat</p>
				</div>
				<div class="sitestaffr-landing-card-tiles">
					<div class="sitestaffr-landing-tile" data-choice="voice">
						<div class="sitestaffr-landing-tile-icon">
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
								<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
								<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
								<line x1="12" y1="19" x2="12" y2="23"></line>
								<line x1="8" y1="23" x2="16" y2="23"></line>
							</svg>
						</div>
						<span class="sitestaffr-landing-tile-label">Voice</span>
						<span class="sitestaffr-landing-tile-desc">Talk with our AI assistant</span>
					</div>
					<div class="sitestaffr-landing-tile" data-choice="text">
						<div class="sitestaffr-landing-tile-icon">
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
								<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
							</svg>
						</div>
						<span class="sitestaffr-landing-tile-label">Text</span>
						<span class="sitestaffr-landing-tile-desc">Type your questions</span>
					</div>
				</div>
			`;

			// Tile click handlers
			card.querySelector('[data-choice="voice"]').addEventListener('click', async () => {
				this.closeLandingCard();
				if (this.core) {
					await this.core.toggleConversation();
				}
			});

			card.querySelector('[data-choice="text"]').addEventListener('click', () => {
				this.closeLandingCard();
				this.openChatPanel();
			});

			card.querySelector('.sitestaffr-landing-card-close').addEventListener('click', () => {
				this.closeLandingCard();
			});

			// Propagate widget CSS variables to card (appended to body).
			const widgetStyle = getComputedStyle(this.widget);
			['--widget-bg-color', '--widget-hover-bg-color', '--widget-icon-color'].forEach(prop => {
				const val = widgetStyle.getPropertyValue(prop).trim();
				if (val) card.style.setProperty(prop, val);
			});

			// Propagate tooltip-bottom class so panel can raise itself above the raised widget.
			if (this.widget.classList.contains('sitestaffr-tooltip--bottom')) {
				card.classList.add('sitestaffr-tooltip--bottom');
			}

			document.body.appendChild(card);
			this.landingCardEl = card;
			return card;
		}

		openLandingCard() {
			const card = this.createLandingCard();
			this.widget.classList.add('panel-open');
			// Force reflow for animation
			void card.offsetWidth;
			this._isOpeningOwnPanel = true;
			document.dispatchEvent(new CustomEvent('sitestaffr:panel-opening'));
			this._isOpeningOwnPanel = false;
			card.classList.add('is-open');
			this.landingCardOpen = true;
		}

		closeLandingCard() {
			if (this.landingCardEl) {
				this.landingCardEl.classList.remove('is-open');
				this.widget.classList.remove('panel-open');
			}
			this.landingCardOpen = false;
		}

		/* =============================================
		   CHAT PANEL
		   ============================================= */

		createChatPanel() {
			if (this.panelEl) return this.panelEl;

			const settings = window.sitestaffr_widget_data || {};
			const businessName = 'AI Assistant';

			const panel = document.createElement('div');
			panel.className = `sitestaffr-chat-panel position-${this.position}`;

			// Mute button SVGs
			const speakerOnSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>';
			const speakerOffSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line></svg>';

			panel.innerHTML = `
				<div class="sitestaffr-chat-header">
					<span class="sitestaffr-chat-header-title">${this._escapeHtml(businessName)}</span>
					<div class="sitestaffr-chat-header-actions">
						${this.soundsEnabled ? `<button type="button" class="sitestaffr-chat-header-btn sitestaffr-chat-mute-btn" aria-label="Toggle sounds">${speakerOnSvg}</button>` : ''}
						<button type="button" class="sitestaffr-chat-header-btn sitestaffr-chat-close-btn" aria-label="Close chat">
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
						</button>
					</div>
				</div>
				<div class="sitestaffr-chat-messages">
					<div class="sitestaffr-chat-typing">
						<span class="sitestaffr-chat-typing-dot"></span>
						<span class="sitestaffr-chat-typing-dot"></span>
						<span class="sitestaffr-chat-typing-dot"></span>
					</div>
				</div>
				<div class="sitestaffr-chat-char-count"></div>
				<div class="sitestaffr-chat-input-area">
					<div class="sitestaffr-chat-input-wrapper">
						<textarea class="sitestaffr-chat-textarea" placeholder="Type a message..." rows="1" maxlength="2000"></textarea>
					</div>
					<button type="button" class="sitestaffr-chat-send-btn" aria-label="Send message">
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
					</button>
				</div>
			`;

			// Event handlers
			const textarea = panel.querySelector('.sitestaffr-chat-textarea');
			const sendBtn = panel.querySelector('.sitestaffr-chat-send-btn');
			const closeBtn = panel.querySelector('.sitestaffr-chat-close-btn');
			const muteBtn = panel.querySelector('.sitestaffr-chat-mute-btn');
			const charCount = panel.querySelector('.sitestaffr-chat-char-count');

			sendBtn.addEventListener('click', () => this.handleSendClick());

			textarea.addEventListener('keydown', (e) => {
				if (e.key === 'Enter' && !e.shiftKey) {
					e.preventDefault();
					this.handleSendClick();
				}
			});

			textarea.addEventListener('input', () => {
				// Auto-resize
				textarea.style.height = 'auto';
				textarea.style.height = Math.min(textarea.scrollHeight, 100) + 'px';
				// Character count
				this.updateCharCount();
			});

			closeBtn.addEventListener('click', () => this.closeChatPanel());

			if (muteBtn) {
				muteBtn.addEventListener('click', () => {
					if (this.chatInstance) {
						const muted = this.chatInstance.toggleMute();
						muteBtn.innerHTML = muted ? speakerOffSvg : speakerOnSvg;
						muteBtn.classList.toggle('is-muted', muted);
					}
				});
			}

			// Propagate widget CSS variables to panel (panel is appended to body,
			// so it can't inherit from #sitestaffr-widget via CSS cascade).
			const widgetStyle = getComputedStyle(this.widget);
			['--widget-bg-color', '--widget-hover-bg-color', '--widget-icon-color'].forEach(prop => {
				const val = widgetStyle.getPropertyValue(prop).trim();
				if (val) panel.style.setProperty(prop, val);
			});

			// Propagate tooltip-bottom class so panel can raise itself above the raised widget.
			if (this.widget.classList.contains('sitestaffr-tooltip--bottom')) {
				panel.classList.add('sitestaffr-tooltip--bottom');
			}

			document.body.appendChild(panel);
			this.panelEl = panel;
			this._bindChatLinkClicks(panel);
			return panel;
		}

		/**
		 * Links in AI messages navigate in the same tab, so following one ends
		 * the conversation. Record that as the end reason before the page
		 * unloads. Delegated from the panel so streamed bubbles are covered too.
		 */
		_bindChatLinkClicks(panel) {
			panel.addEventListener('click', (e) => {
				const target = e.target;
				if (!target || typeof target.closest !== 'function') return;
				if (!target.closest('a[data-sitestaffr-link]')) return;
				// Never preventDefault — the browser still follows the link.
				if (this.chatInstance) {
					this.chatInstance.endConversationViaLinkClick();
				}
			});
		}

		async openChatPanel() {
			// Inactive plan — always show contact form
			if (sitestaffr_widget_data.entitlement === 'inactive') {
				this._entitlementInactive = true;
			}

			// Check if daily cap was already reached today (persists across page loads)
			if (!this._dailyCapReached && !this._entitlementInactive) {
				try {
					var capDate = sessionStorage.getItem('sitestaffr_daily_cap');
					if (capDate === new Date().toISOString().slice(0, 10)) {
						this._dailyCapReached = true;
					}
				} catch (e) { /* private browsing */ }
			}

			if (this._dailyCapReached || this._entitlementInactive) {
				const panel = this.createChatPanel();
				this.widget.classList.add('panel-open');
				void panel.offsetWidth;
				this._isOpeningOwnPanel = true;
				document.dispatchEvent(new CustomEvent('sitestaffr:panel-opening'));
				this._isOpeningOwnPanel = false;
				panel.classList.add('is-open');
				this.chatPanelOpen = true;
				this.showContactForm();
				return;
			}

			const panel = this.createChatPanel();
			this.widget.classList.add('panel-open');
			// Force reflow
			void panel.offsetWidth;
			this._isOpeningOwnPanel = true;
			document.dispatchEvent(new CustomEvent('sitestaffr:panel-opening'));
			this._isOpeningOwnPanel = false;
			panel.classList.add('is-open');
			this.chatPanelOpen = true;

			// Refresh nonces for page-cached environments before creating chat instance
			if (this.core) {
				await this.core._refreshNoncesOnce();
			}

			// Initialize chat instance
			if (!this.chatInstance || this.chatInstance.ended) {
				this._clearMessages();

				this.chatInstance = new SiteStaffrTextChat({
					ajaxUrl: this.config.ajaxUrl,
					pluginUrl: this.config.pluginUrl,
					channelSource: 'widget',
					persona: this.config.persona || 'default',
					saveNonce: sitestaffr_widget_data.save_nonce || '',
					finalizeNonce: sitestaffr_widget_data.finalize_nonce || '',
					chatNonce: sitestaffr_widget_data.chat_nonce || '',
					soundsEnabled: this.soundsEnabled,
				}, {
					onMessage: (role, text) => this.appendMessage(role, text),
					onTextDelta: (delta) => this.appendStreamDelta(delta),
					onStreamStart: () => this.showTypingIndicator(),
					onStreamEnd: () => { this.hideTypingIndicator(); this.finalizeStreamMessage(); },
					onFieldUpdate: (field, value) => { /* future: show capture card */ },
					onEndSession: (reason) => {
						if (reason === 'inactivity') {
							this.appendNotice('This conversation has ended due to inactivity.');
						}
					},
				onShowContactForm: () => this.showContactForm(),
				onDisableInput: () => {
					var textarea = this.panelEl.querySelector('.sitestaffr-chat-textarea');
					var sendBtn = this.panelEl.querySelector('.sitestaffr-chat-send-btn');
					if (textarea) { textarea.disabled = true; textarea.placeholder = ''; }
					if (sendBtn) { sendBtn.disabled = true; }
				},
				onError: (message) => this.appendNotice(message),
				});

				// Display greeting instantly (no API call needed)
				const greeting = sitestaffr_widget_data.chat_greeting || 'Hi! How can I help you today?';
				this.appendMessage('assistant', greeting);
				this.chatInstance.start();
			}

			// Focus textarea
			const textarea = panel.querySelector('.sitestaffr-chat-textarea');
			if (textarea) {
				setTimeout(() => textarea.focus(), 300);
			}
		}

		closeChatPanel() {
			if (this.chatInstance && !this.chatInstance.ended) {
				this.chatInstance.endConversation('visitor_close');
			}
			if (this.panelEl) {
				this.panelEl.classList.remove('is-open');
			}
			this.widget.classList.remove('panel-open');
			this.chatPanelOpen = false;
		}

		toggleChatPanel() {
			if (this.chatPanelOpen) {
				this.closeChatPanel();
			} else {
				this.openChatPanel();
			}
		}

		/* =============================================
		   CHAT INTERACTION
		   ============================================= */

		handleSendClick() {
			if (!this.panelEl || !this.chatInstance) {
				return;
			}

			const textarea = this.panelEl.querySelector('.sitestaffr-chat-textarea');
			const text = textarea.value.trim();
			if (!text) return;

			if (!this.chatInstance.sendMessage(text)) return;

			textarea.value = '';
			textarea.style.height = 'auto';
			this.updateCharCount();
		}

		appendMessage(role, text) {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');
			const bubble = document.createElement('div');
			bubble.className = `sitestaffr-chat-message sitestaffr-chat-message--${role === 'user' ? 'visitor' : 'assistant'}`;
			if (role === 'user') {
				bubble.textContent = text;
			} else {
				bubble.innerHTML = this._renderMarkdown(text);
			}
			// Insert before typing indicator
			const typing = messagesEl.querySelector('.sitestaffr-chat-typing');
			messagesEl.insertBefore(bubble, typing);
			this.scrollToBottom();
		}

		appendStreamDelta(delta) {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');

			if (!this._streamingBubble) {
				this._streamingBubble = document.createElement('div');
				this._streamingBubble.className = 'sitestaffr-chat-message sitestaffr-chat-message--assistant sitestaffr-chat-message--streaming';
				const typing = messagesEl.querySelector('.sitestaffr-chat-typing');
				messagesEl.insertBefore(this._streamingBubble, typing);
			}

			this._streamingBubble.textContent += delta;
			this.scrollToBottom();
		}

		finalizeStreamMessage() {
			if (this._streamingBubble) {
				this._streamingBubble.innerHTML = this._renderMarkdown(this._streamingBubble.textContent);
				this._streamingBubble.classList.remove('sitestaffr-chat-message--streaming');
				this._streamingBubble = null;
			}
		}

		/**
		 * Lightweight markdown to HTML for chat bubbles.
		 * Supports: **bold**, *italic*, `code`, unordered lists (- item), ordered lists (1. item).
		 * Escapes HTML to prevent XSS.
		 */
		_renderMarkdown(text) {
			// Escape HTML entities first
			const escaped = text
				.replace(/&/g, '&amp;')
				.replace(/</g, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/"/g, '&quot;');

			const lines = escaped.split('\n');
			const result = [];
			let inUl = false;
			let inOl = false;

			for (let i = 0; i < lines.length; i++) {
				let line = lines[i];

				// Headings: "## heading" or "### heading"
				const headingMatch = line.match(/^\s*(#{1,4})\s+(.+)/);
				if (headingMatch) {
					if (inUl) { result.push('</ul>'); inUl = false; }
					if (inOl) { result.push('</ol>'); inOl = false; }
					result.push('<p><strong>' + this._renderInline(headingMatch[2]) + '</strong></p>');
					continue;
				}

				// Unordered list: "- item" or "* item"
				const ulMatch = line.match(/^\s*[-*]\s+(.+)/);
				// Ordered list: "1. item"
				const olMatch = line.match(/^\s*\d+\.\s+(.+)/);

				if (ulMatch || olMatch) {
					const itemText = ulMatch ? ulMatch[1] : olMatch[1];
					// Lines ending with ":" are section headers, not list items
					if (itemText.trim().endsWith(':')) {
						if (inUl) { result.push('</ul>'); inUl = false; }
						if (inOl) { result.push('</ol>'); inOl = false; }
						result.push('<p><strong>' + this._renderInline(itemText) + '</strong></p>');
					} else {
						if (inOl) { result.push('</ol>'); inOl = false; }
						if (!inUl) { result.push('<ul>'); inUl = true; }
						result.push('<li>' + this._renderInline(itemText) + '</li>');
					}
				} else {
					if (inUl) { result.push('</ul>'); inUl = false; }
					if (inOl) { result.push('</ol>'); inOl = false; }
					if (line.trim() === '') {
						result.push('<br>');
					} else {
						result.push('<p>' + this._renderInline(line) + '</p>');
					}
				}
			}

			if (inUl) result.push('</ul>');
			if (inOl) result.push('</ol>');

			return result.join('');
		}

		_renderInline(text) {
			// Links first so URL characters can't be mangled by bold/italic passes.
			// http(s) or site-relative URLs only; text is already HTML-escaped.
			// Same tab, not _blank: following a link ends the conversation, and
			// the delegated handler stamps ended_by=link_clicked before unload.
			return text
				.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+|\/[^)\s]*)\)/g, '<a href="$2" class="sitestaffr-chat-link" data-sitestaffr-link="1">$1</a>')
				// Anything still in link syntax had an unusable href — typically a
				// "(#)" placeholder the model invented when it had no real URL.
				// Show the label so the visitor never sees raw markdown brackets.
				.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
				.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
				.replace(/\*(.+?)\*/g, '<em>$1</em>')
				.replace(/`(.+?)`/g, '<code>$1</code>');
		}

		showTypingIndicator() {
			if (!this.panelEl) return;
			const typing = this.panelEl.querySelector('.sitestaffr-chat-typing');
			if (typing) {
				typing.classList.add('is-visible');
				this.scrollToBottom();
			}
		}

		hideTypingIndicator() {
			if (!this.panelEl) return;
			const typing = this.panelEl.querySelector('.sitestaffr-chat-typing');
			if (typing) {
				typing.classList.remove('is-visible');
			}
		}

		_escapeHtml(text) {
			var div = document.createElement('div');
			div.textContent = text;
			return div.innerHTML;
		}

		appendNotice(text) {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');
			const notice = document.createElement('div');
			notice.className = 'sitestaffr-chat-notice';
			notice.textContent = text;
			const typing = messagesEl.querySelector('.sitestaffr-chat-typing');
			messagesEl.insertBefore(notice, typing);
			this.scrollToBottom();
		}

		showContactForm() {
			const panel = this.panelEl;
			if (!panel) return;

			// Hide chat input area and messages (contact form replaces the chat UI)
			const inputArea = panel.querySelector('.sitestaffr-chat-input-area');
			const charCount = panel.querySelector('.sitestaffr-chat-char-count');
			const messagesEl = panel.querySelector('.sitestaffr-chat-messages');
			if (inputArea) inputArea.style.display = 'none';
			if (charCount) charCount.style.display = 'none';
			if (messagesEl) messagesEl.style.display = 'none';

			// Remove existing form if any
			const existing = panel.querySelector('.sitestaffr-contact-form');
			if (existing) return;

			const formHTML = `
				<div class="sitestaffr-contact-form">
					<p class="sitestaffr-contact-form-intro">Want us to reach out? Leave your details below.</p>
					<form class="sitestaffr-contact-form-fields">
						<input type="text" name="contact_name" placeholder="Your name" required class="sitestaffr-contact-input" />
						<input type="email" name="contact_email" placeholder="Your email" required class="sitestaffr-contact-input" />
						<textarea name="contact_message" placeholder="How can we help?" required rows="3" class="sitestaffr-contact-textarea"></textarea>
						<button type="submit" class="sitestaffr-contact-submit">Send Message</button>
					</form>
				</div>
			`;

			if (messagesEl) messagesEl.insertAdjacentHTML('afterend', formHTML);

			const form = panel.querySelector('.sitestaffr-contact-form-fields');
			const self = this;
			form.addEventListener('submit', function(e) {
				e.preventDefault();
				const submitBtn = form.querySelector('.sitestaffr-contact-submit');
				submitBtn.disabled = true;
				submitBtn.textContent = 'Sending...';

				const name = form.querySelector('[name="contact_name"]').value;
				const email = form.querySelector('[name="contact_email"]').value;
				const message = form.querySelector('[name="contact_message"]').value;

				const endReason = self._entitlementInactive ? 'entitlement_contact' : 'daily_cap_contact';

				if (self.chatInstance) {
					// Existing path: chat was active, append to conversation
					self.chatInstance.contactMemory.name = name;
					self.chatInstance.contactMemory.email = email;
					self.chatInstance.contactMemory.reason = message;
					self.chatInstance.submissionSource = 'contact_form';
					self.chatInstance.transcript.push({
						role: 'user',
						text: message,
						timestamp: new Date().toISOString()
					});
					self.chatInstance.endConversation(endReason);
				} else {
					// No chat instance (entitlement fallback) — create minimal instance to save
					self.chatInstance = new SiteStaffrTextChat({
						ajaxUrl: self.config.ajaxUrl,
						pluginUrl: self.config.pluginUrl,
						channelSource: 'widget',
						persona: self.config.persona || 'default',
						saveNonce: sitestaffr_widget_data.save_nonce || '',
						finalizeNonce: sitestaffr_widget_data.finalize_nonce || '',
						chatNonce: sitestaffr_widget_data.chat_nonce || '',
						soundsEnabled: false,
					}, {
						onMessage: function() {},
						onTextDelta: function() {},
						onStreamStart: function() {},
						onStreamEnd: function() {},
						onFieldUpdate: function() {},
						onEndSession: function() {},
						onShowContactForm: function() {},
						onDisableInput: function() {},
						onError: function() {},
					});
					self.chatInstance.startTime = Date.now();
					self.chatInstance.contactMemory.name = name;
					self.chatInstance.contactMemory.email = email;
					self.chatInstance.contactMemory.reason = message;
					self.chatInstance.submissionSource = 'contact_form';
					self.chatInstance.transcript.push({
						role: 'user',
						text: message,
						timestamp: new Date().toISOString()
					});
					self.chatInstance.endConversation(endReason);
				}

				const formContainer = panel.querySelector('.sitestaffr-contact-form');
				formContainer.innerHTML = '<p class="sitestaffr-contact-form-success">Thanks! We\'ll be in touch.</p>';
			});

			// Flag so reopening panel shows form
			this._dailyCapReached = true;
		}

		updateCharCount() {
			if (!this.panelEl) return;
			const textarea = this.panelEl.querySelector('.sitestaffr-chat-textarea');
			const charCountEl = this.panelEl.querySelector('.sitestaffr-chat-char-count');
			if (!textarea || !charCountEl) return;

			const len = textarea.value.length;
			if (len > 0) {
				charCountEl.textContent = len + ' / 2000';
				charCountEl.classList.add('is-visible');
				charCountEl.classList.toggle('near-limit', len > 1800);
			} else {
				charCountEl.classList.remove('is-visible');
			}
		}

		scrollToBottom() {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');
			if (messagesEl) {
				messagesEl.scrollTop = messagesEl.scrollHeight;
			}
		}

		_clearMessages() {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');
			if (!messagesEl) return;
			// Remove all children except typing indicator
			const typing = messagesEl.querySelector('.sitestaffr-chat-typing');
			messagesEl.innerHTML = '';
			if (typing) messagesEl.appendChild(typing);
			this._streamingBubble = null;
		}

		_escapeHtml(text) {
			const div = document.createElement('div');
			div.textContent = text;
			return div.innerHTML;
		}

		/* =============================================
		   VOICE STATE HANDLERS (unchanged)
		   ============================================= */

		/**
		 * Handle state change from core
		 */
		handleStateChange(state, oldState) {
			this.widget.classList.remove(`state-${this.currentState}`);
			this.currentState = state;
			this.widget.classList.add(`state-${this.currentState}`);
			this.updateTooltip();
		}

		/**
		 * Handle error from core
		 */
		handleError(message) {
			showSiteStaffrGlobalNotice(message, message && message.includes('Too many attempts') ? 10000 : 6000);

			if (this.tooltip) {
				const strings = sitestaffr_widget_data.strings || {};
				this.tooltip.textContent = strings.idle || 'Click to start conversation';
			}

			const isRateLimitError = message && message.includes('Too many attempts');
			const timeout = isRateLimitError ? 10000 : 3000;

			if (this.errorTimeout) {
				clearTimeout(this.errorTimeout);
			}

			this.errorTimeout = setTimeout(() => {
				if (this.currentState === 'error') {
					this.handleStateChange('idle', 'error');
				}
			}, timeout);
		}

		/**
		 * Handle microphone permission denied — fall back to text or show recovery.
		 */
		handleMicDenied() {
			if (this.mode === 'both') {
				// Text is available — switch to it
				this.mode = 'text';
				this.openChatPanel();
				setTimeout(() => {
					this._showRecoveryMessage('To use voice chat, allow microphone access in your browser\'s address bar settings, then refresh the page.');
				}, 300);
			} else if (this.mode === 'voice') {
				// No text fallback — show recovery with retry button
				this._showMicDeniedRecovery();
			}
		}

		/**
		 * Show mic-denied recovery for voice-only widgets (no text fallback).
		 * Displays a message in the widget area with a "Try Again" button.
		 */
		_showMicDeniedRecovery() {
			// Remove any existing recovery
			const existing = document.getElementById('sitestaffr-mic-recovery');
			if (existing) existing.remove();

			const recovery = document.createElement('div');
			recovery.id = 'sitestaffr-mic-recovery';
			recovery.className = 'sitestaffr-mic-overlay is-visible';
			recovery.innerHTML =
				'<div class="sitestaffr-mic-overlay-text">' +
					'To use voice chat, allow microphone access in your browser\'s address bar settings, then refresh the page.' +
					'<br><br>' +
					'<button type="button" class="sitestaffr-mic-retry-btn">Try Again</button>' +
					'<br>' +
					'<button type="button" class="sitestaffr-mic-dismiss-btn">Dismiss</button>' +
				'</div>';

			recovery.querySelector('.sitestaffr-mic-retry-btn').addEventListener('click', async () => {
				recovery.remove();
				if (this.core) {
					await this.core.toggleConversation();
				}
			});

			recovery.querySelector('.sitestaffr-mic-dismiss-btn').addEventListener('click', () => {
				recovery.remove();
			});

			document.body.appendChild(recovery);
		}

		/**
		 * Handle minutes exhausted — silently fall back to text chat.
		 */
		handleMinutesExhausted() {
			this.mode = 'text';
			this.openChatPanel();
		}

		/**
		 * Show a system message in the chat panel.
		 */
		_showRecoveryMessage(text) {
			if (!this.panelEl) return;
			const messagesEl = this.panelEl.querySelector('.sitestaffr-chat-messages');
			if (!messagesEl) return;
			const bubble = document.createElement('div');
			bubble.className = 'sitestaffr-chat-bubble sitestaffr-chat-bubble--system';
			bubble.textContent = text;
			messagesEl.appendChild(bubble);
			messagesEl.scrollTop = messagesEl.scrollHeight;
		}

		handleConversationStart() {
			this.inConversation = true;
			this.updateTooltip();
		}

		handleConversationEnd(reason) {
			this.inConversation = false;
			this.updateTooltip();
		}

		showSearchIndicator(query) {
			var displayQuery = query && query.length > 40 ? query.slice(0, 40) + '...' : query;
			this.searchQuery = displayQuery || '';
			this.updateTooltip();
		}

		hideSearchIndicator() {
			this.searchQuery = null;
			this.updateTooltip();
		}

		updateTooltip() {
			if (!this.tooltip) return;

			const strings = sitestaffr_widget_data.strings || {};
			let text = '';
			const isActiveConversationState =
				this.currentState === 'connecting' ||
				this.inConversation ||
				(this.core && this.core.isInConversation());

			if (isActiveConversationState) {
				switch (this.currentState) {
					case 'connecting':
						text = 'Connecting...';
						break;
					case 'listening':
						text = strings.listening || 'Listening...';
						break;
					case 'thinking':
						text = strings.thinking || 'Processing...';
						break;
					case 'searching':
						text = this.searchQuery ? 'Searching ' + this.searchQuery + '...' : 'Searching...';
						break;
					case 'speaking':
						text = strings.speaking || 'Speaking...';
						break;
					case 'error':
						return;
					default:
						text = 'In call - Click to hang up';
				}
			} else {
				text = this.idleText || strings.idle || 'Click to start conversation';
			}

			this.tooltip.textContent = text;
		}

		showStaticError(message) {
			this.widget.classList.add('state-error');
			if (this.tooltip) {
				this.tooltip.textContent = message;
			}
		}
	}

	/**
	 * Initialize widget on DOM ready
	 */
	function initializeWidget() {
		const widgetElement = document.getElementById('sitestaffr-widget');

		if (widgetElement) {
			// Core is needed for voice modes
			const mode = widgetElement.dataset.mode || (window.sitestaffr_widget_data && sitestaffr_widget_data.mode) || 'voice';

			if (mode !== 'text' && typeof SiteStaffrCore === 'undefined') {
				console.error('SiteStaffr: Core not loaded. Make sure sitestaffr-core.js is enqueued first.');
				return;
			}

			new SiteStaffrWidget(widgetElement);
		}
	}

	// Initialize when DOM is ready
	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', initializeWidget);
	} else {
		initializeWidget();
	}

})();
