import Yoga from 'yoga-layout';
import widestLine from 'widest-line';
import { wrapTextWithMetadata } from './wrap-text.js';
import getMaxWidth from './get-max-width.js';
import squashTextNodes from './squash-text-nodes.js';
import renderBorder from './render-border.js';
import renderBackground from './render-background.js';
// If parent container is `<Box>`, text nodes will be treated as separate nodes in
// the tree and will have their own coordinates in the layout.
// To ensure text nodes are aligned correctly, take X and Y of the first text node
// and use it as offset for the rest of the nodes
// Only first node is taken into account, because other text nodes can't have margin or padding,
// so their coordinates will be relative to the first node anyway
const getTextOffset = (node) => {
    const yogaNode = node.childNodes[0]?.yogaNode;
    if (yogaNode) {
        return {
            x: yogaNode.getComputedLeft(),
            y: yogaNode.getComputedTop(),
        };
    }
    return { x: 0, y: 0 };
};
export const renderNodeToScreenReaderOutput = (node, options = {}) => {
    if (options.skipStaticElements && node.internal_static) {
        return '';
    }
    if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) {
        return '';
    }
    let output = '';
    if (node.nodeName === 'ink-text') {
        output = squashTextNodes(node);
    }
    else if (node.nodeName === 'ink-box' || node.nodeName === 'ink-root') {
        const separator = node.style.flexDirection === 'row' ||
            node.style.flexDirection === 'row-reverse'
            ? ' '
            : '\n';
        const childNodes = node.style.flexDirection === 'row-reverse' ||
            node.style.flexDirection === 'column-reverse'
            ? [...node.childNodes].reverse()
            : [...node.childNodes];
        output = childNodes
            .map(childNode => {
            const screenReaderOutput = renderNodeToScreenReaderOutput(childNode, {
                parentRole: node.internal_accessibility?.role,
                skipStaticElements: options.skipStaticElements,
            });
            return screenReaderOutput;
        })
            .filter(Boolean)
            .join(separator);
    }
    if (node.internal_accessibility) {
        const { role, state } = node.internal_accessibility;
        if (state) {
            const stateKeys = Object.keys(state);
            const stateDescription = stateKeys.filter(key => state[key]).join(', ');
            if (stateDescription) {
                output = `(${stateDescription}) ${output}`;
            }
        }
        if (role && role !== options.parentRole) {
            output = `${role}: ${output}`;
        }
    }
    return output;
};
// After nodes are laid out, render each to output object, which later gets rendered to terminal
const renderNodeToOutput = (node, output, options) => {
    const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements, flowIds, nextFlowId, } = options;
    if (skipStaticElements && node.internal_static) {
        return;
    }
    const { yogaNode } = node;
    if (yogaNode) {
        if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) {
            return;
        }
        // Left and top positions in Yoga are relative to their parent node
        const x = offsetX + yogaNode.getComputedLeft();
        const y = offsetY + yogaNode.getComputedTop();
        // Transformers are functions that transform final text output of each component
        // See Output class for logic that applies transformers
        let newTransformers = transformers;
        if (typeof node.internal_transform === 'function') {
            newTransformers = [node.internal_transform, ...transformers];
        }
        if (node.nodeName === 'ink-text') {
            const sourceText = squashTextNodes(node);
            if (sourceText.length > 0) {
                const maxWidth = getMaxWidth(yogaNode);
                const textWrap = node.style.textWrap ?? 'wrap';
                const wrapped = wrapTextWithMetadata(sourceText, maxWidth, textWrap, widestLine(sourceText) > maxWidth);
                const flowKey = node.attributes.selectionFlow ?? node;
                let flowId = flowIds.get(flowKey);
                if (flowId === undefined) {
                    flowId = nextFlowId.value++;
                    flowIds.set(flowKey, flowId);
                }
                const boundaries = [...wrapped.boundaries];
                const breakAfter = node.attributes.selectionBreakAfter;
                boundaries.push(breakAfter === 'soft' || breakAfter === 'hard'
                    ? {
                        kind: breakAfter,
                        joiner: typeof node.attributes.selectionJoiner === 'string'
                            ? node.attributes.selectionJoiner
                            : breakAfter === 'hard'
                                ? '\n'
                                : '',
                    }
                    : null);
                const textOffset = getTextOffset(node);
                if (textOffset.x > 0) {
                    const padding = wrapped.text
                        .split('\n')
                        .map(line => line.length > 0 ? ' '.repeat(textOffset.x) : '')
                        .join('\n');
                    output.write(x, y + textOffset.y, padding, {
                        transformers: newTransformers,
                        semantic: {
                            flowId,
                            selectable: false,
                            selectableRows: wrapped.selectableRows.map(() => false),
                            boundaries: wrapped.boundaries.map(() => null),
                        },
                    });
                }
                output.write(x + textOffset.x, y + textOffset.y, wrapped.text, {
                    transformers: newTransformers,
                    semantic: {
                        flowId,
                        selectable: node.attributes.selectable !== false,
                        selectableRows: wrapped.selectableRows,
                        boundaries,
                    },
                });
            }
            return;
        }
        let clipped = false;
        if (node.nodeName === 'ink-box') {
            renderBackground(x, y, node, output);
            renderBorder(x, y, node, output);
            const clipHorizontally = node.style.overflowX === 'hidden' || node.style.overflow === 'hidden';
            const clipVertically = node.style.overflowY === 'hidden' || node.style.overflow === 'hidden';
            if (clipHorizontally || clipVertically) {
                const x1 = clipHorizontally
                    ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT)
                    : undefined;
                const x2 = clipHorizontally
                    ? x +
                        yogaNode.getComputedWidth() -
                        yogaNode.getComputedBorder(Yoga.EDGE_RIGHT)
                    : undefined;
                const y1 = clipVertically
                    ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP)
                    : undefined;
                const y2 = clipVertically
                    ? y +
                        yogaNode.getComputedHeight() -
                        yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM)
                    : undefined;
                output.clip({ x1, x2, y1, y2 });
                clipped = true;
            }
        }
        if (node.nodeName === 'ink-root' || node.nodeName === 'ink-box') {
            for (const childNode of node.childNodes) {
                renderNodeToOutput(childNode, output, {
                    offsetX: x,
                    offsetY: y,
                    transformers: newTransformers,
                    skipStaticElements,
                    flowIds,
                    nextFlowId,
                });
            }
            if (clipped) {
                output.unclip();
            }
        }
    }
};
export default renderNodeToOutput;
//# sourceMappingURL=render-node-to-output.js.map