import { Component } from '../component-class';
import { ComponentLike, DOMApi } from '../types';
import { getFirstNode } from '../render-core';
import { Cell, MergedCell } from '../reactive';
import { RENDERED_NODES_PROPERTY, COMPONENT_ID_PROPERTY } from '../shared';

export { getFirstNode };
type GenericReturnType = Array<ComponentLike | Node> | ComponentLike | Node;
export type InverseFn = (ctx: Component<any>) => GenericReturnType | null;
type ListComponentArgs<T> = {
    tag: Cell<T[]> | MergedCell;
    key: string | null;
    ctx: Component<any>;
    ItemComponent: (item: T, index?: number | MergedCell) => GenericReturnType;
    inverseFn?: InverseFn;
    hasIndex?: boolean;
};
type RenderTarget = HTMLElement | DocumentFragment;
/**
 * Normalize an arbitrary `{{#each}}` input value into a real Array<T>.
 *
 * Glimmer-VM's `{{#each}}` semantics treat any non-array, non-iterable
 * value as falsy (renders the inverse). For iterables that aren't plain
 * arrays (Set, Map, custom Symbol.iterator classes, generators), the body
 * iterates with the spread elements. Ember's ArrayProxy is normalized via
 * its `.content` slot.
 *
 * Returns:
 *   - The same array if `Array.isArray(value)` (covers plain arrays and
 *     Ember `A()` / NativeArray, which IS-A Array).
 *   - `[...value]` if `value[Symbol.iterator]` is callable.
 *   - Recursive normalization on `value.content` for ArrayProxy-shaped
 *     objects (defensive: falls back to `[]` if access throws or the proxy
 *     is destroyed).
 *   - `[]` for everything else (null, undefined, false, '', 0, NaN, true,
 *     non-iterable strings, plain objects, functions, numbers).
 *
 * Strings are intentionally treated as `[]` — Glimmer's `{{#each}}` does
 * NOT iterate string characters, and the upstream tests require `'hello'`
 * to render the inverse block.
 */
export declare function normalizeIterableValue<T>(value: unknown): T[];
/**
 * Compute positions in `arr` that form the Longest Increasing Subsequence.
 * Items at these positions are already in correct relative order and don't
 * need to be relocated.  O(n log n) time, O(n) space (reused).
 */
export declare function longestIncreasingSubsequence(arr: number[], out?: Set<number>): Set<number>;
export declare class BasicListComponent<T extends {
    id: number;
}> {
    keyMap: Map<string, GenericReturnType>;
    indexMap: Map<string, number>;
    indexFormulaMap: Map<string, MergedCell> | null;
    itemMarkers: Map<string, Comment>;
    markerSet: Set<Comment>;
    private _existKeys;
    private _existNewIdx;
    private _existOldIdx;
    private _itemKeys;
    private _lisResult;
    private _updatingKeys;
    private _moveSet;
    private _freshMoveKeys;
    private _processedKeys;
    protected _keysToRemove: string[];
    protected _rowsToRemove: GenericReturnType[];
    [RENDERED_NODES_PROPERTY]: Array<Node>;
    [COMPONENT_ID_PROPERTY]: number;
    ItemComponent: (item: T, index: number | MergedCell, ctx: Component<any>) => GenericReturnType;
    inverseFn: InverseFn | null;
    inverseContent: GenericReturnType | null;
    bottomMarker: Comment;
    topMarker: Comment;
    key: string;
    tag: Cell<T[]> | MergedCell;
    isFirstRender: boolean;
    get ctx(): this;
    protected keysForItems(items: T[], keyForItem: (item: T, index: number, items: T[]) => string): Set<string>;
    /**
     * Detach this list's child-id set before bulk destruction.
     *
     * This lets child destructors skip parent-sibling bookkeeping and avoids
     * allocating a replacement empty Set on every fast cleanup.
     */
    protected detachTreeChildren(): void;
    /**
     * Fast-path for updates that preserve all existing items and only append
     * new ones at the end.
     *
     * We can safely skip the removal scan only when every old position still
     * points to the same key in the incoming list prefix.
     */
    protected isAppendOnlySuperset(items: T[], amountOfKeys: number, keyForItem: (item: T, index: number, items: T[]) => string): boolean;
    private _relocateFragment;
    api: DOMApi;
    hasIndex: boolean;
    constructor({ tag, ctx, key, ItemComponent, inverseFn, hasIndex }: ListComponentArgs<T>, outlet: RenderTarget, topMarker: Comment);
    private relocateItem;
    protected removeMarker(key: string): void;
    /**
     * Per-`items[]` first-occurrence cache for duplicate-key qualification.
     *
     * Both `@identity` and explicit-key paths have to detect when a base key
     * (object identity, or the value of `item[this.key]`) has already been
     * seen at an earlier index in the *current* items array, so subsequent
     * occurrences can be position-qualified (`baseKey:i`) and treated as
     * distinct rows by the diff algorithm.
     *
     * Two-phase strategy to avoid per-syncList Map allocation in the
     * overwhelmingly common no-duplicates case (krausest, sane apps):
     *
     *  1. First call for a fresh items[] does a single pass over items[]
     *     adding every base key to a reusable instance Set
     *     (`_dupDetectSet`). If the Set's final size equals items.length,
     *     there are no dupes — we set `_dupHasDupes = false` and return.
     *     No Map is allocated; no entry object is allocated.
     *
     *  2. If dupes ARE detected, we lazily build the Map<baseKey,
     *     firstIndex> on the SAME pass (using a reusable instance Map,
     *     `_dupFirstIdxMap`) and set `_dupHasDupes = true`.
     *
     * The cached verdict is keyed by `_dupItemsRef`, an instance-level
     * single-slot identity cache. Per-row callers compare `items` against
     * `_dupItemsRef`; if they match, the cached verdict is consulted. Otherwise
     * detection runs.
     *
     * The cache is invalidated explicitly at the top of every `syncList`
     * (and `_dupItemsRef` is set to null) — it's intentionally narrow-scoped
     * to a single sync pass. Inside one syncList we may receive several calls
     * to `keyForItem` from `isAppendOnlySuperset`, `keysForItems`, and
     * `updateItems`; the first hit pays the O(n) detection cost, all
     * subsequent calls hit the cached verdict.
     */
    protected _dupItemsRef: T[] | null;
    protected _dupHasDupes: boolean;
    protected _dupDetectSet: Set<string>;
    protected _dupFirstIdxMap: Map<string, number>;
    /**
     * Run dedup detection for `items[]` if it differs from the cached ref.
     * After return, `_dupHasDupes` and (if true) `_dupFirstIdxMap` are
     * populated. Returns true if dupes were detected.
     *
     * `_dupDetectSet` is cleared once at the start of detection. We don't
     * clear it on the no-dupes path (the contents are scratch and will be
     * cleared on next detection). We don't clear `_dupFirstIdxMap` on the
     * no-dupes path either — `_dupHasDupes === false` ensures callers won't
     * read it; we clear lazily on the next `_dupHasDupes = true` transition.
     */
    private detectDupes;
    private setupKeyForItem;
    renderInverse(): void;
    destroyInverseSync(): void;
    /**
     * Remove all DOM nodes between topMarker and bottomMarker.
     * Used by destroyInverseSync/Async to ensure inverse content is fully cleaned up
     * regardless of RENDERED_NODES_PROPERTY state.
     */
    protected clearInverseNodes(): void;
    destroyInverseAsync(): Promise<void>;
    keyForItem(item: T, index: number, items?: T[]): string;
    private getTargetNode;
    updateItems(items: T[], amountOfKeys: number, removedCount: number): void;
}
export declare class SyncListComponent<T extends {
    id: number;
}> extends BasicListComponent<T> {
    private _syncInProgress;
    constructor(params: ListComponentArgs<T>, outlet: RenderTarget, topMarker: Comment);
    fastCleanup(): boolean;
    syncList(items: T[]): void;
    destroyItem(row: GenericReturnType, key: string): void;
}
export declare class AsyncListComponent<T extends {
    id: number;
}> extends BasicListComponent<T> {
    destroyPromise: Promise<void[]> | null;
    constructor(params: ListComponentArgs<any>, outlet: RenderTarget, topMarker: Comment);
    fastCleanup(): Promise<boolean>;
    syncList(items: T[]): Promise<void>;
    destroyItem(row: GenericReturnType, key: string): Promise<void>;
}
