// something comes from https://github.com/excalidraw/excalidraw/blob/b1311a407a636c87ee0ca326fd20599d0ce4ba9b/src/utils.ts
import {} from '../../consts.js';
const RS_LTR_CHARS = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
    '\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
const RS_RTL_CHARS = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
// eslint-disable-next-line no-misleading-character-class
const RE_RTL_CHECK = new RegExp(`^[^${RS_LTR_CHARS}]*[${RS_RTL_CHARS}]`);
export const isRTL = (text) => RE_RTL_CHECK.test(text);
const getMeasureCtx = (function initMeasureContext() {
    let ctx = null;
    let canvas = null;
    return () => {
        if (!canvas) {
            canvas = document.createElement('canvas');
            ctx = canvas.getContext('2d');
        }
        return ctx;
    };
})();
export const isChrome = globalThis.navigator?.userAgent.indexOf('Chrome') !== -1;
export const isSafari = !isChrome && globalThis.navigator?.userAgent.indexOf('Safari') !== -1;
export function wrapFontFamily(fontFamily) {
    return `"${fontFamily}"`;
}
const cachedFontFamily = new Map();
export function getLineHeight(fontFamily, fontSize) {
    const ctx = getMeasureCtx();
    const wrappedFontFamily = wrapFontFamily(fontFamily);
    if (cachedFontFamily.has(wrappedFontFamily)) {
        const cache = cachedFontFamily.get(wrappedFontFamily);
        return (fontSize / cache.fontSize) * cache.lineHeight;
    }
    const font = `${fontSize}px ${wrapFontFamily(fontFamily)}`;
    ctx.font = `${fontSize}px ${wrapFontFamily(fontFamily)}`;
    const textMetrcs = ctx.measureText('M');
    const lineHeight = textMetrcs.fontBoundingBoxAscent + textMetrcs.fontBoundingBoxDescent;
    // cached when font property does not fallback
    if (font === ctx.font) {
        cachedFontFamily.set(wrappedFontFamily, { fontSize, lineHeight });
    }
    return lineHeight;
}
export function getFontString({ fontStyle, fontWeight, fontSize, fontFamily, }) {
    const lineHeight = getLineHeight(fontFamily, fontSize);
    return `${fontStyle} ${fontWeight} ${fontSize}px/${lineHeight}px ${wrapFontFamily(fontFamily)}, sans-serif`.trim();
}
export function normalizeText(text) {
    return (text
        // replace tabs with spaces so they render and measure correctly
        .replace(/\t/g, '        ')
        // normalize newlines
        .replace(/\r?\n|\r/g, '\n'));
}
export function splitIntoLines(text) {
    return normalizeText(text).split('\n');
}
export function getLineWidth(text, font) {
    const ctx = getMeasureCtx();
    if (font !== ctx.font)
        ctx.font = font;
    const width = ctx.measureText(text).width;
    return width;
}
export function getTextRect(text, fontFamily, fontSize) {
    const ctx = getMeasureCtx();
    const font = `${fontSize}px "${fontFamily}"`;
    if (font !== ctx.font)
        ctx.font = font;
    const metrics = ctx.measureText(text);
    const lineHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
    return {
        w: metrics.width,
        h: lineHeight,
    };
}
export function getTextWidth(text, font) {
    const lines = splitIntoLines(text);
    let width = 0;
    lines.forEach(line => {
        width = Math.max(width, getLineWidth(line, font));
    });
    return width;
}
export function parseTokens(text) {
    // Splitting words containing "-" as those are treated as separate words
    // by css wrapping algorithm eg non-profit => non-, profit
    const words = text.split('-');
    if (words.length > 1) {
        // non-proft org => ['non-', 'profit org']
        words.forEach((word, index) => {
            if (index !== words.length - 1) {
                words[index] = word += '-';
            }
        });
    }
    // Joining the words with space and splitting them again with space to get the
    // final list of tokens
    // ['non-', 'profit org'] =>,'non- proft org' => ['non-','profit','org']
    return words.join(' ').split(' ');
}
export const charWidth = (() => {
    const cachedCharWidth = {};
    const calculate = (char, font) => {
        const ascii = char.charCodeAt(0);
        if (!cachedCharWidth[font]) {
            cachedCharWidth[font] = [];
        }
        if (!cachedCharWidth[font][ascii]) {
            const width = getLineWidth(char, font);
            cachedCharWidth[font][ascii] = width;
        }
        return cachedCharWidth[font][ascii];
    };
    const getCache = (font) => {
        return cachedCharWidth[font];
    };
    return {
        calculate,
        getCache,
    };
})();
export const truncateTextByWidth = (text, font, width) => {
    let totalWidth = 0;
    let i = 0;
    for (; i < text.length; i++) {
        const char = text[i];
        totalWidth += charWidth.calculate(char, font);
        if (totalWidth > width) {
            break;
        }
    }
    return text.slice(0, i);
};
export function wrapText(text, font, maxWidth) {
    // if maxWidth is not finite or NaN which can happen in case of bugs in
    // computation, we need to make sure we don't continue as we'll end up
    // in an infinite loop
    if (!Number.isFinite(maxWidth) || maxWidth < 0) {
        return text;
    }
    const lines = [];
    const originalLines = text.split('\n');
    const spaceWidth = getLineWidth(' ', font);
    let currentLine = '';
    let currentLineWidthTillNow = 0;
    const push = (str) => {
        if (str.trim()) {
            lines.push(str);
        }
    };
    const resetParams = () => {
        currentLine = '';
        currentLineWidthTillNow = 0;
    };
    originalLines.forEach(originalLine => {
        const currentLineWidth = getTextWidth(originalLine, font);
        // Push the line if its <= maxWidth
        if (currentLineWidth <= maxWidth) {
            lines.push(originalLine);
            return; // continue
        }
        const words = parseTokens(originalLine);
        resetParams();
        let index = 0;
        while (index < words.length) {
            const currentWordWidth = getLineWidth(words[index], font);
            // This will only happen when single word takes entire width
            if (currentWordWidth === maxWidth) {
                push(words[index]);
                index++;
            }
            // Start breaking longer words exceeding max width
            else if (currentWordWidth > maxWidth) {
                // push current line since the current word exceeds the max width
                // so will be appended in next line
                push(currentLine);
                resetParams();
                while (words[index].length > 0) {
                    const currentChar = String.fromCodePoint(
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    words[index].codePointAt(0));
                    const width = charWidth.calculate(currentChar, font);
                    currentLineWidthTillNow += width;
                    words[index] = words[index].slice(currentChar.length);
                    if (currentLineWidthTillNow >= maxWidth) {
                        push(currentLine);
                        currentLine = currentChar;
                        currentLineWidthTillNow = width;
                    }
                    else {
                        currentLine += currentChar;
                    }
                }
                // push current line if appending space exceeds max width
                if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
                    push(currentLine);
                    resetParams();
                    // space needs to be appended before next word
                    // as currentLine contains chars which couldn't be appended
                    // to previous line unless the line ends with hyphen to sync
                    // with css word-wrap
                }
                else if (!currentLine.endsWith('-')) {
                    currentLine += ' ';
                    currentLineWidthTillNow += spaceWidth;
                }
                index++;
            }
            else {
                // Start appending words in a line till max width reached
                while (currentLineWidthTillNow < maxWidth && index < words.length) {
                    const word = words[index];
                    currentLineWidthTillNow = getLineWidth(currentLine + word, font);
                    if (currentLineWidthTillNow > maxWidth) {
                        push(currentLine);
                        resetParams();
                        break;
                    }
                    index++;
                    // if word ends with "-" then we don't need to add space
                    // to sync with css word-wrap
                    const shouldAppendSpace = !word.endsWith('-');
                    currentLine += word;
                    if (shouldAppendSpace) {
                        currentLine += ' ';
                    }
                    // Push the word if appending space exceeds max width
                    if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
                        if (shouldAppendSpace) {
                            lines.push(currentLine.slice(0, -1));
                        }
                        else {
                            lines.push(currentLine);
                        }
                        resetParams();
                        break;
                    }
                }
            }
        }
        if (currentLine.slice(-1) === ' ') {
            // only remove last trailing space which we have added when joining words
            currentLine = currentLine.slice(0, -1);
            push(currentLine);
        }
    });
    return lines.join('\n');
}
function transformDelta(delta) {
    const result = [];
    let tmpString = delta.insert;
    while (tmpString.length > 0) {
        const index = tmpString.indexOf('\n');
        if (index === -1) {
            result.push({
                insert: tmpString,
                attributes: delta.attributes,
            });
            break;
        }
        if (tmpString.slice(0, index).length > 0) {
            result.push({
                insert: tmpString.slice(0, index),
                attributes: delta.attributes,
            });
        }
        result.push('\n');
        tmpString = tmpString.slice(index + 1);
    }
    return result;
}
/**
 * convert a delta insert array to chunks, each chunk is a line
 */
export function deltaInsertsToChunks(delta) {
    if (delta.length === 0) {
        return [[]];
    }
    const transformedDelta = delta.flatMap(transformDelta);
    function* chunksGenerator(arr) {
        let start = 0;
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === '\n') {
                const chunk = arr.slice(start, i);
                start = i + 1;
                yield chunk;
            }
            else if (i === arr.length - 1) {
                yield arr.slice(start);
            }
        }
        if (arr.at(-1) === '\n') {
            yield [];
        }
    }
    return Array.from(chunksGenerator(transformedDelta));
}
export function normalizeTextBound(text, bound, dragging = false) {
    if (!text.text)
        return bound;
    const yText = text.text;
    const { fontStyle, fontWeight, fontSize, fontFamily } = text;
    const lineHeightPx = getLineHeight(fontFamily, fontSize);
    const font = getFontString({
        fontStyle,
        fontWeight,
        fontSize,
        fontFamily,
    });
    let lines = [];
    const deltas = yText.toDelta();
    const widestCharWidth = [...yText.toString()]
        .map(char => getTextWidth(char, font))
        .sort((a, b) => a - b)
        .pop() ?? getTextWidth('W', font);
    if (bound.w < widestCharWidth) {
        bound.w = widestCharWidth;
    }
    const width = bound.w;
    const insertDeltas = deltas.flatMap(delta => ({
        insert: wrapText(delta.insert, font, width),
        attributes: delta.attributes,
    }));
    lines = deltaInsertsToChunks(insertDeltas);
    if (!dragging && !text.hasMaxWidth) {
        lines = deltaInsertsToChunks(deltas);
        const widestLineWidth = Math.max(...yText
            .toString()
            .split('\n')
            .map(text => getTextWidth(text, font)));
        bound.w = widestLineWidth;
    }
    bound.h = lineHeightPx * lines.length;
    return bound;
}
export function getFontFacesByFontFamily(fontFamily) {
    const fonts = document.fonts;
    return ([...fonts.keys()]
        .filter(fontFace => {
        return fontFace.family === fontFamily;
    })
        // remove duplicate font faces
        .filter((item, index, arr) => arr.findIndex(fontFace => fontFace.family === item.family &&
        fontFace.weight === item.weight &&
        fontFace.style === item.style) === index));
}
export function isFontWeightSupported(fontFamily, weight) {
    const fontFaces = getFontFacesByFontFamily(fontFamily);
    const fontFace = fontFaces.find(fontFace => fontFace.weight === weight);
    return !!fontFace;
}
export function isFontStyleSupported(fontFamily, style) {
    const fontFaces = getFontFacesByFontFamily(fontFamily);
    const fontFace = fontFaces.find(fontFace => fontFace.style === style);
    return !!fontFace;
}
//# sourceMappingURL=utils.js.map