/// <reference types="crank.d.ts" />
import { removeEventTargetDelegates, addEventTargetDelegates, clearEventListeners, CustomEventTarget } from './event-target.js?module';
import { isPromiseLike, unwrap, wrap, arrayify, safeRace, markStart, measureMark, isIteratorLike } from './_utils.js?module';
export { setProfiling } from './_utils.js?module';

const NOOP = () => { };
function getTagName(tag) {
    return typeof tag === "function"
        ? tag.name || "Anonymous"
        : typeof tag === "string"
            ? tag
            : // tag is symbol, using else branch to avoid typeof tag === "symbol"
                tag.description || "Anonymous";
}
/*** SPECIAL TAGS ***/
/**
 * A special tag for grouping multiple children within the same parent.
 *
 * All non-string iterables which appear in the element tree are implicitly
 * wrapped in a fragment element.
 *
 * This tag is just the empty string, and you can use the empty string in
 * createElement calls or transpiler options directly to avoid having to
 * reference this export.
 */
const Fragment = "";
// TODO: We assert the following symbol tags as Components because TypeScript
// support for symbol tags in JSX doesn't exist yet.
// https://github.com/microsoft/TypeScript/issues/38367
/**
 * A special tag for rendering into a new root node via a root prop.
 *
 * This tag is useful for creating element trees with multiple roots, for
 * things like modals or tooltips.
 *
 * Renderer.prototype.render() implicitly wraps top-level in a Portal element
 * with the root set to the second argument passed in.
 */
const Portal = Symbol.for("crank.Portal");
/**
 * A special tag which preserves whatever was previously rendered in the
 * element's position.
 *
 * Copy elements are useful for when you want to prevent a subtree from
 * rerendering as a performance optimization. Copy elements can also be keyed,
 * in which case the previously rendered keyed element will be copied.
 */
const Copy = Symbol.for("crank.Copy");
/**
 * A special tag for rendering text nodes.
 *
 * Strings in the element tree are implicitly wrapped in a Text element with
 * value set to the string.
 */
const Text = Symbol.for("crank.Text");
/** A special tag for injecting raw nodes or strings via a value prop. */
const Raw = Symbol.for("crank.Raw");
const ElementSymbol = Symbol.for("crank.Element");
/**
 * Elements are the basic building blocks of Crank applications. They are
 * JavaScript objects which are interpreted by special classes called renderers
 * to produce and manage stateful nodes.
 *
 * @template {Tag} [TTag=Tag] - The type of the tag of the element.
 *
 * @example
 * // specific element types
 * let div: Element<"div">;
 * let portal: Element<Portal>;
 * let myEl: Element<MyComponent>;
 *
 * // general element types
 * let host: Element<string | symbol>;
 * let component: Element<Component>;
 *
 * Typically, you use a helper function like createElement to create elements
 * rather than instatiating this class directly.
 */
class Element {
    constructor(tag, props) {
        this.tag = tag;
        this.props = props;
    }
}
// See Element interface
Element.prototype.$$typeof = ElementSymbol;
function isElement(value) {
    return value != null && value.$$typeof === ElementSymbol;
}
const DEPRECATED_PROP_PREFIXES = ["crank-", "c-", "$"];
const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static", "copy"];
/**
 * Creates an element with the specified tag, props and children.
 *
 * This function is usually used as a transpilation target for JSX transpilers,
 * but it can also be called directly. It additionally extracts special props so
 * they aren't accessible to renderer methods or components, and assigns the
 * children prop according to any additional arguments passed to the function.
 */
function createElement(tag, props, ...children) {
    if (props == null) {
        props = {};
    }
    if ("static" in props) {
        console.error(`The \`static\` prop is deprecated. Use \`copy\` instead.`);
        props["copy"] = props["static"];
        delete props["static"];
    }
    for (let i = 0; i < DEPRECATED_PROP_PREFIXES.length; i++) {
        const propPrefix = DEPRECATED_PROP_PREFIXES[i];
        for (let j = 0; j < DEPRECATED_SPECIAL_PROP_BASES.length; j++) {
            const propBase = DEPRECATED_SPECIAL_PROP_BASES[j];
            const deprecatedPropName = propPrefix + propBase;
            if (deprecatedPropName in props) {
                const targetPropBase = propBase === "static" ? "copy" : propBase;
                console.error(`The \`${deprecatedPropName}\` prop is deprecated. Use \`${targetPropBase}\` instead.`);
                props[targetPropBase] = props[deprecatedPropName];
                delete props[deprecatedPropName];
            }
        }
    }
    if (children.length > 1) {
        props.children = children;
    }
    else if (children.length === 1) {
        props.children = children[0];
    }
    return new Element(tag, props);
}
/** Clones a given element, shallowly copying the props object. */
function cloneElement(el) {
    if (!isElement(el)) {
        throw new TypeError(`Cannot clone non-element: ${String(el)}`);
    }
    return new Element(el.tag, { ...el.props });
}
function narrow(value) {
    if (typeof value === "boolean" || value == null) {
        return;
    }
    else if (typeof value === "string" || isElement(value)) {
        return value;
    }
    else if (typeof value[Symbol.iterator] === "function") {
        return createElement(Fragment, null, value);
    }
    return value.toString();
}
/*** RETAINER FLAGS ***/
const DidDiff = 1 << 0;
const DidCommit = 1 << 1;
const IsCopied = 1 << 2;
const IsUpdating = 1 << 3;
const IsExecuting = 1 << 4;
const IsRefreshing = 1 << 5;
const IsScheduling = 1 << 6;
const IsSchedulingFallback = 1 << 7;
const IsUnmounted = 1 << 8;
// TODO: Is this flag still necessary or can we use IsUnmounted?
const IsErrored = 1 << 9;
const IsResurrecting = 1 << 10;
// TODO: Maybe we can get rid of IsSyncGen and IsAsyncGen
const IsSyncGen = 1 << 11;
const IsAsyncGen = 1 << 12;
const IsInForOfLoop = 1 << 13;
const IsInForAwaitOfLoop = 1 << 14;
const NeedsToYield = 1 << 15;
const PropsAvailable = 1 << 16;
const IsSchedulingRefresh = 1 << 17;
function getFlag(ret, flag) {
    return !!(ret.f & flag);
}
function setFlag(ret, flag, value = true) {
    if (value) {
        ret.f |= flag;
    }
    else {
        ret.f &= ~flag;
    }
}
/**
 * @internal
 * Retainers are objects which act as the internal representation of elements,
 * mirroring the element tree.
 */
class Retainer {
    constructor(el) {
        this.f = 0;
        this.el = el;
        this.ctx = undefined;
        this.children = undefined;
        this.fallback = undefined;
        this.value = undefined;
        this.oldProps = undefined;
        this.pendingDiff = undefined;
        this.onNextDiff = undefined;
        this.graveyard = undefined;
        this.lingerers = undefined;
    }
}
function cloneRetainer(ret) {
    const clone = new Retainer(ret.el);
    clone.f = ret.f;
    clone.ctx = ret.ctx;
    clone.children = ret.children;
    clone.fallback = ret.fallback;
    clone.value = ret.value;
    clone.scope = ret.scope;
    clone.oldProps = ret.oldProps;
    clone.pendingDiff = ret.pendingDiff;
    clone.onNextDiff = ret.onNextDiff;
    clone.graveyard = ret.graveyard;
    clone.lingerers = ret.lingerers;
    return clone;
}
/**
 * Finds the value of the element according to its type.
 *
 * @returns A node, an array of nodes or undefined.
 */
function getValue(ret, isNested = false, index) {
    if (getFlag(ret, IsScheduling) && isNested) {
        return ret.fallback ? getValue(ret.fallback, isNested, index) : undefined;
    }
    else if (ret.fallback && !getFlag(ret, DidDiff)) {
        return ret.fallback
            ? getValue(ret.fallback, isNested, index)
            : ret.fallback;
    }
    else if (ret.el.tag === Portal) {
        return;
    }
    else if (ret.el.tag === Fragment || typeof ret.el.tag === "function") {
        if (index != null && ret.ctx) {
            ret.ctx.index = index;
        }
        return unwrap(getChildValues(ret, index));
    }
    return ret.value;
}
/**
 * Walks an element's children to find its child values.
 *
 * @param ret - The retainer whose child values we are reading.
 * @param startIndex - Starting index to thread through for context index updates.
 *
 * @returns An array of nodes.
 */
function getChildValues(ret, startIndex) {
    const values = [];
    const lingerers = ret.lingerers;
    const rawChildren = ret.children;
    const isChildrenArray = Array.isArray(rawChildren);
    const childrenLength = rawChildren === undefined
        ? 0
        : isChildrenArray
            ? rawChildren.length
            : 1;
    let currentIndex = startIndex;
    for (let i = 0; i < childrenLength; i++) {
        if (lingerers != null && lingerers[i] != null) {
            const rets = lingerers[i];
            for (const ret of rets) {
                const value = getValue(ret, true, currentIndex);
                if (Array.isArray(value)) {
                    for (let j = 0; j < value.length; j++) {
                        values.push(value[j]);
                    }
                    if (currentIndex != null) {
                        currentIndex += value.length;
                    }
                }
                else if (value) {
                    values.push(value);
                    if (currentIndex != null) {
                        currentIndex++;
                    }
                }
            }
        }
        const child = isChildrenArray
            ? rawChildren[i]
            : rawChildren;
        if (child) {
            const value = getValue(child, true, currentIndex);
            if (Array.isArray(value)) {
                for (let j = 0; j < value.length; j++) {
                    values.push(value[j]);
                }
                if (currentIndex != null) {
                    currentIndex += value.length;
                }
            }
            else if (value) {
                values.push(value);
                if (currentIndex != null) {
                    currentIndex++;
                }
            }
        }
    }
    if (lingerers != null && lingerers.length > childrenLength) {
        for (let i = childrenLength; i < lingerers.length; i++) {
            const rets = lingerers[i];
            if (rets != null) {
                for (const ret of rets) {
                    const value = getValue(ret, true, currentIndex);
                    if (Array.isArray(value)) {
                        for (let j = 0; j < value.length; j++) {
                            values.push(value[j]);
                        }
                        if (currentIndex != null) {
                            currentIndex += value.length;
                        }
                    }
                    else if (value) {
                        values.push(value);
                        if (currentIndex != null) {
                            currentIndex++;
                        }
                    }
                }
            }
        }
    }
    return values;
}
function stripSpecialProps(props) {
    let _;
    let result;
    ({ key: _, ref: _, copy: _, hydrate: _, children: _, ...result } = props);
    return result;
}
const defaultAdapter = {
    create() {
        throw new Error("adapter must implement create");
    },
    adopt() {
        throw new Error("adapter must implement adopt() for hydration");
    },
    scope: ({ scope }) => scope,
    read: (value) => value,
    text: ({ value }) => value,
    raw: ({ value }) => value,
    patch: NOOP,
    arrange: NOOP,
    remove: NOOP,
    finalize: NOOP,
};
/**
 * An abstract class which is subclassed to render to different target
 * environments. Subclasses call super() with a custom RenderAdapter object.
 * This class is responsible for kicking off the rendering process and caching
 * previous trees by root.
 *
 * @template TNode - The type of the node for a rendering environment.
 * @template TScope - Data which is passed down the tree.
 * @template TRoot - The type of the root for a rendering environment.
 * @template TResult - The type of exposed values.
 */
class Renderer {
    constructor(adapter) {
        this.cache = new WeakMap();
        this.adapter = { ...defaultAdapter, ...adapter };
    }
    /**
     * Renders an element tree into a specific root.
     *
     * @param children - An element tree. Rendering null deletes cached renders.
     * @param root - The root to be rendered into. The renderer caches renders
     * per root.
     * @param bridge - An optional context that will be the ancestor context of
     * all elements in the tree. Useful for connecting different renderers so
     * that events/provisions/errors properly propagate. The context for a given
     * root must be the same between renders.
     *
     * @returns The result of rendering the children, or a possible promise of
     * the result if the element tree renders asynchronously.
     */
    render(children, root, bridge) {
        const ret = getRootRetainer(this, bridge, { children, root });
        return renderRoot(this.adapter, root, ret, children);
    }
    hydrate(children, root, bridge) {
        const ret = getRootRetainer(this, bridge, {
            children,
            root,
            hydrate: true,
        });
        return renderRoot(this.adapter, root, ret, children);
    }
}
/*** PRIVATE RENDERER FUNCTIONS ***/
function getRootRetainer(renderer, bridge, { children, root, hydrate, }) {
    let ret;
    const bridgeCtx = bridge && bridge[_ContextState];
    if (typeof root === "object" && root !== null) {
        ret = renderer.cache.get(root);
    }
    const adapter = renderer.adapter;
    if (ret === undefined) {
        ret = new Retainer(createElement(Portal, { children, root, hydrate }));
        ret.value = root;
        ret.ctx = bridgeCtx;
        ret.scope = adapter.scope({
            tag: Portal,
            tagName: getTagName(Portal),
            props: stripSpecialProps(ret.el.props),
            scope: undefined,
            root,
        });
        // remember that typeof null === "object"
        if (typeof root === "object" && root !== null && children != null) {
            renderer.cache.set(root, ret);
        }
    }
    else if (ret.ctx !== bridgeCtx) {
        throw new Error("A previous call to render() was passed a different context");
    }
    else {
        ret.el = createElement(Portal, { children, root, hydrate });
        if (typeof root === "object" && root !== null && children == null) {
            renderer.cache.delete(root);
        }
    }
    return ret;
}
function renderRoot(adapter, root, ret, children) {
    const commitLabel = "commit (" + getTagName(ret.el.tag) + ")";
    markStart("diff");
    const diff = diffChildren(adapter, root, ret, ret.ctx, ret.scope, ret, children);
    const schedulePromises = [];
    if (isPromiseLike(diff)) {
        return diff.then(() => {
            measureMark("diff");
            markStart(commitLabel);
            commit(adapter, ret, ret, ret.ctx, ret.scope, root, 0, schedulePromises, undefined);
            measureMark(commitLabel);
            if (schedulePromises.length > 0) {
                return Promise.all(schedulePromises).then(() => {
                    if (typeof root !== "object" || root === null) {
                        unmount(adapter, ret, ret.ctx, root, ret, false);
                    }
                    return adapter.read(unwrap(getChildValues(ret)));
                });
            }
            if (typeof root !== "object" || root === null) {
                unmount(adapter, ret, ret.ctx, root, ret, false);
            }
            return adapter.read(unwrap(getChildValues(ret)));
        });
    }
    measureMark("diff");
    markStart(commitLabel);
    commit(adapter, ret, ret, ret.ctx, ret.scope, root, 0, schedulePromises, undefined);
    measureMark(commitLabel);
    if (schedulePromises.length > 0) {
        return Promise.all(schedulePromises).then(() => {
            if (typeof root !== "object" || root === null) {
                unmount(adapter, ret, ret.ctx, root, ret, false);
            }
            return adapter.read(unwrap(getChildValues(ret)));
        });
    }
    if (typeof root !== "object" || root === null) {
        unmount(adapter, ret, ret.ctx, root, ret, false);
    }
    return adapter.read(unwrap(getChildValues(ret)));
}
function diffChild(adapter, root, host, ctx, scope, parent, newChildren) {
    let child = narrow(newChildren);
    let ret = parent.children;
    let graveyard;
    let diff;
    if (typeof child === "object") {
        let childCopied = false;
        // Check key match
        const oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
        const newKey = child.props.key;
        if (oldKey !== newKey) {
            if (typeof ret === "object") {
                (graveyard = graveyard || []).push(ret);
            }
            ret = undefined;
        }
        if (child.tag === Copy) {
            childCopied = true;
        }
        else if (typeof ret === "object" &&
            ret.el === child &&
            getFlag(ret, DidCommit)) {
            childCopied = true;
        }
        else {
            if (ret && ret.el.tag === child.tag) {
                ret.el = child;
                if (child.props.copy && typeof child.props.copy !== "string") {
                    childCopied = true;
                }
            }
            else if (ret) {
                let candidateFound = false;
                for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
                    if (candidate.el.tag === child.tag) {
                        const clone = cloneRetainer(candidate);
                        setFlag(clone, IsResurrecting);
                        predecessor.fallback = clone;
                        const fallback = ret;
                        ret = candidate;
                        ret.el = child;
                        ret.fallback = fallback;
                        setFlag(ret, DidDiff, false);
                        candidateFound = true;
                        break;
                    }
                }
                if (!candidateFound) {
                    const fallback = ret;
                    ret = new Retainer(child);
                    ret.fallback = fallback;
                }
            }
            else {
                ret = new Retainer(child);
            }
            if (childCopied && getFlag(ret, DidCommit)) ;
            else if (child.tag === Raw || child.tag === Text) ;
            else if (child.tag === Fragment) {
                diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
            }
            else if (typeof child.tag === "function") {
                diff = diffComponent(adapter, root, host, ctx, scope, ret);
            }
            else {
                diff = diffHost(adapter, root, ctx, scope, ret);
            }
        }
        if (typeof ret === "object") {
            if (childCopied) {
                setFlag(ret, IsCopied);
                diff = getInflightDiff(ret);
            }
            else {
                setFlag(ret, IsCopied, false);
            }
        }
    }
    else if (typeof child === "string") {
        if (typeof ret === "object" && ret.el.tag === Text) {
            ret.el.props.value = child;
        }
        else {
            if (typeof ret === "object") {
                (graveyard = graveyard || []).push(ret);
            }
            ret = new Retainer(createElement(Text, { value: child }));
        }
    }
    else {
        if (typeof ret === "object") {
            (graveyard = graveyard || []).push(ret);
        }
        ret = undefined;
    }
    parent.children = ret;
    if (isPromiseLike(diff)) {
        const diff1 = diff.finally(() => {
            setFlag(parent, DidDiff);
            if (graveyard) {
                if (parent.graveyard) {
                    for (let i = 0; i < graveyard.length; i++) {
                        parent.graveyard.push(graveyard[i]);
                    }
                }
                else {
                    parent.graveyard = graveyard;
                }
            }
        });
        let onNextDiffs;
        const diff2 = (parent.pendingDiff = safeRace([
            diff1,
            new Promise((resolve) => (onNextDiffs = resolve)),
        ]));
        if (parent.onNextDiff) {
            parent.onNextDiff(diff2);
        }
        parent.onNextDiff = onNextDiffs;
        return diff2;
    }
    else {
        setFlag(parent, DidDiff);
        if (graveyard) {
            if (parent.graveyard) {
                for (let i = 0; i < graveyard.length; i++) {
                    parent.graveyard.push(graveyard[i]);
                }
            }
            else {
                parent.graveyard = graveyard;
            }
        }
        if (parent.onNextDiff) {
            parent.onNextDiff(diff);
            parent.onNextDiff = undefined;
        }
        parent.pendingDiff = undefined;
    }
}
function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
    // Fast path for the common single non-keyed child case
    if (!Array.isArray(newChildren) &&
        (typeof newChildren !== "object" ||
            newChildren === null ||
            typeof newChildren[Symbol.iterator] !== "function") &&
        !Array.isArray(parent.children)) {
        return diffChild(adapter, root, host, ctx, scope, parent, newChildren);
    }
    const oldRetained = wrap(parent.children);
    const newRetained = [];
    const newChildren1 = arrayify(newChildren);
    const diffs = [];
    let childrenByKey;
    let seenKeys;
    let isAsync = false;
    let oi = 0;
    let oldLength = oldRetained.length;
    let graveyard;
    for (let ni = 0, newLength = newChildren1.length; ni < newLength; ni++) {
        // length checks to prevent index out of bounds deoptimizations.
        let ret = oi >= oldLength ? undefined : oldRetained[oi];
        let child = narrow(newChildren1[ni]);
        {
            // aligning new children with old retainers
            let oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
            let newKey = typeof child === "object" ? child.props.key : undefined;
            if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
                console.error(`Duplicate key found in <${getTagName(parent.el.tag)}>`, newKey);
                child = cloneElement(child);
                newKey = child.props.key = undefined;
            }
            if (oldKey === newKey) {
                if (childrenByKey !== undefined && newKey !== undefined) {
                    childrenByKey.delete(newKey);
                }
                oi++;
            }
            else {
                childrenByKey = childrenByKey || createChildrenByKey(oldRetained, oi);
                if (newKey === undefined) {
                    while (ret !== undefined && oldKey !== undefined) {
                        oi++;
                        ret = oldRetained[oi];
                        oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
                    }
                    oi++;
                }
                else {
                    ret = childrenByKey.get(newKey);
                    if (ret !== undefined) {
                        childrenByKey.delete(newKey);
                    }
                    (seenKeys = seenKeys || new Set()).add(newKey);
                }
            }
        }
        let diff = undefined;
        if (typeof child === "object") {
            let childCopied = false;
            if (child.tag === Copy) {
                childCopied = true;
            }
            else if (typeof ret === "object" &&
                ret.el === child &&
                getFlag(ret, DidCommit)) {
                // If the child is the same as the retained element, we skip
                // re-rendering.
                childCopied = true;
            }
            else {
                if (ret && ret.el.tag === child.tag) {
                    ret.el = child;
                    if (child.props.copy && typeof child.props.copy !== "string") {
                        childCopied = true;
                    }
                }
                else if (ret) {
                    let candidateFound = false;
                    // we do not need to add the retainer to the graveyard if it is the
                    // fallback of another retainer
                    // search for the tag in fallback chain
                    for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
                        if (candidate.el.tag === child.tag) {
                            // If we find a retainer in the fallback chain with the same tag,
                            // we reuse it rather than creating a new retainer to preserve
                            // state. This behavior is useful for when a Suspense component
                            // re-renders and the children are re-rendered quickly.
                            const clone = cloneRetainer(candidate);
                            setFlag(clone, IsResurrecting);
                            predecessor.fallback = clone;
                            const fallback = ret;
                            ret = candidate;
                            ret.el = child;
                            ret.fallback = fallback;
                            setFlag(ret, DidDiff, false);
                            candidateFound = true;
                            break;
                        }
                    }
                    if (!candidateFound) {
                        const fallback = ret;
                        ret = new Retainer(child);
                        ret.fallback = fallback;
                    }
                }
                else {
                    ret = new Retainer(child);
                }
                if (childCopied && getFlag(ret, DidCommit)) ;
                else if (child.tag === Raw || child.tag === Text) ;
                else if (child.tag === Fragment) {
                    diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
                }
                else if (typeof child.tag === "function") {
                    diff = diffComponent(adapter, root, host, ctx, scope, ret);
                }
                else {
                    diff = diffHost(adapter, root, ctx, scope, ret);
                }
            }
            if (typeof ret === "object") {
                if (childCopied) {
                    setFlag(ret, IsCopied);
                    diff = getInflightDiff(ret);
                }
                else {
                    setFlag(ret, IsCopied, false);
                }
            }
            if (isPromiseLike(diff)) {
                isAsync = true;
            }
        }
        else if (typeof child === "string") {
            if (typeof ret === "object" && ret.el.tag === Text) {
                ret.el.props.value = child;
            }
            else {
                if (typeof ret === "object") {
                    (graveyard = graveyard || []).push(ret);
                }
                ret = new Retainer(createElement(Text, { value: child }));
            }
        }
        else {
            if (typeof ret === "object") {
                (graveyard = graveyard || []).push(ret);
            }
            ret = undefined;
        }
        diffs[ni] = diff;
        newRetained[ni] = ret;
    }
    // cleanup remaining retainers
    for (; oi < oldLength; oi++) {
        const ret = oldRetained[oi];
        if (typeof ret === "object" &&
            (typeof ret.el.props.key === "undefined" ||
                !seenKeys ||
                !seenKeys.has(ret.el.props.key))) {
            (graveyard = graveyard || []).push(ret);
        }
    }
    if (childrenByKey !== undefined && childrenByKey.size > 0) {
        graveyard = graveyard || [];
        for (const ret of childrenByKey.values()) {
            graveyard.push(ret);
        }
    }
    parent.children = unwrap(newRetained);
    if (isAsync) {
        const diffs1 = Promise.all(diffs)
            .then(() => undefined)
            .finally(() => {
            setFlag(parent, DidDiff);
            if (graveyard) {
                if (parent.graveyard) {
                    for (let i = 0; i < graveyard.length; i++) {
                        parent.graveyard.push(graveyard[i]);
                    }
                }
                else {
                    parent.graveyard = graveyard;
                }
            }
        });
        let onNextDiffs;
        const diffs2 = (parent.pendingDiff = safeRace([
            diffs1,
            new Promise((resolve) => (onNextDiffs = resolve)),
        ]));
        if (parent.onNextDiff) {
            parent.onNextDiff(diffs2);
        }
        parent.onNextDiff = onNextDiffs;
        return diffs2;
    }
    else {
        setFlag(parent, DidDiff);
        if (graveyard) {
            if (parent.graveyard) {
                for (let i = 0; i < graveyard.length; i++) {
                    parent.graveyard.push(graveyard[i]);
                }
            }
            else {
                parent.graveyard = graveyard;
            }
        }
        if (parent.onNextDiff) {
            parent.onNextDiff(diffs);
            parent.onNextDiff = undefined;
        }
        parent.pendingDiff = undefined;
    }
}
function getInflightDiff(ret) {
    // It is not enough to check pendingDiff because pendingDiff is the diff for
    // children, but not the diff of an async component retainer's current run.
    // For the latter we check ctx.inflight.
    if (ret.ctx && ret.ctx.inflight) {
        return ret.ctx.inflight[1];
    }
    else if (ret.pendingDiff) {
        return ret.pendingDiff;
    }
}
function createChildrenByKey(children, offset) {
    const childrenByKey = new Map();
    for (let i = offset; i < children.length; i++) {
        const child = children[i];
        if (typeof child === "object" &&
            typeof child.el.props.key !== "undefined") {
            childrenByKey.set(child.el.props.key, child);
        }
    }
    return childrenByKey;
}
function diffHost(adapter, root, ctx, scope, ret) {
    const el = ret.el;
    const tag = el.tag;
    if (el.tag === Portal) {
        root = ret.value = el.props.root;
    }
    if (getFlag(ret, DidCommit)) {
        scope = ret.scope;
    }
    else {
        scope = ret.scope = adapter.scope({
            tag,
            tagName: getTagName(tag),
            props: el.props,
            scope,
            root,
        });
    }
    return diffChildren(adapter, root, ret, ctx, scope, ret, ret.el.props.children);
}
function commit(adapter, host, ret, ctx, scope, root, index, schedulePromises, hydrationNodes) {
    if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
        return getValue(ret);
    }
    const el = ret.el;
    const tag = el.tag;
    if (typeof tag === "function" ||
        tag === Fragment ||
        tag === Portal ||
        tag === Raw ||
        tag === Text) {
        if (typeof el.props.copy === "string") {
            console.error(`String copy prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
        }
        if (typeof el.props.hydrate === "string") {
            console.error(`String hydrate prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
        }
    }
    let value;
    let skippedHydrationNodes;
    if (hydrationNodes &&
        el.props.hydrate != null &&
        !el.props.hydrate &&
        typeof el.props.hydrate !== "string") {
        skippedHydrationNodes = hydrationNodes;
        hydrationNodes = undefined;
    }
    if (typeof tag === "function") {
        ret.ctx.index = index;
        value = commitComponent(ret.ctx, schedulePromises, hydrationNodes);
    }
    else {
        if (tag === Fragment) {
            value = commitChildren(adapter, host, ctx, scope, root, ret, index, schedulePromises, hydrationNodes);
        }
        else if (tag === Text) {
            value = commitText(adapter, ret, el, scope, hydrationNodes, root);
        }
        else if (tag === Raw) {
            value = commitRaw(adapter, host, ret, scope, hydrationNodes, root);
        }
        else {
            value = commitHost(adapter, ret, ctx, root, schedulePromises, hydrationNodes);
        }
        if (ret.fallback) {
            unmount(adapter, host, ctx, root, ret.fallback, false);
            ret.fallback = undefined;
        }
    }
    if (skippedHydrationNodes) {
        skippedHydrationNodes.splice(0, value == null ? 0 : Array.isArray(value) ? value.length : 1);
    }
    if (!getFlag(ret, DidCommit)) {
        setFlag(ret, DidCommit);
        if (typeof tag !== "function" &&
            tag !== Fragment &&
            tag !== Portal &&
            typeof el.props.ref === "function") {
            el.props.ref(adapter.read(value));
        }
    }
    return value;
}
function commitChildren(adapter, host, ctx, scope, root, parent, index, schedulePromises, hydrationNodes) {
    let values = [];
    const rawChildren = parent.children;
    const isChildrenArray = Array.isArray(rawChildren);
    const childrenLength = rawChildren === undefined
        ? 0
        : isChildrenArray
            ? rawChildren.length
            : 1;
    for (let i = 0; i < childrenLength; i++) {
        let child = isChildrenArray
            ? rawChildren[i]
            : rawChildren;
        let schedulePromises1;
        let isSchedulingFallback = false;
        while (child &&
            ((!getFlag(child, DidDiff) && child.fallback) ||
                getFlag(child, IsScheduling))) {
            // If the child is scheduling, it is a component retainer so ctx will be
            // defined.
            if (getFlag(child, IsScheduling) && child.ctx.schedule) {
                (schedulePromises1 = schedulePromises1 || []).push(child.ctx.schedule.promise);
                isSchedulingFallback = true;
            }
            if (!getFlag(child, DidDiff) && getFlag(child, DidCommit)) {
                // If this child has not diffed but has committed, it means it is a
                // fallback that is being resurrected.
                for (const node of getChildValues(child)) {
                    adapter.remove({
                        node,
                        parentNode: host.value,
                        isNested: false,
                        root,
                    });
                }
            }
            child = child.fallback;
            // When a scheduling component is mounting asynchronously but diffs
            // immediately, it will cause previous async diffs to settle due to the
            // chasing mechanism. This would cause earlier renders to resolve sooner
            // than expected, because the render would be missing both its usual
            // children and the children of the scheduling render. Therefore, we need
            // to defer the settling of previous renders until either that render
            // settles, or the scheduling component finally finishes scheduling.
            //
            // To do this, we take advantage of the fact that commits for aborted
            // renders will still fire and walk the tree. During that commit walk,
            // when we encounter a scheduling element, we push a race of the
            // scheduling promise with the inflight diff of the async fallback
            // fallback to schedulePromises to delay the initiator.
            //
            // However, we need to make sure we only use the inflight diffs for the
            // fallback which we are trying to delay, in the case of multiple renders
            // and fallbacks. To do this, we take advantage of the fact that when
            // multiple renders race (e.g., render1->render2->render3->scheduling
            // component), the chasing mechanism will call stale commits in reverse
            // order.
            //
            // We can use this ordering to delay to find which fallbacks we need to
            // add to the race. Each commit call progressively marks an additional
            // fallback as a scheduling fallback, and does not contribute to the
            // scheduling promises if it is further than the last seen level.
            //
            // This prevents promise contamination where newer renders settle early
            // due to diffs from older renders.
            if (schedulePromises1 && isSchedulingFallback && child) {
                if (!getFlag(child, DidDiff)) {
                    const inflightDiff = getInflightDiff(child);
                    schedulePromises1.push(inflightDiff);
                }
                else {
                    // If a scheduling component's fallback has already diffed, we do not
                    // need delay the render.
                    schedulePromises1 = undefined;
                }
                if (getFlag(child, IsSchedulingFallback)) {
                    // This fallback was marked by a more recent commit - keep processing
                    // deeper levels
                    isSchedulingFallback = true;
                }
                else {
                    // First unmarked fallback we've encountered - mark it and stop
                    // contributing to schedulePromises1 for deeper levels.
                    setFlag(child, IsSchedulingFallback, true);
                    isSchedulingFallback = false;
                }
            }
        }
        if (schedulePromises1 && schedulePromises1.length > 1) {
            schedulePromises.push(safeRace(schedulePromises1));
        }
        if (child) {
            const value = commit(adapter, host, child, ctx, scope, root, index, schedulePromises, hydrationNodes);
            if (Array.isArray(value)) {
                for (let j = 0; j < value.length; j++) {
                    values.push(value[j]);
                }
                index += value.length;
            }
            else if (value) {
                values.push(value);
                index++;
            }
        }
    }
    if (parent.graveyard) {
        for (let i = 0; i < parent.graveyard.length; i++) {
            const child = parent.graveyard[i];
            unmount(adapter, host, ctx, root, child, false);
        }
        parent.graveyard = undefined;
    }
    if (parent.lingerers) {
        // if parent.lingerers is set, a descendant component is unmounting
        // asynchronously, so we overwrite values to include lingerering DOM nodes.
        values = getChildValues(parent);
    }
    return values;
}
function commitText(adapter, ret, el, scope, hydrationNodes, root) {
    const value = adapter.text({
        value: el.props.value,
        scope,
        oldNode: ret.value,
        hydrationNodes,
        root,
    });
    ret.value = value;
    return value;
}
function commitRaw(adapter, host, ret, scope, hydrationNodes, root) {
    if (!ret.oldProps || ret.oldProps.value !== ret.el.props.value) {
        const oldNodes = wrap(ret.value);
        for (let i = 0; i < oldNodes.length; i++) {
            const oldNode = oldNodes[i];
            adapter.remove({
                node: oldNode,
                parentNode: host.value,
                isNested: false,
                root,
            });
        }
        ret.value = adapter.raw({
            value: ret.el.props.value,
            scope,
            hydrationNodes,
            root,
        });
    }
    ret.oldProps = stripSpecialProps(ret.el.props);
    return ret.value;
}
function commitHost(adapter, ret, ctx, root, schedulePromises, hydrationNodes) {
    if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
        return getValue(ret);
    }
    const tag = ret.el.tag;
    const props = stripSpecialProps(ret.el.props);
    const oldProps = ret.oldProps;
    let node = ret.value;
    let copyProps;
    let copyChildren = false;
    if (oldProps) {
        for (const propName in props) {
            if (props[propName] === Copy) {
                // The Copy tag can be used to skip the patching of a prop.
                //   <div class={shouldPatchClass ? "class-name" : Copy} />
                props[propName] = oldProps[propName];
                (copyProps = copyProps || new Set()).add(propName);
            }
        }
        if (typeof ret.el.props.copy === "string") {
            const copyMetaProp = new MetaProp("copy", ret.el.props.copy);
            if (copyMetaProp.include) {
                for (const propName of copyMetaProp.props) {
                    if (propName in oldProps) {
                        props[propName] = oldProps[propName];
                        (copyProps = copyProps || new Set()).add(propName);
                    }
                }
            }
            else {
                for (const propName in oldProps) {
                    if (!copyMetaProp.props.has(propName)) {
                        props[propName] = oldProps[propName];
                        (copyProps = copyProps || new Set()).add(propName);
                    }
                }
            }
            copyChildren = copyMetaProp.includes("children");
        }
    }
    const scope = ret.scope;
    let childHydrationNodes;
    let quietProps;
    let hydrationMetaProp;
    if (!getFlag(ret, DidCommit)) {
        if (tag === Portal) {
            if (ret.el.props.hydrate && typeof ret.el.props.hydrate !== "string") {
                childHydrationNodes = adapter.adopt({
                    tag,
                    tagName: getTagName(tag),
                    node,
                    props,
                    scope,
                    root,
                });
                if (childHydrationNodes) {
                    for (let i = 0; i < childHydrationNodes.length; i++) {
                        adapter.remove({
                            node: childHydrationNodes[i],
                            parentNode: node,
                            isNested: false,
                            root,
                        });
                    }
                }
            }
        }
        else {
            if (!node && hydrationNodes) {
                const nextChild = hydrationNodes.shift();
                if (typeof ret.el.props.hydrate === "string") {
                    hydrationMetaProp = new MetaProp("hydration", ret.el.props.hydrate);
                    if (hydrationMetaProp.include) {
                        // if we're in inclusive mode, we add all props to quietProps and
                        // remove props specified in the metaprop
                        quietProps = new Set(Object.keys(props));
                        for (const propName of hydrationMetaProp.props) {
                            quietProps.delete(propName);
                        }
                    }
                    else {
                        quietProps = hydrationMetaProp.props;
                    }
                }
                childHydrationNodes = adapter.adopt({
                    tag,
                    tagName: getTagName(tag),
                    node: nextChild,
                    props,
                    scope,
                    root,
                });
                if (childHydrationNodes) {
                    node = nextChild;
                    for (let i = 0; i < childHydrationNodes.length; i++) {
                        adapter.remove({
                            node: childHydrationNodes[i],
                            parentNode: node,
                            isNested: false,
                            root,
                        });
                    }
                }
            }
            // TODO: For some reason, there are cases where the node is already set
            // and the DidCommit flag is false. Not checking for node fails a test
            // where a child dispatches an event in a schedule callback, the parent
            // listens for this event and refreshes.
            if (!node) {
                node = adapter.create({
                    tag,
                    tagName: getTagName(tag),
                    props,
                    scope,
                    root,
                });
            }
            ret.value = node;
        }
    }
    if (tag !== Portal) {
        adapter.patch({
            tag,
            tagName: getTagName(tag),
            node,
            props,
            oldProps,
            scope,
            root,
            copyProps,
            isHydrating: !!childHydrationNodes,
            quietProps,
        });
    }
    if (!copyChildren) {
        const children = commitChildren(adapter, ret, ctx, scope, tag === Portal ? node : root, ret, 0, schedulePromises, hydrationMetaProp && !hydrationMetaProp.includes("children")
            ? undefined
            : childHydrationNodes);
        adapter.arrange({
            tag,
            tagName: getTagName(tag),
            node: node,
            props,
            children,
            oldProps,
            scope,
            root,
        });
    }
    ret.oldProps = props;
    if (tag === Portal) {
        flush(adapter, ret.value);
        // The root passed to Portal elements are opaque to parents so we return
        // undefined here.
        return;
    }
    return node;
}
class MetaProp {
    constructor(propName, propValue) {
        this.include = true;
        this.props = new Set();
        let noBangs = true;
        let allBangs = true;
        const tokens = propValue.split(/[,\s]+/);
        for (let i = 0; i < tokens.length; i++) {
            const token = tokens[i].trim();
            if (!token) {
                continue;
            }
            else if (token.startsWith("!")) {
                noBangs = false;
                this.props.add(token.slice(1));
            }
            else {
                allBangs = false;
                this.props.add(token);
            }
        }
        if (!allBangs && !noBangs) {
            console.error(`Invalid ${propName} prop "${propValue}".\nUse prop or !prop but not both.`);
            this.include = true;
            this.props.clear();
        }
        else {
            this.include = noBangs;
        }
    }
    includes(propName) {
        if (this.include) {
            return this.props.has(propName);
        }
        else {
            return !this.props.has(propName);
        }
    }
}
function contextContains(parent, child) {
    for (let current = child; current !== undefined; current = current.parent) {
        if (current === parent) {
            return true;
        }
    }
    return false;
}
// When rendering is done without a root, we use this special anonymous root to
// make sure after callbacks are still called.
const ANONYMOUS_ROOT = {};
function flush(adapter, root, initiator) {
    if (root != null) {
        adapter.finalize(root);
    }
    if (typeof root !== "object" || root === null) {
        root = ANONYMOUS_ROOT;
    }
    // The initiator is the context which initiated the rendering process. If
    // initiator is defined we call and clear all flush callbacks which are
    // registered with the initiator or with a child context of the initiator,
    // because they are fully rendered.
    //
    // If no initiator is provided, we can call and clear all flush callbacks
    // which are not scheduling.
    const afterMap = afterMapByRoot.get(root);
    if (afterMap) {
        const afterMap1 = new Map();
        for (const [ctx, callbacks] of afterMap) {
            if (getFlag(ctx.ret, IsScheduling) ||
                (initiator && !contextContains(initiator, ctx))) {
                // copy over callbacks to the new map (defer them)
                afterMap.delete(ctx);
                afterMap1.set(ctx, callbacks);
            }
        }
        if (afterMap1.size) {
            afterMapByRoot.set(root, afterMap1);
        }
        else {
            afterMapByRoot.delete(root);
        }
        for (const [ctx, callbacks] of afterMap) {
            const value = adapter.read(getValue(ctx.ret));
            for (const callback of callbacks) {
                callback(value);
            }
        }
    }
}
function unmount(adapter, host, ctx, root, ret, isNested) {
    // TODO: set the IsUnmounted flag consistently for all retainers
    if (ret.fallback) {
        unmount(adapter, host, ctx, root, ret.fallback, isNested);
        ret.fallback = undefined;
    }
    if (getFlag(ret, IsResurrecting)) {
        return;
    }
    if (ret.lingerers) {
        for (let i = 0; i < ret.lingerers.length; i++) {
            const lingerers = ret.lingerers[i];
            if (lingerers) {
                for (const lingerer of lingerers) {
                    unmount(adapter, host, ctx, root, lingerer, isNested);
                }
            }
        }
        ret.lingerers = undefined;
    }
    if (typeof ret.el.tag === "function") {
        unmountComponent(ret.ctx, isNested);
    }
    else if (ret.el.tag === Fragment) {
        unmountChildren(adapter, host, ctx, root, ret, isNested);
    }
    else if (ret.el.tag === Portal) {
        unmountChildren(adapter, ret, ctx, ret.value, ret, false);
        if (ret.value != null) {
            adapter.finalize(ret.value);
        }
    }
    else {
        unmountChildren(adapter, ret, ctx, root, ret, true);
        if (getFlag(ret, DidCommit)) {
            if (ctx) {
                // Remove the value from every context which shares the same host.
                removeEventTargetDelegates(ctx.ctx, [ret.value], (ctx1) => ctx1[_ContextState].host === host);
            }
            adapter.remove({
                node: ret.value,
                parentNode: host.value,
                isNested,
                root,
            });
        }
    }
}
function unmountChildren(adapter, host, ctx, root, ret, isNested) {
    if (ret.graveyard) {
        for (let i = 0; i < ret.graveyard.length; i++) {
            const child = ret.graveyard[i];
            unmount(adapter, host, ctx, root, child, isNested);
        }
        ret.graveyard = undefined;
    }
    const rawChildren = ret.children;
    if (Array.isArray(rawChildren)) {
        for (let i = 0; i < rawChildren.length; i++) {
            const child = rawChildren[i];
            if (typeof child === "object") {
                unmount(adapter, host, ctx, root, child, isNested);
            }
        }
    }
    else if (rawChildren !== undefined) {
        unmount(adapter, host, ctx, root, rawChildren, isNested);
    }
}
const provisionMaps = new WeakMap();
const scheduleMap = new WeakMap();
const cleanupMap = new WeakMap();
// keys are roots
const afterMapByRoot = new WeakMap();
// TODO: allow ContextState to be initialized for testing purposes
/**
 * @internal
 * The internal class which holds context data.
 */
class ContextState {
    constructor(adapter, root, host, parent, scope, ret) {
        this.adapter = adapter;
        this.root = root;
        this.host = host;
        this.parent = parent;
        // This property must be set after this.parent is set because the Context
        // constructor reads this.parent.
        this.ctx = new Context(this);
        this.scope = scope;
        this.ret = ret;
        this.iterator = undefined;
        this.inflight = undefined;
        this.enqueued = undefined;
        this.onPropsProvided = undefined;
        this.onPropsRequested = undefined;
        this.pull = undefined;
        this.index = 0;
        this.schedule = undefined;
    }
}
const _ContextState = Symbol.for("crank.ContextState");
/**
 * A class which is instantiated and passed to every component as its this
 * value/second parameter. Contexts form a tree just like elements and all
 * components in the element tree are connected via contexts. Components can
 * use this tree to communicate data upwards via events and downwards via
 * provisions.
 *
 * @template [T=*] - The expected shape of the props passed to the component,
 * or a component function. Used to strongly type the Context iterator methods.
 * @template [TResult=*] - The readable element value type. It is used in
 * places such as the return value of refresh and the argument passed to
 * schedule and cleanup callbacks.
 */
class Context extends CustomEventTarget {
    // TODO: If we could make the constructor function take a nicer value, it
    // would be useful for testing purposes.
    constructor(state) {
        super(state.parent ? state.parent.ctx : null);
        this[_ContextState] = state;
    }
    /**
     * The current props of the associated element.
     */
    get props() {
        return this[_ContextState].ret.el.props;
    }
    /**
     * The current value of the associated element.
     *
     * @deprecated
     */
    get value() {
        console.warn("Context.value is deprecated.");
        return this[_ContextState].adapter.read(getValue(this[_ContextState].ret));
    }
    get isExecuting() {
        return getFlag(this[_ContextState].ret, IsExecuting);
    }
    get isUnmounted() {
        return getFlag(this[_ContextState].ret, IsUnmounted);
    }
    *[Symbol.iterator]() {
        const ctx = this[_ContextState];
        setFlag(ctx.ret, IsInForOfLoop);
        try {
            while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
                if (getFlag(ctx.ret, NeedsToYield)) {
                    throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
                }
                else {
                    setFlag(ctx.ret, NeedsToYield);
                }
                yield ctx.ret.el.props;
            }
        }
        finally {
            setFlag(ctx.ret, IsInForOfLoop, false);
        }
    }
    async *[Symbol.asyncIterator]() {
        const ctx = this[_ContextState];
        setFlag(ctx.ret, IsInForAwaitOfLoop);
        try {
            while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
                if (getFlag(ctx.ret, NeedsToYield)) {
                    throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
                }
                else {
                    setFlag(ctx.ret, NeedsToYield);
                }
                if (getFlag(ctx.ret, PropsAvailable)) {
                    setFlag(ctx.ret, PropsAvailable, false);
                    yield ctx.ret.el.props;
                }
                else {
                    const props = await new Promise((resolve) => (ctx.onPropsProvided = resolve));
                    if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
                        break;
                    }
                    yield props;
                }
                if (ctx.onPropsRequested) {
                    ctx.onPropsRequested();
                    ctx.onPropsRequested = undefined;
                }
            }
        }
        finally {
            setFlag(ctx.ret, IsInForAwaitOfLoop, false);
            if (ctx.onPropsRequested) {
                ctx.onPropsRequested();
                ctx.onPropsRequested = undefined;
            }
        }
    }
    /**
     * Re-executes a component.
     *
     * @param callback - Optional callback to execute before refresh
     * @returns The rendered result of the component or a promise thereof if the
     * component or its children execute asynchronously.
     */
    refresh(callback) {
        const ctx = this[_ContextState];
        if (getFlag(ctx.ret, IsUnmounted)) {
            console.error(`Component <${getTagName(ctx.ret.el.tag)}> is unmounted. Check the isUnmounted property if necessary.`);
            return ctx.adapter.read(getValue(ctx.ret));
        }
        else if (getFlag(ctx.ret, IsExecuting)) {
            console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing Check the isExecuting property if necessary.`);
            return ctx.adapter.read(getValue(ctx.ret));
        }
        if (callback) {
            const result = callback();
            if (isPromiseLike(result)) {
                return Promise.resolve(result).then(() => {
                    if (!getFlag(ctx.ret, IsUnmounted)) {
                        return this.refresh();
                    }
                    return ctx.adapter.read(getValue(ctx.ret));
                });
            }
        }
        if (getFlag(ctx.ret, IsScheduling)) {
            setFlag(ctx.ret, IsSchedulingRefresh);
        }
        const commitLabel = "commit (" + getTagName(ctx.ret.el.tag) + ")";
        let diff;
        const schedulePromises = [];
        try {
            setFlag(ctx.ret, IsRefreshing);
            diff = enqueueComponent(ctx);
            if (isPromiseLike(diff)) {
                return diff
                    .then(() => {
                    markStart(commitLabel);
                    const value = commitComponent(ctx, schedulePromises);
                    measureMark(commitLabel);
                    return ctx.adapter.read(value);
                })
                    .then((result) => {
                    if (schedulePromises.length) {
                        return Promise.all(schedulePromises).then(() => {
                            return ctx.adapter.read(getValue(ctx.ret));
                        });
                    }
                    return result;
                })
                    .catch((err) => {
                    const diff = propagateError(ctx, err, schedulePromises);
                    if (diff) {
                        return diff.then(() => {
                            if (schedulePromises.length) {
                                return Promise.all(schedulePromises).then(() => {
                                    return ctx.adapter.read(getValue(ctx.ret));
                                });
                            }
                            return ctx.adapter.read(getValue(ctx.ret));
                        });
                    }
                    if (schedulePromises.length) {
                        return Promise.all(schedulePromises).then(() => {
                            return ctx.adapter.read(getValue(ctx.ret));
                        });
                    }
                    return ctx.adapter.read(getValue(ctx.ret));
                })
                    .finally(() => setFlag(ctx.ret, IsRefreshing, false));
            }
            markStart(commitLabel);
            const result = ctx.adapter.read(commitComponent(ctx, schedulePromises));
            measureMark(commitLabel);
            if (schedulePromises.length) {
                return Promise.all(schedulePromises).then(() => {
                    return ctx.adapter.read(getValue(ctx.ret));
                });
            }
            return result;
        }
        catch (err) {
            // TODO: await schedulePromises
            const diff = propagateError(ctx, err, schedulePromises);
            if (diff) {
                return diff
                    .then(() => {
                    if (schedulePromises.length) {
                        return Promise.all(schedulePromises).then(() => {
                            return ctx.adapter.read(getValue(ctx.ret));
                        });
                    }
                })
                    .then(() => ctx.adapter.read(getValue(ctx.ret)));
            }
            if (schedulePromises.length) {
                return Promise.all(schedulePromises).then(() => {
                    return ctx.adapter.read(getValue(ctx.ret));
                });
            }
            return ctx.adapter.read(getValue(ctx.ret));
        }
        finally {
            if (!isPromiseLike(diff)) {
                setFlag(ctx.ret, IsRefreshing, false);
            }
        }
    }
    schedule(callback) {
        if (!callback) {
            return new Promise((resolve) => this.schedule(resolve));
        }
        const ctx = this[_ContextState];
        let callbacks = scheduleMap.get(ctx);
        if (!callbacks) {
            callbacks = new Set();
            scheduleMap.set(ctx, callbacks);
        }
        callbacks.add(callback);
    }
    after(callback) {
        if (!callback) {
            return new Promise((resolve) => this.after(resolve));
        }
        const ctx = this[_ContextState];
        const root = ctx.root || ANONYMOUS_ROOT;
        let afterMap = afterMapByRoot.get(root);
        if (!afterMap) {
            afterMap = new Map();
            afterMapByRoot.set(root, afterMap);
        }
        let callbacks = afterMap.get(ctx);
        if (!callbacks) {
            callbacks = new Set();
            afterMap.set(ctx, callbacks);
        }
        callbacks.add(callback);
    }
    flush(callback) {
        console.error("Context.flush() method has been renamed to after()");
        this.after(callback);
    }
    cleanup(callback) {
        if (!callback) {
            return new Promise((resolve) => this.cleanup(resolve));
        }
        const ctx = this[_ContextState];
        if (getFlag(ctx.ret, IsUnmounted)) {
            const value = ctx.adapter.read(getValue(ctx.ret));
            callback(value);
            return;
        }
        let callbacks = cleanupMap.get(ctx);
        if (!callbacks) {
            callbacks = new Set();
            cleanupMap.set(ctx, callbacks);
        }
        callbacks.add(callback);
    }
    consume(key) {
        for (let ctx = this[_ContextState].parent; ctx !== undefined; ctx = ctx.parent) {
            const provisions = provisionMaps.get(ctx);
            if (provisions && provisions.has(key)) {
                return provisions.get(key);
            }
        }
    }
    provide(key, value) {
        const ctx = this[_ContextState];
        let provisions = provisionMaps.get(ctx);
        if (!provisions) {
            provisions = new Map();
            provisionMaps.set(ctx, provisions);
        }
        provisions.set(key, value);
    }
    [CustomEventTarget.dispatchEventOnSelf](ev) {
        const ctx = this[_ContextState];
        // dispatchEvent calls the prop callback if it exists
        let propCallback = ctx.ret.el.props["on" + ev.type];
        if (typeof propCallback === "function") {
            propCallback(ev);
        }
        else {
            for (const propName in ctx.ret.el.props) {
                if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
                    propCallback = ctx.ret.el.props[propName];
                    if (typeof propCallback === "function") {
                        propCallback(ev);
                    }
                }
            }
        }
    }
}
function diffComponent(adapter, root, host, parent, scope, ret) {
    let ctx;
    if (ret.ctx) {
        ctx = ret.ctx;
        if (getFlag(ctx.ret, IsExecuting)) {
            console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing`);
            return;
        }
        else if (ctx.schedule) {
            return ctx.schedule.promise.then(() => {
                return diffComponent(adapter, root, host, parent, scope, ret);
            });
        }
    }
    else {
        ctx = ret.ctx = new ContextState(adapter, root, host, parent, scope, ret);
    }
    setFlag(ctx.ret, IsUpdating);
    return enqueueComponent(ctx);
}
function diffComponentChildren(ctx, children, isYield) {
    if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
        return;
    }
    else if (children === undefined) {
        console.error(`Component <${getTagName(ctx.ret.el.tag)}> has ${isYield ? "yielded" : "returned"} undefined. If this was intentional, ${isYield ? "yield" : "return"} null instead.`);
    }
    let diff;
    try {
        // TODO: Use a different flag here to indicate the component is
        // synchronously rendering children
        // We set the isExecuting flag in case a child component dispatches an event
        // which bubbles to this component and causes a synchronous refresh().
        setFlag(ctx.ret, IsExecuting);
        diff = diffChildren(ctx.adapter, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
        if (diff) {
            diff = diff.catch((err) => handleChildError(ctx, err));
        }
    }
    catch (err) {
        diff = handleChildError(ctx, err);
    }
    finally {
        setFlag(ctx.ret, IsExecuting, false);
    }
    return diff;
}
/** Enqueues and executes the component associated with the context. */
function enqueueComponent(ctx) {
    if (!ctx.inflight) {
        const [block, diff] = runComponent(ctx);
        if (block) {
            // if block is a promise, diff is a promise
            ctx.inflight = [block.finally(() => advanceComponent(ctx)), diff];
        }
        return diff;
    }
    else if (!ctx.enqueued) {
        // The enqueuedBlock and enqueuedDiff properties must be set
        // simultaneously, hence the usage of the Promise constructor.
        let resolve;
        ctx.enqueued = [
            new Promise((resolve1) => (resolve = resolve1)).finally(() => advanceComponent(ctx)),
            ctx.inflight[0].finally(() => {
                const [block, diff] = runComponent(ctx);
                resolve(block);
                return diff;
            }),
        ];
    }
    return ctx.enqueued[1];
}
/** Called when the inflight block promise settles. */
function advanceComponent(ctx) {
    ctx.inflight = ctx.enqueued;
    ctx.enqueued = undefined;
}
/**
 * This function is responsible for executing components, and handling the
 * different component types.
 *
 * @returns {[block, diff]} A tuple where:
 * - block is a promise or undefined which represents the duration during which
 *   the component is blocked.
 * - diff is a promise or undefined which represents the duration for diffing
 *   of children.
 *
 * While a component is blocked, further updates to the component are enqueued.
 *
 * Each component type blocks according to its implementation:
 * - Sync function components never block; when props or state change,
 *   updates are immediately passed to children.
 * - Async function components block only while awaiting their own async work
 *   (e.g., during an await), but do not block while their async children are rendering.
 * - Sync generator components block while their children are rendering;
 *   they only resume once their children have finished.
 * - Async generator components can block in two different ways:
 *   - By default, they behave like sync generator components, blocking while
 *     the component or its children are rendering.
 *   - Within a for await...of loop, they block only while waiting for new
 *     props to be requested, and not while children are rendering.
 */
function runComponent(ctx) {
    if (getFlag(ctx.ret, IsUnmounted)) {
        return [undefined, undefined];
    }
    const ret = ctx.ret;
    const initial = !ctx.iterator;
    const tagName = getTagName(ret.el.tag);
    if (initial) {
        setFlag(ctx.ret, IsExecuting);
        clearEventListeners(ctx.ctx);
        let returned;
        try {
            markStart(tagName);
            returned = ret.el.tag.call(ctx.ctx, ret.el.props, ctx.ctx);
        }
        catch (err) {
            setFlag(ctx.ret, IsErrored);
            throw err;
        }
        finally {
            setFlag(ctx.ret, IsExecuting, false);
        }
        if (isIteratorLike(returned)) {
            ctx.iterator = returned;
        }
        else if (!isPromiseLike(returned)) {
            // sync function component
            measureMark(tagName);
            return [
                undefined,
                diffComponentChildren(ctx, returned, false),
            ];
        }
        else {
            // async function component
            const returned1 = returned instanceof Promise ? returned : Promise.resolve(returned);
            returned1.then(() => measureMark(tagName), () => measureMark(tagName));
            return [
                returned1.catch(NOOP),
                returned1.then((returned) => diffComponentChildren(ctx, returned, false), (err) => {
                    setFlag(ctx.ret, IsErrored);
                    throw err;
                }),
            ];
        }
    }
    let iteration;
    if (initial) {
        try {
            setFlag(ctx.ret, IsExecuting);
            markStart(tagName);
            iteration = ctx.iterator.next();
        }
        catch (err) {
            setFlag(ctx.ret, IsErrored);
            throw err;
        }
        finally {
            setFlag(ctx.ret, IsExecuting, false);
        }
        if (isPromiseLike(iteration)) {
            setFlag(ctx.ret, IsAsyncGen);
        }
        else {
            setFlag(ctx.ret, IsSyncGen);
        }
    }
    if (getFlag(ctx.ret, IsSyncGen)) {
        // sync generator component
        if (!initial) {
            try {
                setFlag(ctx.ret, IsExecuting);
                const oldResult = ctx.adapter.read(getValue(ctx.ret));
                markStart(tagName);
                iteration = ctx.iterator.next(oldResult);
                measureMark(tagName);
            }
            catch (err) {
                setFlag(ctx.ret, IsErrored);
                throw err;
            }
            finally {
                setFlag(ctx.ret, IsExecuting, false);
            }
        }
        else {
            measureMark(tagName);
        }
        if (isPromiseLike(iteration)) {
            throw new Error("Mixed generator component");
        }
        if (getFlag(ctx.ret, IsInForOfLoop) &&
            !getFlag(ctx.ret, NeedsToYield) &&
            !getFlag(ctx.ret, IsUnmounted) &&
            !getFlag(ctx.ret, IsSchedulingRefresh)) {
            console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
        }
        setFlag(ctx.ret, NeedsToYield, false);
        setFlag(ctx.ret, IsSchedulingRefresh, false);
        if (iteration.done) {
            setFlag(ctx.ret, IsSyncGen, false);
            ctx.iterator = undefined;
        }
        const diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
        const block = isPromiseLike(diff) ? diff.catch(NOOP) : undefined;
        return [block, diff];
    }
    else {
        if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
            // initializes the async generator loop
            measureMark(tagName);
            pullComponent(ctx, iteration);
            const block = resumePropsAsyncIterator(ctx);
            return [block, ctx.pull && ctx.pull.diff];
        }
        else {
            // We call resumePropsAsyncIterator in case the component exits the
            // for...of loop
            resumePropsAsyncIterator(ctx);
            if (!initial) {
                try {
                    setFlag(ctx.ret, IsExecuting);
                    const oldResult = ctx.adapter.read(getValue(ctx.ret));
                    markStart(tagName);
                    iteration = ctx.iterator.next(oldResult);
                }
                catch (err) {
                    setFlag(ctx.ret, IsErrored);
                    throw err;
                }
                finally {
                    setFlag(ctx.ret, IsExecuting, false);
                }
            }
            if (!isPromiseLike(iteration)) {
                throw new Error("Mixed generator component");
            }
            iteration.then(() => measureMark(tagName), () => measureMark(tagName));
            const diff = iteration.then((iteration) => {
                if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
                    // We have entered a for await...of loop, so we start pulling
                    pullComponent(ctx, iteration);
                }
                else {
                    if (getFlag(ctx.ret, IsInForOfLoop) &&
                        !getFlag(ctx.ret, NeedsToYield) &&
                        !getFlag(ctx.ret, IsUnmounted) &&
                        !getFlag(ctx.ret, IsSchedulingRefresh)) {
                        console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
                    }
                }
                setFlag(ctx.ret, NeedsToYield, false);
                setFlag(ctx.ret, IsSchedulingRefresh, false);
                if (iteration.done) {
                    setFlag(ctx.ret, IsAsyncGen, false);
                    ctx.iterator = undefined;
                }
                return diffComponentChildren(ctx, 
                // Children can be void so we eliminate that here
                iteration.value, !iteration.done);
            }, (err) => {
                setFlag(ctx.ret, IsErrored);
                throw err;
            });
            return [diff.catch(NOOP), diff];
        }
    }
}
/**
 * Called to resume the props async iterator for async generator components.
 *
 * @returns {Promise<undefined> | undefined} A possible promise which
 * represents the duration during which the component is blocked.
 */
function resumePropsAsyncIterator(ctx) {
    if (ctx.onPropsProvided) {
        ctx.onPropsProvided(ctx.ret.el.props);
        ctx.onPropsProvided = undefined;
        setFlag(ctx.ret, PropsAvailable, false);
    }
    else {
        setFlag(ctx.ret, PropsAvailable);
        if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
            return new Promise((resolve) => (ctx.onPropsRequested = resolve));
        }
    }
    return (ctx.pull && ctx.pull.iterationP && ctx.pull.iterationP.then(NOOP, NOOP));
}
/**
 * The logic for pulling from async generator components when they are in a for
 * await...of loop is implemented here.
 *
 * It makes sense to group this logic in a single async loop to prevent race
 * conditions caused by calling next(), throw() and return() concurrently.
 */
async function pullComponent(ctx, iterationP) {
    if (!iterationP || ctx.pull) {
        return;
    }
    ctx.pull = { iterationP: undefined, diff: undefined, onChildError: undefined };
    // TODO: replace done with iteration
    //let iteration: ChildrenIteratorResult | undefined;
    let done = false;
    try {
        let childError;
        while (!done) {
            if (isPromiseLike(iterationP)) {
                ctx.pull.iterationP = iterationP;
            }
            let onDiff;
            ctx.pull.diff = new Promise((resolve) => (onDiff = resolve)).then(() => {
                if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing))) {
                    commitComponent(ctx, []);
                }
            }, (err) => {
                if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing)) ||
                    // TODO: is this flag necessary?
                    !getFlag(ctx.ret, NeedsToYield)) {
                    return propagateError(ctx, err, []);
                }
                throw err;
            });
            let iteration;
            try {
                iteration = await iterationP;
            }
            catch (err) {
                done = true;
                setFlag(ctx.ret, IsErrored);
                setFlag(ctx.ret, NeedsToYield, false);
                onDiff(Promise.reject(err));
                break;
            }
            // this must be set after iterationP is awaited
            let oldResult;
            {
                // The 'floating' flag tracks whether the promise passed to the generator
                // is handled (via await, then, or catch). If handled, we reject the
                // promise so the user can catch errors. If not, we inject the error back
                // into the generator using throw, like for sync generator components.
                let floating = true;
                const oldResult1 = new Promise((resolve, reject) => {
                    ctx.ctx.schedule(resolve);
                    ctx.pull.onChildError = (err) => {
                        reject(err);
                        if (floating) {
                            childError = err;
                            resumePropsAsyncIterator(ctx);
                            return ctx.pull.diff;
                        }
                    };
                });
                oldResult1.catch(NOOP);
                // We use Object.create() to clone the promise for float detection
                // because modern JS engines skip calling .then() on promises awaited
                // with await.
                oldResult = Object.create(oldResult1);
                oldResult.then = function (onfulfilled, onrejected) {
                    floating = false;
                    return oldResult1.then(onfulfilled, onrejected);
                };
                oldResult.catch = function (onrejected) {
                    floating = false;
                    return oldResult1.catch(onrejected);
                };
            }
            if (childError != null) {
                try {
                    setFlag(ctx.ret, IsExecuting);
                    if (typeof ctx.iterator.throw !== "function") {
                        throw childError;
                    }
                    iteration = await ctx.iterator.throw(childError);
                }
                catch (err) {
                    done = true;
                    setFlag(ctx.ret, IsErrored);
                    setFlag(ctx.ret, NeedsToYield, false);
                    onDiff(Promise.reject(err));
                    break;
                }
                finally {
                    childError = undefined;
                    setFlag(ctx.ret, IsExecuting, false);
                }
            }
            // this makes sure we pause before entering a loop if we yield before it
            if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
                setFlag(ctx.ret, PropsAvailable, false);
            }
            done = !!iteration.done;
            let diff;
            try {
                if (!isPromiseLike(iterationP)) {
                    // if iterationP is an iteration and not a promise, the component was
                    // not in a for await...of loop when the iteration started, so we can
                    // skip the diffing of children as it is handled elsewhere.
                    diff = undefined;
                }
                else if (!getFlag(ctx.ret, NeedsToYield) &&
                    getFlag(ctx.ret, PropsAvailable) &&
                    getFlag(ctx.ret, IsInForAwaitOfLoop)) {
                    // logic to skip yielded children in a stale for await of iteration.
                    diff = undefined;
                }
                else {
                    diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
                }
            }
            catch (err) {
                onDiff(Promise.reject(err));
            }
            finally {
                onDiff(diff);
                setFlag(ctx.ret, NeedsToYield, false);
            }
            if (getFlag(ctx.ret, IsUnmounted)) {
                // TODO: move this unmounted branch outside the loop
                while ((!iteration || !iteration.done) &&
                    ctx.iterator &&
                    getFlag(ctx.ret, IsInForAwaitOfLoop)) {
                    try {
                        setFlag(ctx.ret, IsExecuting);
                        iteration = await ctx.iterator.next(oldResult);
                    }
                    catch (err) {
                        setFlag(ctx.ret, IsErrored);
                        // we throw the error here to cause an unhandled rejection because
                        // the promise returned from pullComponent is never awaited
                        throw err;
                    }
                    finally {
                        setFlag(ctx.ret, IsExecuting, false);
                    }
                }
                if ((!iteration || !iteration.done) &&
                    ctx.iterator &&
                    typeof ctx.iterator.return === "function") {
                    try {
                        setFlag(ctx.ret, IsExecuting);
                        await ctx.iterator.return();
                    }
                    catch (err) {
                        setFlag(ctx.ret, IsErrored);
                        throw err;
                    }
                    finally {
                        setFlag(ctx.ret, IsExecuting, false);
                    }
                }
                break;
            }
            else if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
                // we have exited the for...await of, so updates will be handled by the
                // regular runComponent/enqueueComponent logic.
                break;
            }
            else if (!iteration.done) {
                try {
                    setFlag(ctx.ret, IsExecuting);
                    iterationP = ctx.iterator.next(oldResult);
                }
                finally {
                    setFlag(ctx.ret, IsExecuting, false);
                }
            }
        }
    }
    finally {
        if (done) {
            setFlag(ctx.ret, IsAsyncGen, false);
            ctx.iterator = undefined;
        }
        ctx.pull = undefined;
    }
}
function commitComponent(ctx, schedulePromises, hydrationNodes) {
    if (ctx.schedule) {
        ctx.schedule.promise.then(() => {
            commitComponent(ctx, []);
            propagateComponent(ctx);
        });
        return getValue(ctx.ret);
    }
    const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.root, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
    if (getFlag(ctx.ret, IsUnmounted)) {
        return;
    }
    addEventTargetDelegates(ctx.ctx, values);
    // Execute schedule callbacks early to check for async deferral
    const wasScheduling = getFlag(ctx.ret, IsScheduling);
    let schedulePromises1;
    const callbacks = scheduleMap.get(ctx);
    if (callbacks) {
        scheduleMap.delete(ctx);
        setFlag(ctx.ret, IsScheduling);
        const result = ctx.adapter.read(unwrap(values));
        for (const callback of callbacks) {
            const scheduleResult = callback(result);
            if (isPromiseLike(scheduleResult)) {
                (schedulePromises1 = schedulePromises1 || []).push(scheduleResult);
            }
        }
        if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
            const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
                setFlag(ctx.ret, IsScheduling, wasScheduling);
                propagateComponent(ctx);
                if (ctx.ret.fallback) {
                    unmount(ctx.adapter, ctx.host, ctx.parent, ctx.root, ctx.ret.fallback, false);
                }
                ctx.ret.fallback = undefined;
            });
            let onAbort;
            const scheduleP = safeRace([
                scheduleCallbacksP,
                new Promise((resolve) => (onAbort = resolve)),
            ]).finally(() => {
                ctx.schedule = undefined;
            });
            ctx.schedule = { promise: scheduleP, onAbort };
            schedulePromises.push(scheduleP);
        }
        else {
            setFlag(ctx.ret, IsScheduling, wasScheduling);
        }
    }
    else {
        setFlag(ctx.ret, IsScheduling, wasScheduling);
    }
    if (!getFlag(ctx.ret, IsScheduling)) {
        if (!getFlag(ctx.ret, IsUpdating)) {
            propagateComponent(ctx);
        }
        if (ctx.ret.fallback) {
            unmount(ctx.adapter, ctx.host, ctx.parent, ctx.root, ctx.ret.fallback, false);
        }
        ctx.ret.fallback = undefined;
        setFlag(ctx.ret, IsUpdating, false);
    }
    setFlag(ctx.ret, DidCommit);
    // We always use getValue() instead of the unwrapping values because there
    // are various ways in which the values could have been updated, especially
    // if schedule callbacks call refresh() or async mounting is happening.
    return getValue(ctx.ret, true);
}
/**
 * Checks if a target retainer is active (contributing) in the host's retainer tree.
 * Performs a downward traversal from host to find if target is in the active path.
 */
function isRetainerActive(target, host) {
    const stack = [host];
    while (stack.length > 0) {
        const current = stack.pop();
        if (current === target) {
            return true;
        }
        // Add direct children to stack (skip if this is a host boundary)
        // Host boundaries are: DOM elements (string tags) or Portal, but NOT Fragment
        const isHostBoundary = current !== host &&
            ((typeof current.el.tag === "string" && current.el.tag !== Fragment) ||
                current.el.tag === Portal);
        if (current.children && !isHostBoundary) {
            if (Array.isArray(current.children)) {
                for (const child of current.children) {
                    if (child) {
                        stack.push(child);
                    }
                }
            }
            else {
                stack.push(current.children);
            }
        }
        // Add fallback chains (only if current retainer is using fallback)
        if (current.fallback && !getFlag(current, DidDiff)) {
            stack.push(current.fallback);
        }
    }
    return false;
}
/**
 * Propagates component changes up to ancestors when rendering starts from a
 * component via refresh() or multiple for await...of renders. This handles
 * event listeners and DOM arrangement that would normally happen during
 * top-down rendering.
 */
function propagateComponent(ctx) {
    const values = getChildValues(ctx.ret, ctx.index);
    addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
    const host = ctx.host;
    const initiator = ctx.ret;
    // Check if initiator is active in the host's tree
    if (!isRetainerActive(initiator, host)) {
        return;
    }
    // Check if host has been committed (has a node)
    // Fixes #334: refresh() called before component yields
    if (!getFlag(host, DidCommit)) {
        return;
    }
    const props = stripSpecialProps(host.el.props);
    const hostChildren = getChildValues(host, 0);
    ctx.adapter.arrange({
        tag: host.el.tag,
        tagName: getTagName(host.el.tag),
        node: host.value,
        props,
        oldProps: props,
        children: hostChildren,
        scope: host.scope,
        root: ctx.root,
    });
    flush(ctx.adapter, ctx.root, ctx);
}
async function unmountComponent(ctx, isNested) {
    if (getFlag(ctx.ret, IsUnmounted)) {
        return;
    }
    let cleanupPromises;
    // TODO: think about errror handling for callbacks
    const callbacks = cleanupMap.get(ctx);
    if (callbacks) {
        const oldResult = ctx.adapter.read(getValue(ctx.ret));
        cleanupMap.delete(ctx);
        for (const callback of callbacks) {
            const cleanup = callback(oldResult);
            if (isPromiseLike(cleanup)) {
                (cleanupPromises = cleanupPromises || []).push(cleanup);
            }
        }
    }
    let didLinger = false;
    if (!isNested && cleanupPromises) {
        didLinger = true;
        const index = ctx.index;
        const lingerers = ctx.host.lingerers || (ctx.host.lingerers = []);
        let set = lingerers[index];
        if (set == null) {
            set = new Set();
            lingerers[index] = set;
        }
        set.add(ctx.ret);
        await Promise.all(cleanupPromises);
        set.delete(ctx.ret);
        if (set.size === 0) {
            lingerers[index] = undefined;
        }
        if (!lingerers.some(Boolean)) {
            // If there are no lingerers remaining, we can remove the lingerers array
            ctx.host.lingerers = undefined;
        }
    }
    if (getFlag(ctx.ret, IsUnmounted)) {
        // If the component was unmounted while awaiting the cleanup callbacks,
        // we do not need to continue unmounting.
        return;
    }
    setFlag(ctx.ret, IsUnmounted);
    // If component has pending schedule promises, resolve them since component
    // is unmounting
    if (ctx.schedule) {
        ctx.schedule.onAbort();
        ctx.schedule = undefined;
    }
    clearEventListeners(ctx.ctx);
    unmountChildren(ctx.adapter, ctx.host, ctx, ctx.root, ctx.ret, isNested);
    if (didLinger) {
        // If we lingered, we call finalize to ensure rendering is finalized
        if (ctx.root != null) {
            ctx.adapter.finalize(ctx.root);
        }
    }
    if (ctx.iterator) {
        if (ctx.pull) {
            // we let pullComponent handle unmounting
            resumePropsAsyncIterator(ctx);
            return;
        }
        // we wait for inflight value so yields resume with the most up to date
        // props
        if (ctx.inflight) {
            await ctx.inflight[1];
        }
        let iteration;
        if (getFlag(ctx.ret, IsInForOfLoop)) {
            try {
                setFlag(ctx.ret, IsExecuting);
                const oldResult = ctx.adapter.read(getValue(ctx.ret));
                const iterationP = ctx.iterator.next(oldResult);
                if (isPromiseLike(iterationP)) {
                    if (!getFlag(ctx.ret, IsAsyncGen)) {
                        throw new Error("Mixed generator component");
                    }
                    iteration = await iterationP;
                }
                else {
                    if (!getFlag(ctx.ret, IsSyncGen)) {
                        throw new Error("Mixed generator component");
                    }
                    iteration = iterationP;
                }
            }
            catch (err) {
                setFlag(ctx.ret, IsErrored);
                throw err;
            }
            finally {
                setFlag(ctx.ret, IsExecuting, false);
            }
        }
        if ((!iteration || !iteration.done) &&
            ctx.iterator &&
            typeof ctx.iterator.return === "function") {
            try {
                setFlag(ctx.ret, IsExecuting);
                const iterationP = ctx.iterator.return();
                if (isPromiseLike(iterationP)) {
                    if (!getFlag(ctx.ret, IsAsyncGen)) {
                        throw new Error("Mixed generator component");
                    }
                    iteration = await iterationP;
                }
                else {
                    if (!getFlag(ctx.ret, IsSyncGen)) {
                        throw new Error("Mixed generator component");
                    }
                    iteration = iterationP;
                }
            }
            catch (err) {
                setFlag(ctx.ret, IsErrored);
                throw err;
            }
            finally {
                setFlag(ctx.ret, IsExecuting, false);
            }
        }
    }
}
/*** ERROR HANDLING UTILITIES ***/
function handleChildError(ctx, err) {
    if (!ctx.iterator) {
        throw err;
    }
    if (ctx.pull) {
        // we let pullComponent handle child errors
        ctx.pull.onChildError(err);
        return ctx.pull.diff;
    }
    if (!ctx.iterator.throw) {
        throw err;
    }
    resumePropsAsyncIterator(ctx);
    let iteration;
    try {
        setFlag(ctx.ret, IsExecuting);
        iteration = ctx.iterator.throw(err);
    }
    catch (err) {
        setFlag(ctx.ret, IsErrored);
        throw err;
    }
    finally {
        setFlag(ctx.ret, IsExecuting, false);
    }
    if (isPromiseLike(iteration)) {
        return iteration.then((iteration) => {
            if (iteration.done) {
                setFlag(ctx.ret, IsSyncGen, false);
                setFlag(ctx.ret, IsAsyncGen, false);
                ctx.iterator = undefined;
            }
            return diffComponentChildren(ctx, iteration.value, !iteration.done);
        }, (err) => {
            setFlag(ctx.ret, IsErrored);
            throw err;
        });
    }
    if (iteration.done) {
        setFlag(ctx.ret, IsSyncGen, false);
        setFlag(ctx.ret, IsAsyncGen, false);
        ctx.iterator = undefined;
    }
    return diffComponentChildren(ctx, iteration.value, !iteration.done);
}
/**
 * Propagates an error up the context tree by calling handleChildError with
 * each parent.
 *
 * @returns A promise which resolves to undefined when the error has been
 * handled, or undefined if the error was handled synchronously.
 */
function propagateError(ctx, err, schedulePromises) {
    const parent = ctx.parent;
    if (!parent) {
        throw err;
    }
    let diff;
    try {
        diff = handleChildError(parent, err);
    }
    catch (err) {
        return propagateError(parent, err, schedulePromises);
    }
    if (isPromiseLike(diff)) {
        return diff.then(() => void commitComponent(parent, schedulePromises), (err) => propagateError(parent, err, schedulePromises));
    }
    commitComponent(parent, schedulePromises);
}
var crank = { createElement, Fragment };

export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, Text, cloneElement, createElement, crank as default, isElement };
//# sourceMappingURL=crank.js.map
