MediaWiki:Common.js

From Psalms: Layer by Layer
Jump to: navigation, search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// importScript('MediaWiki:Overlays.js');
//importScript('MediaWiki:Insertions.js');
//importScript('MediaWiki:Lineation.js');


	/* Any JavaScript here will be loaded for all users on every page load. */
	
	// Function to check if all <pre class="mermaid"> elements are processed
	function checkMermaidProcessed() {
	    var mermaidPreElements = document.querySelectorAll('pre.mermaid');
	    var allProcessed = true;
	
	    mermaidPreElements.forEach(function (element) {
	        if (!element.hasAttribute('data-processed') || element.getAttribute('data-processed') !== 'true') {
	            allProcessed = false;
	        }
	    });
	
	    return allProcessed;
	}
	
	// Function to wait until all Mermaid diagrams are processed
	function waitForMermaidProcessing(callback) {
	    var interval = setInterval(function () {
	        if (checkMermaidProcessed()) {
	            clearInterval(interval);
	            callback(); // Once all elements are processed, run the callback
	        }
	    }, 100); // Check every 100ms
	}
	
	function toggleVisibility(containerId, className) {
	    //console.log("Toggling visibility for " + className + " in " + containerId);
	    var container = document.getElementById(containerId);
	    if (container) {
	    	if (className==="alternative"){
				
	
				// Match all elements with pinkish FILL or STROKE
				var elements = container.querySelectorAll('[fill^="#f4"], [stroke^="#f4"], [fill^="#f6"], [stroke^="#f6"]');
				
				elements.forEach(function(el) {
				    if (!el.hasAttribute('data-original-display')) {
				        el.setAttribute('data-original-display', el.style.display || "");
				    }
				
				    if (el.style.display === "none") {
				        el.style.display = el.getAttribute('data-original-display') || "";
				    } else {
				        el.style.display = "none";
				    }
				});
	
	    	}else{
		        var elements = container.querySelectorAll("g." + className);
		        for (var i = 0; i < elements.length; i++) {
		            var element = elements[i];
		            
		            if (element.style.display === "none") {
		                element.style.display = ""; // Show element
		            } else {
		                element.style.display = "none"; // Hide element
		            }
		        }
	    	}
	    } else {
	        console.warn("Container with ID \"" + containerId + "\" not found.");
	    }
	}
	
	function attachToggleListeners() {
	    //console.log("Attaching event listeners to toggle links.");
	    var toggleLinks = document.querySelectorAll("[data-container-id][data-class]");
	    //console.log("Found " + toggleLinks.length + " links.");
	
	    // If no toggle links are found, print a warning
	    if (toggleLinks.length === 0) {
	        //console.warn("No toggle links found on the page.");
	    }
	    
	    for (var i = 0; i < toggleLinks.length; i++) {
	        (function (toggleLink) {
	            toggleLink.addEventListener("click", function (event) {
	                event.preventDefault(); // Prevent default link behavior
	                var containerId = toggleLink.getAttribute("data-container-id");
	                var className = toggleLink.getAttribute("data-class");
	                toggleVisibility(containerId, className);
	            });
	        })(toggleLinks[i]);
	    }
	}
	


	


// ===========================


$(document).ready(function () {

	
	// ===========================
	// Begin placeholder auto-loading
	// ===========================



	var observer = new IntersectionObserver(function(entries) {
	    entries.forEach(function(entry) {
	        if (entry.isIntersecting) {
	            var target = entry.target;
	            if (!target.dataset.loaded) { // Don't double-load
	                loadChunk(target);
	            }
	        }
	    });
	}, {
	    rootMargin: '200px'
	});
	
	document.querySelectorAll('.placeholder').forEach(function(placeholder) {
	    observer.observe(placeholder);
	});
	
	function loadChunk(placeholder) {
	    var id = placeholder.id; // e.g., v-1

		var basePage = mw.config.get('wgPageName').replace(/Test/g, "");
		// Remove prefix (v, vv, verse) followed by any number of ".", "_", "-", or space
		var cleanedId = id.replace(/^(vv|v|verse)[\.\_\-\s]*/i, '');
		var nextPage = basePage + cleanedId;
		console.log('Seeking to load ' + nextPage + ' (from original ' + id + ')');
	
	    var xhr = new XMLHttpRequest();
	    xhr.open('GET', '/w/' + nextPage + '?action=render', true);
	    xhr.onreadystatechange = function () {
			if (xhr.readyState === 4) {
	            if (xhr.status === 200) {
	                placeholder.innerHTML = xhr.responseText;
	                placeholder.dataset.loaded = 'true';

					addHeadingsFromChunkToTOC(placeholder);

	            } else if (xhr.status === 404) {
	                placeholder.innerHTML = '<div class="missing-chunk">This section is not yet available.</div>';
	                placeholder.dataset.loaded = 'true'; // Prevent reloading attempts
	            } else {
	                placeholder.innerHTML = '<div class="load-error">Error loading section (status ' + xhr.status + ').</div>';
	                placeholder.dataset.loaded = 'true';
	            }
	        }

	    };
	    xhr.send();
	}

	function addHeadingsFromChunkToTOC(chunkDiv) {
	    if (!chunkDiv) return;
	
	    var targetText = chunkDiv.id.replace(/_/,' ') + ' (loading...)';
	    var allSpans = document.querySelectorAll('.toctext');
	
	    var parentTocSpan = null;
	    for (var i = 0; i < allSpans.length; i++) {
	        if (allSpans[i].textContent === targetText) {
	            allSpans[i].textContent = chunkDiv.id.replace(/_/,' '); // Remove (loading...)
	            parentTocSpan = allSpans[i];
	            break;
	        }
	    }
	
	    if (!parentTocSpan) {
	        console.warn('Parent TOC span not found for chunk:', chunkDiv.id);
	        return;
	    }


		// Clean up the corresponding H1 heading (remove " (loading...)" from text and ID)
		var originalHeadingId = chunkDiv.id + '_(loading...)';
		var h1span = document.getElementById(originalHeadingId);
		
		if (h1span && h1span.classList.contains('mw-headline')) {
		    var cleanedId = chunkDiv.id;
		
		    // Update ID
		    h1span.id = cleanedId;
		
		    // Update display text
		    h1span.textContent = cleanedId.replace(/_/g, ' ');
		}
		

	
	    var parentTocLi = parentTocSpan.closest('li');
	    if (!parentTocLi) {
	        console.warn('Parent TOC li not found for chunk:', chunkDiv.id);
	        return;
	    }
	
	    // Find or create a sub-UL under this parent LI
	    var subUl = parentTocLi.querySelector('ul');
	    if (!subUl) {
	        subUl = document.createElement('ul');
	        subUl.className = 'toc-sublist'; // optional styling class
	        parentTocLi.appendChild(subUl);
	    }
	
	    // Now find all new headings inside the chunk
	    var newHeadings = chunkDiv.querySelectorAll('h2, h3');
	
	    for (var j = 0; j < newHeadings.length; j++) {
	        var heading = newHeadings[j];
	
	        // Ensure the heading has an id
	        if (!heading.id) {
	            heading.id = 'heading-' + Math.random().toString(36).substr(2, 9);
	        }
	
	        // Create new TOC <li>
	        var li = document.createElement('li');
	        li.className = 'toclevel-' + (heading.tagName === 'H2' ? '1' : '2');
	
	        var a = document.createElement('a');
	        a.href = '#' + heading.id;
	        a.className = 'nav-link';
	
	        var spanNumber = document.createElement('span');
	        spanNumber.className = 'tocnumber';
	        spanNumber.textContent = ''; // optional numbering if needed
	
	        var spanText = document.createElement('span');
	        spanText.className = 'toctext';
	        spanText.textContent = heading.textContent;
	
	        a.appendChild(spanNumber);
	        a.appendChild(spanText);
	        li.appendChild(a);
	        subUl.appendChild(li);
	    }
	}


	
	// ===========================
	// End placeholder auto-loading
	// ===========================

	


	
	
	// ===========================
	// Auto-create links for notes such as "v. 2 preferred diagram"
	// ===========================

	
    var headingLinks = {};


	if (document.getElementById("createDiagramLinks")) {
	    // 1. Build a mapping from "v. 2 preferred" ➔ "Preferred_2"
	    var currentVerseRange = "";
	
	    var headings = document.querySelectorAll('h1, h2');
	    for (var i = 0; i < headings.length; i++) {
	        var heading = headings[i];
	        if (heading.tagName === 'H1') {
	            currentVerseRange = heading.innerText.trim().toLowerCase(); // e.g., "v. 2"
	        }
	        if (heading.tagName === 'H2') {
	            var description = heading.innerText.trim();
	            var combinedKey = (currentVerseRange + " " + description).toLowerCase(); // e.g., "v. 2 preferred"
	
	            // Ensure heading has a usable ID
	            if (!heading.id) {
	                heading.id = description.replace(/\s+/g, "_") + "_" + currentVerseRange.replace(/[^0-9]/g, "");
	            }
	
	            headingLinks[combinedKey] = heading.id;
	        }
	    }
	
	    // 3. Apply to whole document
	    linkifyTextNodes(document.body);
	}
	

    // 2. Search and replace text nodes
    function linkifyTextNodes(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            var text = node.nodeValue;
            var parent = node.parentNode;

            for (var phrase in headingLinks) {
                var regex = new RegExp("\\b(" + phrase + ") diagram\\b", "i");
                var match = regex.exec(text);
                if (match) {
                    var before = text.slice(0, match.index);
                    var after = text.slice(match.index + match[0].length);

                    var anchor = document.createElement('a');
                    anchor.href = "#" + headingLinks[phrase];
                    anchor.textContent = match[0];

                    parent.insertBefore(document.createTextNode(before), node);
                    parent.insertBefore(anchor, node);
                    if (after) parent.insertBefore(document.createTextNode(after), node);
                    parent.removeChild(node);
                    break; // Stop after first match in this node
                }
            }
        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'A') {
            for (var j = 0; j < node.childNodes.length; j++) {
                linkifyTextNodes(node.childNodes[j]);
            }
        }
    }

	// ===========================
	// End links for notes such as "v. 2 preferred diagram"
	// ===========================



    //console.log("Document ready. Attaching event listeners to toggle links.");

    // Now attach event listeners for toggling visibility
    attachToggleListeners();
    
    // Wait until all Mermaid diagrams are processed
    waitForMermaidProcessing(function () {
        //console.log("Mermaid diagrams are fully processed.");

        $('div[id^="verse-"]').each(function () {
            var parentDiv = $(this);
            var svg = parentDiv.find('svg');

            if (svg.length > 0) {
                var preElement = parentDiv.find('pre.mermaid');  // The <pre> element containing the SVG
                var preWidth = preElement.width();
                var preHeight = preElement.height();
                var viewBox = svg[0].getAttribute('viewBox');

                if (viewBox) {
                    var viewBoxValues = viewBox.split(' ');
                    var viewBoxWidth = parseFloat(viewBoxValues[2]);
                    var viewBoxHeight = parseFloat(viewBoxValues[3]);
                    var scaleX = preWidth / viewBoxWidth;
                    var scaleY = preHeight / viewBoxHeight;
                    var scale = Math.min(scaleX, scaleY);

                    svg.css({
                        'width': (preWidth) + 'px',
                        'max-width': (preWidth) + 'px',
                        'height': (viewBoxHeight * scale) + 'px',
                        'position': 'relative',  // Ensure the SVG has a positioning context
                        'left': '-10px'  // Offset the SVG to the left, because firefox and others misalign it to the right. This removes the horizontal scrollbar
                    });

					// Initialize panzoom
					var panZoomInstance = Panzoom(svg[0], { 
						contain: 'outside',
						minScale: 1,  // default is 0.125
						maxScale: 10,  // default is 4
						panOnlyWhenZoomed: true, //default is false
						zoomSpeed: 0.040, // default is 6.5% per mouse wheel event
						pinchSpeed: 1.5 // default is zoom two times faster than the distance between fingers
					});
                    parentDiv[0].addEventListener('wheel', function (e) {
						e.preventDefault();
    					panZoomInstance.zoomWithWheel(e, { step: 0.04 }); // custom override per event
					});
                    parentDiv[0].addEventListener('dblclick', function (event) {
	        	    var rect = parentDiv[0].getBoundingClientRect();
				    var offsetX = event.clientX - rect.left;
				    var offsetY = event.clientY - rect.top;
				    if (event.shiftKey) {
				        // Shift + Double-click → Zoom Out
				        panZoomInstance.zoomOut({ focal: { x: offsetX, y: offsetY } });
				    } else {
				        // Regular Double-click → Zoom In
				        panZoomInstance.zoomIn({ focal: { x: offsetX, y: offsetY } });
				    }
	            });

                    // Resize handler to keep SVG scaled on window resize
                    var resizeHandler = function () {
                        var newWidth = preElement.width();

                        svg.css({
                            'width': (newWidth) + 'px',
                            'max-width': (newWidth) + 'px'
                            // Do not change the height to avoid reflowing the html page
                        });
                    };
                    
                    // Listen for resize events
                    $(window).on('resize', resizeHandler);
                }
            }
        
            
        });

        // Initially hide elements with the "highlight-phrase" class
        document.querySelectorAll(".highlight-phrase").forEach(function (element) {
            element.style.display = "none"; // Hide elements initially
        });



        // Bind lightbox functionality
        $('.lightbox-button').on('click', function () {
            // Get the target <div> ID from the button's data-target attribute
            var targetDivId = $(this).data('target'); // e.g., '#verse-1'
            var parentDiv = $(targetDivId); // Find the corresponding <div> by ID
            var associatedSvg = parentDiv.find('svg'); // Find the SVG inside the <pre>

            if (associatedSvg.length > 0) {
                openLightbox(associatedSvg[0]);
            }
        });

        // Open the lightbox and display the SVG in full-screen
        function openLightbox(svgElement) {
            // Create lightbox container if it doesn't exist            
            var lightbox = $('<div id="lightbox-overlay" class="lightbox-overlay">')
                .appendTo('body')
                .css({
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: '100%',
                    backgroundColor: 'rgba(128, 128, 128, 0.8)',
                    display: 'flex',
                    justifyContent: 'center',
                    alignItems: 'center',
                    zIndex: 9999,
                });

            // Create the SVG container in the lightbox
                var lightboxSvgContainer = $('<div class="lightbox-svg-container">')
                .appendTo(lightbox)
                .css({
                    width: '95%',
                    height: '95%',
                    overflow: 'hidden',
                    backgroundColor: 'rgba(255, 255, 255, 1.0)',
                });

            var lightboxSvg = $(svgElement).clone().appendTo(lightboxSvgContainer);  // Clone the SVG
            // resize the svg to the available space
            lightboxSvg.css({
                'width': '100%',
                'max-width': '100%',
                'height': '100%'
            });

            // Apply panzoom to the cloned SVG in the lightbox
			var panZoomInstanceLightbox = Panzoom(lightboxSvg[0], { 
				contain: 'outside',
				minScale: 1,  // default is 0.125
				maxScale: 10,  // default is 4
				panOnlyWhenZoomed: true, //default is false
				zoomSpeed: 0.040, // default is 6.5% per mouse wheel event
				pinchSpeed: 1.5 // default is zoom two times faster than the distance between fingers
			});
            lightboxSvg[0].addEventListener('wheel', function (e) {
				e.preventDefault();
				panZoomInstanceLightbox.zoomWithWheel(e, { step: 0.04 }); // custom override per event
			});
            lightboxSvg[0].addEventListener('dblclick', function (event) {
        	    var rect = lightboxSvg[0].getBoundingClientRect();
			    var offsetX = event.clientX - rect.left;
			    var offsetY = event.clientY - rect.top;
			    if (event.shiftKey) {
			        // Shift + Double-click → Zoom Out
			        panZoomInstanceLightbox.zoomOut({ focal: { x: offsetX, y: offsetY } });
			    } else {
			        // Regular Double-click → Zoom In
			        panZoomInstanceLightbox.zoomIn({ focal: { x: offsetX, y: offsetY } });
			    }
            });

            var closeButton = $('<button class="lightbox-close-button">Close</button>')
                .appendTo(lightbox)
                .css({
                    position: 'absolute',
                    top: '10px',
                    right: '10px',
                    backgroundColor: '#fff',
                    color: '#000',
                    border: '1px solid #bbb',
                    borderRadius: '1rem',
                    padding: '10px 20px',
                    cursor: 'pointer',
                    zIndex: 10000
                })
                .on('click', function () {
                    // Close the lightbox when the close button is clicked
                    lightbox.remove();
                });

            lightbox.on('click', function (event) {
                // Close the lightbox when clicking outside the SVG
                if ($(event.target).is(lightbox)) {
                    lightbox.remove();
                }
            });

            // Close the lightbox with the Escape key
            $(document).on('keydown', function (event) {
                if (event.key === "Escape" || event.keyCode === 27) {
                    lightbox.remove();
                    $(document).off('keydown');  // Remove the keydown listener to prevent multiple bindings
                }
            });
        }
    });

	// Bidirectional hover for Hebrew and Gloss (ES5-compatible)
	var hoverElements = document.querySelectorAll(".hebrew, .gloss");

	// If no toggle links are found, print a warning
	    if (hoverElements.length === 0) {
	        console.warn("No hover elements found on the page.");
	    }
    	
	for (var i = 0; i < hoverElements.length; i++) {
	    (function (el) {
	        el.addEventListener("mouseenter", function () {
	            //var classList = el.className.split(" ");
	            
	            var className = (typeof el.className === 'object' && el.className.baseVal) ? el.className.baseVal : el.className;
				var classList = className.split(" ");

	            for (var j = 0; j < classList.length; j++) {
	                var cls = classList[j];
	                if (cls.indexOf("id-") === 0) {
	                    var matches = document.getElementsByClassName(cls);
	                    for (var k = 0; k < matches.length; k++) {
	                        matches[k].classList.add("highlighted");
	                    }
	                }
	            }
	        });
	
	        el.addEventListener("mouseleave", function () {
	            //var classList = el.className.split(" ");
	            
	            var className = (typeof el.className === 'object' && el.className.baseVal) ? el.className.baseVal : el.className;
				var classList = className.split(" ");
	            for (var j = 0; j < classList.length; j++) {
	                var cls = classList[j];
	                if (cls.indexOf("id-") === 0) {
	                    var matches = document.getElementsByClassName(cls);
	                    for (var k = 0; k < matches.length; k++) {
	                        matches[k].classList.remove("highlighted");
	                    }
	                }
	            }
	        });
	    })(hoverElements[i]);
	}
	
	

});

// Stopping fixed elements
$(document).ready(function () {
  var $box = $('.fixed-box');
  var $wrapper = $box.parent();
  var stopY = 515;

  $(window).on('scroll', function () {
    var scrollY = window.scrollY || window.pageYOffset;

    if (scrollY >= stopY) {
      $box.css({
        position: 'absolute',
        top: stopY + 'px', // place it exactly where it was fixed
        left: 0,
        width: '100%'
      });
    } else {
      $box.css({
        position: 'fixed',
        top: '0px',
        left: 0,
        width: '100%'
      });
    }
  });
});




/* ======================================


        BuildTextTable js


  ======================================*/
  
  var selectedHebrewID = null;
var selectedHebrewSpan = null;
var overlay = document.getElementById('buildOverlay');
var modeField = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');

// Split vs Align overlay mode switching
function updateOverlayModeClass() {
  var modeInput = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');
  var overlay = document.getElementById("buildOverlay");
  if (!modeInput || !overlay) return;

  overlay.classList.remove("mode-split", "mode-align");

  if (modeInput.value === "Split Words") {
    overlay.classList.add("mode-split");
  } else if (modeInput.value === "Align English") {
    overlay.classList.add("mode-align");

    var englishSpans = overlay.querySelectorAll("td.cbc-cell span");
    console.log("Aligning English: " + englishSpans.length);

    for (var i = 0; i < englishSpans.length; i++) {
      var engSpan = englishSpans[i];
      var classMatch = engSpan.className.match(/id-[\w-]+/);
      if (!classMatch) continue;

      var idClass = classMatch[0];
      var hebrewSpan = overlay.querySelector("td.hebrew-cell span." + idClass);
      if (!hebrewSpan) continue;

      var computedStyle = window.getComputedStyle(hebrewSpan);
      engSpan.style.color = computedStyle.color;
    }
  }
}



function splitSpan(span, event) {
	    var classMatch = span.className.match(/id-(\d+[a-z]?)-(\d+)-(\d+)/);
		var isIDClass = !!classMatch;
		
		  // Disallow splitting unless class is 'english' or has an id-*
		  if (!isIDClass && !span.classList.contains("english")) {
		    console.warn("Cannot split span " + span.textContent + " with class " + span.className);
		    return;
		  }

	  console.log("Splitting span for span " + span.textContent + " and class " + span.className);
	
  // get range / position to know exactly where we are
  var range;
  if (document.caretRangeFromPoint) {
    range = document.caretRangeFromPoint(event.clientX, event.clientY);
  } else if (document.caretPositionFromPoint) {
    var pos = document.caretPositionFromPoint(event.clientX, event.clientY);
    if (!pos) return;
    range = document.createRange();
    range.setStart(pos.offsetNode, pos.offset);
  } else {
    return;
  }

  var offset = range.startOffset;
  var text = span.textContent;
  if (offset <= 0 || offset >= text.length) return;

  var before = text.slice(0, offset);
  var after = text.slice(offset);

  var span1 = document.createElement("span");
  span1.textContent = before.trim();
	
	var span2 = document.createElement("span");
	span2.textContent = after.trim();

  var td = span.closest("td");
  if (td && td.classList.contains("hebrew-cell")) {
  	
	  // parse class
	  var verse = classMatch[1];
	  var wordIndex = classMatch[2];
	  var letterIndex = parseInt(classMatch[3], 10);
	  var baseID = verse + "-" + wordIndex;
	  var newLetterIndex = letterIndex + before.length;
/*
	  // check for merging instead of splitting
	  if (event.shiftKey) {
	    mergeSplitSpans(baseID);
	    return;
	  }
*/
	  // update Hebrew spans with a newLetterIndex
	  span1.className = "hebrew id-" + baseID + "-" + letterIndex;
	  span2.className = "hebrew id-" + baseID + "-" + newLetterIndex;
	
  }else if (td && td.classList.contains("cbc-cell")){

  	  // don't change the class list for the CBC cells
	  span1.className = span.className;
	  span2.className = span.className;
	  
  } else {
  	
    // Fallback
    span1.className = span.className;
    span2.className = span.className;
  }

  attachSpanClickHandler(span1);
  attachSpanClickHandler(span2);

  var parent = span.parentNode;
  if (parent) {
    parent.insertBefore(span1, span);
    //parent.insertBefore(document.createTextNode(" "), span);
    
    var interveningSpace = document.createElement("span");
	interveningSpace.className = "gap";
	interveningSpace.textContent = " ";
	interveningSpace.addEventListener("click", function (event) {
	  attemptGapMerge(interveningSpace, event);
	});
	parent.insertBefore(interveningSpace, span);

    
    parent.insertBefore(span2, span);
    parent.removeChild(span);
  }	
	
}

function attachSpanClickHandler(span) {
  span.addEventListener("click", function (event) {
    event.stopPropagation();

    var selectedMode = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');
    if (!selectedMode) return;

    var mode = selectedMode.value;
    var isHebrew = span.classList.contains("hebrew");
    var isEnglish = span.classList.contains("english") || span.classList.contains("gloss");

    console.log("Clicked span:", span.innerText, "| Mode:", mode, "| Shift:", event.shiftKey);

    if (mode === "Split Words") {
      splitSpan(span, event);

    } else if (mode === "Align English") {

      if (event.shiftKey && isEnglish) {
        // ✅ SHIFT-CLICK ENGLISH → unalign it
        span.className = span.className
          .split(/\s+/)
          .filter(function (cls) {
            return cls.indexOf("id-") !== 0 && cls !== "aligned-english";
          })
          .join(" ");
        span.style.color = ""; // optional: reset color
        return;
      }

      if (event.shiftKey && isHebrew) {
        // ✅ SHIFT-CLICK HEBREW → deselect it
        document.querySelectorAll("span.hebrew").forEach(function (el) {
          el.classList.remove("hebrew-selected");
        });
        return;
      }

      if (isHebrew) {
        document.querySelectorAll("span.hebrew").forEach(function (el) {
          el.classList.remove("hebrew-selected");
        });
        span.classList.add("hebrew-selected");

        var idMatch = span.className.match(/id-[\w-]+/);
        selectedHebrewID = idMatch ? idMatch[0].substring(3) : null;
        selectedHebrewSpan = span;

      } else if (isEnglish && selectedHebrewID) {
        // Align to selected Hebrew
        var newClasses = span.className.split(" ").filter(function (cls) {
          return cls.indexOf("id-") !== 0;
        });
        newClasses.push("id-" + selectedHebrewID);
        newClasses.push("aligned-english");
        span.className = newClasses.join(" ");

        if (selectedHebrewSpan) {
          var color = window.getComputedStyle(selectedHebrewSpan).color;
          span.style.color = color;
        }
      }
    }
  });
}



/*

function mergeSplitSpans(baseID) {
  var all = document.querySelectorAll('span[class*="id-' + baseID + '-"]');
  if (all.length <= 1) return;

  var mergedText = '';
  for (var i = 0; i < all.length; i++) {
    mergedText += all[i].textContent;
  }

  var parent = all[0].parentNode;
  var firstSpan = all[0];

  for (var i = 1; i < all.length; i++) {
    parent.removeChild(all[i]);
  }

  var newClassName = firstSpan.className.split(" ").map(function (cls) {
    return cls.indexOf("id-") === 0 ? "id-" + baseID + "-1" : cls;
  }).join(" ");

  firstSpan.className = newClassName;
  firstSpan.textContent = mergedText;
  attachSpanClickHandler(firstSpan);
}
*/
function mergeTwoSpans(spanA, spanB) {
  if (!spanA || !spanB || spanA.tagName !== "SPAN" || spanB.tagName !== "SPAN") return;
  if (spanA.parentNode !== spanB.parentNode) return;

  var parent = spanA.parentNode;

  // Merge text
  var mergedText = spanA.textContent + spanB.textContent;

  // Determine baseID if present
  var baseID = null;
  var langClass = null;
  var classesA = spanA.className.split(/\s+/);
  var newClasses = [];

  for (var i = 0; i < classesA.length; i++) {
    var cls = classesA[i];
    if (cls.indexOf("id-") === 0) {
      var idParts = cls.split("-");
      if (idParts.length >= 3) {
        baseID = idParts[1] + "-" + idParts[2]; // e.g., "2a-3"
      }
    } else {
      if (cls === "hebrew" || cls === "english") langClass = cls;
      newClasses.push(cls); // Preserve other classes
    }
  }

  // If a baseID was found, add new id-class as id-XXX-1
  if (baseID) {
    newClasses = newClasses.filter(function(cls) {
      return cls.indexOf("id-") !== 0;
    });
    newClasses.push("id-" + baseID + "-1");
  }

  // Update spanA
  spanA.className = newClasses.join(" ");
  spanA.textContent = mergedText;
  attachSpanClickHandler(spanA);

  // Remove spanB
  parent.removeChild(spanB);
}

function attemptGapMerge(gapSpan, event) {
  var isGap = gapSpan.classList.contains("gap");

  if (!isGap && !event.shiftKey) {
    return; // require Shift unless it's a "gap" span
  }

  var prev = gapSpan.previousElementSibling;
  var next = gapSpan.nextElementSibling;

  if (prev && next && prev.tagName === "SPAN" && next.tagName === "SPAN") {
    mergeTwoSpans(prev, next);
    gapSpan.parentNode.removeChild(gapSpan);
  }
}



function wrapCellWords(cell, verse, isHebrew) {
  if (!cell) return;

  var text = cell.textContent;
  if (typeof text !== "string") return;

  var pieces = text.match(/\S+|\s+/g); // Match non-space chunks or space chunks
  if (!pieces) return; // Nothing to wrap

  var lineDiv = document.createElement("div");
  lineDiv.className = "line";
  lineDiv.setAttribute("data-line", verse);

  var wordCount = 0;

  for (var i = 0; i < pieces.length; i++) {
    var piece = pieces[i];
    var span = document.createElement("span");
    span.textContent = piece;

    var isSpace = /^\s+$/.test(piece);

    if (isSpace) {
      span.className = "gap";
      span.addEventListener("click", function (event) {
    		attemptGapMerge(span, event);
			});
    } else {
      wordCount++;
      if (isHebrew) {
        var wordID = verse + "-" + wordCount + "-1";
        span.className = "hebrew id-" + wordID;
      } else {
        span.className = "english";
      }
      attachSpanClickHandler(span);
    }

    lineDiv.appendChild(span);
  }

  cell.innerHTML = "";
  cell.appendChild(lineDiv);
}
function wrapGapsBetweenSpans() {

  var lines = document.querySelectorAll(".psalm-table .line");

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

    var spans = Array.from(line.querySelectorAll("span"));

    // PART 1: Split "english id-none" spans that contain whitespace
    for (var j = spans.length - 1; j >= 0; j--) {
      var span = spans[j];

      if (
        span.classList.contains("english") &&
        span.classList.contains("id-none") &&
        span.textContent.match(/\s/)
      ) {

        var words = span.textContent.match(/\S+|\s+/g);
        if (!words) {
          console.warn("⚠️ Could not match any words in:", span);
          continue;
        }

        for (var k = words.length - 1; k >= 0; k--) {
          var part = words[k];
          var newSpan = document.createElement("span");

          if (/^\s+$/.test(part)) {
            newSpan.className = "gap";
            newSpan.textContent = part;
            newSpan.addEventListener("click", function (event) {
              attemptGapMerge(this, event);
            });
          } else {
            newSpan.className = "english id-none";
            newSpan.textContent = part;
            attachSpanClickHandler(newSpan);
          }

          line.insertBefore(newSpan, span);
        }

        line.removeChild(span);
      }
    }

    // PART 2: Insert gap spans between adjacent spans (if no element exists between)
    var updatedSpans = Array.from(line.querySelectorAll("span"));

    for (var j = updatedSpans.length - 1; j > 0; j--) {
      var prev = updatedSpans[j - 1];
      var next = updatedSpans[j];

      if (prev.nextElementSibling !== next) {
        continue;
      }

      var gapSpan = document.createElement("span");
      gapSpan.className = "gap";
      gapSpan.textContent = " ";
      gapSpan.addEventListener("click", function (event) {
        attemptGapMerge(this, event);
      });

      line.insertBefore(gapSpan, next);
      console.log("  ➕ Inserted inter-word gap span between:", prev, next);
    }

  }

}

function createSpans() {
  var rows = document.querySelectorAll("#buildOverlay tr");

  for (var i = 1; i < rows.length; i++) {
    var row = rows[i];
    if (!row.cells || row.cells.length < 3) continue;

    var hebrewCell = row.cells[0];
    var verseCell = row.cells[1];
    var englishCell = row.cells[2];
    var verse = verseCell ? verseCell.textContent.replace(/\s+/g, '').trim() : i;

    if (hebrewCell.getAttribute("data-wrapped") !== "true") {
      wrapCellWords(hebrewCell, verse, true);
      hebrewCell.setAttribute("data-wrapped", "true");
    }
    if (englishCell.getAttribute("data-wrapped") !== "true") {
      wrapCellWords(englishCell, verse, false);
      englishCell.setAttribute("data-wrapped", "true");
    }
  }
}


function saveSpansToDocDebug() {
  var overlay = document.getElementById("buildOverlay");
  if (!overlay) {
    console.warn("No #buildOverlay found in the document.");
    return;
  }

  var allSpans = overlay.querySelectorAll(".line span");
  console.log("Saving spans to doc... found " + allSpans.length + " spans.");

  for (var i = 0; i < allSpans.length; i++) {
    console.log("Span:", allSpans[i].outerHTML);
  }
}


function saveSpansToDoc() {
  var allSpans = document.querySelectorAll('#buildOverlay .line span');
  var output = [];

  console.log("Saving spans to doc... at most " + allSpans.length);

  for (var i = 0; i < allSpans.length; i++) {
    var span = allSpans[i];

	console.log("Testing span " + span.innerContent + " with class " + span.className);
	
    // Skip gaps and non-hebrew/english spans
    if (span.classList.contains("gap")) continue;
    if (!span.classList.contains("hebrew") && !span.classList.contains("english")) continue;

    var classes = span.className.split(/\s+/);
    var idMatch = null;
    var lang = span.classList.contains("hebrew") ? "Hebrew" : "English";

    // Extract WordID (if any)
    for (var j = 0; j < classes.length; j++) {
      if (classes[j].indexOf('id-') === 0) {
        idMatch = classes[j].substring(3);
        break;
      }
    }

    if (!idMatch) idMatch = "none"; // fallback if no ID

    // Clean up word
    var word = span.textContent.trim();

    // Get enclosing line
    var lineDiv = span.closest('.line');
    var lineID = lineDiv ? lineDiv.getAttribute('data-line') : '';

    // Compute position among siblings of the same language
    var siblings = lineDiv ? lineDiv.querySelectorAll('span.' + lang.toLowerCase()) : [];
    var index = Array.prototype.indexOf.call(siblings, span) + 1;

    // Compose the output line
    output.push(
      '{{Overlay/Span' +
      '|Language=' + lang +
      '|WordID=' + idMatch +
      '|Word=' + word +
      '|LineID=' + lineID +
      '|Index=' + index +
      '}}'
    );
  }

  // Output to textarea
  var textarea = document.querySelector('textarea[name$="[SpanText]"]');
  if (textarea) {
    textarea.value = output.join("\n");
    console.log("Saved " + output.length + " spans.");
  } else {
    console.warn("No output textarea found.");
  }
}


function skipToNextHebrewWord (event) {
		  // Only act on Tab without Shift, and if in "Align English" mode
		  if (event.key === "Tab" && !event.shiftKey) {
		    var modeInput = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');
		    if (!modeInput || modeInput.value !== "Align English") return;
		
		    var allHebrew = Array.from(document.querySelectorAll("span.hebrew"));
		    var selected = document.querySelector("span.hebrew.hebrew-selected");
		
		    if (!selected) return;
		
		    event.preventDefault(); // prevent default tab behavior (focus move)
		
		    var index = allHebrew.indexOf(selected);
		    if (index >= 0 && index < allHebrew.length - 1) {
		      var next = allHebrew[index + 1];
		      if (next) next.click(); // simulate clicking next Hebrew word
		    }
		  }
		}

$(document).ready(function () {

	// Set "Split Words" radio button as default checked
	var defaultRadio = document.querySelector('input[name="BuildTextTable[Mode]"][value="Split Words"]');
	if (defaultRadio) {
	  defaultRadio.checked = true;
	}
	  updateOverlayModeClass();
	
	  document.querySelectorAll('input[name="BuildTextTable[Mode]"]').forEach(function (radio) {
	    radio.addEventListener("change", updateOverlayModeClass);
	  });


	
	var createBtn = document.getElementById("createSpans");
	if (createBtn) {
	  createBtn.onclick = createSpans;
	}
	
	var wrapSpaces = document.getElementById("wrapSpaces");
	if (wrapSpaces) {
	  wrapSpaces.onclick = wrapGapsBetweenSpans;
	}


	var divWrap = document.getElementById("divWrapSpaces");
	if (divWrap){
		var isCollapsed = divWrap.classList.contains("mw-collapsed");
		
		if (isCollapsed) {
		  // need to create spans in the first place
		  createBtn.click();
		} else {
		  wrapSpaces.click();
		  divWrap.textContent = "Spaces now wrapped.";
		}
	}
	
	var saveBtn = document.getElementById("saveSpans");
	if (saveBtn) {
	  saveBtn.onclick = saveSpansToDoc;  
	}

	  var spans = document.querySelectorAll('span.hebrew, span.english');
	  for (var i = 0; i < spans.length; i++) {
	    attachSpanClickHandler(spans[i]);
	  }

	
	document.querySelectorAll('input[name="BuildTextTable[Mode]"]').forEach(function (radio) {
	  radio.addEventListener("change", function (event) {
	    var selectedMode = event.target.value;
	    console.log("Mode changed to: " + selectedMode);
	    updateOverlayModeClass();
	  });
	});
	
	var overlay = document.getElementById("buildOverlay");
	if (overlay) {
	  document.addEventListener("keydown", skipToNextHebrewWord);
	}

	
});


/* ===============================


           Overlays  js


==============================*/

importScript('MediaWiki:BuildTextTable.js');

function saveRenderedTableAsSubpage(currentPage) {
  var api = new mw.Api();

  // If currentPage is not a string, get the title from MediaWiki
  if (typeof currentPage !== "string") {
    var divTrigger = document.getElementById("triggerForSavingSubpage");
    if (divTrigger)
      currentPage = divTrigger.textContent;
  }
  if (typeof currentPage !== "string") {
    currentPage = mw.config.get("wgPageName");
  }

  var targetPage = currentPage.replace(/ /g, "_") + "/Rendered";
  var psalmTable = document.querySelector("table.psalm-table");

  if (!psalmTable) {
    alert("No psalm-table found.");
    return;
  }

  var renderedWikitext = psalmTable.outerHTML
    .replace(/psalm-table/gi, 'psalm-table pre-rendered')
    .replace(/<tbody>/gi, '')
    .replace(/<\/tbody>/gi, '');

  console.log("API endpoint is:", mw.util.wikiScript('api'));

  api.postWithEditToken({
    action: "edit",
    title: targetPage,
    text: renderedWikitext,
    summary: "Programmatically saving rendered Psalm table",
    format: "json",
    contentmodel: "wikitext",
    contentformat: "text/x-wiki"
  }).then(function (data) {
    if (data && data.edit && data.edit.result === "Success") {
      alert("Successfully saved to: " + targetPage);
    } else {
      alert("Edit failed. See console.");
      console.error(data);
    }
  }).catch(function (err) {
    alert("API call failed.");
    console.error(err);
  });
}




$(document).ready(function () {

/*
	if (document.querySelector("#triggerForSavingSubpage")) {
		var saveButton = document.querySelector("#wpSave");
		if (saveButton) {
			saveButton.addEventListener("click", saveRenderedTableAsSubpage);
		}
	}
*/
	if (document.getElementById("overlay-1")){
		// set section colored boxes to their actual colors
		  $('input[name*="[sectionColor]"]').each(function () {
		    $(this).css('background-color', $(this).val());
		  });
		
		  $('input[name*="[sectionColor]"]').on('input', function () {
		    $(this).css('background-color', $(this).val());
		  });
	}
	
	var overlayTableDiv = document.getElementById("overlay-1");
	var overlayTable = null;
	if (overlayTableDiv){
		overlayTable = overlayTableDiv.querySelector('table');
	}

	var INSERT_BLANK_AFTER_SECTIONS = false;
	var INCLUDE_EMOTION_COLUMN = true;
	var SECTION_HEADING = 'Structure'
	var DEBUG = true;


	function mergeCellsInRange(headerText, heading) {
	    if (!overlayTable) return;
	
	    var cells = overlayTable.querySelectorAll(
	        '[section-type="' + headerText + '"][section-heading="' + heading + '"]'
	    );
	    if (!cells || cells.length === 0) return;
	
	    var firstCell = cells[0];
	    firstCell.rowSpan = cells.length;
	
	    var imageRegex = /^File:([^ ]+\.(jpg|jpeg|png|gif|svg))$/i;
	    var match = heading.match(imageRegex);
	
	    if (match) {
	        var filename = match[1];
	        var encodedFilename = encodeURIComponent(filename);
	        var thumbBase = '/mediawiki/thumb.php?f=' + encodedFilename;
	        var src = thumbBase + '&width=50';
	        var srcset = thumbBase + '&width=75 1.5x, ' + thumbBase + '&width=100 2x';
	        var linkHref = '/w/File:' + encodedFilename;
	
	        firstCell.innerHTML =
	            '<div class="center"><div class="floatnone">' +
	            '<a href="' + linkHref + '" class="image">' +
	            '<img alt="' + filename + '" src="' + src + '" decoding="async" width="50" height="50" class="aag-icon" ' +
	            'srcset="' + srcset + '" data-file-width="1200" data-file-height="1200">' +
	            '</a></div></div>';
	    } else {
	        firstCell.textContent = heading;
	    }
	
	    for (var k = 1; k < cells.length; k++) {
	        if (cells[k] && cells[k].parentNode) {
	            cells[k].parentNode.removeChild(cells[k]);
	        }
	    }
	}



    function applySections(){
    	
	    var overlays = document.querySelectorAll("div.overlay");
	
	    // If no overlays are found, print a warning
	    if (overlays.length === 0) {
	        console.warn("No overlays found on the page.");
	    }
	    for (var i = 0; i < overlays.length; i++) {
	        var overlay = overlays[i];
	        var containerId = overlay.id;
	        var encoded = overlay.getAttribute("data-sections");
	
	    	if (encoded) console.log("Applying sections:" + encoded);

	        if (!encoded) continue;
	        
			try {
			    var decoded = decodeURIComponent(encoded);
			    var sections = JSON.parse(decoded);
			
			    var sectionInsertions = []; // store all 'Section' entries
			    var speakerAndAddresseeBarsAdded = false;
			    var sectionsAdded = false;
			    var subSectionsAdded = false;
			
			
				// first, cycle through to BUILD all the sections
			    for (var i = 0; i < sections.length; i++) {
			        var section = sections[i];

			        if (section.Level === 'Speaker' && !speakerAndAddresseeBarsAdded) {
			            adjustSectionColumns("includeSpeakerBars", true);
			            speakerAndAddresseeBarsAdded = true;

			        } else if (section.Level === 'Section') {
			            if (!sectionsAdded) {
			                adjustSectionColumns("includeSections", true);
			                sectionsAdded = true;
			            }
			
			            if (INSERT_BLANK_AFTER_SECTIONS) {
			                sectionInsertions.push(section);
			            }
			
			        } else if (section.Level === 'Subsection') {
			            if (!subSectionsAdded) {
			                adjustSectionColumns("includeSubsections", true);
			                subSectionsAdded = true;
			            }
			        }
			    }
			
				// then, cycle through to HIGHLIGHT
			    for (i = 0; i < sections.length; i++) {
			        var section = sections[i];
		            highlightCellsInLineRange(
		            	section.Level, 
		            	section.Heading,
		            	section.FirstLine, 
		            	section.LastLine, 
		            	section.Color);

		            if (section.Emotion)
		            {
		            	// handle emotions as well
			            highlightCellsInLineRange(
			            	"E", 
			            	section.Emotion,
			            	section.FirstLine, 
			            	section.LastLine, 
			            	section.Color);
		            }
			    }			
			
			
				// merge
				// then, cycle through to HIGHLIGHT
			    for (i = 0; i < sections.length; i++) {
			        var section = sections[i];
			        //console.log("Seeking to merge " + section.Level + " " + section.Heading + " with first " + section.FirstLine + " and last " + section.LastLine);
					mergeCellsInRange(section.Level, section.Heading, section.FirstLine, section.LastLine);	
		            if (section.Emotion)
		            {
		            	// handle emotions as well
			            mergeCellsInRange(
			            	"E", 
			            	section.Emotion,
			            	section.FirstLine, 
			            	section.LastLine);
		            }
			    }			
			    
				if (INSERT_BLANK_AFTER_SECTIONS) {
				    // Now insert all the blank rows AFTER all highlighting is done
				    for (var j = 0; j < sectionInsertions.length; j++) {
				        var lastRow = getRowFromLineId(sectionInsertions[j].LastLine);
				        var allRows = overlayTable.getElementsByTagName('tr');
				        // only if NOT the last row of the table
				        if (lastRow ===! allRows[allRows.length-1])
				        	insertBlankRowAfter(lastRow);
				    }
				    
				    // add empty columns
				    insertBlankColumnAt(1);
				    insertBlankColumnAt(3);
				    insertBlankColumnAt(8);
				}
			
			} catch (e) {
			    console.error("Error processing sections for", containerId, e);
			}


	    }

	}
	
	

	function highlightCellsInLineRange(headerText, heading, firstLine, lastLine, highlightColor) {
	  if (!overlayTable) return;
	
	  var rows = overlayTable.querySelectorAll('tr');
	  if (rows.length === 0) return;
	
	  var headerRow = overlayTable.querySelector('thead tr') || rows[0];
	  var headers = headerRow.getElementsByTagName('th');
	
	  // Find the target column
	  var targetColIndex = -1;
	  for (var i = 0; i < headers.length; i++) {
	    if (headers[i].textContent.trim() === headerText.trim()
	    || (headerText === "Section" && headers[i].textContent === SECTION_HEADING)) {
	    	//console.log("Found " + headers[i].textContent + " at column " + i);
	    	targetColIndex = i;
	    	break;
	    }
	  }
	
	  if (targetColIndex === -1) {
	    console.warn("Header not found: " + headerText);
	    return;
	  }
	
	  // collect all the affected cells
	  for (var r = 1; r < rows.length; r++) {
	    var row = rows[r];
	    var lineDiv = row.querySelector('div.line[data-line]');
	    if (!lineDiv) continue;
	
	    var lineId = lineDiv.getAttribute('data-line');
	    if (!lineId || typeof lineId !== 'string') continue;
	
	    if (isLineInRange(lineId, firstLine, lastLine)) {
	    	// found an affected cell!
	      var cells = row.getElementsByTagName('td');
	      if (targetColIndex < cells.length) {
	        cells[targetColIndex].style.backgroundColor = highlightColor;
	        cells[targetColIndex].setAttribute('section-type',headerText);
	        cells[targetColIndex].setAttribute('section-heading', heading);
	      }
	    }
	  }
	
	
	}

	// constants for adding sections
	var SECTION_LEVEL = 1;    
	var SECTION_HEADER = 2;    
	var SECTION_BEGIN = 3;    
	var SECTION_END = 4;    
	var SECTION_COLOR =5;    
	var SECTION_EMOTION = 6;    

	function parseLineId(lineStr) {
	    if (typeof lineStr !== "string") return null;
	    lineStr = lineStr.trim();
	
	    // Match line IDs like "1", "1a", "12b", etc.
	    var match = /^(\d+)([a-z]?)$/i.exec(lineStr);
	    if (!match) return null;
	
	    var base = parseInt(match[1], 10);
	    var letter = match[2] ? match[2].toLowerCase() : "";
	
	    return {
	        base: base,
	        offset: letter ? (letter.charCodeAt(0) - 96) : 0 // a = 1, b = 2, ...
	    };
	}
	
	function toSortableValue(parsed) {
	    if (!parsed) return -1;
	    return parsed.base + parsed.offset / 100;
	}

	function rangesOverlap(startA, endA, startB, endB) {
	  var aStart = toSortableValue(parseLineId(startA));
	  var aEnd = toSortableValue(parseLineId(endA));
	  var bStart = toSortableValue(parseLineId(startB));
	  var bEnd = toSortableValue(parseLineId(endB));
	  return aStart <= bEnd && bStart <= aEnd;
	}
	

	// used on Overlays when adding sections
	function addSectionToSpreadsheetOld(cell, colIndex) {
		

		var addButton = document.querySelector('#overlay-sections a.oo-ui-buttonElement-button');
		var rows = document.querySelectorAll('#overlay-sections .multipleTemplateInstance tr');
	
	    var sectionRow = null;
	    var sectionColumn = '';
		var upperLimit = findUpperSectionLimit(cell).getAttribute('data-line');
	    var lowerLimit = findLowerSectionLimit(cell).getAttribute('data-line');
		var classList = cell.className.split(/\s+/);
		var matchedBar = null;
		
		for (var i = 0; i < classList.length; i++) {
			if (/-bar$/.test(classList[i])) {
				matchedBar = classList[i];
				break;
			}
		}
		
		if (matchedBar) {
			// Capitalize first letter, lower case the rest
			var prefix = matchedBar.replace(/-bar$/, '');
			sectionColumn = prefix.charAt(0).toUpperCase() + prefix.slice(1).toLowerCase();
		} else if (colIndex < headerCells.length) {
			sectionColumn = headerCells[colIndex].textContent;
		}

		if (DEBUG) console.log("Considering adding section to spreadsheet from cell " + cell.getAttribute('data-line') + " with upper limit " + upperLimit + " and lower limit " + lowerLimit);
		

		// hunt for a matching row
	    for (var i = 0; i < rows.length; i++) {
	        var cells = rows[i].cells;
	

			var container = rows[i].querySelector('td.instanceMain');
			if (!container) {
				if (DEBUG) console.warn("Row " + i + " has no instanceMain cell.");
				continue;
			}
			
			var beginField = container.querySelector('input[origname="Overlay Section[firstLine]"]');
			var endField = container.querySelector('input[origname="Overlay Section[lastLine]"]');
			
			if (DEBUG) {
				var beginVal = beginField ? beginField.value : "(missing)";
				var endVal = endField ? endField.value : "(missing)";
				console.log("Looking at row " + i + " with limits " + beginVal + " and " + endVal);
			}

			var colorField = container.querySelector('input[origname="Overlay Section[sectionColor]"]');

			
			if (
			  colorField && beginField && endField &&
			  colorField.value === selectedColor &&
			  rangesOverlap(beginField.value, endField.value, upperLimit, lowerLimit)
			) {
			  sectionRow = rows[i];
			  break;
			}


	    }
	
	    if (!sectionRow && addButton) {
			if (DEBUG) console.log("Adding section to spreadsheet");

		    // If no matching row, click Add and fill the new one
	        addButton.click();
	
	        setTimeout(function () {

				var sectionsGrid = document.querySelector('#overlay-sections table.multipleTemplateInstanceTable');

				if (sectionsGrid.length === 0) {
				    console.warn("Could not find sections grid.");
				    return;
				}



	        var instances = document.querySelectorAll('#overlay-sections .multipleTemplateInstance');
			if (instances.length === 0) {
				console.warn("No new section instance found.");
				return;
			}
			
			var lastInstance = instances[instances.length - 1];
			var container = lastInstance.querySelector('td.instanceMain');
			
			if (!container) {
				console.warn("Could not find input container in last instance.");
				return;
			}
			
			if (DEBUG) {
				console.log("Filling in values: " + sectionColumn + ", " + upperLimit + ", " + lowerLimit + ", " + selectedColor);
			}
				
			// Helper to set input/select/textarea values safely
			function setFieldValue(selector, value) {
				var field = container.querySelector(selector);
				if (field) {
					field.value = value;
				} else if (DEBUG) {
					console.warn("Missing field for selector: " + selector);
				}
			}
				
			
			setFieldValue('select[origname="Overlay Section[sectionLevel]"]', sectionColumn);
			//setFieldValue('textarea[origname="Overlay Section[sectionName]"]', sectionHeader);
			setFieldValue('input[origname="Overlay Section[firstLine]"]', upperLimit);
			setFieldValue('input[origname="Overlay Section[lastLine]"]', lowerLimit);
			setFieldValue('input[origname="Overlay Section[sectionColor]"]', selectedColor);
			setFieldValue('input[origname="Overlay Section[sectionAnnotation]"]', selectedAnnotation);
			//setFieldValue('input[origname="Overlay Section[emotionLabel]"]', ''); // Optional: add default emotion

	            if (DEBUG) console.log("Added a new row for " + sectionColumn + " color " + selectedColor);
	        }, 150); // allow enough time for row to be added (used to be 150)
	    } else if (sectionRow) {
	        // Row already exists — update it
			var container = sectionRow.querySelector('td.instanceMain');
			
			if (!container) {
				console.warn("Could not find input container in matched row.");
				return;
			}
		
			// Helper to set input/select/textarea values safely
			function setFieldValue(selector, value) {
				var field = container.querySelector(selector);
				if (field) {
					field.value = value;
				} else if (DEBUG) {
					console.warn("Missing field for selector: " + selector);
				}
			}
							
			setFieldValue('select[origname="Overlay Section[sectionLevel]"]', sectionColumn);
			//setFieldValue('textarea[origname="Overlay Section[sectionName]"]', sectionHeader);
			setFieldValue('input[origname="Overlay Section[firstLine]"]', upperLimit);
			setFieldValue('input[origname="Overlay Section[lastLine]"]', lowerLimit);
			setFieldValue('input[origname="Overlay Section[sectionColor]"]', selectedColor);
			setFieldValue('input[origname="Overlay Section[sectionAnnotation]"]', selectedAnnotation);
			//setFieldValue('input[origname="Overlay Section[emotionLabel]"]', ''); // Or your desired default
			
			if (DEBUG) {
				console.log("Modified existing row for " + sectionColumn + " with color " + selectedColor);
			}
			

	    } else {
	        console.warn("No add button found or spreadsheet table missing.");
	    }
	}

// Compare line + letter pairs, treating "1" as 1.0, "1a" as 1.1, "1b" as 1.2, etc.
function isLineInRange(line, firstLine, lastLine) {
    function parse(lineStr) {
        if (typeof lineStr !== "string") return null;
        lineStr = lineStr.trim();

        // Match "1", "1a", "12b", etc.
        var match = /^(\d+)([a-z]?)$/i.exec(lineStr);
        if (!match) return null;

        var number = parseInt(match[1], 10);
        var letter = match[2] ? match[2].toLowerCase() : null;

        return {
            base: number,
            offset: letter ? (letter.charCodeAt(0) - 96) : 0  // 'a' = 1, 'b' = 2, ..., no letter = 0
        };
    }

    var lineVal = parse(line);
    var firstVal = parse(firstLine);
    var lastVal = parse(lastLine);

    if (!lineVal || !firstVal || !lastVal) {
        console.warn("Invalid line format:", line, firstLine, lastLine);
        return false;
    }

    var value = toSortableValue(lineVal);
    var first = toSortableValue(firstVal);
    var last = toSortableValue(lastVal);

    return value >= first && value <= last;
}

	// compare line + letter pairs, seeing if a given line is between another first + last
	function isLineInRangeStrict(line, firstLine, lastLine) {
	    function parse(lineStr) {
	        lineStr = lineStr.trim();  // Remove any leading/trailing spaces
	        var match = /^(\d+)([a-z])$/i.exec(lineStr);
	        if (!match) return null;
	        return {
	            number: parseInt(match[1], 10),
	            letter: match[2].toLowerCase()
	        };
	    }
	
	    var lineVal = parse(line);
	    var firstVal = parse(firstLine);
	    var lastVal = parse(lastLine);
	
	    if (!lineVal || !firstVal || !lastVal) {
	        console.warn("Invalid line format:", line, firstLine, lastLine);
	        return false;
	    }
	
	    function compare(a, b) {
	        if (a.number !== b.number) {
	            return a.number - b.number;
	        }
	        return a.letter.charCodeAt(0) - b.letter.charCodeAt(0);
	    }
	
	    return compare(firstVal, lineVal) <= 0 && compare(lineVal, lastVal) <= 0;
	}

	// when building sections, handle colors & large sections
	function handleSectionCellClick (event) {
		  var cell = event.currentTarget;
		  var selectedBg = selectedColor;
		  //var selectedBg = window.selectedBg || '#f8c936';
		  cell.style.backgroundColor = selectedBg;
		
		  var row = cell.parentNode;
		  var table = row;
		  while (table && table.tagName !== 'TABLE') {
		    table = table.parentNode;
		  }
		  if (!table) return null;
		
		  var rows = table.getElementsByTagName('tr');
		
		  // Determine current row and column index
		  var rowIndex = -1;	
		  var colIndex = -1;
	
		  for (var i = 0; i < rows.length; i++) {
		    if (rows[i] === row) {
		      rowIndex = i;
		      break;
		    }
		  }
		
		  if (rowIndex === -1) return null;
		
		  var cells = rows[rowIndex].cells;
		  for (var j = 0, col = 0; j < cells.length; j++) {
		    if (cells[j] === cell) {
		      colIndex = col;
		      break;
		    }
		    col += cells[j].colSpan || 1;
		  }	
		
		  //var aboveCell = findCellAbove(cell, rows, rowIndex, colIndex, false);
		  //if (aboveCell) aboveCell.style.backgroundColor='blue';
	
		  var aboveMatchingCell = findCellAbove(cell, rows, rowIndex, colIndex, true);
		  if (aboveMatchingCell){
		  	//aboveMatchingCell.style.backgroundColor='orange';
		  	highlightCellsVertically(aboveMatchingCell, cell);
		  } 
	
		  var belowMatchingCell = findCellBelow(cell, rows, rowIndex, colIndex, true);
		  if (belowMatchingCell){
		  	//aboveMatchingCell.style.backgroundColor='orange';
		  	highlightCellsVertically(cell, belowMatchingCell);
		  } 


		// create section
		addSectionToSpreadsheetOld(cell, colIndex);

	}


	var sectionLevelsAdded = 0;
	var speakerAndAddresseeBarsAdded = false;
	var sectionsAdded = false;
	var subSectionsAdded = false;
	var sectionCheckboxes = document.querySelectorAll(".pf-checkbox-input-container");
	
	// Attach checkbox listeners for the Overlays form to ADD columns
	if (sectionCheckboxes){
		for (var i = 0; i < sectionCheckboxes.length; i++) {
		    var checkboxes = sectionCheckboxes[i].querySelectorAll('input[type="checkbox"]');
		    for (var j = 0; j < checkboxes.length; j++) {
		        (function (checkbox) {
		            checkbox.addEventListener("click", function () {
		            	
		            	var name = checkbox.name; 
						var match = name.match(/\[([^\]]+)\]/);
						var firstKey = match ? match[1] : null;
		                adjustSectionColumns(firstKey, checkbox.checked);
		            });
		        })(checkboxes[j]);
		    }
		}
	}
	
	// standard styling for section headers and cells
	var stylePropsTh = {
	    padding: '1rem',
	    textAlign: 'center',
	    verticalAlign: 'middle'
	};
	
	var stylePropsTd = {
	    minWidth: '1rem',
	    border: '1px solid #ccc',
	    backgroundColor: 'white'
	};

	function adjustSectionColumns(sectionFlag, checked) {
	    var allRows = overlayTable.querySelectorAll('tr');
	    var headerRow = overlayTable.querySelector('thead tr') || allRows[0];
	    var bodyRows = overlayTable.querySelectorAll('tbody tr');

	    // Speaker + Addressee
	    if (sectionFlag === "includeSpeakerBars") {
	        if (checked && !speakerAndAddresseeBarsAdded) {
	            var newTh1 = newElement('th', 'Speaker', stylePropsTh, 'speaker');
	            var newTh2 = newElement('th', 'Addressee', stylePropsTh, 'addressee');
	            headerRow.insertBefore(newTh1, headerRow.firstChild);
	            headerRow.appendChild(newTh2);
	
	            for (var k = 1; k < bodyRows.length; k++) {
	                var row = bodyRows[k];
	                var speakerTd = newElement('td', '', stylePropsTd, 'speaker-bar');
					speakerTd.setAttribute('data-line', getLineIdFromRow(row));
					speakerTd.textContent = getLineIdFromRow(row);
					speakerTd.style.color = 'lightgray';
	                speakerTd.onclick = handleSectionCellClick;
	                row.insertBefore(speakerTd, row.firstChild);
	                var addresseeTd = newElement('td', '', stylePropsTd, 'addressee-bar');
	                addresseeTd.onclick = handleSectionCellClick;
					addresseeTd.setAttribute('data-line', getLineIdFromRow(row));
					addresseeTd.textContent = getLineIdFromRow(row);
					addresseeTd.style.color = 'lightgray';
	                row.appendChild(addresseeTd);
	            }
	            speakerAndAddresseeBarsAdded = true;
	        } else if (!checked && speakerAndAddresseeBarsAdded) {
	            removeColumn('Addressee', headerRow, bodyRows);
	            removeColumn('Speaker', headerRow, bodyRows);
	            speakerAndAddresseeBarsAdded = false;
	        }
	    }
	
	    // Section
	    if (sectionFlag === "includeSections") {
	        if (checked && !sectionsAdded) {
	            var sectionHeader = newElement('th', SECTION_HEADING, stylePropsTh, 'section');
	            var insertIndex = 0; //getInsertIndex(headerRow, 'Speaker', 0);
	            if (speakerAndAddresseeBarsAdded)
	            	insertIndex = 1;
	            headerRow.insertBefore(sectionHeader, headerRow.children[insertIndex]);
	            //console.log("Inserted " + sectionFlag + " at index " + insertIndex);
	            for (var m = 1; m < bodyRows.length; m++) {
	                var row = bodyRows[m];
	                var sectionTd = newElement('td', '', stylePropsTd, 'section');
					sectionTd.setAttribute('data-line', getLineIdFromRow(row));
					sectionTd.textContent = getLineIdFromRow(row);
					sectionTd.style.color = 'lightgray';
	                sectionTd.onclick = handleSectionCellClick;
	                row.insertBefore(sectionTd, row.children[insertIndex]);
	            }
	            
	            
	            if (INCLUDE_EMOTION_COLUMN){
		            sectionHeader = newElement('th', 'E', stylePropsTh, 'emotion');
		            insertIndex = 4; //getInsertIndex(headerRow, 'Speaker', 0);
		            if (speakerAndAddresseeBarsAdded)
		            	insertIndex++;
		            headerRow.insertBefore(sectionHeader, headerRow.children[insertIndex]);
		            //console.log("Inserted emotion column at index " + insertIndex);
		            for (m = 1; m < bodyRows.length; m++) {
		                row = bodyRows[m];
		                sectionTd = newElement('td', '', stylePropsTd, 'emotion');
						sectionTd.setAttribute('data-line', getLineIdFromRow(row));
						sectionTd.textContent = getLineIdFromRow(row);
		                sectionTd.onclick = handleSectionCellClick;
		                row.insertBefore(sectionTd, row.children[insertIndex]);
		            }
	            }
	            sectionsAdded = true;
	            
	        } else if (!checked && sectionsAdded) {
	            removeColumn(SECTION_HEADING, headerRow, bodyRows);
	            sectionsAdded = false;
	        }
	    }
	
	    // Subsection
	    if (sectionFlag === "includeSubsections") {
	        if (checked && !subSectionsAdded) {
	            var subsectionHeader = newElement('th', 'Subsection', stylePropsTh, 'subsection');
	            var subInsertIndex = 1; //getInsertIndex(headerRow, 'Speaker', 0);
	            if (speakerAndAddresseeBarsAdded)
	            	subInsertIndex = 2;
	            if (!sectionsAdded)
	            	subInsertIndex --;
	            //var subInsertIndex = getInsertIndex(headerRow, 'Section', -1);
	            headerRow.insertBefore(subsectionHeader, headerRow.children[subInsertIndex]);
	            for (var n = 1; n < bodyRows.length; n++) {
	                var row = bodyRows[n];
	                var subsectionTd = newElement('td', '', stylePropsTd, 'subsection');
					subsectionTd.setAttribute('data-line', getLineIdFromRow(row));
					subsectionTd.textContent = getLineIdFromRow(row);
	                subsectionTd.onclick = handleSectionCellClick;
	                row.insertBefore(subsectionTd, row.children[subInsertIndex]);
	            }
	            subSectionsAdded = true;
	        } else if (!checked && subSectionsAdded) {
	            removeColumn('Subsection', headerRow, bodyRows);
	            subSectionsAdded = false;
	        }
	    }
	}

	
	// ensure the mode & css are aligned upon load
	  var overlay = document.getElementById('buildOverlay');
	  var modeField = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');
	
	/*
	  if (overlay && modeField) {
	    if (modeField.value.includes("Align")) {
	      overlay.classList.add("mode-align");
	    } else {
	      overlay.classList.add("mode-split");
	    }
	  }
*/



	// BEGIN TEXT OVERLAY CODE

	// === TEXT OVERLAY COLOR PICKER LOGIC ===
	// var selectedColor = 'red';
	var selectedAnnotation = 'psalmist';
	var coloredWords = [];
	

	/*
	document.querySelectorAll('input[name="Text Overlay[Color]"]').forEach(function (radio) {
	  radio.addEventListener('change', function (e) {
	    selectedColor = e.target.value;
	    console.warn("Selected color:", selectedColor);
	  });
	});*/
	
	// 2. Set default color based on default participant
	
	// set up color map
    var divColorMap = document.getElementById("color-map");
	var colorMap = {};
	var colorPicker = document.querySelector("div.color-picker");
	if (divColorMap) {
	    var encoded = divColorMap.textContent;
	    if (encoded) {
			try {
			    var decoded = decodeURIComponent(encoded);
			    colorMap = JSON.parse(decoded);
			    // console.log("Color map successfully decoded.");
			} catch (e) {
			    console.error("Error processing color map:", e);
			}
	    }
	}
	else{
		//console.log("No color map found");	
	}
	selectedColor = '';
	
	/*
	// 3a. Setup listener for radio buttons (when using radio buttons)
	document.querySelectorAll('input[name="Text Overlay[Participant]"]').forEach(function (radio) {
	    radio.addEventListener('change', function (e) {
	        selectedAnnotation = e.target.value;
	        selectedColor = colorMap[selectedAnnotation] || '';
	        console.warn("Selected:", selectedAnnotation, "→", selectedColor);
	    });
	});*/
	
	
	// 3b. Setup listener for color grid (when using that)
		
	// Color selection via grid
	document.querySelectorAll('.color-cell').forEach(function(cell) {
	  cell.addEventListener('click', function () {
	    // Remove highlight from all cells on the color grid
	    document.querySelectorAll('.color-cell').forEach(function(c) {
	      c.style.outline = 'none';
	    });
	
	    // Highlight this one
	    cell.style.outline = '4px solid black';
	
	    // Set selectedColor
	    selectedColor = cell.dataset.color || '';
        selectedAnnotation = cell.dataset.participant || 'unknown';
	  //  console.log("Selected color from grid: " + selectedColor + " for annotation: " + selectedAnnotation);
	  });
	});
	
	
	// handle any overlay colors at page load
    applyAllOverlayAnnotations();
    applySections();
    


	
	// on click - individual words
	document.querySelectorAll('div.overlay span.hebrew').forEach(function (span) {
	  span.style.cursor = 'pointer';
	  	
	  span.addEventListener('click', function (event) {
		event.stopPropagation(); // prevent the line-level click from firing
		console.log("In click handler to highlight a word");
	  	
	    var idMatch = span.className.match(/id-[\w-]+/);
	    if (!idMatch) return;
	
	    var id = idMatch[0];
	    var word = span.textContent;
	    var glossEl = document.querySelector('.gloss.' + id);
	    var gloss = glossEl ? glossEl.textContent : '';
	
	    if (!coloredWords.some(function (w) { return w.id === id; })) {
	      coloredWords.push({ id: id, hebrew: word, gloss: gloss, annotation:selectedAnnotation, color: selectedColor });
	      
			// Add a new form instance by clicking the "Add another" button
			var addButton = document.querySelector('#overlay-annotations .multipleTemplateAdder a');
			if (addButton) {
			  addButton.click();
			
			  // Slight delay to allow the DOM to update
			  setTimeout(function () {
			    // Get the list of all current instances
			    
			    var allInstances = document.querySelectorAll('#overlay-annotations .multipleTemplateInstance');
			    var latest = allInstances[allInstances.length - 1];
			
			    if (latest) {
			      // Fill in WordID and Color
			      var hebrewInput = latest.querySelector('input[name$="[Hebrew]"]');
			      var wordIdInput = latest.querySelector('input[name$="[WordID]"]');
			      var annotationInput = latest.querySelector('input[name$="[Participant]"]');
			      var colorInput = latest.querySelector('input[name$="[Color]"]');
			
			      if (hebrewInput) hebrewInput.value = word;
			      if (wordIdInput) wordIdInput.value = id;
			      if (annotationInput) annotationInput.value = selectedAnnotation;
		            if (colorInput) {
		            	colorInput.value = selectedColor;
		            	colorInput.style.backgroundColor = selectedColor;
		            }

			    }
			  }, 100); // Adjust delay if needed
			}
	     
	    }
	console.log("Setting to " + selectedColor);
	    span.style.backgroundColor = selectedColor;
	    if (glossEl) glossEl.style.backgroundColor = selectedColor;
	  });
	});
	
	
	
document.querySelectorAll('#overlay-1 div.overlay div.line').forEach(function (lineDiv) {
  lineDiv.style.cursor = 'pointer';

  lineDiv.addEventListener('click', function (event) {
    if (!selectedColor || !selectedAnnotation) {
      console.warn("No color or annotation selected.");
      return;
    }

    var lineId = lineDiv.dataset.line;
    if (!lineId) return;

    var matchingLines = document.querySelectorAll('div.line[data-line="' + CSS.escape(lineId) + '"]');
    var lineText = matchingLines[0].textContent.trim();

    // Shift-click: remove annotation and matching form row
    if (event.shiftKey) {
      coloredWords = coloredWords.filter(function (w) {
        return !(w.id === lineId && w.annotation === selectedAnnotation && w.color === selectedColor);
      });

      // Search Overlay Section entries and remove matching ones
      document.querySelectorAll('#overlay-annotations .multipleTemplateInstance').forEach(function (instance) {
        var levelInput = instance.querySelector('select[name$="[sectionLevel]"]');
        var firstLineInput = instance.querySelector('input[name$="[firstLine]"]');
        var lastLineInput = instance.querySelector('input[name$="[lastLine]"]');
        var colorInput = instance.querySelector('input[name$="[sectionColor]"]');

        if (
          firstLineInput && firstLineInput.value === lineId &&
          lastLineInput && lastLineInput.value === lineId &&
          colorInput && colorInput.value === selectedColor
        ) {
          instance.remove();
        }
      });

      matchingLines.forEach(function (div) {
        div.style.backgroundColor = '';
      });

      console.log("Removed annotation for line:", lineId);
      return;
    }

    // Normal click: add annotation if not present
    if (!coloredWords.some(function (w) { return w.id === lineId; })) {
      coloredWords.push({
        id: lineId,
        hebrew: lineText,
        gloss: '',
        annotation: selectedAnnotation,
        color: selectedColor
      });

      var addButton = document.querySelector('#overlay-annotations .multipleTemplateAdder a');
      if (addButton) {
        addButton.click();

        setTimeout(function () {
          var allInstances = document.querySelectorAll('#overlay-annotations .multipleTemplateInstance');
          var latest = allInstances[allInstances.length - 1];
          if (latest) {
          	
		      // Fill in WordID and Color
		      var hebrewInput = latest.querySelector('input[name$="[Hebrew]"]');
		      var wordIdInput = latest.querySelector('input[name$="[WordID]"]');
		      var annotationInput = latest.querySelector('input[name$="[Participant]"]');
		      var colorInput = latest.querySelector('input[name$="[Color]"]');

		      if (hebrewInput) hebrewInput.value = lineText;
		      if (wordIdInput) wordIdInput.value = lineId;
		      if (annotationInput) annotationInput.value = selectedAnnotation;
	            if (colorInput) {
	            	colorInput.value = selectedColor;
	            	colorInput.style.backgroundColor = selectedColor;
	            }
          }
        }, 200);
      }
    }

    matchingLines.forEach(function (div) {
      div.style.backgroundColor = selectedColor;
    });

    console.log("Highlighted all lines with data-line:", lineId);
  });
});
	
	
	
	
	// export a list to be saved... since this is NOT using PageForms
	window.exportAnnotations = function () {
	  var output = document.getElementById('annotation-output');
	  if (output) {
	    output.textContent = coloredWords.map(function (w) {
	      return w.id + ": " + w.hebrew + " / " + w.gloss + " [" + w.color + "]";
	    }).join('\n');
	  } else {
	    console.warn("No #annotation-output element found.");
	  }
	};
	
	
function applyOverlayColors(containerId, annotations) {
  var container = document.getElementById(containerId);
  if (!container) return;

  for (var wordID in annotations) {
    if (annotations.hasOwnProperty(wordID) && wordID.trim() !== "") {
      var rawKey = annotations[wordID] || "";
      var normalizedKey = rawKey.toLowerCase();

      // Try direct match
      var color = colorMap[normalizedKey];

      // Fallback: append ".1" if no color found and key lacks suffix
      if (!color && normalizedKey.indexOf('.') === -1) {
        normalizedKey = normalizedKey + ".1";
        color = colorMap[normalizedKey];
        console.log("Fallback to \"" + normalizedKey + "\" for " + wordID);
      }

      console.log("Seeking color for \"" + wordID + "\" using key \"" + normalizedKey + "\":", color);

      if (!color) continue;

      var elements = container.querySelectorAll("." + CSS.escape(wordID));
      var lines = container.querySelectorAll('[data-line="' + wordID + '"]');

      console.log("Applying color " + color + " to " + elements.length + " elements and " + lines.length + " lines");

      for (var j = 0; j < elements.length; j++) {
        elements[j].style.backgroundColor = color;
      }
      for (var k = 0; k < lines.length; k++) {
        lines[k].style.backgroundColor = color;
      }
    }
  }
}




	function applyAllOverlayAnnotations() {
	    var overlays = document.querySelectorAll("div.overlay");
	
	    // If no overlays are found, print a warning
	    if (overlays.length === 0) {
	        console.warn("No overlay container found on the page.");
	    }
	    for (var i = 0; i < overlays.length; i++) {
	        var overlay = overlays[i];
	        var containerId = overlay.id;
	        var encoded = overlay.getAttribute("data-annotations");
	
	        if (!encoded) continue;
	        
	        try {
	            var decoded = decodeURIComponent(encoded);
	            var annotations = JSON.parse(decoded);
	            console.log("Found annotations: " + JSON.stringify(annotations, null, 2));
	            
	            applyOverlayColors(containerId, annotations);
	        } catch (e) {
	            console.error("Error processing annotations for", containerId, e);
	        }
/*
	        encoded = overlay.getAttribute("data-sections");
	
	        if (!encoded) continue;
	        
	        try {
	            decoded = decodeURIComponent(encoded);
	            annotations = JSON.parse(decoded);
	            applyOverlaySections(containerId, annotations);
	        } catch (e) {
	            console.error("Error processing sections for", containerId, e);
	        }

*/

	    }
	}
	
	


	
	function hexToRgb(hex) {
	  // Remove leading '#' if present
	  hex = hex.replace(/^#/, '');
	
	  if (hex.length === 3) {
	    // Convert shorthand (#f00) to full form (#ff0000)
	    hex = hex.replace(/(.)/g, '$1$1');
	  }
	
	  var bigint = parseInt(hex, 16);
	  var r = (bigint >> 16) & 255;
	  var g = (bigint >> 8) & 255;
	  var b = bigint & 255;
	
	  return 'rgb(' + r + ', ' + g + ', ' + b + ')';
	}

	
	function findUpperSectionLimit(currentCell) {
	    var table = currentCell.closest('table');
	    if (!table) return currentCell;
	
	    var rows = table.getElementsByTagName('tr');
	    var row = currentCell.parentNode;
	    var rowIndex = -1;
	    for (var i = 0; i < rows.length; i++) {
	        if (rows[i] === row) {
	            rowIndex = i;
	            break;
	        }
	    }
	    if (rowIndex === -1) return currentCell;
	
	    var cells = row.cells;
	    var colIndex = -1;
	    for (var j = 0; j < cells.length; j++) {
	        if (cells[j] === currentCell) {
	            colIndex = j;
	            break;
	        }
	    }
	    if (colIndex === -1) return currentCell;
	
	    var lastMatch = currentCell;
	    var targetColor = hexToRgb(selectedColor);
	
	    for (var r = rowIndex - 1; r >= 0; r--) {
	        var aboveRow = rows[r];
	        var cell = aboveRow.cells[colIndex];
	        if (!cell) break;
	
	        var cellColor = window.getComputedStyle(cell).backgroundColor;
	        if (cellColor === targetColor) {
	            lastMatch = cell;
	        } else {
	            break;
	        }
	    }
	
	    return lastMatch;
	}


	function findLowerSectionLimit(currentCell) {
	    var table = currentCell.closest('table');
	    if (!table) return currentCell;
	
	    var rows = table.getElementsByTagName('tr');
	    var row = currentCell.parentNode;
	    var rowIndex = -1;
	    for (var i = 0; i < rows.length; i++) {
	        if (rows[i] === row) {
	            rowIndex = i;
	            break;
	        }
	    }
	    if (rowIndex === -1) return currentCell;
	
	    var cells = row.cells;
	    var colIndex = -1;
	    for (var j = 0; j < cells.length; j++) {
	        if (cells[j] === currentCell) {
	            colIndex = j;
	            break;
	        }
	    }
	    if (colIndex === -1) return currentCell;
	
	    var lastMatch = currentCell;
	    var targetColor = hexToRgb(selectedColor);
	
	    for (var r = rowIndex + 1; r < rows.length; r++) {
	        var belowRow = rows[r];
	        var cell = belowRow.cells[colIndex];
	        if (!cell) break;
	
	        var cellColor = window.getComputedStyle(cell).backgroundColor;
	        if (cellColor === targetColor) {
	            lastMatch = cell;
	        } else {
	            break;
	        }
	    }
	
	    return lastMatch;
	}


	
	function findCellAbove(currentCell, rows, rowIndex, colIndex, matchColor) {

		//console.log("Looking for cell above row " + rowIndex + " and " + colIndex );

	  // Search upwards for the cell in the same visual column
	  var runningRow = rowIndex - 1;
	  while (runningRow >= 0) {
	  	//console.log("Checking row " + runningRow);
	    var aboveRow = rows[runningRow];
	    var aboveCells = aboveRow.cells;
	    var colPos = 0;
	
	    for (var k = 0; k < aboveCells.length; k++) {
	      var cell = aboveCells[k];
	      var span = cell.colSpan || 1;

			//console.log("Looking at colPos " + colPos + " with " + cell.colSpan);

	      if (colPos <= colIndex && colIndex < colPos + span) {
			if (matchColor) {
			    var cellColor = window.getComputedStyle(cell).backgroundColor;
			    var targetColor = hexToRgb(selectedColor);
			
			    if (cellColor === targetColor) {
			        return cell;
			    } else if (cellColor !== 'rgb(255, 255, 255)') {
			    	//console.log("Found interrupting cell with color " + cellColor);
			        return null; // cell has a DIFFERENT color
			    }
			}
			else{
				return cell;
	      	}
			/*
	        var rowSpan = cell.rowSpan || 1;
	        var cellBottomRow = runningRow + rowSpan - 1;
	        if (cellBottomRow >= rowIndex) {
	        	console.log("Found cell");
	          return cell;
	        }*/
	      }
	
	      colPos += span;
	    }
	
	    runningRow--;
	  }
	
	  return null;
	}





	if (document.getElementById("export-sections-button")) {
		document.getElementById("export-sections-button").addEventListener("click", function () {
	    var overlay = document.querySelector(".overlay");
	    if (!overlay) return;
	
	    var annotationsRaw = overlay.getAttribute("data-annotations");
	    var annotations = JSON.parse(annotationsRaw.replace(/&quot;/g, '"'));
	
	    // build groups from the color map list
	    var groups = {};
	    for (var lineID in annotations) {
	        if (annotations.hasOwnProperty(lineID)) {
	            var color = annotations[lineID];
	            if (!groups[color]) {
	                groups[color] = [];
	            }
	            groups[color].push(lineID);
	        }
	    }
	
	    var output = "";
	    for (var color in groups) {
	        if (groups.hasOwnProperty(color)) {
	            var lines = groups[color];
	
	            // Sort lines numerically where possible
	            lines.sort(function (a, b) {
	                var re = /^(\d+)([a-z]*)$/;
	                var ma = re.exec(a);
	                var mb = re.exec(b);
	                var na = ma ? parseInt(ma[1], 10) : 0;
	                var nb = mb ? parseInt(mb[1], 10) : 0;
	                if (na !== nb) return na - nb;
	                return a.localeCompare(b);
	            });
	
	            var first = lines[0];
	            var last = lines[lines.length - 1];
	
	            // Find the line element
	            var lineEl = overlay.querySelector('[data-line="' + first + '"]');
	            var levelClass = 'unknown';
	            if (lineEl) {
	                var row = lineEl.closest("tr");
	                if (row) {
	                    var sectionCell = row.querySelector('td[class^="section-"]');
	                    if (sectionCell) {
	                        var classList = sectionCell.className.split(/\s+/);
	                        for (var i = 0; i < classList.length; i++) {
	                            if (classList[i].indexOf("section-") === 0) {
	                                levelClass = classList[i]; // e.g. "section-2"
	                                break;
	                            }
	                        }
	                    }
	                }
	            }
	
	            output += "{{Overlay Section |level=" + levelClass + " |firstLine=" + first + " |lastLine=" + last + " |color=" + color + "}},";
	        }
	    }
	
	    // Set value into input box by name
	    var inputs = document.getElementsByName('Text Overlay[Sections]');
	    if (inputs.length > 0) {
	        inputs[0].value = output.replace(/,$/, '');
	    }
	});
	}


	// Helper functions


	function compareLineIds(a, b) {
	    var re = /^(\d+)([a-z]*)$/i;
	    var ma = re.exec(a);
	    var mb = re.exec(b);
	    if (!ma || !mb) return 0;
	
	    var na = parseInt(ma[1], 10);
	    var nb = parseInt(mb[1], 10);
	
	    if (na !== nb) return na - nb;
	
	    // Compare letters if numbers match
	    var sa = ma[2];
	    var sb = mb[2];
	    if (sa < sb) return -1;
	    if (sa > sb) return 1;
	    return 0;
	}

	
	function getColorForOverlay(name) {
	    var colors = {
	        "warm.1": "#f8c936",
	        "cool.2": "#5bc0de",
	        "vibrant.2": "#d9534f"
	        // Add more mappings as needed
	    };
	    return colors[name] || name; // fallback to raw value
	}

	
	function newElement(tag, content, styleProps, className) {
	    var el = document.createElement(tag);
	    el.textContent = content;
	    setStyle(el, styleProps);
	    if (className) el.className = className;

	    return el;
	}
	
	function setStyle(element, styleProps) {
	    for (var prop in styleProps) {
	        element.style[prop] = styleProps[prop];
	    }
	}
	
	function removeColumn(label, headerRow, bodyRows) {
	    var index = -1;
	    for (var i = 0; i < headerRow.children.length; i++) {
	        if (headerRow.children[i].textContent === label) {
	            index = i;
	            headerRow.removeChild(headerRow.children[i]);
	            break;
	        }
	    }
	    if (index !== -1) {
	        for (var j = 1; j < bodyRows.length; j++) {
	            if (bodyRows[j].children[index]) {
	                bodyRows[j].removeChild(bodyRows[j].children[index]);
	            }
	        }
	    }
	}
	
	// Finds where to insert a new column *after* the given label
	function getInsertIndex(headerRow, afterLabel, defaultIndex) {
	    for (var i = 0; i < headerRow.children.length; i++) {
	        if (headerRow.children[i].textContent === afterLabel) {
				//console.log("Found " + headerRow + " at index " + i);
	            return i + 1;
	        }
	    }
	    if (defaultIndex > -1)
	    	return defaultIndex;

		console.warn("Did not find " + afterLabel);
	    return headerRow.children.length; // default: append
	}


	function getLineIdFromRow(row) {
	    if (!row){
	    	console.warn("Attempting to get line from a null row");
	    	return null;		
	    } 
	
	    if (!row || row.tagName !== 'TR') {
	    	console.warn("Attempting to get line from a non-row");
	    	return null;
	    }
	
	    var divs = row.getElementsByTagName('div');
	    for (var i = 0; i < divs.length; i++) {
	        if (divs[i].className.indexOf('line') !== -1 && divs[i].getAttribute('data-line')) {
	            return divs[i].getAttribute('data-line');
	        }
	    }
	
    	console.warn("No matching data-line attribute found");
	    return null; // No matching <div class="line" data-line=...> found
	}

	function getRowFromLineId(lineId) {
	    if (!lineId || !overlayTable) return null;
	
	    var rows = overlayTable.getElementsByTagName('tr');
	
	    for (var i = 0; i < rows.length; i++) {
	        var divs = rows[i].getElementsByTagName('div');
	        for (var j = 0; j < divs.length; j++) {
	            if (divs[j].className.indexOf('line') !== -1 && divs[j].getAttribute('data-line') === lineId) {
	                return rows[i];
	            }
	        }
	    }
	
	    return null; // No matching row found
	}

	function insertBlankRowAfter(row) {
		
	    if (!row || row.tagName !== 'TR') return;
	
	    var newRow = document.createElement('tr');
	    var newCell = document.createElement('td');
	    newCell.colSpan = row.cells.length; // or a fixed number like 7
	    newCell.style.backgroundColor = 'black';
	    newCell.innerHTML = '&nbsp;';
	    newRow.appendChild(newCell);
	
	    if (row.nextSibling) {
	        row.parentNode.insertBefore(newRow, row.nextSibling);
	    } else {
	        row.parentNode.appendChild(newRow);
	    }
	
	    return newRow;
	}
	
	function insertBlankColumnAt(colIndex) {
	    if (!overlayTable) return;
	
	    var rows = overlayTable.getElementsByTagName('tr');
	    if (rows.length < 2) return; // skip if there's no body
	
	    var newCell = document.createElement('td');
	    newCell.style.backgroundColor = 'black';
	    newCell.innerHTML = '&nbsp;';
	    newCell.rowSpan = rows.length;
	
	    // insert into first row 
	    var firstRow = rows[0];
	
	    if (colIndex >= 0 && colIndex <= firstRow.children.length) {
	        firstRow.insertBefore(newCell, firstRow.children[colIndex]);
	    } else {
	        firstRow.appendChild(newCell);
	    }
	}
	function findCellBelow(currentCell, rows, rowIndex, colIndex, matchColor) {

		//console.log("Looking for cell below row " + rowIndex + " and " + colIndex );

	  // Search downwards for the cell in the same visual column
	  var runningRow = rowIndex + 1;
	  while (runningRow < rows.length) {
	  	//console.log("Checking row " + runningRow);
	    var belowRow = rows[runningRow];
	    var belowCells = belowRow.cells;
	    var colPos = 0;
	
	    for (var k = 0; k < belowCells.length; k++) {
	      var cell = belowCells[k];
	      var span = cell.colSpan || 1;

			//console.log("Looking at colPos " + colPos + " with " + cell.colSpan);

	      if (colPos <= colIndex && colIndex < colPos + span) {

			if (matchColor) {
			    var cellColor = window.getComputedStyle(cell).backgroundColor;
			    var targetColor = hexToRgb(selectedColor);
			
			    if (cellColor === targetColor) {
			        return cell;
			    } else if (cellColor !== 'rgb(255, 255, 255)') {

			        return null; // cell has a DIFFERENT color
			    }
			} else{
				//console.log("colPos looks good");
				return cell;
	      	}
			/*
	        var rowSpan = cell.rowSpan || 1;
	        var cellBottomRow = runningRow + rowSpan - 1;
	        if (cellBottomRow >= rowIndex) {
	        	console.log("Found cell");
	          return cell;
	        }*/
	      }
	
	      colPos += span;
	    }
	
	    runningRow++;
	  }
	
	  return null;
	}	

function highlightCellsVertically(cellA, cellB) {
  if (!cellA || !cellB) return;

  var table = cellA.closest('table');
  if (!table) return;

  var rows = Array.from(table.rows);

  var rowIndexA = rows.indexOf(cellA.parentNode);
  var rowIndexB = rows.indexOf(cellB.parentNode);
  if (rowIndexB <= rowIndexA) return;

  // Calculate visual column index of cellA
  var colIndex = -1;
  var headerRow = rows[rowIndexA];
  var colPos = 0;

  for (var j = 0; j < headerRow.cells.length; j++) {
    var c = headerRow.cells[j];
    var colspan = c.colSpan || 1;
    if (c === cellA) {
      colIndex = colPos;
      break;
    }
    colPos += colspan;
  }

  if (colIndex === -1) return;

  // Get section-* class from cellA
  var sectionClassA = null;
  var classListA = cellA.className.split(/\s+/);
  for (var s = 0; s < classListA.length; s++) {
    if (
      classListA[s].indexOf("speaker") === 0 ||
      classListA[s].indexOf("addressee") === 0 ||
      classListA[s].indexOf("section") === 0 ||
      classListA[s].indexOf("subsection") === 0
    ) {
      sectionClassA = classListA[s];
      break;
    }
  }

  if (!sectionClassA) return;

  // Loop through the rows and highlight matching cells
  for (var i = rowIndexA + 1; i <= rowIndexB; i++) {
    var row = rows[i];
    var colPos = 0;

    for (var j = 0; j < row.cells.length; j++) {
      var c = row.cells[j];
      var colspan = c.colSpan || 1;

      if (colPos <= colIndex && colIndex < colPos + colspan) {
        if (!c.querySelector('div.line')) {
          var classListC = c.className.split(/\s+/);
          for (var k = 0; k < classListC.length; k++) {
            if (classListC[k] === sectionClassA) {
              c.style.backgroundColor = selectedColor;
              break;
            }
          }
        }
        break; // Only highlight one cell per row in the target column
      }

      colPos += colspan;
    }
  }

  // Optionally set rowspan — commented out
  // cellA.rowSpan = rowIndexB - rowIndexA + 1;
}


});










/* ======================================


        Insertions js


  ======================================*/
  
var debug = 1;
var lastClickedInsertionPoint = null;

function createInsertionSpan(parentLine, refNode, options) {
  var insert = document.createElement("span");
  insert.className = "insertion";
  insert.textContent = options.text || "⟪insert⟫";
  //insert.setAttribute("data-before", options.before || "");
  //insert.setAttribute("data-after", options.after || "");
  insert.setAttribute("data-new", options.isNew ? "true" : "false");
  //if (options.isSplit) insert.setAttribute("data-split", "true");

  /*// Count non-insertion spans before this position
  var children = Array.from(parentLine.children);
  var insertBeforeIndex = refNode ? children.indexOf(refNode.nextSibling) : children.length;

  var textBefore = "";
  for (var i = 0; i < insertBeforeIndex; i++) {
    var el = children[i];
    if (el.tagName === "SPAN" && !el.classList.contains("insertion")) {
      textBefore += (el.textContent || "") + " ";
    }
  }

  textBefore = textBefore.trim();
  if (options.beginning)
	textBefore = "";
	
  var wordCount = textBefore === "" ? 0 : textBefore.split(/\s+/).length;

  insert.setAttribute("data-index", wordCount);
  insert.setAttribute("data-beforetext", textBefore);
  //insert.setAttribute("data-refnode", refNode && refNode.innerText);
*/
  insert.addEventListener("click", insertionClickHandler);
  return insert;
}


function showInsertInput(span) {
  var input = document.getElementById("input_1");
  var modal = document.getElementById("enter-insertion");
  var table = document.querySelector("#text-insertion table");
  var line = span.closest(".line");

  if (!line || !modal || !input || !table) return;

  // Clear previous highlights
  document.querySelectorAll(".insertion.active").forEach(el => el.classList.remove("active"));

  // Highlight the current one
  span.classList.add("active");

  input.value = span.textContent.replace(/⟪insert⟫/, "").trim();
  lastClickedInsertionPoint = span;

  requestAnimationFrame(function () {
    var rect = line.getBoundingClientRect();
    var tableRect = table.getBoundingClientRect();

    modal.style.position = "absolute";
    modal.style.left = tableRect.left + window.scrollX + "px";
    modal.style.top = rect.bottom + window.scrollY + "px";
    modal.style.width = tableRect.width + "px";
    modal.style.removeProperty("display");
    modal.style.display = "block";

    input.focus();
  });
}




function insertBetweenSpans(target, event) {
  if (debug) {
    console.log("[insertBetweenSpans] Target element:", target);
    console.log("[insertBetweenSpans] Target tag:", target.tagName, "class:", target.className);
    console.log("[insertBetweenSpans] Target outerHTML:", target.outerHTML);
  }

  var line = target.closest ? target.closest(".line") : (typeof closestPolyfill === "function" ? closestPolyfill(target, ".line") : null);

  if (!line) {
    console.warn("[insertBetweenSpans] Could not find .line container for target.");
    if (target.parentNode) {
      console.warn("[insertBetweenSpans] Parent node tag/class:", target.parentNode.tagName, target.parentNode.className);
      console.warn("[insertBetweenSpans] Parent outerHTML:", target.parentNode.outerHTML);
    }

    var ancestry = [];
    var curr = target;
    while (curr && curr !== document) {
      ancestry.push(curr.tagName + (curr.className ? `.${curr.className}` : ""));
      curr = curr.parentNode;
    }
    console.warn("[insertBetweenSpans] DOM ancestry (bottom-up):", ancestry.join(" ← "));

    return;
  }

	var insert = createInsertionSpan(line, refNode, {
	  isNew: true,
	  after: refNode && refNode.innerText,
	  before: refNode && refNode.nextSibling && refNode.nextSibling.innerText
	  
	});

  var range = document.caretRangeFromPoint
    ? document.caretRangeFromPoint(event.clientX, event.clientY)
    : (document.caretPositionFromPoint && document.caretPositionFromPoint(event.clientX, event.clientY));

  var node = range && range.startContainer;
  var refNode = node && node.nodeType === 3 ? node.parentNode : node;

  if (debug) {
    console.log("[insertBetweenSpans] Insert range object:", range);
    console.log("[insertBetweenSpans] Resolved refNode:", refNode);
    if (refNode) console.log("[insertBetweenSpans] refNode.outerHTML:", refNode.outerHTML);
  }

  if (refNode && refNode.parentNode === line) {
    line.insertBefore(insert, refNode.nextSibling);
    insert.addEventListener("click", insertionClickHandler);
  } else {
    line.appendChild(insert);
    insert.addEventListener("click", insertionClickHandler);
  }

  showInsertInput(insert);
}


function insertIntoSpan(span, event) {
  if (debug) console.groupCollapsed("[insertIntoSpan] Splitting span");

  var clickX = event.clientX;
  var clickY = event.clientY;

  var range = document.caretRangeFromPoint
    ? document.caretRangeFromPoint(clickX, clickY)
    : document.caretPositionFromPoint && document.caretPositionFromPoint(clickX, clickY);

  if (!range) {
    console.warn("[insertIntoSpan] No range found for click");
    return;
  }

  var fullText = span.textContent;
  var offset = range.startOffset || 0;
  if (offset < 0 || offset > fullText.length) offset = fullText.length;

  if (debug) console.log(`[insertIntoSpan] Splitting at offset ${offset} of text: "${fullText}"`);

  var parent = span.parentNode;

  // Insertion at beginning — don’t create empty 'before'
  if (offset === 0) {
    var insert = createInsertionSpan(parent, span, {
	  beginning: true,
	  isNew: true,
	  isSplit: true
	});
    parent.insertBefore(insert, span);

    // Reattach event handlers
    span.addEventListener("click", insertionClickHandler);
    insert.addEventListener("click", insertionClickHandler);
    showInsertInput(insert);
    console.groupEnd();
    return;
  }

  // Insertion at end — don’t create empty 'after'
  if (offset === fullText.length) {
    var insert = createInsertionSpan(parent, span, {
	  after: span.textContent || "",
	  isNew: true,
	  isSplit: true
	});
	
	parent.insertBefore(insert, span.nextSibling);

    // Reattach event handlers
    span.addEventListener("click", insertionClickHandler);
    insert.addEventListener("click", insertionClickHandler);
    showInsertInput(insert);
    console.groupEnd();
    return;
  }

  // Normal mid-span insertion
  var before = document.createElement("span");
  before.className = span.className;
  before.textContent = fullText.slice(0, offset);
	
	var insert = createInsertionSpan(parent, span, {
	  before: span.className || "",
	  after: span.className || "",
	  isNew: true,
	  isSplit: true
	});

  var after = document.createElement("span");
  after.className = span.className;
  after.textContent = fullText.slice(offset);

  parent.replaceChild(after, span);
  parent.insertBefore(insert, after);
  parent.insertBefore(before, insert);

  before.addEventListener("click", insertionClickHandler);
  after.addEventListener("click", insertionClickHandler);
  insert.addEventListener("click", insertionClickHandler);

  showInsertInput(insert);
  console.groupEnd();
}


function saveInsertions() {
  var outputTextarea = document.getElementById('input_2');
  var insertions = [];

  document.querySelectorAll('span.insertion, span.insertion-point').forEach(function (span) {
    var text = span.textContent.trim();
    var parent = span.closest('.line');
    var children = Array.from(parent.children);

    var countBefore = 0;
    var textBefore = "";

    for (var i = 0; i < children.length; i++) {
      var el = children[i];
      if (el === span) break;

      if (el.tagName === "SPAN" && !el.classList.contains("insertion") && !el.classList.contains("insertion-point")) {
        countBefore++;
        textBefore += (el.textContent || "") + " ";
      }
    }

    textBefore = textBefore.trim();
    var wordsBefore = textBefore === "" ? [] : textBefore.split(/\s+/);
    var wordCountBefore = wordsBefore.length;

    if (debug) {
      console.log("[saveInsertions] Found insertion:", {
        text: text,
        index: countBefore,
        textBefore: textBefore,
        wordCountBefore: wordCountBefore
      });
    }

    if (!text) return;

    insertions.push({
      text: text,
      lineID: parent.getAttribute("data-line"),
      index: countBefore,
      textBefore: textBefore,
      wordsBefore: wordCountBefore
    });
  });

  var result = JSON.stringify(insertions, null, 2);
  outputTextarea.value = result;

  if (debug) console.log("[saveInsertions] Final output JSON:", result);
}


function insertionClickHandler(e) {
  var span = e.currentTarget;
  if (debug) console.groupCollapsed("[insertionClickHandler] for span " + span.innerText);

  if (span.className && span.className.indexOf("insertion") !== -1) {
    if (debug) console.log("[span click] Clicked existing insertion point");
    showInsertInput(span); // ✅ fills input with textContent
    if (debug) console.groupEnd();
    return;
  }

  // clicked somewhere else: new insertion into a span
  insertIntoSpan(span, e);
  if (debug) console.groupEnd();
}


$(document).ready(function () {
  if (document.getElementById('apply-insertion')) {
    if (debug) console.log("[ready] Binding click to #apply-insertion");

	document.getElementById('apply-insertion').addEventListener('click', function () {
	  var textarea = document.getElementById('input_1');
	  var text = textarea.value;
	  var modal = document.getElementById('enter-insertion');
	
	  if (debug) console.log("[apply-insertion] Clicked. Text to insert:", text);
	
	  if (lastClickedInsertionPoint && typeof text === 'string') {
	    if (debug) console.log("[apply-insertion] Inserting into:", lastClickedInsertionPoint);
	    lastClickedInsertionPoint.textContent = text;
	    lastClickedInsertionPoint.removeAttribute("data-new");
	    textarea.value = "";
	    modal.style.display = "none"; // ✅ hide the box
	  } else {
	    console.warn("[apply-insertion] No insertion point selected or input missing.");
	  }
	});

  }

function cancelInsertion() {
  var modal = document.getElementById('enter-insertion');
  modal.style.display = "none";
  document.getElementById('input_1').value = "";

  if (lastClickedInsertionPoint) {
    lastClickedInsertionPoint.classList.remove("active");

    // ✅ Only remove if it's marked as a new insertion
    if (lastClickedInsertionPoint.dataset.new === "true") {
      if (debug) console.log("[cancelInsertion] Removing new insertion span");
      lastClickedInsertionPoint.remove();
    }
  }

  lastClickedInsertionPoint = null;
}



if (document.getElementById('cancel-insertion')) {
  document.getElementById('cancel-insertion').addEventListener('click', function () {
    if (debug) console.log("[cancel-insertion] Clicked");
    cancelInsertion(true);  // true = remove the span
  });
}

document.addEventListener('keydown', function (e) {
  var modal = document.getElementById('enter-insertion');

  // ESC = cancel
  if (e.key === 'Escape') {
    if (modal && modal.style.display === 'block') {
      if (debug) console.log("[Esc] Cancel via Esc key");
      cancelInsertion(false);  // false = keep the span, just hide the box
    }
  }

  // ENTER/RETURN = apply
  if (e.key === 'Enter') {
    if (modal && modal.style.display === 'block') {
      if (debug) console.log("[Enter] Apply via Enter key");

      var applyBtn = document.getElementById('apply-insertion');
      if (applyBtn) applyBtn.click();
    }
  }
});






if (document.getElementById("text-insertion")) {
	
  if (debug) console.log("[ready] Attaching individual listeners to spans inside #text-insertion");

  var container = document.getElementById("text-insertion");
  var spans = container.querySelectorAll(".line span");

  spans.forEach(function (span) {
    span.addEventListener("click", insertionClickHandler);
  });

	var table = container.querySelector("table.psalm-table");

  if (!table)
  {
	  console.warn("Could not find psalm table");
	  return;
  }

	  // Fallback: click outside of spans (e.g. empty part of line)
	table.addEventListener("click", function (e) {
	  var target = e.target;
	
	  if (debug) console.log("[container click] Fallback click handler target:", target);
	
	  // Ignore clicks inside buttons, modals, inputs, etc.
	  if (target.closest("button, input, textarea, #enter-insertion")) {
	    if (debug) console.log("[container click] Ignored: clicked inside UI element");
	    return;
	  }
	
	  // Only handle if not a <span>, or if not already handled
	  if (target === container || target.tagName !== "SPAN") {
	    var line = target.closest(".line");
	    if (line) {
	      if (debug) console.log("[container click] Inserting between spans inside line:", line);
	      insertBetweenSpans(target, e);
	    } else {
	      if (debug) console.log("[container click] Clicked outside any .line — no insertion");
	    }
	  }
	});

}



  if (document.getElementById('saveInsertions')) {
    if (debug) console.log("[ready] Binding click to #saveInsertions");

    document.getElementById('saveInsertions').addEventListener('click', saveInsertions);
  }
});








/* ======================================


        Lineation js


  ======================================*/
  var debug = 1;

function insertHebrewGaps() {
  var lines = document.querySelectorAll('.lineation .psalm-table .hebrew-cell .line');

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

    // Clean out text nodes (e.g. spaces) between spans
    var child = line.firstChild;
    while (child) {
      var next = child.nextSibling;
      if (child.nodeType === 3 && /^\s*$/.test(child.nodeValue)) {
        line.removeChild(child);
      }
      child = next;
    }

    // Collect spans after cleanup
    var spans = line.querySelectorAll('span.hebrew');
    if (debug === 3) console.log('[insertHebrewGaps] Processing line:', line, spans.length, 'spans');

    for (var j = spans.length - 1; j > 0; j--) {
      var idA = getHebrewSpanIDParts(spans[j - 1]);
      var idB = getHebrewSpanIDParts(spans[j]);

      if (!idA || !idB || idA.line !== idB.line || idA.word !== idB.word) {
        var gap = document.createElement('span');
        gap.className = 'hebrew-gap';
        gap.innerHTML = '&nbsp;';
        gap.style.cursor = 'col-resize';
        gap.addEventListener('click', handleHebrewGapClick);
        line.insertBefore(gap, spans[j]);

        if (debug === 3) {
          console.log('[insertHebrewGaps] Inserted gap between:', spans[j - 1].className, 'and', spans[j].className);
        }
      } else if (debug === 3) {
        console.log('[insertHebrewGaps] Skipped gap between same word parts:', idA.word);
      }
    }
    // Add merge gap after Hebrew span loop
	var merge = document.createElement('span');
	merge.className = 'hebrew-gap merge-gap';
	merge.innerHTML = '&nbsp;';
	merge.style.cursor = 'ns-resize';
	merge.title = 'Shift-click to merge with next line';
	merge.addEventListener('click', handleHebrewGapClick);
	line.appendChild(merge);

  }
}


function getHebrewSpanIDParts(span) {
  var match = span.className.match(/id-(\d+[a-z]*)-(\d+)-(\d+)/);
  if (match) {
    return {
      line: match[1],       // e.g. "1a"
      word: match[2],       // e.g. "1"
      lexeme: match[3]      // e.g. "3"
    };
  }
  return null;
}

function insertHebrewGapsOnLine(line) {
  if (!line) return;

  // 1. Remove old gaps and text nodes
  var oldGaps = line.querySelectorAll('span.hebrew-gap');
  for (var i = 0; i < oldGaps.length; i++) {
    oldGaps[i].parentNode.removeChild(oldGaps[i]);
  }

  var child = line.firstChild;
  while (child) {
    var next = child.nextSibling;
    if (child.nodeType === 3 && /^\s*$/.test(child.nodeValue)) {
      line.removeChild(child);
    }
    child = next;
  }

  // 2. Insert new word-splitting gaps
  var spans = line.querySelectorAll('span.hebrew');
  for (var j = spans.length - 1; j > 0; j--) {
    var idA = getHebrewSpanIDParts(spans[j - 1]);
    var idB = getHebrewSpanIDParts(spans[j]);
    if (!idA || !idB || idA.line !== idB.line || idA.word !== idB.word) {
      var gap = document.createElement('span');
      gap.className = 'hebrew-gap';
      gap.innerHTML = '&nbsp;';
      gap.style.cursor = 'col-resize';
      gap.addEventListener('click', handleHebrewGapClick);
      line.insertBefore(gap, spans[j]);
    }
  }

  // 3. Always add merge gap at end
  var merge = document.createElement('span');
  merge.className = 'hebrew-gap merge-gap';
  merge.innerHTML = '&nbsp;';
  merge.style.cursor = 'ns-resize';
  merge.title = 'Shift-click to merge with next line';
  merge.addEventListener('click', handleHebrewGapClick);
  line.appendChild(merge);

  if (debug === 3) console.log('[insertHebrewGapsOnLine] Added merge gap at end of line.');
}

function getNextElementSibling(el) {
  var next = el.nextSibling;
  while (next && next.nodeType !== 1) { // 1 = ELEMENT_NODE
    next = next.nextSibling;
  }
  return next;
}

function handleMergeGapClick(event) {
  var gap = event.currentTarget;

  var lineDiv = closestByClass(gap, 'line');
  var lineID = lineDiv.getAttribute('data-line');
  var tr = closestByTag(gap, 'tr');
	var nextTr = getNextElementSibling(tr);
	
	if (!nextTr) {
	  if (debug === 2) console.warn('[merge] No line below to merge with.');
	  return;
	}
	
	var nextHebrewLine = nextTr.querySelector('.hebrew-cell .line');
	var nextEnglishLine = nextTr.querySelector('.cbc-cell .line');

  if (!nextHebrewLine || !nextEnglishLine) {
    if (debug === 1) console.warn('[merge] Incomplete next row.');
    return;
  }else{
    if (debug === 1) console.warn('[merge] Merging with ' + nextHebrewLine);
  }

  // Merge Hebrew
  var currentHebrew = lineDiv.innerHTML;
  var mergedHebrew = currentHebrew + nextHebrewLine.innerHTML;
  lineDiv.innerHTML = mergedHebrew;

  // Merge English
  var currentEnglish = tr.querySelector('.cbc-cell .line').innerHTML;
  var nextEnglish = nextEnglishLine.innerHTML;
  tr.querySelector('.cbc-cell .line').innerHTML = currentEnglish + ' ' + nextEnglish;

  // Remove next row
  nextTr.parentNode.removeChild(nextTr);

  // Rebuild gaps
  insertHebrewGapsOnLine(lineDiv);

  if (debug === 3) {
    console.log('[merge] Merged line', lineID, 'with next line.');
  }
}

function handleHebrewGapClick(event) {
  var gap = event.currentTarget;

  if (gap.classList.contains('merge-gap')) {
    if (debug === 1) console.log('[gapClick] Merging line with next');
    handleMergeGapClick(event);
    return;
  }

  // Otherwise, normal split
  handleSplitGapClick(event);
}


function handleSplitGapClick(event) {
  var gap = event.currentTarget;
  var lineDiv = closestByClass(gap, 'line');
  var lineID = lineDiv.getAttribute('data-line');
  var tr = closestByTag(gap, 'tr');

  var tbody = tr.parentNode;
  var rows = Array.prototype.slice.call(tbody.children);
  var rowIndex = rows.indexOf(tr);

  var allHebrew = lineDiv.querySelectorAll('span.hebrew, span.hebrew-gap');
  var allHebrewArr = Array.prototype.slice.call(allHebrew);
  var gapIndex = allHebrewArr.indexOf(gap);
  var beforeHebrew = allHebrewArr.slice(0, gapIndex);
  var afterHebrew = allHebrewArr.slice(gapIndex + 1);

  var firstHebrewAfter = null;
  for (var i = 0; i < afterHebrew.length; i++) {
    if (afterHebrew[i].className.indexOf('hebrew') !== -1) {
      firstHebrewAfter = afterHebrew[i];
      break;
    }
  }

  var targetID = null;
  if (firstHebrewAfter) {
    var match = firstHebrewAfter.className.match(/id-[\w\-]+/);
    if (match) targetID = match[0];
  }

  var englishLine = tr.querySelector('.cbc-cell .line');
  var englishSpans = englishLine.querySelectorAll('span.english');
  var englishArr = Array.prototype.slice.call(englishSpans);
  var splitIndex = 0;

  if (targetID) {
    for (var i = 0; i < englishArr.length; i++) {
      if (englishArr[i].className.indexOf(targetID) !== -1) {
        splitIndex = i;
        break;
      }
    }
  } else {
    splitIndex = englishArr.length;
  }

  var beforeEnglish = englishArr.slice(0, splitIndex);
  var afterEnglish = englishArr.slice(splitIndex);

  // Update current row
  lineDiv.innerHTML = joinOuterHTML(beforeHebrew);
  englishLine.innerHTML = joinOuterHTML(beforeEnglish, ' ');

	// Rebuild gaps for the original line (adds merge-gap to end)
	insertHebrewGapsOnLine(lineDiv);


  // Create new row
  var newTr = document.createElement('tr');
  newTr.innerHTML =
    '<td dir="rtl" class="hebrew-cell"><div class="line" data-line="' + lineID + '">' +
    joinOuterHTML(afterHebrew) +
    '</div></td>' +
    '<td class="verse-cell">' + lineID + '</td>' +
    '<td class="cbc-cell"><div class="line" data-line="' + lineID + '">' +
    joinOuterHTML(afterEnglish, ' ') +
    '</div></td>';

  tbody.insertBefore(newTr, tr.nextSibling);
  insertHebrewGapsOnLine(newTr.querySelector('.line'));

  if (debug === 1) {
    console.log('[split] Created new row beneath line:', lineID);
  }
}

function closestByClass(el, className) {
  while (el && el !== document) {
    if (el.classList && el.classList.contains(className)) return el;
    el = el.parentNode;
  }
  return null;
}

function closestByTag(el, tagName) {
  tagName = tagName.toUpperCase();
  while (el && el !== document) {
    if (el.tagName === tagName) return el;
    el = el.parentNode;
  }
  return null;
}

function joinOuterHTML(nodes, separator) {
  var html = [];
  for (var i = 0; i < nodes.length; i++) {
    html.push(nodes[i].outerHTML || '');
  }
  return html.join(typeof separator === 'string' ? separator : '');
}

$(document).ready(function () {
  insertHebrewGaps();
  if (debug === 3) console.log('[ready] Hebrew gaps initialized.');
  
  var buttonSave = document.getElementById('saveLineation');
  if (buttonSave) {
	  document.getElementById('saveLineation').addEventListener('click', function () {
	  var lines = document.querySelectorAll('.psalm-table .hebrew-cell .line');
	  var output = [];
	  var currentVerse = null;
	  var lineCounter = 1;
	
	  for (var i = 0; i < lines.length; i++) {
	    var line = lines[i];
	    var firstHebrew = line.querySelector('span.hebrew');
	
	    if (!firstHebrew) continue;
	
	    var classes = firstHebrew.className.split(/\s+/);
	    var wordID = null;
	    var verseID = null;
	
	    for (var j = 0; j < classes.length; j++) {
	      if (classes[j].indexOf('id-') === 0) {
	        wordID = classes[j].substring(3); // remove "id-"
	        verseID = wordID.split('-')[0];
	        break;
	      }
	    }
	
	    if (!wordID || !verseID) continue;
	
	    if (verseID !== currentVerse) {
	      currentVerse = verseID;
	      lineCounter = 1;
	    }
	
	    output.push({
	      verse: verseID,
	      line: lineCounter,
	      wordID: wordID
	    });
	
	    lineCounter++;
	  }
	
	  var textarea = document.getElementById('input_2');
	  textarea.value = JSON.stringify(output, null, 2);
	});
  }
  
});