document.addEventListener( 'DOMContentLoaded', () => {
	const toolbarEle = document.getElementsByClassName( 'toolbar' );
	const pageID = toolbarEle[ 0 ].getAttribute( 'page-id' );
	const commentForm = document.querySelector( '.cf-front-comment-wrapper .comment-form' );
	let submitButton = '';
	if( commentForm ) {
		submitButton = commentForm.querySelector('button[type="submit"], input[type="submit"]');
	}
	

	// to remove any theme's template comments listing area.
	document.getElementById('comments')?.remove();

	if ( 0 === document.getElementsByClassName('cf-front-comment-list').length ) {
		cfGetComments( pageID );
	}

	// Remove mdspan-front-comment class from all marks on page load
	// Marks are injected server-side and should not be highlighted by default
	// They will only be highlighted when clicked from the slider
	const allMarks = document.querySelectorAll( 'mark[data-icfp-highlight-id]' );
	allMarks.forEach( ( mark ) => {
		mark.classList.remove( 'mdspan-front-comment' );
	} );

	const frontCommentClass = 'mdspan-front-comment';
	const frontBoardPrefix = 'el_front_';

	const commentCountTop = document.querySelector(
		'.cf-front-comment-count_wrap'
	);

	let contentQuerySelectorClass = commentCountTop?.parentElement?.className;
	contentQuerySelectorClass = '.' + contentQuerySelectorClass?.trim().replace(/\s+/g, '.');
	// document
	// 	.querySelector( contentQuerySelectorClass )
	// 	?.addEventListener( 'mouseup', ( e ) => {
	// 		// if (
	// 		// 	document.body.classList.contains( 'single-post' ) ||
	// 		// 	document.body.classList.contains( 'single' )
	// 		// ) {
	// 			const restrictedCommentCountTop = document.querySelector(
	// 				'.cf-front-comment-count_wrap'
	// 			);
	// 			if (
	// 				restrictedCommentCountTop &&
	// 				restrictedCommentCountTop.contains( e.target )
	// 			) {
	// 				return;
	// 			}
	// 			const selection = window.getSelection();
	// 			selectVal = selection;
	// 			if ( selection.toString() === '' ) {
	// 				return;
	// 			}

	// 			const textSrc = document.getElementById( 'text_src' );
	// 			if ( textSrc ) {
	// 				textSrc.value = '';
	// 			}

	// 			const mediaSrc = document.getElementById( 'media_src' );
	// 			if ( mediaSrc ) {
	// 				mediaSrc.value = '';
	// 			}

	// 			Array.from( document.querySelectorAll( '.cf-media-highlighted' ) ).forEach(
	// 				( el ) => el.classList.remove( 'cf-media-highlighted' )
	// 			);
	// 			// restrict for text selection within the tag
	// 			if (selection.rangeCount > 0) {
	// 				const range = selection.getRangeAt(0);
	// 				let startContainer = range.startContainer;
	// 				let endContainer = range.endContainer;

	// 				// If the start or end container is a text node, use their parent element
	// 				if (startContainer.nodeType === Node.TEXT_NODE) {
	// 					startContainer = startContainer.parentElement;
	// 				}
	// 				if (endContainer.nodeType === Node.TEXT_NODE) {
	// 					endContainer = endContainer.parentElement;
	// 				}

	// 				// Define the parent tags where selection is allowed (like <p>, <li>)
	// 				//const allowedParentTags = ['P', 'LI'];
	// 				const disAllowedParentTags = ['DIV', 'FIGURE'];
	// 				// Define the child tags where selection is NOT allowed (like <b>, <i>, <a>)
	// 				const disallowedChildTags = [
	// 					'A', 'EM', 'STRONG', 'B', 'I', 'U', 'S', 'SMALL',
	// 					'MARK', 'SUB', 'SUP', 'CODE', 'VAR', 'KBD', 'PRE', 'BR',
	// 					'ABBR', 'CITE', 'Q', 'TIME'
	// 				];

	// 				// Find the common ancestor
	// 				const commonAncestor = range.commonAncestorContainer;
	// 				let parentTag = commonAncestor.nodeType === Node.ELEMENT_NODE 
	// 								? commonAncestor 
	// 								: commonAncestor.parentElement;

	// 				// Restrict the selection to allowed parent tags
	// 				//if (!allowedParentTags.includes(parentTag.tagName)) {
	// 				if (disAllowedParentTags.includes(parentTag.tagName)) {
	// 					// Adjust the selection if it goes outside the allowed parent tag
	// 					selection.removeAllRanges();
	// 					//alert(`Selection must be within a single parent tag like <${parentTag.tagName.toLowerCase()}>.`);
	// 					alert( 'Please select text within the proper structure.' );
	// 					return false;
	// 				}

	// 				// Check if the selection crosses into any disallowed child tags
	// 				const closestStartDisallowedTag = startContainer.closest(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));
	// 				const closestEndDisallowedTag = endContainer.closest(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));

	// 				// If the selection starts or ends inside a disallowed tag, reset the selection
	// 				if (closestStartDisallowedTag || closestEndDisallowedTag) {
	// 					const startTagName = closestStartDisallowedTag ? closestStartDisallowedTag.tagName.toLowerCase() : '';
	// 					const endTagName = closestEndDisallowedTag ? closestEndDisallowedTag.tagName.toLowerCase() : '';
	// 					const tagNames = startTagName === endTagName 
	// 									? `<${startTagName}>`
	// 									: `<${startTagName}> or <${endTagName}>`;
	// 					selection.removeAllRanges();
	// 					alert(`Selection cannot include content from child tag(s): ${tagNames}.`);
	// 					return false;
	// 				}

	// 				// Clone the selected content to check for disallowed tags inside the selection
	// 				const selectedContent = range.cloneContents();
	// 				const selectedTags = selectedContent.querySelectorAll(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));

	// 				// If any disallowed tags are found within the selected text, reset the selection and alert
	// 				if (selectedTags.length > 0) {
	// 					const tagList = Array.from(selectedTags).map(el => `<${el.tagName.toLowerCase()}>`).join(', ');
	// 					selection.removeAllRanges();
	// 					alert(`Selection cannot include content from disallowed tags: ${tagList}.`);
	// 					return false;
	// 				}
	// 			}
	// 			// restrict for multiple selected li tag
	// 			if ( selection.rangeCount > 0 ) {
	// 				const range = selection.getRangeAt( 0 );
	// 				const selectedContent = range.cloneContents();

	// 				const div = document.createElement( 'div' );
	// 				div.appendChild( selectedContent );
	// 				const selectedHTML = div.innerHTML;

	// 				// Create a temporary container to parse the HTML
	// 				const tempContainer = document.createElement( 'div' );
	// 				tempContainer.innerHTML = selectedHTML;

	// 				// Count the number of <li> tags
	// 				const liTags = tempContainer.getElementsByTagName( 'li' );
	// 				const liCount = liTags?.length;

	// 				if ( liCount > 1 ) {
	// 					return;
	// 				}
	// 			}
	// 			const selectionRect = selection
	// 				.getRangeAt( 0 )
	// 				.getBoundingClientRect();
	// 			const toolbarRect = toolbarEle[ 0 ].getBoundingClientRect();
	// 			const distanceFromTop = window.scrollY;
	// 			let top =
	// 				selectionRect.top +
	// 				distanceFromTop -
	// 				toolbarRect.height -
	// 				12;
	// 			let left =
	// 				selectionRect.left +
	// 				( selectionRect.width - toolbarRect.width ) / 2;
	// 			let arrowDirection = 'bottom';

	// 			if ( top < 0 ) {
	// 				top =
	// 					selectionRect.top +
	// 					distanceFromTop +
	// 					selectionRect.height +
	// 					12;
	// 				left = selectionRect.left;
	// 				arrowDirection = 'top';
	// 			}

	// 			toolbarEle[ 0 ].classList.remove( 'toolbar--bottom' );
	// 			toolbarEle[ 0 ].classList.remove( 'toolbar--top' );
	// 			toolbarEle[ 0 ].classList.add( `toolbar--${ arrowDirection }` );
	// 			toolbarEle[ 0 ].style.transform = `translate(${ left }px, ${ top }px)`;
	// 			toolbarEle[ 0 ].style.opacity = 1;
	// 			Array.from(
	// 				document.querySelectorAll( '.mdspan-front-comment' )
	// 			).forEach( ( el ) =>
	// 				el.classList.remove( 'mdspan-front-comment' )
	// 			);
	// 		//}
	// 	} );

		
	const targetElement = document.querySelector(contentQuerySelectorClass);
	if (targetElement) {
		const isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);

		const handleTextSelection = (e) => {
			const processSelection = () => {
				const selection = window.getSelection();
				if (!selection || selection.toString().trim() === '') return;

				const restrictedCommentCountTop = document.querySelector('.cf-front-comment-count_wrap');
				if (restrictedCommentCountTop && restrictedCommentCountTop.contains(e.target)) return;

				selectVal = selection;

				const textSrc = document.getElementById('text_src');
				if (textSrc) textSrc.value = '';

				const mediaSrc = document.getElementById('media_src');
				if (mediaSrc) mediaSrc.value = '';

				Array.from(document.querySelectorAll('.cf-media-highlighted')).forEach(el => el.classList.remove('cf-media-highlighted'));

				// if (selection.rangeCount > 0) {
				// 	const range = selection.getRangeAt(0);
				// 	let startContainer = range.startContainer;
				// 	let endContainer = range.endContainer;

				// 	if (startContainer.nodeType === Node.TEXT_NODE) startContainer = startContainer.parentElement;
				// 	if (endContainer.nodeType === Node.TEXT_NODE) endContainer = endContainer.parentElement;

				// 	const disAllowedParentTags = ['DIV', 'FIGURE'];
				// 	const disallowedChildTags = [
				// 		'A', 'EM', 'STRONG', 'B', 'I', 'U', 'S', 'SMALL', 'MARK', 'SUB', 'SUP', 'CODE', 'VAR',
				// 		'KBD', 'PRE', 'BR', 'ABBR', 'CITE', 'Q', 'TIME'
				// 	];

				// 	const commonAncestor = range.commonAncestorContainer;
				// 	let parentTag = commonAncestor.nodeType === Node.ELEMENT_NODE
				// 		? commonAncestor
				// 		: commonAncestor.parentElement;

				// 	if (disAllowedParentTags.includes(parentTag.tagName)) {
				// 		selection.removeAllRanges();
				// 		alert('Please select text within the proper structure.');
				// 		return false;
				// 	}

				// 	const closestStartDisallowedTag = startContainer.closest(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));
				// 	const closestEndDisallowedTag = endContainer.closest(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));

				// 	if (closestStartDisallowedTag || closestEndDisallowedTag) {
				// 		const startTagName = closestStartDisallowedTag ? closestStartDisallowedTag.tagName.toLowerCase() : '';
				// 		const endTagName = closestEndDisallowedTag ? closestEndDisallowedTag.tagName.toLowerCase() : '';
				// 		const tagNames = startTagName === endTagName
				// 			? `<${startTagName}>`
				// 			: `<${startTagName}> or <${endTagName}>`;
				// 		selection.removeAllRanges();
				// 		alert(`Selection cannot include content from child tag(s): ${tagNames}.`);
				// 		return false;
				// 	}

				// 	const selectedContent = range.cloneContents();
				// 	const selectedTags = selectedContent.querySelectorAll(disallowedChildTags.map(tag => tag.toLowerCase()).join(', '));

				// 	if (selectedTags.length > 0) {
				// 		const tagList = Array.from(selectedTags).map(el => `<${el.tagName.toLowerCase()}>`).join(', ');
				// 		selection.removeAllRanges();
				// 		alert(`Selection cannot include content from disallowed tags: ${tagList}.`);
				// 		return false;
				// 	}
				// }

				if (selection.rangeCount > 0) {
					const range = selection.getRangeAt(0);
					const selectedContent = range.cloneContents();
					const div = document.createElement('div');
					div.appendChild(selectedContent);
					const liCount = div.getElementsByTagName('li').length;

					if (liCount > 1) return;
				}

				const selectionRect = selection.getRangeAt(0).getBoundingClientRect();
				const toolbarRect = toolbarEle[0].getBoundingClientRect();
				const distanceFromTop = window.scrollY;

				let top = selectionRect.top + distanceFromTop - toolbarRect.height - 12;
				let left = selectionRect.left + (selectionRect.width - toolbarRect.width) / 2;
				let arrowDirection = 'bottom';

				if (top < 0) {
					top = selectionRect.top + distanceFromTop + selectionRect.height + 12;
					left = selectionRect.left;
					arrowDirection = 'top';
				}

				toolbarEle[0].classList.remove('toolbar--bottom', 'toolbar--top');
				toolbarEle[0].classList.add(`toolbar--${arrowDirection}`);
				toolbarEle[0].style.transform = `translate(${left}px, ${top}px)`;
				toolbarEle[0].style.opacity = 1;

				Array.from(document.querySelectorAll('.mdspan-front-comment')).forEach(el => el.classList.remove('mdspan-front-comment'));
			};

			// Use delay only on mobile devices
			if (isMobile) {
				setTimeout(processSelection, 200);
			} else {
				processSelection();
			}
		};

		targetElement.addEventListener('mouseup', handleTextSelection);
		// targetElement.addEventListener('touchend', handleTextSelection);
	}
	const mobileMediaQuery = window.matchMedia('(max-width: 767px)');

	document
	.getElementById('cf-front-cancel')
	?.addEventListener('click', function () {
		
		// Check if the media query matches (for mobile)
		if (mobileMediaQuery.matches) {
		document.querySelector('.cf-front-comment-wrapper').style.transform = 'translateY(100%)';
		} else {
		document.querySelector('.cf-front-comment-wrapper').style.transform = 'translateX(100%)';
		}

		// Remove class from all elements with '.mdspan-front-comment'
		Array.from(document.querySelectorAll('.mdspan-front-comment')).forEach((el) =>
		el.classList.remove('mdspan-front-comment')
		);
	});

	document.addEventListener('click', function (e) {
		const cf_front_comment = document.getElementById('cf_front_comment_wrapper');
	  
		if (cf_front_comment) {
		  const cfFrontCommentStyle = window.getComputedStyle(cf_front_comment);
		  const transformValue = cfFrontCommentStyle?.transform;
	  
		  let translateX = 0;
		  let translateY = 0;
	  
		  if (transformValue && transformValue !== 'none') {
			const match = transformValue.match(/matrix.*\((.+)\)/);
			if (match) {
			  const matrixValues = match[1].split(', ');
			  translateX = parseFloat(matrixValues[4]); // 5th element
			  translateY = parseFloat(matrixValues[5]); // 6th element
			}
		  }
	  
		  // Check if the click is outside and the panel is in original position
		  if (!cf_front_comment.contains(e.target) && translateX === 0 && translateY === 0) {
			const commentWrapper = document.querySelector('.cf-front-comment-wrapper');
		  
			if (commentWrapper) {
			  if (mobileMediaQuery.matches) {
				commentWrapper.style.transform = 'translateY(100%)'; // This is the intended mobile close
			  } else {
				commentWrapper.style.transform = 'translateX(100%)'; // Desktop close
			  }
			}
	  
			// Clear the comment status
			const commentStatus = document.getElementById('comment-status');
			if (commentStatus) commentStatus.innerHTML = '';
	  
			// Remove class from all elements with '.mdspan-front-comment'
			document.querySelectorAll('.mdspan-front-comment').forEach((el) =>
			  el.classList.remove('mdspan-front-comment')
			);
		  }
		}
	  });
	  	let selectedText = '';
		let clickedFromBottom = false;
	document
		.getElementById( 'cf-front-comment-count' )
		?.addEventListener( 'click', function () {
	
			      
      // Apply appropriate transform based on media query
      if (mobileMediaQuery.matches) {
		document.querySelector(
			'.cf-front-comment-wrapper'
		).style.transform = 'translateY(0px)';
      } else {
		document.querySelector(
			'.cf-front-comment-wrapper'
		).style.transform = 'translateX(0px)';
      }
	  
	  
		document.querySelector(
				'.cf-front-comment-inner-wrapper'
			).style.display = 'block';
			clickedFromBottom = true;
	} );

	
		let clickedFromTop = false;

		// Track selected text
		document.addEventListener('mouseup', function () {
			selectedText = window.getSelection().toString().trim();
		});

	
		//document.getElementById('cf-front-comment-count-top').addEventListener('click', function(event) {
		document.body.addEventListener('click', function(event) {

			if ( event.target.closest('#cf-front-comment-count-top') ) {
				 if (mobileMediaQuery.matches) {
					
					document.querySelector(
						'.cf-front-comment-wrapper'
					).style.transform = 'translateY(0px)';
				  } else {
					document.querySelector(
						'.cf-front-comment-wrapper'
					).style.transform = 'translateX(0px)';
				 }
				  
				document.querySelector(
					'.cf-front-comment-inner-wrapper'
				).style.display = 'block';

			}
			
		} );
		document.addEventListener('click', function (event) {
			if (event.target.closest('#cf-front-comment-count-top')) {
				clickedFromTop = true;
			}
		});

	document.addEventListener( 'selectionchange', () => {
		const selection = window.getSelection().toString();
		if ( ! selection ) {
			toolbarEle[ 0 ].style.opacity = '0';
		}

		const selectedComments = document.querySelectorAll(
			'.cf-front-highlight'
		);
		selectedComments.forEach(
			( singleComment ) =>
				( singleComment.outerHTML = singleComment.innerText )
		);
	} );

	const elements = document.querySelectorAll( '.cf_comment_front' );
	Array.from( elements ).forEach( ( button, index ) => {
		button.addEventListener( 'click', ( e ) => {
			//toolbarEle[0].style.opacity = '0';
			const selectionObj = window.getSelection();
			// Update outer scope selectedText variable
			selectedText = selectionObj.toString().trim();
			
			// If selection is empty, try to get it from the stored value
			if ( ! selectedText ) {
				const textSrc = commentForm ? commentForm.querySelector('#text_src') : null;
				if ( textSrc && textSrc.value ) {
					selectedText = textSrc.value.trim();
				}
			}
			const tempHighlightId = frontBoardPrefix + Math.floor( performance.now() * 1000 );
			cfWrapSelectedText( frontCommentClass, tempHighlightId );
			cfBoardDisplay( selectedText, tempHighlightId );

			// Check if the comment form has a hidden input field for text_src
			const hiddenInput = commentForm.querySelector('#text_src');
			if (hiddenInput) {
				hiddenInput.value = selectedText;
			} else {
				// Create a hidden input field to store the src value
				const hiddenInput = document.createElement('input');
				hiddenInput.setAttribute('type', 'hidden');
				hiddenInput.setAttribute('name', 'text_src');
				hiddenInput.setAttribute('value', selectedText);
				hiddenInput.setAttribute('id', 'text_src');
				commentForm.querySelector('.form-submit').appendChild(hiddenInput);
			}
		} );
	} );

	const commentform = jQuery( '.cf-front-comment-wrapper .comment-form' ); // find the comment form
	commentform.prepend( '<div id="comment-status" ></div>' ); // add info panel before the form to provide feedback or errors
	const statusdiv = jQuery( '#comment-status' ); // define the infopanel

	commentform.submit( function ( event ) {
		event.stopImmediatePropagation();

		// Check for duplicate selected text before submitting.
		const textSrc = document.querySelector('.cf-front-comment-wrapper #text_src');
		// Get selected text from hidden input if outer scope variable is empty
		const currentSelectedText = ( selectedText && selectedText.trim() ) 
			? selectedText.trim() 
			: ( textSrc && textSrc.value ? textSrc.value.trim() : '' );
		
		if ( currentSelectedText ) {
			// Check if a comment already exists for this text.
			const checkData = {
				action: 'icfp_check_duplicate_comment',
				nonce: cf_public_nonce.nonce,
				selection: currentSelectedText,
				pageID: pageID,
			};
			
			// Make synchronous check (or use async with promise).
			let isDuplicate = false;
			jQuery.ajax({
				type: 'post',
				url: cf_public_localizer.ajax_url,
				data: checkData,
				async: false, // Synchronous to prevent form submission if duplicate
				success: function( response ) {
					const result = JSON.parse( response );
					if ( result.is_duplicate ) {
						isDuplicate = true;
						statusdiv.html(
							'<p class="ajax-error">A comment already exists for this selected text. Please select different text or reply to the existing comment.</p>'
						);
					}
				}
			});
			
			if ( isDuplicate ) {
				return false; // Prevent form submission
			}
		}

		//serialize and store form data in a variable
		const formdata = commentform.serialize();
		//Add a status message
		statusdiv.html( '<p>Processing...</p>' );
		//Extract action URL from commentform
		const formurl = commentform.attr( 'action' );
		//Post Form with data
		jQuery.ajax( {
			type: 'post',
			url: formurl,
			data: formdata,
			error( XMLHttpRequest, textStatus, errorThrown ) {
				if( XMLHttpRequest.responseText.includes( "Duplicate comment detected" ) ) {
					statusdiv.html(
						'<p class="ajax-error" >Duplicate comment detected; it looks as though you’ve already said that!</p>'
					);
				} else {
					statusdiv.html(
						'<p class="ajax-error" >You might have left one of the fields blank, or be posting too quickly</p>'
					);
				}
			},
			success( data, textStatus ) {
				if( ! data.includes( "wp-die-message" || ! data.includes( "error-page" ) ) ) {
				//if ( data == 'success' || textStatus == 'success' ) {
					let successMessage = '';

				// Only show info message if no text was selected
				if ((clickedFromTop || clickedFromBottom) && !selectedText) {
					successMessage = '<p class="cf-ajax-info">You didn\'t highlight any text. This comment has been saved as a general post comment.</p>';
				}

				statusdiv.html(successMessage);

				// reset states
				clickedFromTop = false;
				clickedFromBottom = false;
				selectedText = '';

					document.getElementById('cancel-comment-reply-link').click();

					// Check if media_src input field has a value
					let selection = '';
					const mediaSrc = document.querySelector('.cf-front-comment-wrapper #media_src');
					if ( null !== mediaSrc && mediaSrc.value ) {
						selection = mediaSrc.value;
					} else {
						const textSrc = document.querySelector('.cf-front-comment-wrapper #text_src');
						if ( null !== textSrc && textSrc.value ) {
							selection = textSrc.value;
						}
					}
			
					const commentData = {
						action: 'icfp_add_front_comment',
						nonce: cf_public_nonce.nonce,
						selection: selection || '',
						pageID,
						commentText: document.querySelector(
							'.cf-front-comment-wrapper #comment'
						).value,
					};
					jQuery.post(
						cf_public_localizer.ajax_url,
						commentData,
						function ( data ) {
							const commentResponse = JSON.parse( data );
							
							// Check if there's an error message (e.g., duplicate comment).
							if ( ! commentResponse.success && commentResponse.message ) {
								statusdiv.html(
									'<p class="ajax-error">' + commentResponse.message + '</p>'
								);
								return;
							}
							
							if ( commentResponse.success && commentResponse.comments.length > 0 ) {
								const commentCountElementTop = document.querySelector(
									'.cf-comment-count-top'
								);
								if ( commentCountElementTop ) {
									commentCountElementTop.innerHTML = commentResponse.comments.length;
								}

								const commentCountElementBottom = document.querySelector(
									'.cf-comment-count-bottom'
								);
								if ( commentCountElementBottom ) {
									commentCountElementBottom.innerHTML = commentResponse.comments.length;
								}

								const commentWrapperElement = document.querySelector(
									'.cf-comment-wrapper-count'
								);
								if ( commentWrapperElement ) {
									commentWrapperElement.innerHTML = commentResponse.comments.length;
								}
								
								const newComment = commentResponse.comments[ 0 ];
								const newCommentId = commentResponse.comment_id || ( newComment && newComment.comment_ID ? newComment.comment_ID : null );
								
								if ( newCommentId ) {
									const contentAreas = document.getElementsByClassName( 'entry-content' );
									const postContent = document.getElementsByClassName( 'wp-block-post-content' );
									const searchContainers = [];
									
									if ( contentAreas.length > 0 ) {
										for ( let i = 0; i < contentAreas.length; i++ ) {
											searchContainers.push( contentAreas[ i ] );
										}
									}
									if ( postContent.length > 0 ) {
										for ( let i = 0; i < postContent.length; i++ ) {
											searchContainers.push( postContent[ i ] );
										}
									}
									
									let markFound = false;
									for ( let c = 0; c < searchContainers.length && !markFound; c++ ) {
										const container = searchContainers[ c ];
										const marks = container.querySelectorAll( 'mark[data-icfp-temp-highlight]' );
										
										for ( let i = 0; i < marks.length; i++ ) {
											const mark = marks[ i ];
											if ( mark.textContent.trim() === selection.trim() ) {
												mark.setAttribute( 'id', `icfp-highlight-${newCommentId}` );
												mark.setAttribute( 'data-icfp-highlight-id', `icfp-highlight-${newCommentId}` );
												mark.setAttribute( 'data-comment-id', newCommentId );
												mark.removeAttribute( 'data-icfp-temp-highlight' );
												markFound = true;
												break;
											}
										}
									}
									
									if ( ! markFound ) {
										console.warn( '⚠️ Temporary mark not found after comment submission:', {
											commentId: newCommentId
										} );
									}
								}
							}

							// Remove highlight class while preserving submitted comment marks.
							const allHighlights = document.getElementsByClassName( 'mdspan-front-comment' );
							for ( let i = 0; i < allHighlights.length; i++ ) {
								const el = allHighlights[ i ];
								el.classList.remove( 'mdspan-front-comment' );
							}

							document.querySelector( '.cf-front-comment-wrapper #comment' ).value = '';
							document.getElementById( 'cf_front_comment_slider_wrapper' ).innerHTML = '';
							cfGetComments( pageID );
							if (mobileMediaQuery.matches) {
								document.querySelector('.cf-front-comment-wrapper').style.transform = 'translateY(0px)';
							} else {
								document.querySelector('.cf-front-comment-wrapper').style.transform = 'translateX(0px)';
							}
		
							// cfHideFrontCommentBoard();
						}
					);
				} else {
					statusdiv.html(
						'<p class="ajax-error" >You might have left one of the fields blank, or be posting too quickly</p>'
					);
					commentform.find( 'textarea[name=comment]' ).val( '' );
				}
	
			},
		} );

		return false;
	} );

	/**
	 * Function to get comments for the front-end comment board.
	 */
	function cfGetComments( pageID, sortBy = 'comment_date' ) {
		const commentData = {
			action: 'icfp_get_comment',
			nonce: cf_public_nonce.nonce,
			pageID,
			sortBy,
		};

		jQuery.ajax( {
			type: 'post',
			url: cf_public_localizer.ajax_url,
			data: commentData,
			success( data ) {
				const commentResponse = JSON.parse( data );
				if ( commentResponse.success && '' !== commentResponse.content ) {
					const commentList = document.getElementById('cf_front_comment_slider_wrapper');
					commentList.insertAdjacentHTML('beforeend', commentResponse.content);
					cfAddShowMoreLink();
				}
			},
		} );
	}

	document.getElementById('cancel-comment-reply-link')?.addEventListener('click', function (e) {
		e.preventDefault();
		document.querySelector('.cf-front-comment-wrapper .comment-reply-title').style.display = 'none';
		if( null !== document.querySelector('.cf-front-comment-wrapper .logged-in-as') ) {
			document.querySelector('.cf-front-comment-wrapper .logged-in-as').style.display = 'block';
		}

		if (submitButton) {
			submitButton.value = 'Post Comment';
		}
	} );

	document.getElementById('cf_sort_comments')?.addEventListener('change', function (e) {
		e.preventDefault();
		const sortBy = e.target.value;
		document.getElementById( 'cf_front_comment_slider_wrapper' ).innerHTML = '';
		cfGetComments( pageID, sortBy );
	} );

	/**
	 * Function to add show more link to comments.
	 */
	function cfAddShowMoreLink() {
		// Select all ul elements with the class 'children'
		const commentLists = document.querySelectorAll('ul.children');

		commentLists.forEach(function(ul) {
			const liElements = ul.querySelectorAll('li');
			const maxVisible = 5;
	
			// Check if the number of li elements exceeds the limit
			if (liElements.length > maxVisible) {
				// Hide the extra li elements
				for (let i = maxVisible; i < liElements.length; i++) {
					liElements[i].style.display = 'none';
				}
	
				// Check if a "Show more" link already exists to avoid duplication
				let existingShowMoreLink = ul.nextElementSibling && ul.nextElementSibling.classList.contains('show-more-link');
	
				if (!existingShowMoreLink) {
					// Create the "Show more" link
					const showMoreLink = document.createElement('a');
					showMoreLink.href = '#';
					showMoreLink.innerText = 'Show more replies';
					showMoreLink.classList.add('show-more-link'); // Add a class to identify the link
					showMoreLink.style.display = 'block';
					showMoreLink.style.marginTop = '10px';
					showMoreLink.style.cursor = 'pointer';
	
					// Append the "Show more" link after the ul
					ul.parentNode.insertBefore(showMoreLink, ul.nextSibling);
	
					// Event listener to show hidden li elements when "Show more" is clicked
					showMoreLink.addEventListener('click', function (event) {
						event.preventDefault(); // Prevent default link behavior
	
						// Show all hidden li elements
						liElements.forEach(function(li) {
							li.style.display = 'list-item';
						});
	
						// Hide the "Show more" link after clicking
						showMoreLink.style.display = 'none';
					});
				}
			}
		});
	}

	let lastScrollY = window.scrollY;
	const toolbar = document.querySelector('.cf-front-comment-count_wrap');

	window.addEventListener('scroll', () => {
		const currentY = window.scrollY;

		if (currentY > lastScrollY) {
			// down
			toolbar.classList.add('cf-hide-toolbar');
		} else {
			// up
			toolbar.classList.remove('cf-hide-toolbar');
		}

		lastScrollY = currentY;
	});

	/**
	 * Restore mark elements for all comments on page load.
	 * This ensures that mark elements persist after page refresh.
	 *
	 * @param {string} pageID - The page/post ID.
	 */
	function cfRestoreCommentMarks( pageID ) {
		if ( ! pageID ) {
			return;
		}

		const commentData = {
			action: 'icfp_get_comment_marks',
			nonce: cf_public_nonce.nonce,
			pageID: pageID,
		};

		jQuery.ajax( {
			type: 'post',
			url: cf_public_localizer.ajax_url,
			data: commentData,
			success( data ) {
				const response = JSON.parse( data );
				
				if ( response.success && Array.isArray( response.marks ) && response.marks.length > 0 ) {
					let restoredCount = 0;
					let failedCount = 0;
					let existingCount = 0;
					
					response.marks.forEach( function( markData ) {
						try {
							cfRestoreSingleMark( markData );
						} catch ( error ) {
							console.error( 'Error restoring mark:', error, markData );
						}
					});
					
					
					// Dispatch custom event to notify that marks have been restored
					// This allows other scripts (like React) to update comment wrappers
					// Use a small delay to ensure DOM is updated
					setTimeout( function() {
						document.dispatchEvent( new CustomEvent( 'icfp-marks-restored' ) );
					}, 100 );
				}
			},
			error( xhr, status, error ) {
				console.warn( 'Failed to restore comment marks:', error );
			},
		} );
	}

	/**
	 * Restore a single mark element for a comment.
	 *
	 * @param {Object} markData - The mark data object containing comment_id, text, datatext, block_id, etc.
	 */
	function cfRestoreSingleMark( markData ) {
		const { comment_id, text, datatext, block_id, block_name, block_attrs, range_info } = markData;

		// Require at least text and either datatext or comment_id
		if ( ! text || ( ! datatext && ! comment_id ) ) {
			console.warn( 'Cannot restore mark - missing required data:', {
				comment_id: comment_id,
				hasText: !!text,
				hasDatatext: !!datatext,
				markData: markData
			} );
			return;
		}
		
		// If no datatext, generate one from comment_id for older comments
		const effectiveDatatext = datatext || `legacy_${comment_id}`;
		
		// 🚨 HARD STOP: If mark already exists, DO NOTHING
const existing = document.querySelector(
	`mark[datatext="${effectiveDatatext}"], span[datatext="${effectiveDatatext}"]`
);

if ( existing ) {
	return;
}

		// Check if mark already exists (don't duplicate)
		// Optimize: Use getElementsByTagName instead of querySelector
		let existingMark = null;
		const contentAreas = document.getElementsByClassName( 'entry-content' );
		const postContent = document.getElementsByClassName( 'wp-block-post-content' );
		const searchContainers = [];
		
		if ( contentAreas.length > 0 ) {
			for ( let i = 0; i < contentAreas.length; i++ ) {
				searchContainers.push( contentAreas[ i ] );
			}
		}
		if ( postContent.length > 0 ) {
			for ( let i = 0; i < postContent.length; i++ ) {
				searchContainers.push( postContent[ i ] );
			}
		}
		
		for ( let c = 0; c < searchContainers.length && !existingMark; c++ ) {
			const container = searchContainers[ c ];
			const marks = container.getElementsByTagName( 'mark' );
			const spans = container.getElementsByTagName( 'span' );
			
			for ( let i = 0; i < marks.length && !existingMark; i++ ) {
				const mark = marks[ i ];
				const markDatatext = mark.getAttribute( 'datatext' );
				const markHighlightId = mark.getAttribute( 'data-icfp-highlight-id' );
				const markCommentId = mark.getAttribute( 'data-comment-id' );
				const expectedHighlightId = `icfp-highlight-${comment_id}`;
				
				// Check by datatext first (most reliable), then by data-icfp-highlight-id, then by data-comment-id, then by id
				if ( markDatatext === effectiveDatatext || 
				     ( comment_id && markHighlightId === expectedHighlightId ) ||
				     ( comment_id && markCommentId === String( comment_id ) ) ||
				     ( ! datatext && comment_id && mark.id === expectedHighlightId ) ) {
					existingMark = mark;
					break;
				}
			}
			if ( !existingMark ) {
				for ( let i = 0; i < spans.length && !existingMark; i++ ) {
					const span = spans[ i ];
					const spanDatatext = span.getAttribute( 'datatext' );
					const spanHighlightId = span.getAttribute( 'data-icfp-highlight-id' );
					const spanCommentId = span.getAttribute( 'data-comment-id' );
					const expectedHighlightId = `icfp-highlight-${comment_id}`;
					
					// Check by datatext first (most reliable), then by data-icfp-highlight-id, then by data-comment-id, then by id
					if ( spanDatatext === effectiveDatatext || 
					     ( comment_id && spanHighlightId === expectedHighlightId ) ||
					     ( comment_id && spanCommentId === String( comment_id ) ) ||
					     ( ! datatext && comment_id && span.id === expectedHighlightId ) ) {
						existingMark = span;
						break;
					}
				}
			}
		}
		
		if ( existingMark ) {
			// Safety check: Verify the existing mark is not already inside another mark with the same datatext
			const parentMark = existingMark.closest( `mark[datatext="${effectiveDatatext}"], span[datatext="${effectiveDatatext}"]` );
			if ( parentMark && parentMark !== existingMark ) {
				console.warn( 'Mark already exists inside another mark with same datatext, skipping:', {
					comment_id: comment_id,
					datatext: effectiveDatatext
				} );
				return;
			}
			
			// Mark already exists, just ensure it has the correct attributes
			if ( ! existingMark.id ) {
				existingMark.setAttribute( 'id', `icfp-highlight-${comment_id}` );
			}
			if ( ! existingMark.getAttribute( 'data-icfp-highlight-id' ) ) {
				existingMark.setAttribute( 'data-icfp-highlight-id', `icfp-highlight-${comment_id}` );
			}
			if ( ! existingMark.getAttribute( 'data-comment-id' ) ) {
				existingMark.setAttribute( 'data-comment-id', comment_id );
			}
			// Ensure datatext is set if missing
			if ( ! existingMark.getAttribute( 'datatext' ) && effectiveDatatext ) {
				existingMark.setAttribute( 'datatext', effectiveDatatext );
			}
			// Don't add the highlight class here - it will be added only when user clicks on a comment
			// Remove it if it exists (marks should not be highlighted by default on page load)
			existingMark.classList.remove( 'mdspan-front-comment' );
			return;
		}
		
		// NEW APPROACH: Use DOM Range API with exact offsets if range_info is available
		// This is the correct way to restore marks at exact positions, not by string search
		// Note: blockId might be regenerated, so we also need block_name to find the correct block
		if ( range_info && 
		     range_info.startOffset !== undefined && 
		     range_info.endOffset !== undefined && 
		     ( range_info.blockId || markData.block_name ) ) {
			const result = cfRestoreMarkByRange( markData, range_info, effectiveDatatext, comment_id );
			if ( result === true ) {
				return; // Successfully restored, don't continue with old string-based method
			}
			// If result is false, fall through to old method
		}

		// Find the target block if block_id is provided
		// Note: Block IDs are regenerated on each page load, so stored block_id may not exist
		// We'll try to find it, but if not found, we'll fall back to searching by text
		let targetBlock = null;
		let blockIdFound = false;
		
		if ( block_id ) {
			// Try getElementById first (fastest if block_id matches an element id)
			targetBlock = document.getElementById( block_id );
			
			// If not found by ID, search in content areas
			if ( ! targetBlock ) {
				for ( let c = 0; c < searchContainers.length && !targetBlock; c++ ) {
					const container = searchContainers[ c ];
					// Try common block tags first
					const commonBlockTags = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'ul', 'ol', 'li' ];
					for ( let t = 0; t < commonBlockTags.length && !targetBlock; t++ ) {
						const elements = container.getElementsByTagName( commonBlockTags[ t ] );
						for ( let i = 0; i < elements.length; i++ ) {
							const el = elements[ i ];
							if ( el.getAttribute( 'data-icfp-block-id' ) === block_id ) {
								targetBlock = el;
								blockIdFound = true;
								break;
							}
						}
					}
					
					// Last resort: search all elements (slower but comprehensive)
					if ( ! targetBlock ) {
						const allElements = container.getElementsByTagName( '*' );
						for ( let i = 0; i < allElements.length && !targetBlock; i++ ) {
							const el = allElements[ i ];
							if ( el.getAttribute( 'data-icfp-block-id' ) === block_id ) {
								targetBlock = el;
								blockIdFound = true;
								break;
							}
						}
					}
				}
			} else {
				blockIdFound = true;
			}
			
			if ( ! blockIdFound ) {
				console.warn( 'Target block not found for block_id (block IDs are regenerated on each page load):', block_id, markData );
				// Block ID was regenerated, but we'll still try to restore the mark by searching for text
				// The React code will update the wrapper's data-block-id after the mark is restored
				targetBlock = null; // Reset to search by text
			}
		}

		// If no target block found (either no block_id provided, or block_id was regenerated)
		// Search in content areas and find the block that contains the text
		// Use smart matching: check for existing marks with same datatext, then match by block_name/block_attrs
		if ( ! targetBlock ) {
			const contentAreas = document.getElementsByClassName( 'entry-content' );
			const postContent = document.getElementsByClassName( 'wp-block-post-content' );
			const searchContainers = [];
			
			if ( contentAreas.length > 0 ) {
				for ( let i = 0; i < contentAreas.length; i++ ) {
					searchContainers.push( contentAreas[ i ] );
				}
			}
			if ( postContent.length > 0 ) {
				for ( let i = 0; i < postContent.length; i++ ) {
					searchContainers.push( postContent[ i ] );
				}
			}
			
			// Search for the text in all blocks to find which one contains it
			const normalizedSearchText = text.replace( /\s+/g, ' ' ).trim().toLowerCase();
			
			// First pass: collect all blocks that contain the text
			// Store both the block and its position in document for better matching
			const candidateBlocksWithInfo = [];
			for ( let c = 0; c < searchContainers.length; c++ ) {
				const container = searchContainers[ c ];
				const commonBlockTags = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'ul', 'ol', 'li' ];
				for ( let t = 0; t < commonBlockTags.length; t++ ) {
					const elements = container.getElementsByTagName( commonBlockTags[ t ] );
					for ( let i = 0; i < elements.length; i++ ) {
						const el = elements[ i ];
						// Only check blocks (elements with data-icfp-block-id)
						if ( el.hasAttribute && el.hasAttribute( 'data-icfp-block-id' ) ) {
							const blockText = el.textContent || '';
							const normalizedBlockText = blockText.replace( /\s+/g, ' ' ).trim().toLowerCase();
							if ( normalizedBlockText.includes( normalizedSearchText ) ) {
								// Check if this block already has a mark with the same text (to avoid duplicates)
								const existingMarks = el.getElementsByTagName( 'mark' );
								let hasExactMatch = false;
								for ( let m = 0; m < existingMarks.length; m++ ) {
									const mark = existingMarks[ m ];
									const markText = mark.textContent?.trim().toLowerCase().replace( /\s+/g, ' ' );
									if ( markText === normalizedSearchText ) {
										hasExactMatch = true;
										break;
									}
								}
								
								candidateBlocksWithInfo.push( {
									block: el,
									hasExistingMark: hasExactMatch,
									blockName: el.getAttribute( 'data-icfp-block-name' ),
									blockAttrs: el.getAttribute( 'data-icfp-block-attrs' )
								} );
							}
						}
					}
				}
			}
			
			// Extract just the blocks for compatibility
			const candidateBlocks = candidateBlocksWithInfo.map( info => info.block );
			
			// If multiple blocks found, use smart matching
			if ( candidateBlocks.length > 1 ) {
				// Strategy 1: Check if any block already has a mark with the same datatext, data-icfp-highlight-id, or data-comment-id
				// This is the most reliable way to identify the correct block
				for ( let i = 0; i < candidateBlocks.length && !targetBlock; i++ ) {
					const candidate = candidateBlocks[ i ];
					const marks = candidate.getElementsByTagName( 'mark' );
					for ( let j = 0; j < marks.length; j++ ) {
						const mark = marks[ j ];
						const markDatatext = mark.getAttribute( 'datatext' );
						const markHighlightId = mark.getAttribute( 'data-icfp-highlight-id' );
						const markCommentId = mark.getAttribute( 'data-comment-id' );
						const expectedHighlightId = `icfp-highlight-${comment_id}`;
						
						// Check by datatext, data-icfp-highlight-id, or data-comment-id
						if ( markDatatext === effectiveDatatext || 
						     markHighlightId === expectedHighlightId ||
						     markCommentId === String( comment_id ) ) {
							targetBlock = candidate;
							break;
						}
					}
				}
				
				// Strategy 2: Match by block_name and block_attrs
				// Collect all blocks with matching block_name, then use block_attrs and document position as tiebreakers
				if ( ! targetBlock && block_name ) {
					const matchingBlocks = [];
					
					// First, collect all blocks with matching block_name
					for ( let i = 0; i < candidateBlocks.length; i++ ) {
						const candidate = candidateBlocks[ i ];
						const candidateBlockName = candidate.getAttribute( 'data-icfp-block-name' );
						if ( candidateBlockName === block_name ) {
							matchingBlocks.push( candidate );
						}
					}
					
					if ( matchingBlocks.length === 1 ) {
						// Only one block with matching name - use it
						targetBlock = matchingBlocks[ 0 ];
					} else if ( matchingBlocks.length > 1 ) {
						// Multiple blocks with same name - use block_attrs and document position
						// Get all blocks in document order for position comparison
						const allBlocks = [];
						for ( let c = 0; c < searchContainers.length; c++ ) {
							const container = searchContainers[ c ];
							const commonBlockTags = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'ul', 'ol', 'li' ];
							for ( let t = 0; t < commonBlockTags.length; t++ ) {
								const elements = container.getElementsByTagName( commonBlockTags[ t ] );
								for ( let i = 0; i < elements.length; i++ ) {
									const el = elements[ i ];
									if ( el.hasAttribute && el.hasAttribute( 'data-icfp-block-id' ) ) {
										allBlocks.push( el );
									}
								}
							}
						}
						
						// Find indices of matching blocks
						const matchingIndices = matchingBlocks.map( block => allBlocks.indexOf( block ) );
						
						// If block_attrs is provided, try to match by attrs first
						if ( block_attrs && Array.isArray( block_attrs ) && block_attrs.length > 0 ) {
							for ( let i = 0; i < matchingBlocks.length && !targetBlock; i++ ) {
								const candidate = matchingBlocks[ i ];
								const candidateAttrs = candidate.getAttribute( 'data-icfp-block-attrs' );
								if ( candidateAttrs ) {
									try {
										const parsedAttrs = JSON.parse( candidateAttrs );
										if ( JSON.stringify( parsedAttrs ) === JSON.stringify( block_attrs ) ) {
											targetBlock = candidate;
										}
									} catch ( e ) {
										// If parsing fails, continue to next candidate
									}
								}
							}
						}
						
						// If still no match and we have multiple blocks with same name,
						// prefer the one that appears later in the document (more likely where comment was added)
						if ( ! targetBlock ) {
							let bestCandidate = null;
							let bestIndex = -1;
							
							for ( let i = 0; i < matchingBlocks.length; i++ ) {
								const candidate = matchingBlocks[ i ];
								const candidateIndex = matchingIndices[ i ];
								
								// Prefer blocks that appear later in the document
								if ( candidateIndex > bestIndex ) {
									bestCandidate = candidate;
									bestIndex = candidateIndex;
								}
							}
							
							if ( bestCandidate ) {
								targetBlock = bestCandidate;
							}
						}
					}
				}
				
				// Strategy 3: If still no match, prefer blocks that don't already have marks with the same text
				// This helps avoid duplicates when the same text appears multiple times
				if ( ! targetBlock ) {
					// Use the candidateBlocksWithInfo to check for existing marks more efficiently
					for ( let i = 0; i < candidateBlocksWithInfo.length && !targetBlock; i++ ) {
						const candidateInfo = candidateBlocksWithInfo[ i ];
						const candidate = candidateInfo.block;
						
						// Prefer blocks that don't already have this text marked
						if ( ! candidateInfo.hasExistingMark ) {
							// Also check if block_name matches (if available)
							if ( ! block_name || candidateInfo.blockName === block_name ) {
								targetBlock = candidate;
							}
						}
					}
					
					// If still no match, use the last candidate (most likely where comment was added)
					if ( ! targetBlock && candidateBlocks.length > 0 ) {
						targetBlock = candidateBlocks[ candidateBlocks.length - 1 ];
					}
				}
			}
			
			// If only one candidate or no smart match found, use the first candidate
			if ( ! targetBlock && candidateBlocks.length > 0 ) {
				targetBlock = candidateBlocks[ 0 ];
			}
			
			// Final fallback: use content area if no block found
			if ( ! targetBlock ) {
				if ( contentAreas.length > 0 ) {
					targetBlock = contentAreas[ 0 ];
				} else if ( postContent.length > 0 ) {
					targetBlock = postContent[ 0 ];
				}
			}
		}

		if ( ! targetBlock ) {
			console.warn( 'Could not find target block or content area for mark:', markData );
			return;
		}

		// Search for the text ONLY in the target block (not in nested blocks)
		// This ensures we don't match text from other blocks
		const walker = document.createTreeWalker(
			targetBlock,
			NodeFilter.SHOW_TEXT,
			{
				acceptNode: function( node ) {
					// Skip text nodes that are inside other blocks (nested blocks)
					let parent = node.parentElement;
					while ( parent && parent !== targetBlock ) {
						if ( parent.hasAttribute && parent.hasAttribute( 'data-icfp-block-id' ) ) {
							// This text node is inside a nested block, skip it
							return NodeFilter.FILTER_REJECT;
						}
						parent = parent.parentElement;
					}
					return NodeFilter.FILTER_ACCEPT;
				}
			},
			false
		);

		const charMap = [];
		let fullText = '';

		while ( walker.nextNode() ) {
			const node = walker.currentNode;
			const value = node.nodeValue || '';
			if ( ! value ) {
				continue;
			}

			// Additional verification: ensure this text node is actually within targetBlock
			// and not in a nested block
			if ( block_id ) {
				let textNodeParent = node.parentElement;
				let isInTargetBlock = false;
				let checkParent = textNodeParent;
				while ( checkParent && checkParent !== document.body ) {
					if ( checkParent === targetBlock ) {
						isInTargetBlock = true;
						break;
					}
					// If we encounter another block before reaching targetBlock, skip this node
					if ( checkParent.hasAttribute && checkParent.hasAttribute( 'data-icfp-block-id' ) ) {
						// This is a nested block, skip this text node
						break;
					}
					checkParent = checkParent.parentElement;
				}
				
				if ( ! isInTargetBlock ) {
					continue; // Skip text nodes that are not in the target block
				}
			}

			for ( let i = 0; i < value.length; i++ ) {
				charMap.push( { node, offset: i } );
				fullText += value[ i ];
			}
		}

		function isWhitespace( ch ) {
			return /\s/.test( ch );
		}

		// Normalize text for comparison (flexible whitespace)
		const normalizedText = text.replace( /\s+/g, ' ' ).trim();
		const normalizedFullText = fullText.replace( /\s+/g, ' ' );

		// Use exact match first (case-insensitive but exact phrase)
		let startIndex = normalizedFullText.toLowerCase().indexOf( normalizedText.toLowerCase() );
		
		// If exact match not found, don't use loose word-based matching
		// This prevents matching wrong text in wrong blocks
		if ( startIndex === -1 ) {
			console.warn( 'Exact text not found in target block:', {
				text: text,
				block_id: block_id,
				markData: markData
			} );
			return; // Don't proceed with loose matching
		}

		if ( startIndex >= charMap.length ) {
			console.warn( 'Invalid start index for mark:', markData );
			return;
		}

		// Find the actual character positions accounting for original whitespace
		let actualStartIndex = 0;
		let actualEndIndex = 0;
		let normalizedPos = 0;

		for ( let i = 0; i < charMap.length; i++ ) {
			const ch = fullText[ i ];
			if ( normalizedPos === startIndex ) {
				actualStartIndex = i;
			}
			if ( ! isWhitespace( ch ) ) {
				normalizedPos++;
			}
			if ( normalizedPos === startIndex + normalizedText.replace( /\s+/g, '' ).length ) {
				actualEndIndex = i + 1;
				break;
			}
		}

		if ( actualStartIndex >= charMap.length || actualEndIndex > charMap.length ) {
			console.warn( 'Invalid character positions for mark:', markData );
			return;
		}

		// Create range and wrap with mark element
		const startChar = charMap[ actualStartIndex ];
		const endChar = charMap[ actualEndIndex - 1 ] || charMap[ charMap.length - 1 ];

		if ( ! startChar || ! endChar ) {
			console.warn( 'Invalid character map for mark:', markData );
			return;
		}

		// CRITICAL: Verify that the start and end nodes are actually within the target block
		// This prevents matching text from nested blocks or other blocks
		// If block_id was found, do strict verification; otherwise, just verify nodes are in targetBlock
		if ( targetBlock ) {
			// Check if startChar node is within targetBlock
			let startNodeParent = startChar.node;
			if ( startNodeParent.nodeType === Node.TEXT_NODE ) {
				startNodeParent = startNodeParent.parentElement;
			}
			let startInTarget = false;
			let checkNode = startNodeParent;
			while ( checkNode && checkNode !== document.body ) {
				if ( checkNode === targetBlock ) {
					startInTarget = true;
					break;
				}
				// If we hit another block, it's not in target
				if ( checkNode.hasAttribute && checkNode.hasAttribute( 'data-icfp-block-id' ) ) {
					break; // Found another block, not in target
				}
				checkNode = checkNode.parentElement;
			}
			
			// Check if endChar node is within targetBlock
			let endNodeParent = endChar.node;
			if ( endNodeParent.nodeType === Node.TEXT_NODE ) {
				endNodeParent = endNodeParent.parentElement;
			}
			let endInTarget = false;
			checkNode = endNodeParent;
			while ( checkNode && checkNode !== document.body ) {
				if ( checkNode === targetBlock ) {
					endInTarget = true;
					break;
				}
				// If we hit another block, it's not in target
				if ( checkNode.hasAttribute && checkNode.hasAttribute( 'data-icfp-block-id' ) ) {
					break; // Found another block, not in target
				}
				checkNode = checkNode.parentElement;
			}
			
			if ( ! startInTarget || ! endInTarget ) {
				console.warn( 'Matched text nodes are not in target block:', {
					block_id: block_id,
					startInTarget: startInTarget,
					endInTarget: endInTarget,
					markData: markData
				} );
				return; // Don't create mark if nodes are not in target block
			}
		} else if ( targetBlock ) {
			// If block_id wasn't found (regenerated), still verify nodes are in the found targetBlock
			let startNodeParent = startChar.node;
			if ( startNodeParent.nodeType === Node.TEXT_NODE ) {
				startNodeParent = startNodeParent.parentElement;
			}
			let startInTarget = false;
			let checkNode = startNodeParent;
			while ( checkNode && checkNode !== document.body ) {
				if ( checkNode === targetBlock ) {
					startInTarget = true;
					break;
				}
				if ( checkNode.hasAttribute && checkNode.hasAttribute( 'data-icfp-block-id' ) ) {
					break;
				}
				checkNode = checkNode.parentElement;
			}
			
			let endNodeParent = endChar.node;
			if ( endNodeParent.nodeType === Node.TEXT_NODE ) {
				endNodeParent = endNodeParent.parentElement;
			}
			let endInTarget = false;
			checkNode = endNodeParent;
			while ( checkNode && checkNode !== document.body ) {
				if ( checkNode === targetBlock ) {
					endInTarget = true;
					break;
				}
				if ( checkNode.hasAttribute && checkNode.hasAttribute( 'data-icfp-block-id' ) ) {
					break;
				}
				checkNode = checkNode.parentElement;
			}
			
			if ( ! startInTarget || ! endInTarget ) {
				console.warn( 'Matched text nodes are not in target block (found by block_name):', {
					block_name: block_name,
					startInTarget: startInTarget,
					endInTarget: endInTarget,
					markData: markData
				} );
				return;
			}
		}

		try {
			const range = document.createRange();
			range.setStart( startChar.node, startChar.offset );
			range.setEnd( endChar.node, endChar.offset + 1 );

			// Check if range already contains a mark/span with datatext
			// Optimize: Use getElementsByTagName instead of querySelector
			let existingInRange = null;
			const ancestor = range.commonAncestorContainer;
			if ( ancestor && ancestor.nodeType === Node.ELEMENT_NODE ) {
				const marks = ancestor.getElementsByTagName( 'mark' );
				const spans = ancestor.getElementsByTagName( 'span' );
				
				for ( let i = 0; i < marks.length && !existingInRange; i++ ) {
					if ( marks[ i ].getAttribute( 'datatext' ) === datatext ) {
						existingInRange = marks[ i ];
						break;
					}
				}
				if ( !existingInRange ) {
					for ( let i = 0; i < spans.length && !existingInRange; i++ ) {
						if ( spans[ i ].getAttribute( 'datatext' ) === datatext ) {
							existingInRange = spans[ i ];
							break;
						}
					}
				}
			}

			if ( existingInRange ) {
				return; // Already wrapped
			}

			// Expand range to include complete inline elements to avoid splitting tags like <strong>
			// This prevents "partially selected non-Text node" errors
			const inlineTags = [ 'STRONG', 'EM', 'B', 'I', 'U', 'CODE', 'MARK', 'A', 'SPAN', 'SUP', 'SUB' ];
			
			// Helper to check if element is a mark/span (we don't want to expand into these)
			const isMarkOrSpan = ( el ) => {
				return el && ( el.tagName === 'MARK' || el.tagName === 'SPAN' );
			};

			// Expand start to include complete inline elements, but don't expand into mark/span elements
			let startNode = range.startContainer;
			if ( startNode.nodeType === Node.TEXT_NODE ) {
				let current = startNode.parentElement;
				while ( current ) {
					// Don't expand into mark/span elements to avoid duplication
					if ( isMarkOrSpan( current ) ) {
						break;
					}
					if ( inlineTags.includes( current.tagName ) ) {
						// If selection starts inside this inline element, expand to include it
						const textBefore = startNode.textContent.substring( 0, range.startOffset );
						if ( textBefore.length > 0 ) {
							try {
								range.setStartBefore( current );
							} catch ( e ) {
								// Can't expand, continue
							}
						}
						break; // Found inline element, stop
					}
					current = current.parentElement;
					if ( ! current || current === range.commonAncestorContainer ) {
						break;
					}
				}
			}

			// Expand end to include complete inline elements, but don't expand into mark/span elements
			let endNode = range.endContainer;
			if ( endNode.nodeType === Node.TEXT_NODE ) {
				let current = endNode.parentElement;
				while ( current ) {
					// Don't expand into mark/span elements to avoid duplication
					if ( isMarkOrSpan( current ) ) {
						break;
					}
					if ( inlineTags.includes( current.tagName ) ) {
						// If selection ends inside this inline element, expand to include it
						const textAfter = endNode.textContent.substring( range.endOffset );
						if ( textAfter.length > 0 ) {
							try {
								range.setEndAfter( current );
							} catch ( e ) {
								// Can't expand, continue
							}
						}
						break; // Found inline element, stop
					}
					current = current.parentElement;
					if ( ! current || current === range.commonAncestorContainer ) {
						break;
					}
				}
			}

		// Verify the range is within a block before creating mark
		let rangeBlock = null;
		let checkNode = range.startContainer;
		if ( checkNode.nodeType === Node.TEXT_NODE ) {
			checkNode = checkNode.parentElement;
		}
		while ( checkNode && checkNode !== document.body ) {
			if ( checkNode.hasAttribute && checkNode.hasAttribute( 'data-icfp-block-id' ) ) {
				rangeBlock = checkNode;
				break;
			}
			checkNode = checkNode.parentElement;
		}

		// STRICT VERIFICATION: If block_id was provided AND found, the range MUST be in that exact block
		if ( block_id && blockIdFound ) {
			if ( ! rangeBlock ) {
				console.warn( 'Range is not in any block, but block_id was provided and found:', {
					block_id: block_id,
					markData: markData
				} );
				return; // Don't create mark if we can't verify the block
			}
			
			const rangeBlockId = rangeBlock.getAttribute( 'data-icfp-block-id' );
			if ( rangeBlockId !== block_id ) {
				console.warn( 'Range is in wrong block - STRICTLY REJECTING:', {
					expected: block_id,
					actual: rangeBlockId,
					markData: markData,
					rangeText: range.toString().substring( 0, 50 )
				} );
				return; // STRICTLY reject - don't create mark in wrong block
			}
			
			// Additional verification: ensure the targetBlock matches
			if ( targetBlock && targetBlock !== rangeBlock ) {
				console.warn( 'Target block mismatch:', {
					targetBlockId: targetBlock.getAttribute( 'data-icfp-block-id' ),
					rangeBlockId: rangeBlockId,
					markData: markData
				} );
				return; // Don't create mark if blocks don't match
			}
		} else {
			// Block ID was not found (regenerated) or not provided
			// Verify the range is in some block
			if ( ! rangeBlock ) {
				console.warn( 'Range is not in any block:', markData );
				return;
			}
			
			// Verify the range is in the targetBlock we found
			if ( targetBlock && rangeBlock !== targetBlock ) {
				// Check if rangeBlock is within targetBlock
				let isWithin = false;
				let checkParent = rangeBlock;
				while ( checkParent && checkParent !== document.body ) {
					if ( checkParent === targetBlock ) {
						isWithin = true;
						break;
					}
					checkParent = checkParent.parentElement;
				}
				
				if ( ! isWithin ) {
					console.warn( 'Range block is not within target block:', {
						rangeBlockId: rangeBlock.getAttribute( 'data-icfp-block-id' ),
						targetBlockId: targetBlock.getAttribute( 'data-icfp-block-id' ),
						markData: markData
					} );
					return;
				}
			}
		}

			// Create mark element
			const mark = document.createElement( 'mark' );
			// Use effectiveDatatext (which may be generated for older comments)
			mark.setAttribute( 'datatext', effectiveDatatext );
			mark.setAttribute( 'id', `icfp-highlight-${comment_id}` );
			mark.setAttribute( 'data-icfp-highlight-id', `icfp-highlight-${comment_id}` );
			mark.setAttribute( 'data-comment-id', comment_id );
			// Add the highlight class so the mark is visible after restoration
			//mark.classList.add( 'mdspan-front-comment' );

			// Set block_id - use the actual block_id from the range (may differ from stored if regenerated)
			if ( rangeBlock ) {
				const actualBlockId = rangeBlock.getAttribute( 'data-icfp-block-id' );
				if ( actualBlockId ) {
					mark.setAttribute( 'data-icfp-block-id', actualBlockId );
				}
			} else if ( block_id ) {
				// Fallback to stored block_id if rangeBlock not found
				mark.setAttribute( 'data-icfp-block-id', block_id );
			}

			// Try to wrap the range with mark element
			try {
				range.surroundContents( mark );
			} catch ( surroundError ) {
				// If surroundContents fails, use extractContents as fallback
				console.warn( 'surroundContents failed, using extractContents fallback:', {
					error: surroundError.message,
					comment_id: comment_id,
					datatext: effectiveDatatext
				} );
				try {
					const contents = range.extractContents();
					
					// Normalize any existing comment marks/spans in the extracted content
					const walker = document.createTreeWalker(
						contents,
						NodeFilter.SHOW_ELEMENT,
						null,
						false
					);
					const toUnwrap = [];
					while ( walker.nextNode() ) {
						const el = walker.currentNode;
						if ( ( el.tagName === 'MARK' || el.tagName === 'SPAN' ) && 
						     ( el.classList.contains( 'mdspan-front-comment' ) || el.hasAttribute( 'datatext' ) ) ) {
							toUnwrap.push( el );
						}
					}
					toUnwrap.forEach( ( el ) => {
						const parent = el.parentNode;
						if ( parent ) {
							while ( el.firstChild ) {
								parent.insertBefore( el.firstChild, el );
							}
							parent.removeChild( el );
						}
					});
					
					// Move all nodes from contents to mark
					while ( contents.firstChild ) {
						mark.appendChild( contents.firstChild );
					}
					
					// Insert mark at the collapsed range position (range collapsed to start after extractContents)
					range.insertNode( mark );
					
					// Clean up any empty inline elements that might have been left behind
					// Optimize: Use getElementsByTagName instead of querySelectorAll
					if ( mark.parentElement ) {
						const inlineTags = [ 'strong', 'em', 'b', 'i', 'u', 'code', 'sup', 'sub' ];
						inlineTags.forEach( tagName => {
							const elements = mark.parentElement.getElementsByTagName( tagName );
							for ( let i = 0; i < elements.length; i++ ) {
								const el = elements[ i ];
								if ( ! el.textContent || el.textContent.trim() === '' ) {
									if ( el.parentNode ) {
										el.parentNode.removeChild( el );
			}
		}
	}
	
	/**
	 * Restore mark using DOM Range API with exact offsets (the correct way).
	 * This function uses the stored startOffset and endOffset to find the exact text nodes
	 * and create a Range, then wraps it with a mark element.
	 * 
	 * @param {Object} markData - The mark data object
	 * @param {Object} rangeInfo - The range information with startOffset, endOffset, blockId
	 * @param {string} effectiveDatatext - The datatext to use
	 * @param {string|number} commentId - The comment ID
	 */
	function cfRestoreMarkByRange( markData, rangeInfo, effectiveDatatext, commentId ) {
		const { startOffset, endOffset, blockId, textBefore, textAfter } = rangeInfo;
		
		// Find the target block - block IDs are regenerated, so we need to match by block_name and block_attrs
		let targetBlock = null;
		
		// First try by blockId (in case it matches)
		if ( blockId ) {
			targetBlock = document.getElementById( blockId );
			if ( ! targetBlock ) {
				const allBlocks = document.querySelectorAll( `[data-icfp-block-id="${blockId}"]` );
				if ( allBlocks.length > 0 ) {
					targetBlock = allBlocks[ 0 ];
				}
			}
		}
		
		// If blockId doesn't match (regenerated), find by block_name and block_attrs
		if ( ! targetBlock && markData.block_name ) {
			const contentAreas = document.getElementsByClassName( 'entry-content' );
			const postContent = document.getElementsByClassName( 'wp-block-post-content' );
			const searchContainers = [];
			
			if ( contentAreas.length > 0 ) {
				for ( let i = 0; i < contentAreas.length; i++ ) {
					searchContainers.push( contentAreas[ i ] );
				}
			}
			if ( postContent.length > 0 ) {
				for ( let i = 0; i < postContent.length; i++ ) {
					searchContainers.push( postContent[ i ] );
				}
			}
			
			// Find blocks with matching block_name
			const candidateBlocks = [];
			for ( let c = 0; c < searchContainers.length; c++ ) {
				const container = searchContainers[ c ];
				const commonBlockTags = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'ul', 'ol', 'li' ];
				for ( let t = 0; t < commonBlockTags.length; t++ ) {
					const elements = container.getElementsByTagName( commonBlockTags[ t ] );
					for ( let i = 0; i < elements.length; i++ ) {
						const el = elements[ i ];
						if ( el.hasAttribute && el.hasAttribute( 'data-icfp-block-id' ) ) {
							const elBlockName = el.getAttribute( 'data-icfp-block-name' );
							if ( elBlockName === markData.block_name ) {
								candidateBlocks.push( el );
							}
						}
					}
				}
			}
			
			// If multiple blocks with same name, use textBefore/textAfter to identify the correct one
			if ( candidateBlocks.length > 1 && textBefore && textAfter ) {
				for ( let i = 0; i < candidateBlocks.length; i++ ) {
					const block = candidateBlocks[ i ];
					const blockText = block.textContent || '';
					// Check if this block contains the textBefore and textAfter context
					if ( blockText.includes( textBefore ) && blockText.includes( textAfter ) ) {
						// Verify the order: textBefore should come before textAfter
						const beforeIndex = blockText.indexOf( textBefore );
						const afterIndex = blockText.indexOf( textAfter );
						if ( beforeIndex !== -1 && afterIndex !== -1 && beforeIndex < afterIndex ) {
							targetBlock = block;
							break;
						}
					}
				}
			}
			
			// If still no match, use the first candidate (fallback)
			if ( ! targetBlock && candidateBlocks.length > 0 ) {
				targetBlock = candidateBlocks[ 0 ];
			}
		}
		
		if ( ! targetBlock ) {
			console.warn( 'Target block not found for range_info, falling back to old method:', {
				blockId: blockId,
				blockName: markData.block_name,
				textBefore: textBefore?.substring( 0, 30 ),
				textAfter: textAfter?.substring( 0, 30 )
			} );
			// Return false to indicate failure, so caller can fall back to old method
			return false;
		}
		
		// Safety check: Check if mark already exists in this block
		const existingMarks = targetBlock.querySelectorAll( `mark[datatext="${effectiveDatatext}"], mark[data-comment-id="${commentId}"]` );
		if ( existingMarks.length > 0 ) {
			return;
		}
		
		// Walk text nodes in the block and track cumulative character offsets
		// IMPORTANT: We need to walk text nodes in the same way we calculated offsets
		// This means we should NOT skip existing marks, because offsets were calculated including all text
		const walker = document.createTreeWalker(
			targetBlock,
			NodeFilter.SHOW_TEXT,
			{
				acceptNode: function( node ) {
					// Only skip text nodes inside nested blocks
					let parent = node.parentElement;
					while ( parent && parent !== targetBlock ) {
						if ( parent.hasAttribute && parent.hasAttribute( 'data-icfp-block-id' ) ) {
							return NodeFilter.FILTER_REJECT; // Nested block
						}
						parent = parent.parentElement;
					}
					return NodeFilter.FILTER_ACCEPT;
				}
			},
			false
		);
		
		const textNodes = [];
		let cumulativeOffset = 0;
		
		while ( walker.nextNode() ) {
			const node = walker.currentNode;
			const text = node.nodeValue || '';
			const nodeLength = text.length;
			
			textNodes.push( {
				node: node,
				startOffset: cumulativeOffset,
				endOffset: cumulativeOffset + nodeLength,
				text: text
			} );
			
			cumulativeOffset += nodeLength;
		}
		
		// Verify the total text length matches (accounting for potential differences)
		const totalTextLength = cumulativeOffset;
		if ( endOffset > totalTextLength ) {
			console.warn( 'End offset exceeds block text length, adjusting:', {
				startOffset: startOffset,
				endOffset: endOffset,
				totalTextLength: totalTextLength,
				blockText: targetBlock.textContent?.substring( 0, 100 )
			} );
			// The offsets might have been calculated differently, try to find by text content
			// Fall back to old method
			return;
		}
		
		// Find the text nodes that contain startOffset and endOffset
		let startNode = null;
		let startNodeOffset = 0;
		let endNode = null;
		let endNodeOffset = 0;
		
		for ( let i = 0; i < textNodes.length; i++ ) {
			const textNode = textNodes[ i ];
			
			// Check if startOffset is within this node
			if ( ! startNode && startOffset >= textNode.startOffset && startOffset < textNode.endOffset ) {
				startNode = textNode.node;
				startNodeOffset = startOffset - textNode.startOffset;
			}
			
			// Check if endOffset is within this node
			if ( endOffset > textNode.startOffset && endOffset <= textNode.endOffset ) {
				endNode = textNode.node;
				endNodeOffset = endOffset - textNode.startOffset;
				break;
			}
		}
		
		if ( ! startNode || ! endNode ) {
			console.warn( 'Could not find text nodes for range offsets:', {
				startOffset: startOffset,
				endOffset: endOffset,
				totalTextLength: cumulativeOffset,
				textNodesCount: textNodes.length,
				blockText: targetBlock.textContent?.substring( 0, 200 ),
				textNodes: textNodes.map( tn => ( { start: tn.startOffset, end: tn.endOffset, text: tn.text.substring( 0, 20 ) } ) )
			} );
			// Return false to indicate failure, so caller can fall back to old method
			return false;
		}
		
		// Verify text content matches (additional safety check)
		const range = document.createRange();
		range.setStart( startNode, startNodeOffset );
		range.setEnd( endNode, endNodeOffset );
		const rangeText = range.toString().trim();
		const expectedText = markData.text.trim();
		
		if ( rangeText !== expectedText && ! rangeText.includes( expectedText ) && ! expectedText.includes( rangeText ) ) {
			console.warn( 'Range text does not match expected text:', {
				rangeText: rangeText.substring( 0, 50 ),
				expectedText: expectedText.substring( 0, 50 ),
				commentId: commentId
			} );
			// Still proceed, but log warning
		}
		
		// Check if range already contains a mark
		const ancestor = range.commonAncestorContainer;
		if ( ancestor && ancestor.nodeType === Node.ELEMENT_NODE ) {
			const existingMarks = ancestor.querySelectorAll( `mark[datatext="${effectiveDatatext}"], mark[data-comment-id="${commentId}"]` );
			if ( existingMarks.length > 0 ) {
				return;
			}
		}
		
		// Create the mark element
		const mark = document.createElement( 'mark' );
		mark.setAttribute( 'datatext', effectiveDatatext );
		mark.setAttribute( 'id', `icfp-highlight-${commentId}` );
		mark.setAttribute( 'data-icfp-highlight-id', `icfp-highlight-${commentId}` );
		mark.setAttribute( 'data-comment-id', commentId );
		if ( markData.block_name ) {
			mark.setAttribute( 'data-icfp-block-name', markData.block_name );
		}
		
		// Wrap the range contents with the mark element
		try {
			range.surroundContents( mark );
			return true; // Success
		} catch ( e ) {
			console.error( 'Failed to surround contents with mark:', e, {
				commentId: commentId,
				rangeText: rangeText.substring( 0, 50 ),
				error: e.message,
				startNode: startNode,
				endNode: endNode,
				startNodeOffset: startNodeOffset,
				endNodeOffset: endNodeOffset
			} );
			// Return false to indicate failure, so caller can fall back to old method
			return false;
		}
	}
	});
					}
				} catch ( extractError ) {
					console.error( 'Error in fallback extractContents:', {
						error: extractError.message,
						comment_id: comment_id,
						datatext: effectiveDatatext,
						text: text.substring( 0, 50 ),
						markData: markData
					} );
					return;
				}
			}
		} catch ( e ) {
			console.error( 'Error restoring mark element:', {
				error: e.message,
				comment_id: comment_id,
				datatext: effectiveDatatext,
				text: text.substring( 0, 50 ),
				markData: markData
			} );
		}
	}
   
	/**
 * Restore mark using DOM Range API
 */
function cfRestoreMarkByRange( markData, rangeInfo, effectiveDatatext, commentId ) {
	// ... (your existing function body stays the same)
}
		

} );
