/**
 * @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/conversion/upcastdispatcher
 */
import ViewConsumable from './viewconsumable.js';
import ModelRange from '../model/range.js';
import ModelPosition from '../model/position.js';
import type ModelElement from '../model/element.js';
import type ModelNode from '../model/node.js';
import type ViewElement from '../view/element.js';
import type ViewText from '../view/text.js';
import type ViewDocumentFragment from '../view/documentfragment.js';
import type ModelDocumentFragment from '../model/documentfragment.js';
import type { default as Schema, SchemaContextDefinition } from '../model/schema.js';
import type ModelWriter from '../model/writer.js';
import type ViewItem from '../view/item.js';
declare const UpcastDispatcher_base: {
    new (): import("@ckeditor/ckeditor5-utils").Emitter;
    prototype: import("@ckeditor/ckeditor5-utils").Emitter;
};
/**
 * Upcast dispatcher is a central point of the view-to-model conversion, which is a process of
 * converting a given {@link module:engine/view/documentfragment~DocumentFragment view document fragment} or
 * {@link module:engine/view/element~Element view element} into a correct model structure.
 *
 * During the conversion process, the dispatcher fires events for all {@link module:engine/view/node~Node view nodes}
 * from the converted view document fragment.
 * Special callbacks called "converters" should listen to these events in order to convert the view nodes.
 *
 * The second parameter of the callback is the `data` object with the following properties:
 *
 * * `data.viewItem` contains a {@link module:engine/view/node~Node view node} or a
 * {@link module:engine/view/documentfragment~DocumentFragment view document fragment}
 * that is converted at the moment and might be handled by the callback.
 * * `data.modelRange` is used to point to the result
 * of the current conversion (e.g. the element that is being inserted)
 * and is always a {@link module:engine/model/range~Range} when the conversion succeeds.
 * * `data.modelCursor` is a {@link module:engine/model/position~Position position} on which the converter should insert
 * the newly created items.
 *
 * The third parameter of the callback is an instance of {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi}
 * which provides additional tools for converters.
 *
 * You can read more about conversion in the {@glink framework/deep-dive/conversion/upcast Upcast conversion} guide.
 *
 * Examples of event-based converters:
 *
 * ```ts
 * // A converter for links (<a>).
 * editor.data.upcastDispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
 * 	if ( conversionApi.consumable.consume( data.viewItem, { name: true, attributes: [ 'href' ] } ) ) {
 * 		// The <a> element is inline and is represented by an attribute in the model.
 * 		// This is why you need to convert only children.
 * 		const { modelRange } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
 *
 * 		for ( let item of modelRange.getItems() ) {
 * 			if ( conversionApi.schema.checkAttribute( item, 'linkHref' ) ) {
 * 				conversionApi.writer.setAttribute( 'linkHref', data.viewItem.getAttribute( 'href' ), item );
 * 			}
 * 		}
 * 	}
 * } );
 *
 * // Convert <p> element's font-size style.
 * // Note: You should use a low-priority observer in order to ensure that
 * // it is executed after the element-to-element converter.
 * editor.data.upcastDispatcher.on( 'element:p', ( evt, data, conversionApi ) => {
 * 	const { consumable, schema, writer } = conversionApi;
 *
 * 	if ( !consumable.consume( data.viewItem, { style: 'font-size' } ) ) {
 * 		return;
 * 	}
 *
 * 	const fontSize = data.viewItem.getStyle( 'font-size' );
 *
 * 	// Do not go for the model element after data.modelCursor because it might happen
 * 	// that a single view element was converted to multiple model elements. Get all of them.
 * 	for ( const item of data.modelRange.getItems( { shallow: true } ) ) {
 * 		if ( schema.checkAttribute( item, 'fontSize' ) ) {
 * 			writer.setAttribute( 'fontSize', fontSize, item );
 * 		}
 * 	}
 * }, { priority: 'low' } );
 *
 * // Convert all elements which have no custom converter into a paragraph (autoparagraphing).
 * editor.data.upcastDispatcher.on( 'element', ( evt, data, conversionApi ) => {
 * 	// Check if an element can be converted.
 * 	if ( !conversionApi.consumable.test( data.viewItem, { name: data.viewItem.name } ) ) {
 * 		// When an element is already consumed by higher priority converters, do nothing.
 * 		return;
 * 	}
 *
 * 	const paragraph = conversionApi.writer.createElement( 'paragraph' );
 *
 * 	// Try to safely insert a paragraph at the model cursor - it will find an allowed parent for the current element.
 * 	if ( !conversionApi.safeInsert( paragraph, data.modelCursor ) ) {
 * 		// When an element was not inserted, it means that you cannot insert a paragraph at this position.
 * 		return;
 * 	}
 *
 * 	// Consume the inserted element.
 * 	conversionApi.consumable.consume( data.viewItem, { name: data.viewItem.name } ) );
 *
 * 	// Convert the children to a paragraph.
 * 	const { modelRange } = conversionApi.convertChildren( data.viewItem,  paragraph ) );
 *
 * 	// Update `modelRange` and `modelCursor` in the `data` as a conversion result.
 * 	conversionApi.updateConversionResult( paragraph, data );
 * }, { priority: 'low' } );
 * ```
 *
 * @fires viewCleanup
 * @fires element
 * @fires text
 * @fires documentFragment
 */
export default class UpcastDispatcher extends /* #__PURE__ */ UpcastDispatcher_base {
    /**
     * An interface passed by the dispatcher to the event callbacks.
     */
    conversionApi: UpcastConversionApi;
    /**
     * The list of elements that were created during splitting.
     *
     * After the conversion process, the list is cleared.
     */
    private _splitParts;
    /**
     * The list of cursor parent elements that were created during splitting.
     *
     * After the conversion process the list is cleared.
     */
    private _cursorParents;
    /**
     * The position in the temporary structure where the converted content is inserted. The structure reflects the context of
     * the target position where the content will be inserted. This property is built based on the context parameter of the
     * convert method.
     */
    private _modelCursor;
    /**
     * The list of elements that were created during the splitting but should not get removed on conversion end even if they are empty.
     *
     * The list is cleared after the conversion process.
     */
    private _emptyElementsToKeep;
    /**
     * Creates an upcast dispatcher that operates using the passed API.
     *
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi
     * @param conversionApi Additional properties for an interface that will be passed to events fired
     * by the upcast dispatcher.
     */
    constructor(conversionApi: Pick<UpcastConversionApi, 'schema'>);
    /**
     * Starts the conversion process. The entry point for the conversion.
     *
     * @fires element
     * @fires text
     * @fires documentFragment
     * @param viewElement The part of the view to be converted.
     * @param writer An instance of the model writer.
     * @param context Elements will be converted according to this context.
     * @returns Model data that is the result of the conversion process
     * wrapped in `DocumentFragment`. Converted marker elements will be set as the document fragment's
     * {@link module:engine/model/documentfragment~DocumentFragment#markers static markers map}.
     */
    convert(viewElement: ViewElement | ViewDocumentFragment, writer: ModelWriter, context?: SchemaContextDefinition): ModelDocumentFragment;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertItem
     */
    private _convertItem;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertChildren
     */
    private _convertChildren;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#safeInsert
     */
    private _safeInsert;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#updateConversionResult
     */
    private _updateConversionResult;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
     */
    private _splitToAllowedParent;
    /**
     * Registers that a `splitPart` element is a split part of the `originalPart` element.
     *
     * The data set by this method is used by {@link #_getSplitParts} and {@link #_removeEmptyElements}.
     */
    private _registerSplitPair;
    /**
     * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#getSplitParts
     */
    private _getSplitParts;
    /**
     * Mark an element that were created during the splitting to not get removed on conversion end even if it is empty.
     */
    private _keepEmptyElement;
    /**
     * Checks if there are any empty elements created while splitting and removes them.
     *
     * This method works recursively to re-check empty elements again after at least one element was removed in the initial call,
     * as some elements might have become empty after other empty elements were removed from them.
     */
    private _removeEmptyElements;
}
/**
 * Fired before the first conversion event, at the beginning of the upcast (view-to-model conversion) process.
 *
 * @eventName ~UpcastDispatcher#viewCleanup
 * @param viewItem A part of the view to be converted.
 */
export type UpcastViewCleanupEvent = {
    name: 'viewCleanup';
    args: [ViewElement | ViewDocumentFragment];
};
export type UpcastEvent<TName extends string, TItem extends ViewItem | ViewDocumentFragment> = {
    name: TName | `${TName}:${string}`;
    args: [data: UpcastConversionData<TItem>, conversionApi: UpcastConversionApi];
};
/**
 * Conversion data.
 *
 * **Note:** Keep in mind that this object is shared by reference between all conversion callbacks that will be called.
 * This means that callbacks can override values if needed, and these values will be available in other callbacks.
 */
export interface UpcastConversionData<TItem extends ViewItem | ViewDocumentFragment = ViewItem | ViewDocumentFragment> {
    /**
     * The converted item.
     */
    viewItem: TItem;
    /**
     * The position where the converter should start changes.
     * Change this value for the next converter to tell where the conversion should continue.
     */
    modelCursor: ModelPosition;
    /**
     * The current state of conversion result. Every change to
     * the converted element should be reflected by setting or modifying this property.
     */
    modelRange: ModelRange | null;
}
/**
 * Fired when an {@link module:engine/view/element~Element} is converted.
 *
 * `element` is a namespace event for a class of events. Names of actually called events follow the pattern of
 * `element:<elementName>` where `elementName` is the name of the converted element. This way listeners may listen to
 * a conversion of all or just specific elements.
 *
 * @eventName ~UpcastDispatcher#element
 * @param data The conversion data. Keep in mind that this object is shared by reference between all callbacks
 * that will be called. This means that callbacks can override values if needed, and these values
 * will be available in other callbacks.
 * @param conversionApi Conversion utilities to be used by the callback.
 */
export type UpcastElementEvent = UpcastEvent<'element', ViewElement>;
/**
 * Fired when a {@link module:engine/view/text~Text} is converted.
 *
 * @eventName ~UpcastDispatcher#text
 * @see ~UpcastDispatcher#event:element
 */
export type UpcastTextEvent = UpcastEvent<'text', ViewText>;
/**
 * Fired when a {@link module:engine/view/documentfragment~DocumentFragment} is converted.
 *
 * @eventName ~UpcastDispatcher#documentFragment
 * @see ~UpcastDispatcher#event:element
 */
export type UpcastDocumentFragmentEvent = UpcastEvent<'documentFragment', ViewDocumentFragment>;
/**
 * A set of conversion utilities available as the third parameter of the
 * {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast dispatcher}'s events.
 */
export interface UpcastConversionApi {
    /**
     * Stores information about what parts of the processed view item are still waiting to be handled. After a piece of view item
     * was converted, an appropriate consumable value should be
     * {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consumed}.
     */
    consumable: ViewConsumable;
    /**
     * The model's schema instance.
     */
    schema: Schema;
    /**
     * The {@link module:engine/model/writer~Writer} instance used to manipulate the data during conversion.
     */
    writer: ModelWriter;
    /**
     * Custom data stored by converters for the conversion process. Custom properties of this object can be defined and use to
     * pass parameters between converters.
     *
     * The difference between this property and the `data` parameter of
     * {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element} is that the `data` parameters allow you
     * to pass parameters within a single event and `store` within the whole conversion.
     */
    store: unknown;
    /**
     * Starts the conversion of a given item by firing an appropriate event.
     *
     * Every fired event is passed (as the first parameter) an object with the `modelRange` property. Every event may set and/or
     * modify that property. When all callbacks are done, the final value of the `modelRange` property is returned by this method.
     * The `modelRange` must be a {@link module:engine/model/range~Range model range} or `null` (as set by default).
     *
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:text
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:documentFragment
     * @param viewItem Item to convert.
     * @param modelCursor The conversion position.
     * @returns The conversion result:
     * * `result.modelRange` The model range containing the result of the item conversion,
     * created and modified by callbacks attached to the fired event, or `null` if the conversion result was incorrect.
     * * `result.modelCursor` The position where the conversion should be continued.
     */
    convertItem(viewItem: ViewItem, modelCursor: ModelPosition): {
        modelRange: ModelRange | null;
        modelCursor: ModelPosition;
    };
    /**
     * Starts the conversion of all children of a given item by firing appropriate events for all the children.
     *
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:text
     * @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:documentFragment
     * @param viewElement An element whose children should be converted.
     * @param positionOrElement A position or an element of
     * the conversion.
     * @returns The conversion result:
     * * `result.modelRange` The model range containing the results of the conversion of all children
     * of the given item. When no child was converted, the range is collapsed.
     * * `result.modelCursor` The position where the conversion should be continued.
     */
    convertChildren(viewElement: ViewElement | ViewDocumentFragment, positionOrElement: ModelPosition | ModelElement): {
        modelRange: ModelRange | null;
        modelCursor: ModelPosition;
    };
    /**
     * Safely inserts an element to the document, checking the {@link module:engine/model/schema~Schema schema} to find an allowed parent
     * for an element that you are going to insert, starting from the given position. If the current parent does not allow to insert
     * the element but one of the ancestors does, then splits the nodes to allowed parent.
     *
     * If the schema allows to insert the node in a given position, nothing is split.
     *
     * If it was not possible to find an allowed parent, `false` is returned and nothing is split.
     *
     * Otherwise, ancestors are split.
     *
     * For instance, if `<imageBlock>` is not allowed in `<paragraph>` but is allowed in `$root`:
     *
     * ```
     * <paragraph>foo[]bar</paragraph>
     *
     * -> safe insert for `<imageBlock>` will split ->
     *
     * <paragraph>foo</paragraph>[]<paragraph>bar</paragraph>
     *```
     *
     * Example usage:
     *
     * ```
     * const myElement = conversionApi.writer.createElement( 'myElement' );
     *
     * if ( !conversionApi.safeInsert( myElement, data.modelCursor ) ) {
     * 	return;
     * }
     *```
     *
     * The split result is saved and {@link #updateConversionResult} should be used to update the
     * {@link module:engine/conversion/upcastdispatcher~UpcastConversionData conversion data}.
     *
     * @param modelNode The node to insert.
     * @param position The position where an element is going to be inserted.
     * @returns The split result. If it was not possible to find an allowed position, `false` is returned.
     */
    safeInsert(modelNode: ModelNode, position: ModelPosition): boolean;
    /**
     * Updates the conversion result and sets a proper {@link module:engine/conversion/upcastdispatcher~UpcastConversionData#modelRange} and
     * the next {@link module:engine/conversion/upcastdispatcher~UpcastConversionData#modelCursor} after the conversion.
     * Used together with {@link #safeInsert}, it enables you to easily convert elements without worrying if the node was split
     * during the conversion of its children.
     *
     * A usage example in converter code:
     *
     * ```ts
     * const myElement = conversionApi.writer.createElement( 'myElement' );
     *
     * if ( !conversionApi.safeInsert( myElement, data.modelCursor ) ) {
     * 	return;
     * }
     *
     * // Children conversion may split `myElement`.
     * conversionApi.convertChildren( data.viewItem, myElement );
     *
     * conversionApi.updateConversionResult( myElement, data );
     * ```
     */
    updateConversionResult(modelElement: ModelElement, data: UpcastConversionData): void;
    /**
     * Checks the {@link module:engine/model/schema~Schema schema} to find an allowed parent for an element that is going to be inserted
     * starting from the given position. If the current parent does not allow inserting an element but one of the ancestors does, the method
     * splits nodes to allowed parent.
     *
     * If the schema allows inserting the node in the given position, nothing is split and an object with that position is returned.
     *
     * If it was not possible to find an allowed parent, `null` is returned and nothing is split.
     *
     * Otherwise, ancestors are split and an object with a position and the copy of the split element is returned.
     *
     * For instance, if `<imageBlock>` is not allowed in `<paragraph>` but is allowed in `$root`:
     *
     * ```
     * <paragraph>foo[]bar</paragraph>
     *
     * -> split for `<imageBlock>` ->
     *
     * <paragraph>foo</paragraph>[]<paragraph>bar</paragraph>
     * ```
     *
     * In the example above, the position between `<paragraph>` elements will be returned as `position` and the second `paragraph`
     * as `cursorParent`.
     *
     * **Note:** This is an advanced method. For most cases {@link #safeInsert} and {@link #updateConversionResult} should be used.
     *
     * @param modelNode The node to insert.
     * @param modelCursor The position where the element is going to be inserted.
     * @returns The split result. If it was not possible to find an allowed position, `null` is returned.
     * * `position` The position between split elements.
     * * `cursorParent` The element inside which the cursor should be placed to
     * continue the conversion. When the element is not defined it means that there was no split.
     */
    splitToAllowedParent(modelNode: ModelNode, modelCursor: ModelPosition): {
        position: ModelPosition;
        cursorParent?: ModelElement | ModelDocumentFragment;
    } | null;
    /**
     * Returns all the split parts of the given `element` that were created during upcasting through using {@link #splitToAllowedParent}.
     * It enables you to easily track these elements and continue processing them after they are split during the conversion of their
     * children.
     *
     * ```
     * <paragraph>Foo<imageBlock />bar<imageBlock />baz</paragraph> ->
     * <paragraph>Foo</paragraph><imageBlock /><paragraph>bar</paragraph><imageBlock /><paragraph>baz</paragraph>
     * ```
     *
     * For a reference to any of above paragraphs, the function will return all three paragraphs (the original element included),
     * sorted in the order of their creation (the original element is the first one).
     *
     * If the given `element` was not split, an array with a single element is returned.
     *
     * A usage example in the converter code:
     *
     * ```ts
     * const myElement = conversionApi.writer.createElement( 'myElement' );
     *
     * // Children conversion may split `myElement`.
     * conversionApi.convertChildren( data.viewItem, data.modelCursor );
     *
     * const splitParts = conversionApi.getSplitParts( myElement );
     * const lastSplitPart = splitParts[ splitParts.length - 1 ];
     *
     * // Setting `data.modelRange` basing on split parts:
     * data.modelRange = conversionApi.writer.createRange(
     * 	conversionApi.writer.createPositionBefore( myElement ),
     * 	conversionApi.writer.createPositionAfter( lastSplitPart )
     * );
     *
     * // Setting `data.modelCursor` to continue after the last split element:
     * data.modelCursor = conversionApi.writer.createPositionAfter( lastSplitPart );
     * ```
     *
     * **Tip:** If you are unable to get a reference to the original element (for example because the code is split into multiple converters
     * or even classes) but it has already been converted, you may want to check the first element in `data.modelRange`. This is a common
     * situation if an attribute converter is separated from an element converter.
     *
     * **Note:** This is an advanced method. For most cases {@link #safeInsert} and {@link #updateConversionResult} should be used.
     */
    getSplitParts(modelElement: ModelElement): Array<ModelElement>;
    /**
     * Mark an element that was created during splitting to not get removed on conversion end even if it is empty.
     *
     * **Note:** This is an advanced method. For most cases you will not need to keep the split empty element.
     */
    keepEmptyElement(modelElement: ModelElement): void;
}
export {};
