/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const Utils = require("./../../../unisharp/Utils");
const Hashtable = require("./../../../unisharp/Hashtable");
const RefOutArgWrapper = require("./../../../unisharp/RefOutArgWrapper");
const StringBuilder = require("./../../../unisharp/StringBuilder");

const MiscHelper = require("./../../../util/MiscHelper");
const UnitextHelper = require("./../uni/UnitextHelper");
const HtmlTag = require("./HtmlTag");

class HtmlNode {
    
    constructor() {
        this.tagName = null;
        this.attrs = null;
        this.text = null;
        this.whitespacePreserve = false;
        this.whitespaceBefore = false;
        this.parent = null;
        this.children = new Array();
        this.sourceHtmlPosition = 0;
        this.sourceHtmlLength = 0;
        this.sourceHtmlEndPosition = 0;
        this.misc = null;
        this.plainTextBegin = 0;
        this.plainTextEnd = 0;
    }
    
    get totalChildren() {
        let res = this.children.length;
        for (const ch of this.children) {
            res += ch.totalChildren;
        }
        return res;
    }
    
    getHtml(res) {
        let tag = HtmlTag.findTag(this.tagName);
        if (tag !== null && ((tag.isBlock || tag.isTable)) && res.length > 0) 
            res.append("\r\n");
        else if (this.whitespaceBefore) 
            res.append(" ");
        if (this.tagName !== null) {
            res.append("<").append(this.tagName.toLowerCase());
            if (this.attrs !== null) {
                for (const kp of this.attrs.entries) {
                    res.append(" ").append(kp.key.toLowerCase()).append("=\"");
                    MiscHelper.correctHtmlValue(res, kp.value, true, false, false);
                    res.append("\"");
                }
            }
            if (Utils.isNullOrEmpty(this.text) && this.children.length === 0) {
                res.append("/>");
                return;
            }
            res.append(">");
        }
        if (this.children.length > 0) {
            for (const ch of this.children) {
                ch.getHtml(res);
            }
        }
        else if (!Utils.isNullOrEmpty(this.text)) 
            MiscHelper.correctHtmlValue(res, this.text, false, false, false);
        if (this.tagName !== null) 
            res.append("</").append(this.tagName.toLowerCase()).append(">");
    }
    
    getPlaintext(pars) {
        let res = new StringBuilder();
        this._getFullText(res, pars, null);
        return res.toString();
    }
    
    _getFullText(res, pars, liNumStr = null) {
        this.plainTextBegin = res.length;
        this.plainTextEnd = this.plainTextBegin - 1;
        if ((this.tagName === "SCRIPT" || this.tagName === "STYLE" || this.tagName === "META") || this.tagName === "TITLE") 
            return;
        let tag = HtmlTag.findTag(this.tagName);
        let isBlock = false;
        if (tag !== null && ((tag.isBlock || tag.isTable))) 
            isBlock = true;
        let isTab = tag !== null && tag.name === "TABLE";
        let isTd = tag !== null && ((tag.name === "TD" || tag.name === "TH"));
        let isTr = tag !== null && tag.name === "TR";
        let isLi = tag !== null && tag.name === "LI";
        let isOl = tag !== null && tag.name === "OL";
        if (tag !== null && tag.name === "BR") {
            if (this.whitespacePreserve) {
                res.append(' ');
                this.plainTextEnd = res.length - 1;
                return;
            }
            res.append((pars === null ? "\r\n" : pars.newLine));
            this.plainTextEnd = res.length - 1;
            return;
        }
        if (isTab && pars !== null) {
            if (pars.tableStart !== null) 
                res.append(pars.tableStart);
        }
        if ((isBlock && !isTd && !isTr) && res.length > 0) 
            res.append((pars === null ? "\r\n" : pars.newLine));
        if (isLi) {
            if (liNumStr !== null) 
                res.append(liNumStr);
            else 
                res.append('');
        }
        let liNum = 0;
        let liTyp = null;
        if (isOl) {
            let wrapliNum3 = new RefOutArgWrapper();
            let inoutres4 = Utils.tryParseInt(Utils.notNull(this.getAttribute("START"), ""), wrapliNum3);
            liNum = wrapliNum3.value;
            if (!inoutres4) 
                liNum = 1;
            liTyp = Utils.notNull(this.getAttribute("TYPE"), "1");
        }
        if (!Utils.isNullOrEmpty(this.text)) 
            res.append(this.text);
        else if (this.children.length === 1) 
            this.children[0]._getFullText(res, pars, HtmlNode._getLiNum(liNum, liTyp));
        else {
            let first = true;
            for (let i = 0; i < this.children.length; i++) {
                let ch = this.children[i];
                let hn = Utils.as(ch, HtmlNode);
                if (first) 
                    first = false;
                else if (!ch.whitespacePreserve && !isTr) {
                    if (ch.tagName === null || Utils.compareStrings((ch.tagName != null ? ch.tagName : ""), "SPAN", true) === 0) {
                    }
                    else if (i > 0 && ((this.children[i - 1].tagName === null || Utils.compareStrings((this.children[i - 1].tagName != null ? this.children[i - 1].tagName : ""), "SPAN", true) === 0))) {
                    }
                    else 
                        res.append(' ');
                }
                hn._getFullText(res, pars, HtmlNode._getLiNum(liNum, liTyp));
                if (liNum > 0 && hn.tagName === "LI") 
                    liNum++;
            }
        }
        if (isTd) {
            if (this.getAttribute("ROWSPAN") !== null) {
                let span = 0;
                let wrapspan5 = new RefOutArgWrapper();
                let inoutres6 = Utils.tryParseInt(this.getAttribute("ROWSPAN"), wrapspan5);
                span = wrapspan5.value;
                if (inoutres6) {
                    for (; span > 1; span--) {
                        res.append(pars.pageBreak);
                    }
                }
            }
            if (this.getAttribute("COLSPAN") !== null) {
                let span = 0;
                let wrapspan7 = new RefOutArgWrapper();
                let inoutres8 = Utils.tryParseInt(this.getAttribute("COLSPAN"), wrapspan7);
                span = wrapspan7.value;
                if (inoutres8) {
                    for (; span > 1; span--) {
                        res.append(pars.tab);
                    }
                }
            }
            res.append(pars.tableCellEnd);
        }
        else if (isTr) 
            res.append((pars.tableRowEnd != null ? pars.tableRowEnd : pars.newLine));
        else if (isTab) {
        }
        if (isTab && pars !== null) {
            if (pars.tableEnd !== null) 
                res.append(pars.tableEnd);
        }
        this.plainTextEnd = res.length - 1;
    }
    
    static _trimTabText(txt) {
        if (Utils.isNullOrEmpty(txt)) 
            return "";
        let i = 0;
        for (i = txt.length - 1; i >= 0; i--) {
            if (txt[i] === ' ' || (txt.charCodeAt(i)) === 0xD || (txt.charCodeAt(i)) === 0xA) {
            }
            else 
                break;
        }
        if (i < 0) 
            return "";
        if (i < (txt.length - 1)) 
            txt = txt.substring(0, 0 + i + 1);
        for (i = 0; i < txt.length; i++) {
            if (txt[i] === ' ' || (txt.charCodeAt(i)) === 0xD || (txt.charCodeAt(i)) === 0xA) {
            }
            else 
                break;
        }
        if (i > 0) 
            txt = txt.substring(i);
        return txt;
    }
    
    static _getLiNum(num, typ) {
        if (num <= 0) 
            return null;
        let ty = (Utils.isNullOrEmpty(typ) ? '1' : typ[0]);
        if (ty === '1') 
            return (String(num) + ". ");
        if (ty === 'A') 
            return ((String.fromCharCode((('A'.charCodeAt(0)) + ((num - 1))))) + ") ");
        if (ty === 'a') 
            return ((String.fromCharCode((('a'.charCodeAt(0)) + ((num - 1))))) + ") ");
        if (ty === 'i' && ((num - 1) < UnitextHelper.m_Romans.length)) 
            return (UnitextHelper.m_Romans[num - 1].toLowerCase() + ". ");
        if (ty === 'I' && ((num - 1) < UnitextHelper.m_Romans.length)) 
            return (UnitextHelper.m_Romans[num - 1] + ". ");
        return (String(num) + ". ");
    }
    
    correctRusTexts(corr = 0) {
        let attr = this.getAttribute("STYLE");
        if (attr !== null) {
            if (attr.includes("small-caps")) 
                corr = 1;
        }
        for (const ch of this.children) {
            ch.correctRusTexts(corr);
        }
        if (corr > 0 && !Utils.isNullOrEmpty(this.text)) 
            this.text = this.text.toUpperCase();
    }
    
    getAttribute(name) {
        if (this.attrs === null) 
            return null;
        if (this.attrs.containsKey(name)) 
            return this.attrs.get(name);
        return null;
    }
    
    getStyleValue(name) {
        if (this.attrs === null || name === null) 
            return null;
        let val = null;
        let wrapval9 = new RefOutArgWrapper();
        let inoutres10 = this.attrs.tryGetValue("STYLE", wrapval9);
        val = wrapval9.value;
        if (!inoutres10) 
            return null;
        for (const vv of Utils.splitString(val, ';', false)) {
            let vvv = vv.trim();
            let ii = vvv.indexOf(':');
            if (ii < 0) 
                continue;
            if (Utils.compareStrings(name, vvv.substring(0, 0 + ii).trim(), true) === 0) 
                return vv.substring(ii + 1).trim();
        }
        return null;
    }
    
    toString() {
        if (this.tagName === null) {
            if (this.text === null) 
                return "";
            return this.text;
        }
        let tmp = new StringBuilder();
        tmp.append("<").append(this.tagName);
        if (this.attrs !== null) {
            for (const kp of this.attrs.entries) {
                tmp.append(" ").append(kp.key).append("='").append((Utils.notNull(kp.value, ""))).append("'");
            }
        }
        if (this.text !== null) 
            tmp.append(">").append(this.text).append("</").append(this.tagName).append(">");
        else if (this.children.length > 0) 
            tmp.append(">...</").append(this.tagName).append(">");
        else 
            tmp.append("/>");
        return tmp.toString();
    }
    
    get hasText() {
        if (this.text !== null) {
            for (let i = 0; i < this.text.length; i++) {
                if (!Utils.isWhitespace(this.text[i])) 
                    return true;
            }
        }
        for (let j = 0; j < this.children.length; j++) {
            if (this.children[j].hasText) 
                return true;
        }
        return false;
    }
    
    hasTag(tag) {
        if (this.tagName !== null && Utils.compareStrings(this.tagName, tag, true) === 0) 
            return true;
        for (const ch of this.children) {
            if (ch.hasTag(tag)) 
                return true;
        }
        return false;
    }
    
    hasBlockTag() {
        if (this.tagName !== null) {
            let ti = HtmlTag.findTag(this.tagName);
            if (ti !== null && ti.isBlock) 
                return true;
        }
        for (const ch of this.children) {
            if (ch.hasBlockTag()) 
                return true;
        }
        return false;
    }
    
    remove() {
        if (this.parent === null) 
            return;
        let i = this.parent.children.indexOf(this);
        if (i < 0) 
            return;
        Utils.removeItem(this.parent.children, this);
        if (this.children.length > 0) {
            this.parent.children.splice(i, 0, ...this.children);
            this.children.splice(0, this.children.length);
        }
        this.parent = null;
    }
    
    findSubnode(_tagName, attrName, attrValue) {
        if (attrName !== null) 
            attrName = attrName.toUpperCase();
        if (_tagName !== null) 
            _tagName = _tagName.toUpperCase();
        return this._FindSubnode(_tagName, attrName, attrValue);
    }
    
    _FindSubnode(_tagName, attrName, attrValue) {
        if (_tagName !== null && this.tagName === _tagName) {
            if (attrName !== null) {
                let val = this.getAttribute(attrName);
                if (val !== null) {
                    if (attrValue === null) 
                        return this;
                    if (Utils.compareStrings(attrValue, val, true) === 0) 
                        return this;
                }
            }
            else 
                return this;
        }
        for (const ch of this.children) {
            let res = ch._FindSubnode(_tagName, attrName, attrValue);
            if (res !== null) 
                return res;
        }
        return null;
    }
    
    implantateNode(beginChar, endChar, _tagName) {
        if (beginChar >= this.plainTextEnd || (endChar < this.plainTextBegin)) 
            return null;
        let ind = -1;
        if (this.parent !== null) 
            ind = this.parent.children.indexOf(this);
        if (this.children.length === 0) {
            if (Utils.isNullOrEmpty(this.text)) 
                return null;
            if (beginChar < this.plainTextBegin) 
                beginChar = this.plainTextBegin;
            if (endChar > this.plainTextEnd) 
                endChar = this.plainTextEnd;
            if (beginChar === this.plainTextBegin && endChar === this.plainTextEnd) {
                let res = HtmlNode._new11(_tagName, beginChar, endChar);
                if (ind >= 0) {
                    this.parent.children[ind] = res;
                    res.parent = this.parent;
                    res.children.push(this);
                    this.parent = res;
                    return res;
                }
                else {
                    let txt = HtmlNode._new12(beginChar, endChar);
                    txt.text = this.text;
                    this.text = null;
                    res.children.push(txt);
                    txt.parent = res;
                    this.children.push(res);
                    res.parent = this;
                    return res;
                }
            }
            let res1 = HtmlNode._new11(_tagName, beginChar, endChar);
            if (beginChar > this.plainTextBegin) {
                let d = beginChar - this.plainTextBegin;
                let txt = HtmlNode._new14(beginChar - 1);
                txt.plainTextBegin = this.plainTextBegin;
                txt.text = this.text.substring(0, 0 + d);
                this.children.push(txt);
                txt.parent = this;
                this.text = this.text.substring(d);
            }
            if (beginChar <= endChar) {
                let txt = HtmlNode._new12(beginChar, endChar);
                txt.text = this.text;
                if (endChar < this.plainTextEnd) {
                    txt.text = this.text.substring(0, 0 + (endChar - beginChar) + 1);
                    this.text = this.text.substring((endChar - beginChar) + 1);
                }
                else 
                    this.text = "";
                this.children.push(res1);
                res1.parent = this;
                res1.children.push(txt);
                txt.parent = res1;
            }
            if (endChar < this.plainTextEnd) {
                let txt = HtmlNode._new16(endChar + 1);
                txt.plainTextEnd = this.plainTextEnd;
                txt.text = this.text;
                this.children.push(txt);
                txt.parent = this;
                this.text = "";
            }
            this.text = null;
            return res1;
        }
        if (this.children.length === 0) 
            return null;
        for (let i = 0; i < this.children.length; i++) {
            if (this.children[i].plainTextBegin <= beginChar && beginChar <= this.children[i].plainTextEnd) {
                for (let j = i; j < this.children.length; j++) {
                    let tag = this.children[j].tagName;
                    if ((tag === "TD" || tag === "TR" || tag === "TH") || ((this.children[j].plainTextBegin <= endChar && endChar <= this.children[j].plainTextEnd))) {
                        if (i === j) {
                            let res1 = this.children[i].implantateNode(beginChar, endChar, _tagName);
                            if (res1 !== null) 
                                return res1;
                        }
                        let head = null;
                        let tail = null;
                        let ch = this.children[i];
                        if (beginChar > ch.plainTextBegin && ch.text !== null) {
                            head = HtmlNode._new11(ch.tagName, ch.plainTextBegin, beginChar - 1);
                            head.parent = this.parent;
                            head.text = ch.text.substring(0, 0 + beginChar - ch.plainTextBegin);
                            ch.text = ch.text.substring(beginChar - ch.plainTextBegin);
                            ch.plainTextBegin = beginChar;
                        }
                        if ((endChar < this.children[j].plainTextEnd) && this.children[j].text !== null) {
                            let ch1 = this.children[j];
                            tail = HtmlNode._new11(ch1.tagName, endChar + 1, ch1.plainTextEnd);
                            tail.parent = this.parent;
                            tail.text = ch1.text.substring(endChar - ch1.plainTextBegin);
                            ch1.text = ch1.text.substring(0, 0 + endChar - ch1.plainTextBegin);
                            ch1.plainTextEnd = endChar;
                        }
                        beginChar = ch.plainTextBegin;
                        endChar = this.children[j].plainTextEnd;
                        let res = HtmlNode._new11(_tagName, beginChar, endChar);
                        if (((tag === "TD" || tag === "TR" || tag === "TH")) && i === j) {
                            for (const chh of ch.children) {
                                res.children.push(chh);
                                ch.parent = res;
                            }
                            this.children[i].children.splice(0, this.children[i].children.length);
                            this.children[i].children.push(res);
                            res.parent = this.children[i];
                            return res;
                        }
                        for (let k = i; k <= j; k++) {
                            res.children.push(this.children[k]);
                            this.children[k].parent = res;
                        }
                        this.children.splice(i, (j + 1) - i);
                        if (tail !== null) 
                            this.children.splice(i, 0, tail);
                        this.children.splice(i, 0, res);
                        res.parent = this;
                        if (head !== null) 
                            this.children.splice(i, 0, head);
                        return res;
                    }
                }
            }
        }
        return null;
    }
    
    replaceTextRange(bCharPos, eCharPos, _text, _tagName, _attrs = null) {
        if (bCharPos > eCharPos) 
            return null;
        let startNode = this.findTextNodeAt(bCharPos);
        let endNode = this.findTextNodeAt(eCharPos);
        if (startNode === null || endNode === null) 
            return null;
        let _parent = startNode.parent;
        if (_parent !== endNode.parent) 
            return null;
        // пока не лезем в разные уровни
        let startIndex = _parent.children.indexOf(startNode);
        let endIndex = _parent.children.indexOf(endNode);
        // режем начало
        let startOffset = bCharPos - startNode.plainTextBegin;
        let leftText = startNode.text.substring(0, 0 + startOffset);
        // режем конец
        let endOffset = eCharPos - endNode.plainTextBegin;
        let rightText = endNode.text.substring(endOffset + 1);
        // удаляем все узлы между start и end
        for (let i = endIndex; i >= startIndex; i--) {
            _parent.children.splice(i, 1);
        }
        let insertIndex = startIndex;
        // возвращаем left
        if (!Utils.isNullOrEmpty(leftText)) {
            _parent.children.splice(insertIndex, 0, HtmlNode._new20(leftText, _parent, startNode.plainTextBegin, bCharPos - 1));
            insertIndex++;
        }
        // вставляем новый тег
        let inserted = HtmlNode._new21(_tagName, _parent, (_attrs !== null ? new Hashtable(_attrs) : null));
        inserted.children.push(HtmlNode._new22(_text, inserted));
        _parent.children.splice(insertIndex, 0, inserted);
        insertIndex++;
        // возвращаем right
        if (!Utils.isNullOrEmpty(rightText)) 
            _parent.children.splice(insertIndex, 0, HtmlNode._new20(rightText, _parent, eCharPos + 1, endNode.plainTextEnd));
        return inserted;
    }
    
    findTextNodeAt(charPos) {
        return this.findTextNodeAtInternal(this, charPos);
    }
    
    findTextNodeAtInternal(node, charPos) {
        if (node.children.length === 0) 
            return node;
        for (let i = 0; i < node.children.length; i++) {
            if (node.children[i].plainTextBegin <= charPos && charPos <= node.children[i].plainTextEnd) {
                for (let j = i; j < node.children.length; j++) {
                    let tag = node.children[j].tagName;
                    if (((tag === "TD" || tag === "TR" || tag === "TH")) || (((node.children[j].plainTextBegin <= charPos && charPos <= node.children[j].plainTextEnd)))) {
                        if (i === j) {
                            let res1 = this.findTextNodeAtInternal(node.children[j], charPos);
                            if (res1 !== null) 
                                return res1;
                        }
                    }
                }
            }
        }
        return null;
    }
    
    static _new1(_arg1) {
        let res = new HtmlNode();
        res.tagName = _arg1;
        return res;
    }
    
    static _new11(_arg1, _arg2, _arg3) {
        let res = new HtmlNode();
        res.tagName = _arg1;
        res.plainTextBegin = _arg2;
        res.plainTextEnd = _arg3;
        return res;
    }
    
    static _new12(_arg1, _arg2) {
        let res = new HtmlNode();
        res.plainTextBegin = _arg1;
        res.plainTextEnd = _arg2;
        return res;
    }
    
    static _new14(_arg1) {
        let res = new HtmlNode();
        res.plainTextEnd = _arg1;
        return res;
    }
    
    static _new16(_arg1) {
        let res = new HtmlNode();
        res.plainTextBegin = _arg1;
        return res;
    }
    
    static _new20(_arg1, _arg2, _arg3, _arg4) {
        let res = new HtmlNode();
        res.text = _arg1;
        res.parent = _arg2;
        res.plainTextBegin = _arg3;
        res.plainTextEnd = _arg4;
        return res;
    }
    
    static _new21(_arg1, _arg2, _arg3) {
        let res = new HtmlNode();
        res.tagName = _arg1;
        res.parent = _arg2;
        res.attrs = _arg3;
        return res;
    }
    
    static _new22(_arg1, _arg2) {
        let res = new HtmlNode();
        res.text = _arg1;
        res.parent = _arg2;
        return res;
    }
}


module.exports = HtmlNode