import createReconciler from 'react-reconciler';
import { DefaultEventPriority, NoEventPriority, } from 'react-reconciler/constants.js';
import * as Scheduler from 'scheduler';
import Yoga from 'yoga-layout';
import { createContext } from 'react';
import { createTextNode, appendChildNode, insertBeforeNode, removeChildNode, setStyle, setTextNodeValue, createNode, setAttribute, } from './dom.js';
import applyStyles from './styles.js';
import { isDev } from './utils.js';
// We need to conditionally perform devtools connection to avoid
// accidentally breaking other third-party code.
// See https://github.com/vadimdemedes/ink/issues/384
if (isDev()) {
    try {
        await import('./devtools.js');
    }
    catch (error) {
        if (error.code === 'ERR_MODULE_NOT_FOUND') {
            console.warn(`
The environment variable DEV is set to true, so Ink tried to import \`react-devtools-core\`,
but this failed as it was not installed. Debugging with React Devtools requires it.

To install use this command:

$ npm install --save-dev react-devtools-core
				`.trim() + '\n');
        }
        else {
            // eslint-disable-next-line @typescript-eslint/only-throw-error
            throw error;
        }
    }
}
const diff = (before, after) => {
    if (before === after) {
        return;
    }
    if (!before) {
        return after;
    }
    const changed = {};
    let isChanged = false;
    for (const key of Object.keys(before)) {
        const isDeleted = after ? !Object.hasOwn(after, key) : true;
        if (isDeleted) {
            changed[key] = undefined;
            isChanged = true;
        }
    }
    if (after) {
        for (const key of Object.keys(after)) {
            if (after[key] !== before[key]) {
                changed[key] = after[key];
                isChanged = true;
            }
        }
    }
    return isChanged ? changed : undefined;
};
const cleanupYogaNode = (node) => {
    node?.unsetMeasureFunc();
    node?.freeRecursive();
};
let currentUpdatePriority = NoEventPriority;
let currentRootNode;
async function loadPackageJson() {
    const fs = await import('node:fs');
    const content = fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8');
    return JSON.parse(content);
}
const packageJson = isDev()
    ? await loadPackageJson()
    : { name: undefined, version: undefined };
export default createReconciler({
    getRootHostContext: () => ({
        isInsideText: false,
    }),
    prepareForCommit: () => null,
    preparePortalMount: () => null,
    clearContainer: () => false,
    resetAfterCommit(rootNode) {
        if (typeof rootNode.onComputeLayout === 'function') {
            rootNode.onComputeLayout();
        }
        // Since renders are throttled at the instance level and <Static> component children
        // are rendered only once and then get deleted, we need an escape hatch to
        // trigger an immediate render to ensure <Static> children are written to output before they get erased
        if (rootNode.isStaticDirty) {
            rootNode.isStaticDirty = false;
            if (typeof rootNode.onImmediateRender === 'function') {
                rootNode.onImmediateRender();
            }
            return;
        }
        if (typeof rootNode.onRender === 'function') {
            rootNode.onRender();
        }
    },
    getChildHostContext(parentHostContext, type) {
        const previousIsInsideText = parentHostContext.isInsideText;
        const isInsideText = type === 'ink-text' || type === 'ink-virtual-text';
        if (previousIsInsideText === isInsideText) {
            return parentHostContext;
        }
        return { isInsideText };
    },
    shouldSetTextContent: () => false,
    createInstance(originalType, newProps, rootNode, hostContext) {
        if (hostContext.isInsideText && originalType === 'ink-box') {
            throw new Error(`<Box> can’t be nested inside <Text> component`);
        }
        const type = originalType === 'ink-text' && hostContext.isInsideText
            ? 'ink-virtual-text'
            : originalType;
        const node = createNode(type);
        for (const [key, value] of Object.entries(newProps)) {
            if (key === 'children') {
                continue;
            }
            if (key === 'style') {
                setStyle(node, value);
                if (node.yogaNode) {
                    applyStyles(node.yogaNode, value);
                }
                continue;
            }
            if (key === 'internal_transform') {
                node.internal_transform = value;
                continue;
            }
            if (key === 'internal_static') {
                currentRootNode = rootNode;
                node.internal_static = true;
                rootNode.isStaticDirty = true;
                // Save reference to <Static> node to skip traversal of entire
                // node tree to find it
                rootNode.staticNode = node;
                continue;
            }
            setAttribute(node, key, value);
        }
        return node;
    },
    createTextInstance(text, _root, hostContext) {
        if (!hostContext.isInsideText) {
            throw new Error(`Text string "${text}" must be rendered inside <Text> component`);
        }
        return createTextNode(text);
    },
    resetTextContent() { },
    hideTextInstance(node) {
        setTextNodeValue(node, '');
    },
    unhideTextInstance(node, text) {
        setTextNodeValue(node, text);
    },
    getPublicInstance: instance => instance,
    hideInstance(node) {
        node.yogaNode?.setDisplay(Yoga.DISPLAY_NONE);
    },
    unhideInstance(node) {
        node.yogaNode?.setDisplay(Yoga.DISPLAY_FLEX);
    },
    appendInitialChild: appendChildNode,
    appendChild: appendChildNode,
    insertBefore: insertBeforeNode,
    finalizeInitialChildren() {
        return false;
    },
    isPrimaryRenderer: true,
    supportsMutation: true,
    supportsPersistence: false,
    supportsHydration: false,
    // Scheduler integration for concurrent mode
    supportsMicrotasks: true,
    scheduleMicrotask: queueMicrotask,
    // @ts-expect-error @types/react-reconciler is outdated and doesn't include scheduleCallback
    scheduleCallback: Scheduler.unstable_scheduleCallback,
    cancelCallback: Scheduler.unstable_cancelCallback,
    shouldYield: Scheduler.unstable_shouldYield,
    now: Scheduler.unstable_now,
    scheduleTimeout: setTimeout,
    cancelTimeout: clearTimeout,
    noTimeout: -1,
    beforeActiveInstanceBlur() { },
    afterActiveInstanceBlur() { },
    detachDeletedInstance() { },
    getInstanceFromNode: () => null,
    prepareScopeUpdate() { },
    getInstanceFromScope: () => null,
    appendChildToContainer: appendChildNode,
    insertInContainerBefore: insertBeforeNode,
    removeChildFromContainer(node, removeNode) {
        removeChildNode(node, removeNode);
        cleanupYogaNode(removeNode.yogaNode);
    },
    commitUpdate(node, _type, oldProps, newProps) {
        if (currentRootNode && node.internal_static) {
            currentRootNode.isStaticDirty = true;
        }
        const props = diff(oldProps, newProps);
        const style = diff(oldProps['style'], newProps['style']);
        if (!props && !style) {
            return;
        }
        if (props) {
            for (const [key, value] of Object.entries(props)) {
                if (key === 'style') {
                    setStyle(node, value);
                    continue;
                }
                if (key === 'internal_transform') {
                    node.internal_transform = value;
                    continue;
                }
                if (key === 'internal_static') {
                    node.internal_static = true;
                    continue;
                }
                setAttribute(node, key, value);
            }
        }
        if (style && node.yogaNode) {
            applyStyles(node.yogaNode, style);
        }
    },
    commitTextUpdate(node, _oldText, newText) {
        setTextNodeValue(node, newText);
    },
    removeChild(node, removeNode) {
        removeChildNode(node, removeNode);
        cleanupYogaNode(removeNode.yogaNode);
    },
    setCurrentUpdatePriority(newPriority) {
        currentUpdatePriority = newPriority;
    },
    getCurrentUpdatePriority: () => currentUpdatePriority,
    resolveUpdatePriority() {
        if (currentUpdatePriority !== NoEventPriority) {
            return currentUpdatePriority;
        }
        return DefaultEventPriority;
    },
    maySuspendCommit() {
        // Return true to enable Suspense resource preloading
        return true;
    },
    // eslint-disable-next-line @typescript-eslint/naming-convention
    NotPendingTransition: undefined,
    // eslint-disable-next-line @typescript-eslint/naming-convention
    HostTransitionContext: createContext(null),
    resetFormInstance() { },
    requestPostPaintCallback() { },
    shouldAttemptEagerTransition() {
        return false;
    },
    trackSchedulerEvent() { },
    resolveEventType() {
        return null;
    },
    resolveEventTimeStamp() {
        return -1.1;
    },
    preloadInstance() {
        return true;
    },
    startSuspendingCommit() { },
    suspendInstance() { },
    waitForCommitToBeReady() {
        return null;
    },
    rendererPackageName: packageJson.name,
    rendererVersion: packageJson.version,
});
//# sourceMappingURL=reconciler.js.map