import Yoga from 'yoga-layout';
import { measureStyledChars, toStyledCharacters, widestLineFromStyledChars, } from './measure-text.js';
import { wrapOrTruncateStyledChars } from './text-wrap.js';
import squashTextNodes from './squash-text-nodes.js';
let idCounter = 0;
export const createNode = (nodeName) => {
    const node = {
        nodeName,
        style: {},
        attributes: {},
        childNodes: [],
        parentNode: undefined,
        yogaNode: nodeName === 'ink-virtual-text' ? undefined : Yoga.Node.create(),
        internal_accessibility: {},
        internalSticky: false,
        internalStickyAlternate: false,
        internalOpaque: false,
        internalScrollbar: true,
        internalMaxScrollTop: 0,
        internalIsScrollbackDirty: false,
        internalId: idCounter++,
    };
    if (nodeName === 'ink-text') {
        node.yogaNode?.setMeasureFunc(measureTextNode.bind(null, node));
    }
    return node;
};
export const appendChildNode = (node, childNode) => {
    if (childNode.parentNode) {
        removeChildNode(childNode.parentNode, childNode);
    }
    childNode.parentNode = node;
    node.childNodes.push(childNode);
    if (childNode.yogaNode) {
        node.yogaNode?.insertChild(childNode.yogaNode, node.yogaNode.getChildCount());
    }
    if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
        markNodeAsDirty(node);
    }
};
export const insertBeforeNode = (node, newChildNode, beforeChildNode) => {
    if (newChildNode.parentNode) {
        removeChildNode(newChildNode.parentNode, newChildNode);
    }
    newChildNode.parentNode = node;
    const index = node.childNodes.indexOf(beforeChildNode);
    if (index >= 0) {
        node.childNodes.splice(index, 0, newChildNode);
        if (newChildNode.yogaNode) {
            node.yogaNode?.insertChild(newChildNode.yogaNode, index);
        }
        return;
    }
    node.childNodes.push(newChildNode);
    if (newChildNode.yogaNode) {
        node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount());
    }
    if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
        markNodeAsDirty(node);
    }
};
export const removeChildNode = (node, removeNode) => {
    if (removeNode.yogaNode) {
        const parentYogaNode = removeNode.yogaNode.getParent();
        if (parentYogaNode) {
            parentYogaNode.removeChild(removeNode.yogaNode);
        }
    }
    removeNode.parentNode = undefined;
    if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
        markNodeAsDirty(node);
    }
    const index = node.childNodes.indexOf(removeNode);
    if (index >= 0) {
        node.childNodes.splice(index, 1);
    }
};
export const setAttribute = (node, key, value) => {
    if (key === 'internal_accessibility') {
        node.internal_accessibility = value;
        return;
    }
    node.attributes[key] = value;
};
export const setStyle = (node, style) => {
    node.style = style;
};
export const createTextNode = (text) => {
    const node = {
        nodeName: '#text',
        nodeValue: text,
        yogaNode: undefined,
        parentNode: undefined,
        style: {},
    };
    setTextNodeValue(node, text);
    return node;
};
const measureTextNode = function (node, width) {
    const styledChars = toStyledCharacters(node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node));
    const dimensions = measureStyledChars(styledChars);
    // Text fits into container, no need to wrap
    if (dimensions.width <= width) {
        return dimensions;
    }
    // This is happening when <Box> is shrinking child nodes and Yoga asks
    // if we can fit this text node in a <1px space, so we just tell Yoga "no"
    if (dimensions.width >= 1 && width > 0 && width < 1) {
        return dimensions;
    }
    const textWrap = node.style?.textWrap ?? 'wrap';
    const wrappedLines = textWrap === 'wrap' || textWrap.startsWith('truncate')
        ? wrapOrTruncateStyledChars(styledChars, Math.floor(width), textWrap)
        : [styledChars];
    const newWidth = widestLineFromStyledChars(wrappedLines);
    return { width: newWidth, height: wrappedLines.length };
};
const findClosestYogaNode = (node) => {
    if (!node?.parentNode) {
        return undefined;
    }
    return node.yogaNode ?? findClosestYogaNode(node.parentNode);
};
export const setCachedRender = (node, cachedRender) => {
    node.cachedRender = cachedRender;
    if (node.yogaNode) {
        node.yogaNode.setWidth(cachedRender.width);
        node.yogaNode.setHeight(cachedRender.height);
        while (node.yogaNode.getChildCount() > 0) {
            node.yogaNode.removeChild(node.yogaNode.getChild(0));
        }
    }
};
const markNodeAsDirty = (node) => {
    // Mark closest Yoga node as dirty to measure text dimensions again
    const yogaNode = findClosestYogaNode(node);
    yogaNode?.markDirty();
};
export const setTextNodeValue = (node, text) => {
    if (typeof text !== 'string') {
        text = String(text);
    }
    node.nodeValue = text;
    markNodeAsDirty(node);
};
export const getPathToRoot = (node) => {
    const path = [];
    let current = node;
    while (current) {
        path.unshift(current);
        current = current.parentNode;
    }
    return path;
};
export const isNodeSelectable = (node) => {
    let current = node;
    while (current) {
        const { userSelect } = current.style;
        if (userSelect === 'none') {
            return false;
        }
        if (userSelect === 'text' || userSelect === 'all') {
            return true;
        }
        current = current.parentNode;
    }
    return true;
};
//# sourceMappingURL=dom.js.map