// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
/**
 * Implements rendering to opcodes.
 * This module is modified from preact-render-to-string@6.0.3 to generate
 * opcodes instead of HTML strings for Lynx.
 */
// @ts-nocheck
import { Fragment, h, options } from 'preact';
import { CHILDREN, COMMIT, COMPONENT, DIFF, DIFF2, DIFFED, DIRTY, NEXT_STATE, PARENT, RENDER, SKIP_EFFECTS, VNODE, } from '../../../shared/render-constants.js';
/** @typedef {import('preact').VNode} VNode */
const EMPTY_ARR = [];
const isArray = /* @__PURE__ */ Array.isArray;
const assign = /* @__PURE__ */ Object.assign;
// Global state for the current render pass
let beforeDiff, beforeDiff2, afterDiff, renderHook, ummountHook;
/**
 * Render Preact JSX + Components to an HTML string.
 * @param {VNode} vnode	JSX Element / VNode to render
 * @param {object} [context] Initial root context object
 */
export function renderToString(vnode, context) {
    // Performance optimization: `renderToString` is synchronous and we
    // therefore don't execute any effects. To do that we pass an empty
    // array to `options._commit` (`__c`). But we can go one step further
    // and avoid a lot of dirty checks and allocations by setting
    // `options._skipEffects` (`__s`) too.
    const previousSkipEffects = options[SKIP_EFFECTS];
    options[SKIP_EFFECTS] = true;
    // store options hooks once before each synchronous render call
    beforeDiff = options[DIFF];
    beforeDiff2 = options[DIFF2];
    afterDiff = options[DIFFED];
    renderHook = options[RENDER];
    ummountHook = options.unmount;
    const parent = h(Fragment, null);
    parent[CHILDREN] = [vnode];
    const opcodes = [];
    try {
        _renderToString(vnode, context || EMPTY_OBJ, parent, opcodes);
    }
    finally {
        // options._commit, we don't schedule any effects in this library right now,
        // so we can pass an empty queue to this hook.
        if (options[COMMIT])
            options[COMMIT](vnode, EMPTY_ARR);
        options[SKIP_EFFECTS] = previousSkipEffects;
        EMPTY_ARR.length = 0;
    }
    return opcodes;
}
// Installed as setState/forceUpdate for function components
/* v8 ignore start */
function markAsDirty() {
    this.__d = true;
}
/* v8 ignore stop */
const EMPTY_OBJ = {};
export const __OpBegin = 0;
export const __OpEnd = 1;
export const __OpAttr = 2;
export const __OpText = 3;
export const __OpSlot = 4;
/**
 * @param {VNode} vnode
 * @param {Record<string, unknown>} context
 */
function renderClassComponent(vnode, context) {
    const type = /** @type {import("preact").ComponentClass<typeof vnode.props>} */ (vnode.type);
    let c;
    if (vnode[COMPONENT]) {
        c = vnode[COMPONENT];
        c.state = c[NEXT_STATE];
    }
    else {
        c = new type(vnode.props, context);
    }
    vnode[COMPONENT] = c;
    c[VNODE] = vnode;
    c.props = vnode.props;
    c.context = context;
    // turn off stateful re-rendering:
    c[DIRTY] = true;
    if (c.state == null)
        c.state = EMPTY_OBJ;
    if (c[NEXT_STATE] == null) {
        c[NEXT_STATE] = c.state;
    }
    if (type.getDerivedStateFromProps) {
        c.state = assign({}, c.state, type.getDerivedStateFromProps(c.props, c.state));
    }
    if (renderHook)
        renderHook(vnode);
    return c.render(c.props, c.state, context);
}
function cleanupVNode(vnode) {
    if (afterDiff)
        afterDiff(vnode);
    vnode[PARENT] = undefined;
    if (ummountHook)
        ummountHook(vnode);
}
function shouldRenderEtChild(child) {
    return child != null && child !== false && child !== true;
}
function isCompiledEtHostType(type) {
    return type.startsWith('_et_')
        || type.includes(':_et_');
}
function renderEtSlotArray(slotChildrenById, context, vnode, opcodes) {
    for (let slotId = 0; slotId < slotChildrenById.length; slotId += 1) {
        const slotChildren = slotChildrenById[slotId];
        if (!shouldRenderEtChild(slotChildren)) {
            continue;
        }
        opcodes.push(__OpSlot, slotId);
        _renderToString(slotChildren, context, vnode, opcodes);
    }
}
function renderComponentVNode(vnode, type, props, context, opcodes) {
    let cctx = context;
    let rendered;
    let component;
    const opcodesLength = opcodes.length;
    if (type === Fragment) {
        rendered = props.children;
    }
    else {
        const contextType = type.contextType;
        if (contextType != null) {
            const provider = context[contextType.__c];
            cctx = provider ? provider.props.value : contextType.__;
        }
        if (type.prototype && typeof type.prototype.render === 'function') {
            rendered = /**#__NOINLINE__**/ renderClassComponent(vnode, cctx);
            component = vnode[COMPONENT];
        }
        else {
            component = {
                __v: vnode,
                props,
                context: cctx,
                // silently drop state updates
                setState: markAsDirty,
                forceUpdate: markAsDirty,
                __d: true,
                // hooks
                __h: [],
            };
            vnode[COMPONENT] = component;
            component.constructor = type;
            component.render = doRender;
            let count = 0;
            while (component[DIRTY] && count++ < 25) {
                component[DIRTY] = false;
                if (renderHook)
                    renderHook(vnode);
                rendered = component.render(props, component.state, cctx);
            }
            component[DIRTY] = true;
        }
        if (component.getChildContext != null) {
            context = assign({}, context, component.getChildContext());
        }
    }
    const isTopLevelFragment = rendered != null && rendered.type === Fragment
        && rendered.key == null;
    rendered = isTopLevelFragment ? rendered.props.children : rendered;
    try {
        _renderToString(rendered, context, vnode, opcodes);
    }
    catch (e) {
        if (e && typeof e === 'object' && e.then && component && /* _childDidSuspend */ component.__c) {
            component.setState({ /* _suspended */ __a: true });
            if (component[DIRTY]) {
                rendered = renderClassComponent(vnode, context);
                component = vnode[COMPONENT];
                opcodes.length = opcodesLength;
                _renderToString(rendered, context, vnode, opcodes);
            }
        }
        else {
            throw e;
        }
    }
    finally {
        cleanupVNode(vnode);
    }
}
function renderCompiledEtHostVNode(vnode, props, context, opcodes) {
    opcodes.push(__OpBegin, vnode);
    const attributeSlots = props.attributeSlots;
    if (attributeSlots !== undefined) {
        opcodes.push(__OpAttr, 'attributeSlots', attributeSlots);
    }
    // ET host nodes are compiler-generated and encode dynamic element slots in
    // `children`, so the runtime can interpret the prop as a slot array directly.
    const elementSlots = props.children;
    if (elementSlots !== undefined) {
        renderEtSlotArray(elementSlots, context, vnode, opcodes);
    }
    cleanupVNode(vnode);
    opcodes.push(__OpEnd);
}
function renderStringHostVNode(type, vnode, props, context, opcodes) {
    if (__DEV__ && !isCompiledEtHostType(type)) {
        cleanupVNode(vnode);
        throw new Error(`Element Template main-thread renderer received an uncompiled host vnode: ${type}`);
    }
    renderCompiledEtHostVNode(vnode, props, context, opcodes);
}
/**
 * Recursively render VNodes to HTML.
 * @param {VNode|any} vnode
 * @param {any} context
 * @param {VNode} parent
 * @param opcodes
 */
function _renderToString(vnode, context, parent, opcodes) {
    // Ignore non-rendered VNodes/values
    if (vnode == null || vnode === true || vnode === false || vnode === '') {
        return;
    }
    // Text VNodes: escape as HTML
    if (typeof vnode !== 'object') {
        if (typeof vnode === 'function')
            return;
        opcodes.push(__OpText, vnode + '');
        return;
    }
    // Recurse into children / Arrays
    if (isArray(vnode)) {
        parent[CHILDREN] = vnode;
        for (let i = 0; i < vnode.length; i++) {
            const child = vnode[i];
            if (child == null || typeof child === 'boolean')
                continue;
            _renderToString(child, context, parent, opcodes);
        }
        return;
    }
    // VNodes have {constructor:undefined} to prevent JSON injection:
    // if (vnode.constructor !== undefined) return;
    vnode[PARENT] = parent;
    if (beforeDiff)
        beforeDiff(vnode);
    if (beforeDiff2)
        beforeDiff2(vnode, EMPTY_OBJ);
    let type = vnode.type, props = vnode.props;
    // Invoke rendering on Components
    if (typeof type === 'function') {
        renderComponentVNode(vnode, type, props, context, opcodes);
        return;
    }
    if (typeof type === 'string') {
        renderStringHostVNode(type, vnode, props, context, opcodes);
        return;
    }
    if (__DEV__) {
        cleanupVNode(vnode);
        throw new Error('Element Template main-thread renderer received an invalid vnode.');
    }
}
/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
    return this.constructor(props, context);
}
export default renderToString;
export const render = renderToString;
export const renderToStaticMarkup = renderToString;
//# sourceMappingURL=render-to-opcodes.js.map