MediaWiki:Overlays.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:BuildTextTable.js');

	var selectedAnnotation = 'psalmist';
	var coloredWords = [];
	
	// set up color map
    var divColorMap = {}; //document.getElementById("color-map");
	var colorPicker = null; //document.querySelector("div.color-picker");


	var overlayTable = null;
	var colorMap = {};
	var INSERT_BLANK_AFTER_SECTIONS = false;
	var INCLUDE_EMOTION_COLUMN = true;
	var SECTION_HEADING = 'Structure'
	var DEBUG = true;


	// 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'
	};




	var sectionLevelsAdded = 0;
	var speakerAndAddresseeBarsAdded = false;
	var sectionsAdded = false;
	var subSectionsAdded = false;

  function parseJSONFromDiv(divId) {
    var div = document.getElementById(divId);
    if (!div) return null;

    try {
      return JSON.parse(div.textContent.trim());
    } catch (err) {
      console.error("Failed to parse JSON from #" + divId + ":", err);
      return null;
    }
  }

  function setHeatmapScaleClass(overlay, scale) {
    if (!overlay) return;

    var classList = overlay.className.split(/\s+/);
    var newClassList = [];

    for (var i = 0; i < classList.length; i++) {
      if (classList[i].indexOf("heatmap-scale-") !== 0) {
        newClassList.push(classList[i]);
      }
    }

    newClassList.push("heatmap-scale-" + scale);
    overlay.className = newClassList.join(" ");
  }

  function reapplyOverlayColors(overlayId) {
    var overlay = document.getElementById(overlayId);
    if (!overlay) return;

    var annotationsRaw = overlay.getAttribute("data-annotations");
    if (!annotationsRaw) return;

    var annotations;
    try {
      annotations = JSON.parse(annotationsRaw);
    } catch (e) {
      console.error("Invalid annotations JSON:", annotationsRaw);
      return;
    }

    // Assumes this is defined globally elsewhere
    if (typeof applyOverlayColors === "function") {
      applyOverlayColors(overlayId, annotations);
    }
  }

  function handleHeatmapScaleChange(scale) {
    var overlayId = "overlay-heatmap";
    var mapId = scale === "6"
      ? "color-map"
      : scale === "10"
      ? "color-map-medium"
      : scale === "20"
      ? "color-map-full"
      : null;

    if (!mapId) return;

    var newMap = parseJSONFromDiv(mapId);
    if (!newMap) return;

	// set the global variable
    colorMap = newMap;

    var overlay = document.getElementById(overlayId);
    setHeatmapScaleClass(overlay, scale);
    reapplyOverlayColors(overlayId);
    console.log("Heat map scale changed to " + scale);
  }

  function highlightActiveButton(activeButton, allButtons) {
    for (var i = 0; i < allButtons.length; i++) {
      allButtons[i].className = allButtons[i].className.replace(/\bactive\b/, "").trim();
    }
    if (activeButton.className.indexOf("active") === -1) {
      activeButton.className += " active";
    }
  }

  function bindHeatmapButtons() {
    var buttons = document.querySelectorAll(".prominence-scale-link");
    if (buttons)
    	console.log("Binding prominence scale buttons");
    for (var i = 0; i < buttons.length; i++) {
      (function (button) {
        button.addEventListener("click", function (e) {
          if (e.preventDefault) e.preventDefault();
          var scale = this.id.replace("prominence-scale-", "");
          handleHeatmapScaleChange(scale);
          highlightActiveButton(button, buttons);
        });
      })(buttons[i]);
    }
  }



function attachColorGridHandler(gridSelector) {
  if (!gridSelector)
  {
  	console.warn("Cannot find " + gridSelector + " to attach handlers.");
  	return;
  }
	
  var cells = document.querySelectorAll('.color-cell');

  if (!cells.length)  {
  	//console.warn("No color cells found for handlers.");
  	return;
  }

  cells.forEach(function (cell) {
    cell.style.cursor = 'pointer'; // optional UX boost

    cell.addEventListener('click', function () {
      // Clear highlight from all cells in the grid
      cells.forEach(function (c) {
        c.style.outline = 'none';
      });

      // Highlight selected cell
      cell.style.outline = '4px solid black';

      // Set globals
      window.selectedColor = cell.dataset.color || '';
      window.selectedAnnotation = cell.dataset.participant || 'unknown';
    });
  });
}



function setupWordClickHandler(containerSelector) {
  var selector = containerSelector + ' span.hebrew:not(#buildTextTable span.hebrew)';
  var spans = document.querySelectorAll(selector);

  spans.forEach(function (span) {
    span.style.cursor = 'pointer';

    span.addEventListener('click', function (event) {
      event.stopPropagation(); // prevent parent line click

      if (!selectedColor || !selectedAnnotation) {
        console.warn("No color or annotation selected.");
        return;
      }

      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
        });

        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) {
              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);
        }
      }

      console.log("Setting to " + selectedColor);
      span.style.backgroundColor = selectedColor;
      if (glossEl) glossEl.style.backgroundColor = selectedColor;
    });
  });
}



function setupOverlayLineClickHandler(containerSelector) {
    var lines = document.querySelectorAll(containerSelector + ' div.line');
	console.log("Adding click handlers for " + lines.length + " lines");
    lines.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();

            if (event.shiftKey) {
                // Remove from coloredWords
                coloredWords = coloredWords.filter(function (w) {
                    return !(w.id === lineId && w.annotation === selectedAnnotation && w.color === selectedColor);
                });

                // Remove matching form instances
                document.querySelectorAll('#overlay-annotations .multipleTemplateInstance').forEach(function (instance) {
                    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;
            }

            // Add annotation if not already 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) {
                            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 attachExportSectionsHandler(buttonId) {
    var button = document.getElementById(buttonId);
    if (!button) return;

    button.addEventListener("click", function () {
        var overlay = document.querySelector(".overlay");
        if (!overlay) return;

        var annotationsRaw = overlay.getAttribute("data-annotations");
        if (!annotationsRaw) return;

        var annotations = JSON.parse(annotationsRaw.replace(/&quot;/g, '"'));

        // Group by color
        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)) continue;
            var lines = groups[color];

            // Sort numerically and lexically
            lines.sort(function (a, b) {
                var re = /^(\d+)([a-z]*)$/;
                var ma = re.exec(a), mb = re.exec(b);
                var na = ma ? parseInt(ma[1], 10) : 0;
                var nb = mb ? parseInt(mb[1], 10) : 0;
                return na !== nb ? na - nb : a.localeCompare(b);
            });

            var first = lines[0], last = lines[lines.length - 1];

            // Extract level from the row
            var lineEl = overlay.querySelector('[data-line="' + first + '"]');
            var levelClass = 'unknown';
            if (lineEl) {
                var row = lineEl.closest("tr");
                var sectionCell = row && row.querySelector('td[class^="section-"]');
                if (sectionCell) {
                    var match = sectionCell.className.match(/\bsection-\d+\b/);
                    if (match) levelClass = match[0];
                }
            }

            output += "{{Overlay Section |level=" + levelClass +
                      " |firstLine=" + first +
                      " |lastLine=" + last +
                      " |color=" + color + "}},";
        }

        var inputs = document.getElementsByName('Text Overlay[Sections]');
        if (inputs.length > 0) {
            inputs[0].value = output.replace(/,$/, '');
        }
    });
}



	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 applyOverlayColors(containerId, annotations) {
  var container = document.getElementById(containerId);
  if (!container) return;

  for (var wordID in annotations) {
    if (!annotations.hasOwnProperty(wordID) || wordID.trim() === "") continue;

    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);
    }

    if (!color) continue;

    // Support line-wide selectors like "id-3c-*"
    if (wordID.endsWith("-*")) {
      var prefix = wordID.slice(0, -1); // removes the * but leaves the dash
      var allMatching = container.querySelectorAll('[class*="' + prefix + '"]');

      for (var m = 0; m < allMatching.length; m++) {
        var el = allMatching[m];
        if (!el.style.backgroundColor) {
          el.style.backgroundColor = color;
        }
      }
    } else {
      // Word-level match
      var elements = container.querySelectorAll("." + CSS.escape(wordID));
      for (var j = 0; j < elements.length; j++) {
        elements[j].style.backgroundColor = color;
      }

      // Line-level match
      var lines = container.querySelectorAll('[data-line="' + wordID + '"]');
      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;
	}



	// 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;
}


    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 headerCells = document.querySelectorAll('#overlay-sections thead tr th');

	    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);

	}


	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;
	        }
	    }
	}

	

function reverseEngineerImageHTML(htmlString) {
  var tempDiv = document.createElement('div');
  tempDiv.innerHTML = htmlString;

  var img = tempDiv.querySelector('a.image > img');
  if (!img) return htmlString;

  var src = img.getAttribute('src') || '';
  var match = src.match(/\/thumb\.php\?f=([^&]+)/);
  if (!match) return htmlString;

  var filename = decodeURIComponent(match[1]);
  var size = img.getAttribute('width') || '50';

  var wikitext = '[[File:' + filename + '|' + size + 'px|frameless|center|class=aag-icon]]';
  return wikitext;
}

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

  // Try to resolve the page title
  if (typeof currentPage !== "string" || !currentPage) {
    var divTrigger = document.getElementById("triggerForSavingSubpage") || document.getElementById(divID);
    if (divTrigger && divTrigger.textContent) {
      currentPage = divTrigger.textContent.trim();
    } else {
      currentPage = mw.config.get("wgPageName");
    }
  }

  var targetPage = currentPage.replace(/ /g, "_");
  if (divID) {
    targetPage = targetPage + "/" + divID;
  } else {
    targetPage = targetPage + "/Rendered";
  }

  // Find the relevant table
  var psalmTable = null;
  if (divID) {
    var container = document.getElementById(divID);
    if (container) {
      psalmTable = container.querySelector("table.psalm-table");

	  if (!psalmTable) {
	    console.warn("No psalm-table found within " + divID, container);
	  }

    }
  } 
  if (!psalmTable) {
    psalmTable = document.querySelector("table.psalm-table");
  }

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

	var clonedTable = psalmTable.cloneNode(true); // deep clone for in-memory processing
	
	var centerDivs = clonedTable.querySelectorAll('td.subsection-bar .center');
	for (var i = 0; i < centerDivs.length; i++) {
	  var html = centerDivs[i].outerHTML;
	  var wikitext = reverseEngineerImageHTML(html);
	  if (wikitext !== html) {
	    var placeholder = document.createElement('span');
	    placeholder.textContent = wikitext;
	    centerDivs[i].replaceWith(placeholder); // modifies only clone
	  }
	}
	
	var renderedHTML = clonedTable.outerHTML
	  .replace(/psalm-table/gi, 'psalm-table pre-rendered')
	  .replace(/<tbody>/gi, '')
	  .replace(/<\/tbody>/gi, '');

  var wrapped = '<onlyinclude>{{#tag:html|\n' + renderedHTML + '\n}}</onlyinclude>';

  api.postWithEditToken({
    action: "edit",
    title: targetPage,
    text: renderedHTML,
    summary: "Saving rendered text table",
    format: "json",
    contentmodel: "wikitext"
  }).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 () {

	// ensure the mode & css are aligned upon load (SHOULD THIS MOVE TO BUILDTABLE?!?)
	  var overlay = document.getElementById('buildTextTable');
	  var modeField = document.querySelector('input[name="BuildTextTable[Mode]"]:checked');

	var overlayTableDiv = document.getElementById("overlay-1");
	if (overlayTableDiv){
		overlayTable = overlayTableDiv.querySelector('table');
		if (!overlayTable)
		{
			console.warn("No overlay table could be found.");
			return;
		}
	}
		
	// Attach checkbox listeners for the Overlays form to ADD columns
	var sectionCheckboxes = document.querySelectorAll(".pf-checkbox-input-container");
	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) {
	                var name = checkbox.name;
	                var match = name.match(/\[([^\]]+)\]/);
	                var firstKey = match ? match[1] : null;
	
	                // Attach listener
	                checkbox.addEventListener("click", function () {
	                    adjustSectionColumns(firstKey, checkbox.checked);
	                });
	
	                // Run immediately if already checked
	                if (checkbox.checked) {
	                    //adjustSectionColumns(firstKey, true);
	                }
	            })(checkboxes[j]);
	        }
	    }
	}

	// 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());
	  });



	// BEGIN TEXT OVERLAY CODE

	// === TEXT OVERLAY COLOR PICKER LOGIC ===
	// set up color map
    divColorMap = document.getElementById("color-map");
	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);
			}
	    }
	}
	selectedColor = '';
	

	// Color selection via grid
	attachColorGridHandler('#colorPickerTab');
	
	// handle any overlay colors at page load
    applyAllOverlayAnnotations();
    // applySections(); this is old?!?

	// on click - individual words
	document.querySelectorAll('[id^="overlay-"]').forEach(function (overlayContainer) {
	  var selector = "#" + overlayContainer.id;
	  console.log("Initializing handlers for div " + selector);
	  setupOverlayLineClickHandler(selector);
	  setupWordClickHandler(selector);
	});

	
	//document.querySelectorAll('[id^="overlay-"]').forEach(function (overlayContainer) {
    //	setupOverlayLineClickHandler("#" + overlayContainer.id);
	//});

	attachExportSectionsHandler("export-sections-button");




	 // enable a prominence scale 
	 bindHeatmapButtons();
	  


});