/*
 * quote-previews.js
 *
 * Adds a preview to quote links of replies.
 * Only shows the first line, not including quote links, and any special text related to quote links.
 * Puts ... when the reply overflows past the original body.
 *
 * Released under the MIT license
 * Copyright (c) 2025 Anonymous
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

$(document).ready(function() {
    var App = {
      options: {
        add: function(key, description, tab) {
          tab || (tab = 'general');

          var checked = App.options.get(key);
          var $el = $(`
            <div>
              <label>
                <input type="checkbox" style="margin-right: 0;">
                ${description}
              </label>
            </div>`);

          $el
            .find('input')
            .prop('checked', checked)
            .on('change', App.options.check(key));

          window.Options.extend_tab(tab, $el);
        },
        get: function(key) {
          if (localStorage[key])
            return JSON.parse(localStorage[key]);
          return false;
        },
        check: function(key) {
          return function(e) {
            var val = this.checked;
            localStorage[key] = JSON.stringify(val);

            if (key === 'useQuotePreviews') {
              if (val) {
                if (!App.QuotePreviewsLoaded) {
                  App.initializeQuotePreviews();
                  App.QuotePreviewsLoaded = true;
                }
              } else {
                App.QuotePreviewsLoaded = false;
                location.reload();
              }
            }
          };
        }
      },
      QuotePreviewsLoaded: false,
        initializeQuotePreviews: function() {
            function extractNumber(linkText) {
                const match = linkText.match(/\d+/);
                return match ? match[0] : null;
            }

            // Function to get first line of text content (stop at <br> tags, exclude <a> with onclick and <small> tags)
            function getFirstLine(element) {
                let result = '';

                function traverse(node) {
                    let i = 0;
                    while (i < node.childNodes.length) {
                        const child = node.childNodes[i];

                        if (child.nodeType === Node.TEXT_NODE) {
                            let textContent = child.textContent;
                            const nextSibling = child.nextSibling;
                            result += textContent;
                            i++;
                        } else if (child.nodeType === Node.ELEMENT_NODE) {
                            if (child.nodeName === 'BR') {
                                return true; // Signal to stop - found first line break
                            } else if (child.nodeName === 'A' && child.hasAttribute('onclick')) {
                                // Skip <a> with onclick
                                i++;
                                // Also skip the next <br> if it exists
                                if (i < node.childNodes.length &&
                                    node.childNodes[i].nodeType === Node.ELEMENT_NODE &&
                                    node.childNodes[i].nodeName === 'BR') {
                                    i++; // Skip the <br> after the <a>
                                }
                                // Continue processing after skipping
                                continue;
                            } else if (child.nodeName === 'SMALL') {
                                // Skip <small> tags entirely
                                i++;
                                // Also skip the next <br> if it exists after the <small> tag
                                if (i < node.childNodes.length &&
                                    node.childNodes[i].nodeType === Node.ELEMENT_NODE &&
                                    node.childNodes[i].nodeName === 'BR') {
                                    i++; // Skip the <br> after the <small>
                                }
                                continue;
                            } else if (child.nodeName === 'SPAN' && child.style.fontStyle === 'italic' && child.style.borderLeft) {
                                // Skip span elements added by this script (preview spans with border-left)
                                i++;
                                // Continue processing after skipping
                                continue;
                            } else {
                                if (traverse(child)) return true; // Stop if child found BR
                                i++;
                            }
                        } else {
                            i++;
                        }
                    }
                    return false;
                }

                traverse(element);
                return result.trim();
            }

            // Function to check if element is last child or followed by <br>
            function shouldAddPreview(link) {
                const nextSibling = link.nextSibling;
                const parent = link.parentElement;

                // Check if it's the last element in a div.body
                if (parent && parent.classList.contains('body')) {
                    const lastChild = parent.lastElementChild;
                    if (link === lastChild) return true;
                }

                // Check if followed by <br>
                if (nextSibling && nextSibling.nodeName === 'BR') return true;

                return false;
            }

            // Function to process a single link
            function processLink(link) {
                // Skip if already processed
                if (link.dataset.processed) return;

                // Check if parent is div.body
                const parentDiv = link.parentElement;
                if (!parentDiv || !parentDiv.classList.contains('body')) return;

                // Check if should add preview
                if (!shouldAddPreview(link)) return;

                // Check if parent width is 0 - if so, don't create span
                if (parentDiv.offsetWidth === 0) return;

                // Extract number from link content
                const number = extractNumber(link.textContent);
                if (!number) return;

                let previewText;

                // Find target div (try reply_NUMBER first, then thread_NUMBER, then op_NUMBER)
                let targetDiv = document.getElementById(`reply_${number}`);

                if (!targetDiv) {
                    // Can't find any div - use [OMITTED REPLY]
                    previewText = '[OMITTED REPLY]';
                } else {
                    // Check if it's an original post (thread_ or op_)
                    if (targetDiv.id.startsWith('thread_') || targetDiv.id.startsWith('op_')) {
                        previewText = '[ORIGINAL POST]';
                    } else {
                        // Find body div inside target
                        const bodyDiv = targetDiv.querySelector('.body');
                        if (!bodyDiv) {
                            // Found the reply div but no body div - use [NO TEXT]
                            previewText = '[NO TEXT]';
                        } else {
                            // Get first line of body content
                            const firstLine = getFirstLine(bodyDiv);
                            if (!firstLine) {
                                // Found body div but no content - use [NO TEXT]
                                previewText = '[NO TEXT]';
                            } else {
                                previewText = firstLine;
                            }
                        }
                    }
                }
                console.log(`Quoting >>${number}: ${previewText}`);

                // Check if there's already a [OMITTED REPLY] span right after this link and remove it
                let currentNode = link.nextSibling;
                // Skip over any <br> tags to find the span
                while (currentNode && currentNode.nodeType === Node.ELEMENT_NODE && currentNode.nodeName === 'BR') {
                    currentNode = currentNode.nextSibling;
                }
                // Check if we found a [OMITTED REPLY] span
                if (currentNode && currentNode.nodeType === Node.ELEMENT_NODE &&
                    currentNode.classList.contains('omitted-reply-preview')) {
                    console.log(currentNode);
                    // Remove the [OMITTED REPLY] span and any preceding <br>
                    let nodeToRemove = currentNode;
                    let prevNode = nodeToRemove.previousSibling;
                    if (prevNode && prevNode.nodeType === Node.ELEMENT_NODE && prevNode.nodeName === 'BR') {
                        prevNode.remove();
                    }
                    nodeToRemove.remove();
                }

                // Create span element with preview (add quote markup)
                const previewSpan = document.createElement('span');
                previewSpan.textContent = previewText;
                previewSpan.style.color = 'gray';
                previewSpan.style.fontStyle = 'italic';
                previewSpan.style.display = 'block';
                previewSpan.style.maxWidth = parentDiv.offsetWidth + 'px';
                previewSpan.style.whiteSpace = 'nowrap';
                previewSpan.style.overflow = 'hidden';
                previewSpan.style.textOverflow = 'ellipsis';
                previewSpan.style.borderLeft = '3px solid #ccc';
                previewSpan.style.paddingLeft = '8px';

                // Add special class for [OMITTED REPLY] spans
                if (previewText === '[OMITTED REPLY]') {
                    previewSpan.classList.add('omitted-reply-preview');
                }

                // Determine insertion point
                let insertionPoint = link.nextSibling;

                const br = document.createElement('br');
                // Insert br and span
                if (insertionPoint) {
                    parentDiv.insertBefore(br, insertionPoint);
                    parentDiv.insertBefore(previewSpan, insertionPoint);
                } else {
                    parentDiv.appendChild(br);
                    parentDiv.appendChild(previewSpan);
                }

                // Remove any <br> that comes right after the span we just added
                const nextAfterSpan = previewSpan.nextSibling;
                if (nextAfterSpan && nextAfterSpan.nodeType === Node.ELEMENT_NODE && nextAfterSpan.nodeName === 'BR') {
                    nextAfterSpan.remove();
                }

                // Mark as processed (but not if it's [OMITTED REPLY] so it can be retried)
                if (previewText !== '[OMITTED REPLY]') {
                    link.dataset.processed = 'true';
                }
            }

            // Function to find and process all matching links
            function processAllLinks() {
                const links = document.querySelectorAll('a[onclick^="highlightReply"]');
                links.forEach(processLink);
            }

            // Initial processing
            processAllLinks();

            // Set up mutation observer to watch for new elements
            const observer = new MutationObserver(function(mutations) {
                let shouldProcess = false;

                mutations.forEach(function(mutation) {
                    if (mutation.type === 'childList') {
                        // Check if any new nodes were added
                        mutation.addedNodes.forEach(function(node) {
                            if (node.nodeType === Node.ELEMENT_NODE) {
                                // Check if the added node or its descendants contain our target links
                                if (node.matches && node.matches('a[onclick^="highlightReply"]')) {
                                    shouldProcess = true;
                                } else if (node.querySelectorAll) {
                                    const childLinks = node.querySelectorAll('a[onclick^="highlightReply"]');
                                    if (childLinks.length > 0) {
                                        shouldProcess = true;
                                    }
                                }
                            }
                        });
                    }
                });

                if (shouldProcess) {
                    // Small delay to ensure DOM is fully updated
                    setTimeout(processAllLinks, 10);
                }
            });

            // Start observing
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    };

    App.options.add('useQuotePreviews', _('Enable quote previews'));

    if (App.options.get('useQuotePreviews') && !App.QuotePreviewsLoaded) {
      App.initializeQuotePreviews();
      App.QuotePreviewsLoaded = true;
    }
});
