/**
 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */
/**
 * @module engine/model/differ
 */
import Position from './position.js';
import Range from './range.js';
import type { default as MarkerCollection, MarkerData } from './markercollection.js';
import type Element from './element.js';
import type Item from './item.js';
import type RootElement from './rootelement.js';
import type Operation from './operation/operation.js';
/**
 * Calculates the difference between two model states.
 *
 * Receives operations that are to be applied on the model document. Marks parts of the model document tree which
 * are changed and saves the state of these elements before the change. Then, it compares saved elements with the
 * changed elements, after all changes are applied on the model document. Calculates the diff between saved
 * elements and new ones and returns a change set.
 */
export default class Differ {
    /**
     * Reference to the model's marker collection.
     */
    private readonly _markerCollection;
    /**
     * A map that stores changes that happened in a given element.
     *
     * The keys of the map are references to the model elements.
     * The values of the map are arrays with changes that were done on this element.
     */
    private readonly _changesInElement;
    /**
     * A map that stores "element's children snapshots". A snapshot is representing children of a given element before
     * the first change was applied on that element. Snapshot items are objects with two properties: `name`,
     * containing the element name (or `'$text'` for a text node) and `attributes` which is a map of the node's attributes.
     */
    private readonly _elementSnapshots;
    /**
     * A map that stores all changed markers.
     *
     * The keys of the map are marker names.
     * The values of the map are objects with the following properties:
     * - `oldMarkerData`,
     * - `newMarkerData`.
     */
    private readonly _changedMarkers;
    /**
     * A map that stores all roots that have been changed.
     *
     * The keys are the names of the roots while value represents the changes.
     */
    private readonly _changedRoots;
    /**
     * Stores the number of changes that were processed. Used to order the changes chronologically. It is important
     * when changes are sorted.
     */
    private _changeCount;
    /**
     * For efficiency purposes, `Differ` stores the change set returned by the differ after {@link #getChanges} call.
     * Cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
     * return the cached value instead of calculating it again.
     *
     * This property stores those changes that did not take place in graveyard root.
     */
    private _cachedChanges;
    /**
     * For efficiency purposes, `Differ` stores the change set returned by the differ after the {@link #getChanges} call.
     * The cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
     * return the cached value instead of calculating it again.
     *
     * This property stores all changes evaluated by `Differ`, including those that took place in the graveyard.
     */
    private _cachedChangesWithGraveyard;
    /**
     * Set of model items that were marked to get refreshed in {@link #_refreshItem}.
     */
    private _refreshedItems;
    /**
     * Creates a `Differ` instance.
     *
     * @param markerCollection Model's marker collection.
     */
    constructor(markerCollection: MarkerCollection);
    /**
     * Informs whether there are any changes buffered in `Differ`.
     */
    get isEmpty(): boolean;
    /**
     * Buffers the given operation. An operation has to be buffered before it is executed.
     *
     * @param operationToBuffer An operation to buffer.
     */
    bufferOperation(operationToBuffer: Operation): void;
    /**
     * Buffers a marker change.
     *
     * @param markerName The name of the marker that changed.
     * @param oldMarkerData Marker data before the change.
     * @param newMarkerData Marker data after the change.
     */
    bufferMarkerChange(markerName: string, oldMarkerData: MarkerData, newMarkerData: MarkerData): void;
    /**
     * Returns all markers that should be removed as a result of buffered changes.
     *
     * @returns Markers to remove. Each array item is an object containing the `name` and `range` properties.
     */
    getMarkersToRemove(): Array<{
        name: string;
        range: Range;
    }>;
    /**
     * Returns all markers which should be added as a result of buffered changes.
     *
     * @returns Markers to add. Each array item is an object containing the `name` and `range` properties.
     */
    getMarkersToAdd(): Array<{
        name: string;
        range: Range;
    }>;
    /**
     * Returns all markers which changed.
     */
    getChangedMarkers(): Array<{
        name: string;
        data: {
            oldRange: Range | null;
            newRange: Range | null;
        };
    }>;
    /**
     * Checks whether some of the buffered changes affect the editor data.
     *
     * Types of changes which affect the editor data:
     *
     * * model structure changes,
     * * attribute changes,
     * * a root is added or detached,
     * * changes of markers which were defined as `affectsData`,
     * * changes of markers' `affectsData` property.
     */
    hasDataChanges(): boolean;
    /**
     * Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}
     * call) and the new model tree state (actual one). It should be called after all buffered operations are executed.
     *
     * The diff set is returned as an array of {@link module:engine/model/differ~DiffItem diff items}, each describing a change done
     * on the model. The items are sorted by the position on which the change happened. If a position
     * {@link module:engine/model/position~Position#isBefore is before} another one, it will be on an earlier index in the diff set.
     *
     * **Note**: Elements inside inserted element will not have a separate diff item, only the top most element change will be reported.
     *
     * Because calculating the diff is a costly operation, the result is cached. If no new operation was buffered since the
     * previous {@link #getChanges} call, the next call will return the cached value.
     *
     * @param options Additional options.
     * @param options.includeChangesInGraveyard If set to `true`, also changes that happened
     * in the graveyard root will be returned. By default, changes in the graveyard root are not returned.
     * @returns Diff between the old and the new model tree state.
     */
    getChanges(options?: {
        includeChangesInGraveyard?: boolean;
    }): Array<DiffItem>;
    /**
     * Returns all roots that have changed (either were attached, or detached, or their attributes changed).
     *
     * @returns Diff between the old and the new roots state.
     */
    getChangedRoots(): Array<DiffItemRoot>;
    /**
     * Returns a set of model items that were marked to get refreshed.
     */
    getRefreshedItems(): Set<Item>;
    /**
     * Resets `Differ`. Removes all buffered changes.
     */
    reset(): void;
    /**
     * Buffers the root state change after the root was attached or detached
     */
    private _bufferRootStateChange;
    /**
     * Buffers a root attribute change.
     */
    private _bufferRootAttributeChange;
    /**
     * Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
     * in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
     *
     * @internal
     * @param item Item to refresh.
     */
    _refreshItem(item: Item): void;
    /**
     * Buffers all the data related to given root like it was all just added to the editor.
     *
     * Following changes are buffered:
     *
     * * root is attached,
     * * all root content is inserted,
     * * all root attributes are added,
     * * all markers inside the root are added.
     *
     * @internal
     */
    _bufferRootLoad(root: RootElement): void;
    /**
     * Saves and handles an insert change.
     */
    private _markInsert;
    /**
     * Saves and handles a remove change.
     */
    private _markRemove;
    /**
     * Saves and handles an attribute change.
     */
    private _markAttribute;
    /**
     * Saves and handles a model change.
     */
    private _markChange;
    /**
     * Gets an array of changes that have already been saved for a given element.
     */
    private _getChangesForElement;
    /**
     * Saves a children snapshot for a given element.
     */
    private _makeSnapshot;
    /**
     * For a given newly saved change, compares it with a change already done on the element and modifies the incoming
     * change and/or the old change.
     *
     * @param inc Incoming (new) change.
     * @param changes An array containing all the changes done on that element.
     */
    private _handleChange;
    /**
     * Returns an object with a single insert change description.
     *
     * @param parent The element in which the change happened.
     * @param offset The offset at which change happened.
     * @param elementSnapshot The snapshot of the removed element a character.
     * @returns The diff item.
     */
    private _getInsertDiff;
    /**
     * Returns an object with a single remove change description.
     *
     * @param parent The element in which change happened.
     * @param offset The offset at which change happened.
     * @param elementSnapshot The snapshot of the removed element a character.
     * @returns The diff item.
     */
    private _getRemoveDiff;
    /**
     * Returns an array of objects where each one is a single attribute change description.
     *
     * @param range The range where the change happened.
     * @param oldAttributes A map, map iterator or compatible object that contains attributes before the change.
     * @param newAttributes A map, map iterator or compatible object that contains attributes after the change.
     * @returns An array containing one or more diff items.
     */
    private _getAttributesDiff;
    /**
     * Checks whether given element or any of its parents is an element that is buffered as an inserted element.
     */
    private _isInInsertedElement;
    /**
     * Removes deeply all buffered changes that are registered in elements from range specified by `parent`, `offset`
     * and `howMany`.
     */
    private _removeAllNestedChanges;
}
/**
 * The single diff item.
 *
 * Could be one of:
 *
 * * {@link module:engine/model/differ~DiffItemInsert `DiffItemInsert`},
 * * {@link module:engine/model/differ~DiffItemRemove `DiffItemRemove`},
 * * {@link module:engine/model/differ~DiffItemAttribute `DiffItemAttribute`}.
 */
export type DiffItem = DiffItemInsert | DiffItemRemove | DiffItemAttribute;
/**
 * A single diff item for inserted nodes.
 */
export interface DiffItemInsert {
    /**
     * The type of diff item.
     */
    type: 'insert';
    /**
     * Reference to the model element that was inserted.
     *
     * Undefined if the diff item is related to text node insertion.
     *
     * @internal
     */
    _element?: Element;
    /**
     * The name of the inserted elements or `'$text'` for a text node.
     */
    name: string;
    /**
     * Map of attributes that were set on the item while it was inserted.
     */
    attributes: Map<string, unknown>;
    /**
     * The position where the node was inserted.
     */
    position: Position;
    /**
     * The length of an inserted text node. For elements, it is always 1 as each inserted element is counted as a one.
     */
    length: number;
}
/**
 * A single diff item for removed nodes.
 */
export interface DiffItemRemove {
    /**
     * The type of diff item.
     */
    type: 'remove';
    /**
     * Reference to the model element that was removed.
     *
     * Undefined if the diff item is related to text node deletion.
     *
     * Note that this element will have the state after all changes has been performed on the model, not before. For example, if a paragraph
     * was first renamed to `heading1`, and then removed, `element.name` will be `heading1`. Similarly, with attributes. Also, you should
     * not read the element's position, as it will no longer point to the original element position.
     *
     * Instead, you should use {@link ~DiffItemRemove#name `DiffItemRemove#name`},
     * {@link ~DiffItemRemove#attributes `DiffItemRemove#attributes`}, and {@link ~DiffItemRemove#position `DiffItemRemove#position`}.
     *
     * This property should be only used to check instance reference equality. For example, if you want to detect that some particular
     * element was removed, you can check `_element` property. You can use it together with {@link ~DiffItemInsert#_element `#_element`} on
     * insert diff items to detect move, refresh, or rename changes.
     *
     * @internal
     */
    _element?: Element;
    /**
     * The name of the removed element or `'$text'` for a text node.
     */
    name: string;
    /**
     * Map of attributes that were set on the item while it was removed.
     */
    attributes: Map<string, unknown>;
    /**
     * The position where the node was removed.
     */
    position: Position;
    /**
     * The length of a removed text node. For elements, it is always 1, as each removed element is counted as a one.
     */
    length: number;
}
/**
 * A single diff item for attribute change.
 */
export interface DiffItemAttribute {
    /**
     * The type of diff item.
     */
    type: 'attribute';
    /**
     * The name of the changed attribute.
     */
    attributeKey: string;
    /**
     * An attribute previous value (before change).
     */
    attributeOldValue: unknown;
    /**
     * An attribute new value (after change).
     */
    attributeNewValue: unknown;
    /**
     * The range where the change happened.
     */
    range: Range;
}
/**
 * A single diff item for a changed root.
 */
export interface DiffItemRoot {
    /**
     * Name of the changed root.
     */
    name: string;
    /**
     * Set accordingly if the root got attached or detached. Otherwise, not set.
     */
    state?: 'attached' | 'detached';
    /**
     * Keeps all attribute changes that happened on the root.
     *
     * The keys are keys of the changed attributes. The values are objects containing the attribute value before the change
     * (`oldValue`) and after the change (`newValue`).
     *
     * Note, that if the root state changed (`state` is set), then `attributes` property will not be set. All attributes should be
     * handled together with the root being attached or detached.
     */
    attributes?: Record<string, {
        oldValue: unknown;
        newValue: unknown;
    }>;
}
