// Scripts called by ComposeFormattingBar.

document.addEventListener('selectionchange', updateTextAttributes);

document.addEventListener('selectionchange', () => delayedFontSize = 0);

// Update a delayed font element to the correct size.
document.body.addEventListener('input', setFontSizeFromDelayedFontSize);

let delayedFontSize = 0;

function setFontSizeFromDelayedFontSize() {
    if (delayedFontSize > 0) {
        updateInsertedFontElement(delayedFontSize);
        delayedFontSize = 0;
    }
}

// The way this is done isn't ideal and should be redone to be more robust, but it does what
// it says on the tin and still allows undo/redo.
function updateInsertedFontElement(size) {
    let fontElements = document.querySelectorAll('font[size="7"]');
    
    for (element of fontElements) {
        element.removeAttribute('size');
        element.style.fontSize = `${size}px`;
    }
    
    window.updateContent?.();
}

function setFontSize(size) {
    document.execCommand('fontSize', false, 7);
    
    if (document.getSelection().isCollapsed) {
        delayedFontSize = size;
    } else {
        updateInsertedFontElement(size);
    }
}


// The text-decoration property propagates but doesn't inherit, so won't show up on the
// child element. This also means it can't be overridden by a child element.
function nodeHasUnderline(element) {
    if (element.nodeType != Node.ELEMENT_NODE)
        element = element.parentElement;
    
    while (element) {
        let style = window.getComputedStyle(element, null);
        
        if (style.textDecoration.match(/\bunderline\b/))
            return true;
        
        element = element.parentElement;
    }
    
    return false;
}

function nodeHasStrikethrough(element) {
    if (element.nodeType != Node.ELEMENT_NODE)
        element = element.parentElement;

    while (element) {
        let style = window.getComputedStyle(element, null);
        
        if (style.textDecoration.match(/\bline-through\b/))
            return true;
        
        element = element.parentElement;
    }
    
    return false;
}

Object.defineProperty(Node.prototype, "closestElement", {
    get() {
        return this.nodeType == Node.ELEMENT_NODE ? this : this.parentElement
    }
});

// Assumes a valid font-family attribute returned from getComputedStyle(),
// which wraps multi-word families in double quotes, never single, and always
// separates family names with ", " (comma+space).
function parseCSSFontFamily(fontFamilyString) {
    const len = fontFamilyString.length;
    
    if (len === 0)
        return [];
    
    let currentFamily = '';
    
    let inQuote = false;
    let lastWasBackslash = false;
    
    let result = [];
    
    for (let i = 0; i < len; ++i) {
        const c = fontFamilyString[i];
        
        if (c === ',') {
            if (inQuote) {
                currentFamily += c;
            } else {
                result.push(currentFamily);
                currentFamily = '';
                ++i; // Skip the next character, which will be a space.
            }
        } else if (c === '"') {
            if (inQuote) {
                if (lastWasBackslash)
                    currentFamily += c;
                else
                    inQuote = false;
            } else {
                inQuote = true;
            }
        } else if (c === '\\') {
            if (lastWasBackslash)
                currentFamily += c;
            else
                lastWasBacklash = true;
        } else {
            currentFamily += c;
        }
    }
    
    result.push(currentFamily);
    
    return result;
}

// Reports the current typing attributes, e.g., for the formatting bar.
function updateTextAttributes() {
    let selection = window.getSelection();
    // TODO: Update for multiple ranges?
    
    if (selection.rangeCount == 0)
        return null;
        
    let range = selection.getRangeAt(0);
    
    let commonAncestor = range.commonAncestorContainer;
    
    let walker = document.createTreeWalker(commonAncestor, NodeFilter.SHOW_TEXT);
    
    let startNode = range.startContainer;
    let endNode = range.endContainer;
    
    walker.currentNode = startNode;
    
    let style = window.getComputedStyle(startNode.closestElement, null);
    
    let align = style.textAlign;
    
    let alignFromDirection = style.direction === 'ltr' ? 'left' : 'right';
    
    if (!align || align === 'start')
        align = alignFromDirection;
            
    let result = {
        fontFamily: undefined,
        fontSize: undefined,
        bold: true,
        italic: true,
        underline: true,
        strikethrough: true,
        align: align
    };

    let node = startNode;
    
    const boldThreshold = 600; // From WebKit
    
    while (node) {
        if (node === startNode && startNode !== endNode && range.startOffset === node.textContent.length) {
            node = walker.nextNode();
            continue;
        }
        
        let style = window.getComputedStyle(node.closestElement, null);
        let family = style.fontFamily;
        
        if (result.fontFamily === undefined)
            result.fontFamily = parseCSSFontFamily(family);
        else if (parseCSSFontFamily(family).toString() !== result.fontFamily?.toString())
            result.fontFamily = null;
        
        let size = parseInt(style.fontSize);
        
        if (result.fontSize === undefined)
            result.fontSize = size;
        else if (size !== result.fontSize)
            result.fontSize = null;
                
        if (result.bold && parseInt(style.fontWeight) < boldThreshold)
            result.bold = false;
        
        // TODO: Use a threshold like WebKit
        if (result.italic && !style.fontStyle.match(/^(?:\bitalic\b|\boblique\b)/))
            result.italic = false;
            
        if (result.underline && !nodeHasUnderline(node))
            result.underline = false;

        if (result.strikethrough && !nodeHasStrikethrough(node))
            result.strikethrough = false;
        
        let align = style.textAlign;
        
        // This currently doesn't correctly handle elements whose directions don't match the body's
        if (!align || align === 'start')
            align = alignFromDirection;
            
        if (result.align !== align)
            result.align = null;
        
        if (node === endNode)
            break;
            
        node = walker.nextNode();
    }
    
    window.webkit.messageHandlers.textAttributes.postMessage(result);
}
