"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get _dataToHTML () {
        return _dataToHTML;
    },
    get htmlToGridSettings () {
        return htmlToGridSettings;
    },
    get instanceToHTML () {
        return instanceToHTML;
    },
    get replaceTdCellsWithTextContent () {
        return replaceTdCellsWithTextContent;
    }
});
const _mixed = require("./../helpers/mixed");
const ESCAPED_HTML_CHARS = {
    '&nbsp;': '\x20',
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>'
};
const regEscapedChars = new RegExp(Object.keys(ESCAPED_HTML_CHARS).map((key)=>`(${key})`).join('|'), 'gi');
/**
 * Verifies if node is an HTMLTable element.
 *
 * @param {Node} element Node to verify if it's an HTMLTable.
 * @returns {boolean}
 */ function isHTMLTable(element) {
    return (element && element.nodeName || '') === 'TABLE';
}
function instanceToHTML(instance) {
    const hasColumnHeaders = instance.hasColHeaders();
    const hasRowHeaders = instance.hasRowHeaders();
    const coords = [
        hasColumnHeaders ? -1 : 0,
        hasRowHeaders ? -1 : 0,
        instance.countRows() - 1,
        instance.countCols() - 1
    ];
    const data = instance.getData(...coords);
    const countRows = data.length;
    const countCols = countRows > 0 ? data[0].length : 0;
    const TABLE = [
        '<table>',
        '</table>'
    ];
    const THEAD = hasColumnHeaders ? [
        '<thead>',
        '</thead>'
    ] : [];
    const TBODY = [
        '<tbody>',
        '</tbody>'
    ];
    const rowModifier = hasRowHeaders ? 1 : 0;
    const columnModifier = hasColumnHeaders ? 1 : 0;
    for(let row = 0; row < countRows; row += 1){
        const isColumnHeadersRow = hasColumnHeaders && row === 0;
        const CELLS = [];
        for(let column = 0; column < countCols; column += 1){
            const isRowHeadersColumn = !isColumnHeadersRow && hasRowHeaders && column === 0;
            let cell = '';
            if (isColumnHeadersRow) {
                cell = `<th>${instance.getColHeader(column - rowModifier)}</th>`;
            } else if (isRowHeadersColumn) {
                cell = `<th>${instance.getRowHeader(row - columnModifier)}</th>`;
            } else {
                const cellData = data[row][column];
                const { hidden, rowspan, colspan } = instance.getCellMeta(row - columnModifier, column - rowModifier);
                if (!hidden) {
                    const attrs = [];
                    if (rowspan) {
                        attrs.push(`rowspan="${rowspan}"`);
                    }
                    if (colspan) {
                        attrs.push(`colspan="${colspan}"`);
                    }
                    if ((0, _mixed.isEmpty)(cellData)) {
                        cell = `<td ${attrs.join(' ')}></td>`;
                    } else {
                        const value = cellData.toString().replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20/gi, '&nbsp;').replace(/\t/gi, '&#9;');
                        cell = `<td ${attrs.join(' ')}>${value}</td>`;
                    }
                }
            }
            CELLS.push(cell);
        }
        const TR = [
            '<tr>',
            ...CELLS,
            '</tr>'
        ].join('');
        if (isColumnHeadersRow) {
            THEAD.splice(1, 0, TR);
        } else {
            TBODY.splice(-1, 0, TR);
        }
    }
    TABLE.splice(1, 0, THEAD.join(''), TBODY.join(''));
    return TABLE.join('');
}
function _dataToHTML(input) {
    const inputLen = input.length;
    const result = [
        '<table>'
    ];
    for(let row = 0; row < inputLen; row += 1){
        const rowData = input[row];
        const columnsLen = rowData.length;
        const columnsResult = [];
        if (row === 0) {
            result.push('<tbody>');
        }
        for(let column = 0; column < columnsLen; column += 1){
            const cellData = rowData[column];
            const parsedCellData = (0, _mixed.isEmpty)(cellData) ? '' : cellData.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20{2,}/gi, (substring)=>{
                // The way how Excel serializes data with at least two spaces.
                return `<span style="mso-spacerun: yes">${'&nbsp;'.repeat(substring.length - 1)} </span>`;
            }).replace(/\t/gi, '&#9;');
            columnsResult.push(`<td>${parsedCellData}</td>`);
        }
        result.push('<tr>', ...columnsResult, '</tr>');
        if (row + 1 === inputLen) {
            result.push('</tbody>');
        }
    }
    result.push('</table>');
    return result.join('');
}
const TD_OPEN = /<td\b[^>]*>/i;
const TD_CLOSE = /<\/\s*td\s*>/i;
const paragraphRegexp = /<p.*?>/g;
/**
 * Finds the closing `</td>` that matches the first `<td...>` in `html` at `openEndIndex`.
 * Handles nested `<td>...</td>` (e.g. Excel cells with shapes that contain inner tables).
 *
 * @param {string} html Full HTML string.
 * @param {number} openEndIndex Index right after the opening `<td...>` (after the `>`).
 * @returns {{ start: number, length: number }|null} Start index and length of the matching `</td>`, or null.
 */ function findMatchingTdClose(html, openEndIndex) {
    let depth = 1;
    let searchStart = openEndIndex;
    while(depth > 0){
        const tail = html.substring(searchStart);
        const openMatch = tail.match(TD_OPEN);
        const closeMatch = tail.match(TD_CLOSE);
        if (!closeMatch) {
            return null;
        }
        const closeIndex = searchStart + closeMatch.index;
        const closeTagLength = closeMatch[0].length;
        const openIndex = openMatch ? searchStart + openMatch.index : html.length;
        if (openIndex < closeIndex) {
            depth += 1;
            searchStart = openIndex + openMatch[0].length;
        } else {
            depth -= 1;
            if (depth === 0) {
                return {
                    start: closeIndex,
                    length: closeTagLength
                };
            }
            searchStart = closeIndex + closeTagLength;
        }
    }
    return null;
}
function replaceTdCellsWithTextContent(html) {
    const result = [];
    let pos = 0;
    while(pos < html.length){
        const openMatch = html.substring(pos).match(TD_OPEN);
        if (!openMatch) {
            result.push(html.substring(pos));
            break;
        }
        const openStart = pos + openMatch.index;
        const openTag = openMatch[0];
        const openEnd = openStart + openTag.length;
        result.push(html.substring(pos, openStart));
        const closeInfo = findMatchingTdClose(html, openEnd);
        if (!closeInfo) {
            // Malformed HTML (no matching </td>): leave rest as-is to avoid truncation.
            result.push(html.substring(openStart));
            break;
        }
        const cellFragment = html.substring(openStart, closeInfo.start + closeInfo.length);
        const contentEnd = closeInfo.start - openStart;
        const rawContent = cellFragment.substring(openTag.length, contentEnd).trim().replaceAll(/\n\s+/g, ' ') // HTML tags may be split using multiple new lines and whitespaces
        .replaceAll(paragraphRegexp, '\n') // Only paragraphs should split text using new line characters
        .replace(/^\n+/, '') // First paragraph shouldn't start with new line characters
        .replaceAll(/<\/(.*)>\s+$/mg, '</$1>') // HTML tags may end with whitespace.
        .replace(/(<(?!br)([^>]+)>)/gi, '') // Removing HTML tags
        .replaceAll(/^&nbsp;$/mg, ''); // Removing single &nbsp; characters separating new lines
        result.push(`${openTag}${rawContent}</td>`);
        pos = closeInfo.start + closeInfo.length;
    }
    return result.join('');
}
function htmlToGridSettings(element, rootDocument = document) {
    const settingsObj = {};
    const fragment = rootDocument.createDocumentFragment();
    const tempElem = rootDocument.createElement('div');
    fragment.appendChild(tempElem);
    let checkElement = element;
    if (typeof checkElement === 'string') {
        const escapedAdjacentHTML = replaceTdCellsWithTextContent(checkElement);
        tempElem.insertAdjacentHTML('afterbegin', `${escapedAdjacentHTML}`);
        checkElement = tempElem.querySelector('table');
    }
    if (!checkElement || !isHTMLTable(checkElement)) {
        return;
    }
    const generator = tempElem.querySelector('meta[name$="enerator"]');
    const hasRowHeaders = checkElement.querySelector('tbody th') !== null;
    const trElement = checkElement.querySelector('tr');
    const countCols = !trElement ? 0 : Array.from(trElement.cells).reduce((cols, cell)=>cols + cell.colSpan, 0) - (hasRowHeaders ? 1 : 0);
    const fixedRowsBottom = checkElement.tFoot && Array.from(checkElement.tFoot.rows) || [];
    const fixedRowsTop = [];
    let hasColHeaders = false;
    let thRowsLen = 0;
    let countRows = 0;
    if (checkElement.tHead) {
        const thRows = Array.from(checkElement.tHead.rows).filter((tr)=>{
            const isDataRow = tr.querySelector('td') !== null;
            if (isDataRow) {
                fixedRowsTop.push(tr);
            }
            return !isDataRow;
        });
        thRowsLen = thRows.length;
        hasColHeaders = thRowsLen > 0;
        if (thRowsLen > 1) {
            settingsObj.nestedHeaders = Array.from(thRows).reduce((rows, row)=>{
                const headersRow = Array.from(row.cells).reduce((headers, header, currentIndex)=>{
                    if (hasRowHeaders && currentIndex === 0) {
                        return headers;
                    }
                    const { colSpan: colspan, innerHTML } = header;
                    const nextHeader = colspan > 1 ? {
                        label: innerHTML,
                        colspan
                    } : innerHTML;
                    headers.push(nextHeader);
                    return headers;
                }, []);
                rows.push(headersRow);
                return rows;
            }, []);
        } else if (hasColHeaders) {
            settingsObj.colHeaders = Array.from(thRows[0].children).reduce((headers, header, index)=>{
                if (hasRowHeaders && index === 0) {
                    return headers;
                }
                headers.push(header.innerHTML);
                return headers;
            }, []);
        }
    }
    if (fixedRowsTop.length) {
        settingsObj.fixedRowsTop = fixedRowsTop.length;
    }
    if (fixedRowsBottom.length) {
        settingsObj.fixedRowsBottom = fixedRowsBottom.length;
    }
    const dataRows = [
        ...fixedRowsTop,
        ...Array.from(checkElement.tBodies).reduce((sections, section)=>{
            Array.from(section.rows).forEach((row)=>{
                sections.push(row);
            });
            return sections;
        }, []),
        ...fixedRowsBottom
    ];
    countRows = dataRows.length;
    const dataArr = new Array(countRows);
    for(let r = 0; r < countRows; r++){
        dataArr[r] = new Array(countCols);
    }
    const mergeCells = [];
    const rowHeaders = [];
    for(let row = 0; row < countRows; row++){
        const tr = dataRows[row];
        const cells = Array.from(tr.cells);
        const cellsLen = cells.length;
        for(let cellId = 0; cellId < cellsLen; cellId++){
            const cell = cells[cellId];
            const { nodeName, innerHTML, rowSpan: rowspan, colSpan: colspan } = cell;
            const col = dataArr[row].findIndex((value)=>value === undefined);
            if (nodeName === 'TD') {
                if (rowspan > 1 || colspan > 1) {
                    for(let rstart = row; rstart < row + rowspan; rstart++){
                        if (rstart < countRows) {
                            for(let cstart = col; cstart < col + colspan; cstart++){
                                dataArr[rstart][cstart] = null;
                            }
                        }
                    }
                    const styleAttr = cell.getAttribute('style');
                    const ignoreMerge = styleAttr && styleAttr.includes('mso-ignore:colspan');
                    if (!ignoreMerge) {
                        mergeCells.push({
                            col,
                            row,
                            rowspan,
                            colspan
                        });
                    }
                }
                let cellValue = '';
                if (generator && /excel/gi.test(generator.content)) {
                    cellValue = innerHTML.replace(/[\r\n][\x20]{0,2}/g, '\x20').replace(/<br(\s*|\/)>[\r\n]?[\x20]{0,3}/gim, '\r\n');
                } else {
                    cellValue = innerHTML.replace(/<br(\s*|\/)>[\r\n]?/gim, '\r\n');
                }
                dataArr[row][col] = cellValue.replace(regEscapedChars, (match)=>ESCAPED_HTML_CHARS[match]);
            } else {
                rowHeaders.push(innerHTML);
            }
        }
    }
    if (mergeCells.length) {
        settingsObj.mergeCells = mergeCells;
    }
    if (rowHeaders.length) {
        settingsObj.rowHeaders = rowHeaders;
    }
    if (dataArr.length) {
        settingsObj.data = dataArr;
    }
    return settingsObj;
}
