import { htmlEscape } from "./String";

// DOM element references
let getTextFromHtmlEl: HTMLDivElement | null = null;
let nodeToHTMLWrapEl: HTMLDivElement | null = null;
let addHtmlStyleEl: HTMLDivElement | null = null;
let replaceHtmlTextEl: HTMLDivElement | null = null;
let removeHtmlNodeByClassEl: HTMLDivElement | null = null;
let nodeRichTextToTextWithWrapEl: HTMLDivElement | null = null;
let textToNodeRichTextWithWrapEl: HTMLDivElement | null = null;
let removeRichTextStyesEl: HTMLDivElement | null = null;

// Extract plain text from HTML string
export const getTextFromHtml = (html: string): string => {
    if (!getTextFromHtmlEl) {
        getTextFromHtmlEl = document.createElement('div');
    }
    getTextFromHtmlEl.innerHTML = html;
    return getTextFromHtmlEl.textContent || '';
}

// Convert DOM node to HTML string
export const nodeToHTML = (node: Node): string => {
    if (!nodeToHTMLWrapEl) {
        nodeToHTMLWrapEl = document.createElement('div');
    }
    nodeToHTMLWrapEl.innerHTML = '';
    nodeToHTMLWrapEl.appendChild(node);
    return nodeToHTMLWrapEl.innerHTML;
}

// Add inline styles to specified tags in HTML
export const addHtmlStyle = (html: string, tag: string | string[], style: string): string => {
    if (!addHtmlStyleEl) {
        addHtmlStyleEl = document.createElement('div');
    }
    const tags = Array.isArray(tag) ? tag : [tag];
    addHtmlStyleEl.innerHTML = html;

    const walk = (root: Node): void => {
        let childNodes = root.childNodes;
        childNodes.forEach(node => {
            if (node.nodeType === 1) {
                if (tags.includes((node as Element).tagName.toLowerCase())) {
                    (node as HTMLElement).style.cssText = style;
                } else {
                    walk(node);
                }
            }
        });
    }
    walk(addHtmlStyleEl);
    return addHtmlStyleEl.innerHTML;
}

// Remove HTML entities from string
export const removeHTMLEntities = (str: string): string => {
    [['&nbsp;', '&#160;']].forEach(item => {
        str = str.replaceAll(item[0], item[1]);
    });
    return str;
}

// Remove inline styles from HTML string
export const removeHtmlStyle = (html: string): string => {
    return html.replaceAll(/(<[^\s]+)\s+style=["'][^'"]+["']\s*(>)/g, '$1$2');
}

// Search and replace text in HTML string
export const replaceHtmlText = (html: string, searchText: string, replaceText: string): string => {
    if (!replaceHtmlTextEl) {
        replaceHtmlTextEl = document.createElement('div');
    }
    replaceHtmlTextEl.innerHTML = html;

    const walk = (root: Node): void => {
        let childNodes = root.childNodes;
        childNodes.forEach(node => {
            if (node.nodeType === 1) {
                walk(node);
            } else if (node.nodeType === 3) {
                root.replaceChild(
                    document.createTextNode(
                        node.nodeValue?.replaceAll(searchText, replaceText) || ''
                    ),
                    node
                );
            }
        });
    }
    walk(replaceHtmlTextEl);
    return replaceHtmlTextEl.innerHTML;
}

// Remove node by class selector from HTML string
export const removeHtmlNodeByClass = (html: string, selector: string): string => {
    if (!removeHtmlNodeByClassEl) {
        removeHtmlNodeByClassEl = document.createElement('div');
    }
    removeHtmlNodeByClassEl.innerHTML = html;
    const node = removeHtmlNodeByClassEl.querySelector(selector);
    if (node) {
        node.parentNode?.removeChild(node);
    }
    return removeHtmlNodeByClassEl.innerHTML;
}

// Remove formula tags from DOM node
export const removeFormulaTags = (node: Node): void => {
    const walk = (root: Node): void => {
        const childNodes = root.childNodes;
        childNodes.forEach(node => {
            if (node.nodeType === 1) {
                if ((node as Element).classList.contains('ql-formula')) {
                    node.parentNode?.removeChild(node);
                } else {
                    walk(node);
                }
            }
        });
    }
    walk(node);
}

// Convert rich text node content to text with newlines
export const nodeRichTextToTextWithWrap = (html: string): string => {
    if (!nodeRichTextToTextWithWrapEl) {
        nodeRichTextToTextWithWrapEl = document.createElement('div');
    }
    nodeRichTextToTextWithWrapEl.innerHTML = html;
    const childNodes = nodeRichTextToTextWithWrapEl.childNodes;
    let res = '';
    for (let i = 0; i < childNodes.length; i++) {
        const node = childNodes[i];
        if (node.nodeType === 1) {
            removeFormulaTags(node);
            if ((node as Element).tagName.toLowerCase() === 'p') {
                res += node.textContent + '\n';
            } else {
                res += node.textContent;
            }
        } else if (node.nodeType === 3) {
            res += node.nodeValue;
        }
    }
    return res.replace(/\n$/, '');
}

// Convert text with <br> to rich text node content
export const textToNodeRichTextWithWrap = (html: string): string => {
    if (!textToNodeRichTextWithWrapEl) {
        textToNodeRichTextWithWrapEl = document.createElement('div');
    }
    textToNodeRichTextWithWrapEl.innerHTML = html;
    const childNodes = textToNodeRichTextWithWrapEl.childNodes;
    let list: string[] = [];
    let str = '';
    for (let i = 0; i < childNodes.length; i++) {
        const node = childNodes[i];
        if (node.nodeType === 1) {
            if ((node as Element).tagName.toLowerCase() === 'br') {
                list.push(str);
                str = '';
            } else {
                str += node.textContent;
            }
        } else if (node.nodeType === 3) {
            str += node.nodeValue;
        }
    }
    if (str) {
        list.push(str);
    }
    return list
        .map(item => {
            return `<p><span>${htmlEscape(item)}</span></p>`;
        })
        .join('');
}

// Remove rich text styles but preserve math formulas
export const removeRichTextStyes = (html: string): string => {
    if (!removeRichTextStyesEl) {
        removeRichTextStyesEl = document.createElement('div');
    }
    removeRichTextStyesEl.innerHTML = html;

    const formulaList = removeRichTextStyesEl.querySelectorAll('.ql-formula');
    Array.from(formulaList).forEach(el => {
        const placeholder = document.createTextNode('$smmformula$');
        el.parentNode?.replaceChild(placeholder, el);
    });

    const childNodes = removeRichTextStyesEl.childNodes;
    let list: string[] = [];
    for (let i = 0; i < childNodes.length; i++) {
        const node = childNodes[i];
        if (node.nodeType === 1 || node.nodeType === 3) {
            list.push(node.textContent || node.nodeValue || '');
        }
    }

    html = list
        .map(item => {
            return `<p><span>${htmlEscape(item)}</span></p>`;
        })
        .join('');

    if (formulaList.length > 0) {
        html = html.replace(/\$smmformula\$/g, '<span class="smmformula"></span>');
        removeRichTextStyesEl.innerHTML = html;
        const els = removeRichTextStyesEl.querySelectorAll('.smmformula');
        Array.from(els).forEach((el, index) => {
            el.parentNode?.replaceChild(formulaList[index], el);
        });
        html = removeRichTextStyesEl.innerHTML;
    }
    return html;
}

// Focus input element
export const focusInput = (el: HTMLElement): void => {
    let selection = window.getSelection();
    let range = document.createRange();
    range.selectNodeContents(el);
    range.collapse();
    selection?.removeAllRanges();
    selection?.addRange(range);
}

// Focus and select all in input element  
export const selectAllInput = (el: HTMLElement): void => {
    let selection = window.getSelection();
    let range = document.createRange();
    range.selectNodeContents(el);
    selection?.removeAllRanges();
    selection?.addRange(range);
}

// Add xmlns attribute to element
export const addXmlns = (el: Element): void => {
    el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
}