/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import { RuntimeError } from '../../errors';
import { assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan } from '../../util/assert';
import { assertTNode, assertTNodeForLView } from '../assert';
import { TYPE } from '../interfaces/container';
import { isLContainer, isLView } from '../interfaces/type_checks';
import { DESCENDANT_VIEWS_TO_REFRESH, FLAGS, HEADER_OFFSET, HOST, ON_DESTROY_HOOKS, PARENT, PREORDER_HOOK_FLAGS } from '../interfaces/view';
/**
 * For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`)
 * in same location in `LView`. This is because we don't want to pre-allocate space for it
 * because the storage is sparse. This file contains utilities for dealing with such data types.
 *
 * How do we know what is stored at a given location in `LView`.
 * - `Array.isArray(value) === false` => `RNode` (The normal storage value)
 * - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value.
 *   - `typeof value[TYPE] === 'object'` => `LView`
 *      - This happens when we have a component at a given location
 *   - `typeof value[TYPE] === true` => `LContainer`
 *      - This happens when we have `LContainer` binding at a given location.
 *
 *
 * NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient.
 */
/**
 * Returns `RNode`.
 * @param value wrapped value of `RNode`, `LView`, `LContainer`
 */
export function unwrapRNode(value) {
    while (Array.isArray(value)) {
        value = value[HOST];
    }
    return value;
}
/**
 * Returns `LView` or `null` if not found.
 * @param value wrapped value of `RNode`, `LView`, `LContainer`
 */
export function unwrapLView(value) {
    while (Array.isArray(value)) {
        // This check is same as `isLView()` but we don't call at as we don't want to call
        // `Array.isArray()` twice and give JITer more work for inlining.
        if (typeof value[TYPE] === 'object')
            return value;
        value = value[HOST];
    }
    return null;
}
/**
 * Retrieves an element value from the provided `viewData`, by unwrapping
 * from any containers, component views, or style contexts.
 */
export function getNativeByIndex(index, lView) {
    ngDevMode && assertIndexInRange(lView, index);
    ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET');
    return unwrapRNode(lView[index]);
}
/**
 * Retrieve an `RNode` for a given `TNode` and `LView`.
 *
 * This function guarantees in dev mode to retrieve a non-null `RNode`.
 *
 * @param tNode
 * @param lView
 */
export function getNativeByTNode(tNode, lView) {
    ngDevMode && assertTNodeForLView(tNode, lView);
    ngDevMode && assertIndexInRange(lView, tNode.index);
    const node = unwrapRNode(lView[tNode.index]);
    return node;
}
/**
 * Retrieve an `RNode` or `null` for a given `TNode` and `LView`.
 *
 * Some `TNode`s don't have associated `RNode`s. For example `Projection`
 *
 * @param tNode
 * @param lView
 */
export function getNativeByTNodeOrNull(tNode, lView) {
    const index = tNode === null ? -1 : tNode.index;
    if (index !== -1) {
        ngDevMode && assertTNodeForLView(tNode, lView);
        const node = unwrapRNode(lView[index]);
        return node;
    }
    return null;
}
// fixme(misko): The return Type should be `TNode|null`
export function getTNode(tView, index) {
    ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
    ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
    const tNode = tView.data[index];
    ngDevMode && tNode !== null && assertTNode(tNode);
    return tNode;
}
/** Retrieves a value from any `LView` or `TData`. */
export function load(view, index) {
    ngDevMode && assertIndexInRange(view, index);
    return view[index];
}
export function getComponentLViewByIndex(nodeIndex, hostView) {
    // Could be an LView or an LContainer. If LContainer, unwrap to find LView.
    ngDevMode && assertIndexInRange(hostView, nodeIndex);
    const slotValue = hostView[nodeIndex];
    const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
    return lView;
}
/** Checks whether a given view is in creation mode */
export function isCreationMode(view) {
    return (view[FLAGS] & 4 /* LViewFlags.CreationMode */) === 4 /* LViewFlags.CreationMode */;
}
/**
 * Returns a boolean for whether the view is attached to the change detection tree.
 *
 * Note: This determines whether a view should be checked, not whether it's inserted
 * into a container. For that, you'll want `viewAttachedToContainer` below.
 */
export function viewAttachedToChangeDetector(view) {
    return (view[FLAGS] & 128 /* LViewFlags.Attached */) === 128 /* LViewFlags.Attached */;
}
/** Returns a boolean for whether the view is attached to a container. */
export function viewAttachedToContainer(view) {
    return isLContainer(view[PARENT]);
}
export function getConstant(consts, index) {
    if (index === null || index === undefined)
        return null;
    ngDevMode && assertIndexInRange(consts, index);
    return consts[index];
}
/**
 * Resets the pre-order hook flags of the view.
 * @param lView the LView on which the flags are reset
 */
export function resetPreOrderHookFlags(lView) {
    lView[PREORDER_HOOK_FLAGS] = 0;
}
/**
 * Adds the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of
 * parents.
 */
export function markViewForRefresh(lView) {
    if ((lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) === 0) {
        lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */;
        updateViewsToRefresh(lView, 1);
    }
}
/**
 * Removes the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of
 * parents.
 */
export function clearViewRefreshFlag(lView) {
    if (lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) {
        lView[FLAGS] &= ~1024 /* LViewFlags.RefreshView */;
        updateViewsToRefresh(lView, -1);
    }
}
/**
 * Updates the `DESCENDANT_VIEWS_TO_REFRESH` counter on the parents of the `LView` as well as the
 * parents above that whose
 *  1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh
 *  or
 *  2. counter goes from 1 to 0, indicating there are no more descendant views to refresh
 */
function updateViewsToRefresh(lView, amount) {
    let parent = lView[PARENT];
    if (parent === null) {
        return;
    }
    parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
    let viewOrContainer = parent;
    parent = parent[PARENT];
    while (parent !== null &&
        ((amount === 1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 1) ||
            (amount === -1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 0))) {
        parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
        viewOrContainer = parent;
        parent = parent[PARENT];
    }
}
/**
 * Stores a LView-specific destroy callback.
 */
export function storeLViewOnDestroy(lView, onDestroyCallback) {
    if ((lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */) {
        throw new RuntimeError(911 /* RuntimeErrorCode.VIEW_ALREADY_DESTROYED */, ngDevMode && 'View has already been destroyed.');
    }
    if (lView[ON_DESTROY_HOOKS] === null) {
        lView[ON_DESTROY_HOOKS] = [];
    }
    lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
}
/**
 * Removes previously registered LView-specific destroy callback.
 */
export function removeLViewOnDestroy(lView, onDestroyCallback) {
    if (lView[ON_DESTROY_HOOKS] === null)
        return;
    const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
    if (destroyCBIdx !== -1) {
        lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
    }
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 18468 characters]