import { EventManager } from '../../utils/builder';
import { RanElement } from '../../utils';
import type { ConversationNode, ConversationNodeDefinition } from 'ranuts/conversation';
/**
 * Name of the event the paging affordance fires.
 *
 * The element does not fetch anything — it does not know where a conversation lives. It
 * states that the reader asked, having already captured the anchor; the owner prepends its
 * rows and calls {@link Conversation.restoreAnchor}.
 */
export declare const OLDER_REQUEST = "olderrequest";
/**
 * One kind of conversation content: the state machine that owns its events, plus how to
 * put its state on screen.
 *
 * The two halves are declared together because a kind that renders nothing and a kind that
 * renders are the same thing to the projection — only `mount` differs. Omit `mount` for a
 * definition that exists so other definitions can read it through `reader.previous`.
 */
export interface ConversationNodeView<Event = unknown, State = unknown> extends ConversationNodeDefinition<Event, State> {
    /**
     * Builds the row for a newly opened node.
     *
     * @param node The node, with its initial state.
     * @returns The element to insert, or null to render nothing for this node.
     */
    mount?(node: ConversationNode<State>): HTMLElement | null;
    /**
     * Applies a node's latest state to the row `mount` returned.
     *
     * Named apart from the definition's `update`, which folds state, because this folds
     * nothing: it is the one-way write of already-folded state onto an existing element.
     * Called on every publication that touched the node, so it should be cheap — this is
     * the path a streaming message takes once per frame. Omitting it means the row never
     * changes after it is created.
     *
     * @param element The row previously returned by `mount`.
     * @param node The node, with its current state.
     */
    patch?(element: HTMLElement, node: ConversationNode<State>): void;
}
/**
 * `<r-conversation>` — renders an append-only event log as a conversation.
 *
 * The element owns three things that are tedious and easy to get wrong, and nothing else:
 * projecting events into nodes (`ranuts/conversation`), keeping the view pinned to its
 * floor without fighting the reader (`createBottomFollower`), and reconciling rows against
 * the node list. What a message, a tool call, or a status line *looks like* is not its
 * business — that is a registered view.
 *
 * ```ts
 * const chat = document.querySelector('r-conversation');
 * chat.register({
 *   kind: 'message',
 *   match: (e) => (e.type === 'start' ? { id: e.id, role: 'start' } : e.type === 'delta' ? { id: e.id, role: 'update' } : null),
 *   start: () => ({ text: '' }),
 *   update: (state, e) => ({ text: state.text + e.text }),
 *   publication: (e) => (e.type === 'delta' ? 'animation-frame' : 'immediate'),
 *   mount: () => document.createElement('r-markdown'),
 *   patch: (el, node) => { (el as HTMLElement & { content: string }).content = node.state.text; },
 * });
 * chat.push({ type: 'start', id: 'm1' });
 * ```
 *
 * Attributes: `follow` (`true` by default — set `follow="false"` to leave the reader in
 * control from the start), `empty` (text shown while there are no rows), `sheet`.
 * Fires `pinnedchange` with `detail.pinned` whenever bottom-follow is gained or lost, so a
 * "jump to latest" affordance can track it.
 */
export declare class Conversation extends RanElement {
    _events: EventManager;
    _shadowDom: ShadowRoot;
    _scroll: HTMLElement;
    _list: HTMLElement;
    _older: HTMLElement;
    _olderButton: HTMLButtonElement;
    _footer: HTMLElement;
    private _views;
    private _engine;
    private _unsubscribe;
    private _follower;
    /** Row element per node key, so a publication patches rather than rebuilds. */
    private _rows;
    private _emptyRow;
    static get observedAttributes(): string[];
    constructor();
    /**
     * Label for the paging affordance above the first row. Empty hides it.
     *
     * A conversation log only grows, so a client that renders all of it eventually renders
     * more than anyone will read. Paging keeps the rendered set bounded without pretending
     * the rest is gone: the button is the statement that there is more, and
     * {@link Conversation.olderrequest} is the request for it.
     */
    get older(): string;
    set older(value: string);
    /** Whether a page is in flight; the affordance stays visible and goes inert. */
    get loadingOlder(): boolean;
    set loadingOlder(value: boolean);
    /** Whether new content is followed until the reader scrolls away from the floor. */
    get follow(): boolean;
    set follow(value: boolean);
    /** Text shown while the projection has produced no rows. */
    get empty(): string;
    set empty(value: string);
    get sheet(): string;
    set sheet(value: string);
    /** Whether the view is currently following new content. */
    get pinned(): boolean;
    /**
     * Registers one kind of conversation content.
     *
     * Every registration must land before the first {@link push}: the projection is built
     * once from the registered set, and a definition added afterwards would silently miss
     * every event already folded in.
     *
     * @param view The definition and its renderer.
     * @throws When events have already been pushed, or the kind is already registered.
     */
    register<Event, State>(view: ConversationNodeView<Event, State>): void;
    /**
     * Projects one event and renders whatever it changed.
     *
     * @param event The log event, in the shape the registered views match on.
     */
    push<Event>(event: Event): void;
    /**
     * Drops one row and every row opened after it.
     *
     * Editing a message, regenerating an answer and branching are the same operation: the
     * conversation diverges here, and what follows the divergence is no longer part of it.
     *
     * @param key The `kind:id` of the first row to drop.
     * @returns How many rows were dropped. Zero means no such row is live, which is a
     *   caller's cue that its own idea of the conversation is stale.
     */
    truncate(key: string): number;
    /**
     * Runs a burst of pushes as one render.
     *
     * Replaying a stored conversation without this is quadratic: every event publishes and
     * every publication walks the whole transcript. Use it for any replay, restore, or bulk
     * insert; a live stream does not need it, because one delta changes one row.
     *
     * @param run The pushes to run.
     */
    batch(run: () => void): void;
    /** Drops every node and row, keeping the registered views. */
    reset(): void;
    /** Scrolls to the floor and resumes following, whatever the reader did. */
    scrollToBottom(): void;
    /**
     * Remembers a row's position before older content is prepended, so the reader keeps
     * looking at what they were looking at.
     *
     * @param key The node key to hold still; defaults to the topmost rendered row.
     * @returns Whether a row was captured.
     */
    captureAnchor(key?: string): boolean;
    /**
     * Restores the row captured by {@link captureAnchor}.
     *
     * @returns Whether an anchor was restored.
     */
    restoreAnchor(): boolean;
    connectedCallback(): void;
    disconnectedCallback(): void;
    attributeChangedCallback(name: string, old: string, next: string): void;
    handlerExternalCss: () => void;
    private _ensureEngine;
    private _viewFor;
    private _renderNodes;
    /**
     * Asks the owner for the page above the first row, holding the reader's position.
     *
     * The anchor is captured before the request rather than after it lands: by then the
     * prepend has already moved everything, and there is no earlier position left to record.
     * The owner restores it with {@link Conversation.restoreAnchor} once its rows are in.
     */
    private _requestOlder;
    private _syncOlder;
    private _syncEmpty;
}
export default Conversation;
