/**
 * @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/view/upcastwriter
 */
import DocumentFragment from './documentfragment.js';
import Element from './element.js';
import Text from './text.js';
import { isPlainObject } from 'lodash-es';
import Position from './position.js';
import Range from './range.js';
import Selection from './selection.js';
/**
 * View upcast writer. It provides a set of methods used to manipulate non-semantic view trees.
 *
 * It should be used only while working on a non-semantic view
 * (e.g. a view created from HTML string on paste).
 * To manipulate a view which was or is being downcasted from the the model use the
 * {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}.
 *
 * Read more about changing the view in the {@glink framework/architecture/editing-engine#changing-the-view Changing the view}
 * section of the {@glink framework/architecture/editing-engine Editing engine architecture} guide.
 *
 * Unlike `DowncastWriter`, which is available in the {@link module:engine/view/view~View#change `View#change()`} block,
 * `UpcastWriter` can be created wherever you need it:
 *
 * ```ts
 * const writer = new UpcastWriter( viewDocument );
 * const text = writer.createText( 'foo!' );
 *
 * writer.appendChild( text, someViewElement );
 * ```
 */
export default class UpcastWriter {
    /**
     * @param document The view document instance in which this upcast writer operates.
     */
    constructor(document) {
        this.document = document;
    }
    /**
     * Creates a new {@link module:engine/view/documentfragment~DocumentFragment} instance.
     *
     * @param children A list of nodes to be inserted into the created document fragment.
     * @returns The created document fragment.
     */
    createDocumentFragment(children) {
        return new DocumentFragment(this.document, children);
    }
    /**
     * Creates a new {@link module:engine/view/element~Element} instance.
     *
     * Attributes can be passed in various formats:
     *
     * ```ts
     * upcastWriter.createElement( 'div', { class: 'editor', contentEditable: 'true' } ); // object
     * upcastWriter.createElement( 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator
     * upcastWriter.createElement( 'div', mapOfAttributes ); // map
     * ```
     *
     * @param name Node name.
     * @param attrs Collection of attributes.
     * @param children A list of nodes to be inserted into created element.
     * @returns Created element.
     */
    createElement(name, attrs, children) {
        return new Element(this.document, name, attrs, children);
    }
    /**
     * Creates a new {@link module:engine/view/text~Text} instance.
     *
     * @param data The text's data.
     * @returns The created text node.
     */
    createText(data) {
        return new Text(this.document, data);
    }
    /**
     * Clones the provided element.
     *
     * @see module:engine/view/element~Element#_clone
     * @param element Element to be cloned.
     * @param deep If set to `true` clones element and all its children recursively. When set to `false`,
     * element will be cloned without any children.
     * @returns Clone of this element.
     */
    clone(element, deep = false) {
        return element._clone(deep);
    }
    /**
     * Appends a child node or a list of child nodes at the end of this node
     * and sets the parent of these nodes to this element.
     *
     * @see module:engine/view/element~Element#_appendChild
     * @param items Items to be inserted.
     * @param element Element to which items will be appended.
     * @returns Number of appended nodes.
     */
    appendChild(items, element) {
        return element._appendChild(items);
    }
    /**
     * Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
     * this element.
     *
     * @see module:engine/view/element~Element#_insertChild
     * @param index Offset at which nodes should be inserted.
     * @param items Items to be inserted.
     * @param element Element to which items will be inserted.
     * @returns Number of inserted nodes.
     */
    insertChild(index, items, element) {
        return element._insertChild(index, items);
    }
    /**
     * Removes the given number of child nodes starting at the given index and set the parent of these nodes to `null`.
     *
     * @see module:engine/view/element~Element#_removeChildren
     * @param index Offset from which nodes will be removed.
     * @param howMany Number of nodes to remove.
     * @param element Element which children will be removed.
     * @returns The array containing removed nodes.
     */
    removeChildren(index, howMany, element) {
        return element._removeChildren(index, howMany);
    }
    /**
     * Removes given element from the view structure. Will not have effect on detached elements.
     *
     * @param element Element which will be removed.
     * @returns The array containing removed nodes.
     */
    remove(element) {
        const parent = element.parent;
        if (parent) {
            return this.removeChildren(parent.getChildIndex(element), 1, parent);
        }
        return [];
    }
    /**
     * Replaces given element with the new one in the view structure. Will not have effect on detached elements.
     *
     * @param oldElement Element which will be replaced.
     * @param newElement Element which will be inserted in the place of the old element.
     * @returns Whether old element was successfully replaced.
     */
    replace(oldElement, newElement) {
        const parent = oldElement.parent;
        if (parent) {
            const index = parent.getChildIndex(oldElement);
            this.removeChildren(index, 1, parent);
            this.insertChild(index, newElement, parent);
            return true;
        }
        return false;
    }
    /**
     * Removes given element from view structure and places its children in its position.
     * It does nothing if element has no parent.
     *
     * @param element Element to unwrap.
     */
    unwrapElement(element) {
        const parent = element.parent;
        if (parent) {
            const index = parent.getChildIndex(element);
            this.remove(element);
            this.insertChild(index, element.getChildren(), parent);
        }
    }
    /**
     * Renames element by creating a copy of a given element but with its name changed and then moving contents of the
     * old element to the new one.
     *
     * Since this function creates a new element and removes the given one, the new element is returned to keep reference.
     *
     * @param newName New element name.
     * @param  element Element to be renamed.
     * @returns New element or null if the old element was not replaced (happens for detached elements).
     */
    rename(newName, element) {
        const newElement = new Element(this.document, newName, element.getAttributes(), element.getChildren());
        return this.replace(element, newElement) ? newElement : null;
    }
    /**
     * Adds or overwrites element's attribute with a specified key and value.
     *
     * ```ts
     * writer.setAttribute( 'href', 'http://ckeditor.com', linkElement );
     * ```
     *
     * @see module:engine/view/element~Element#_setAttribute
     * @param key Attribute key.
     * @param value Attribute value.
     * @param element Element for which attribute will be set.
     */
    setAttribute(key, value, element) {
        element._setAttribute(key, value);
    }
    /**
     * Removes attribute from the element.
     *
     * ```ts
     * writer.removeAttribute( 'href', linkElement );
     * ```
     *
     * @see module:engine/view/element~Element#_removeAttribute
     * @param key Attribute key.
     * @param element Element from which attribute will be removed.
     */
    removeAttribute(key, element) {
        element._removeAttribute(key);
    }
    /**
     * Adds specified class to the element.
     *
     * ```ts
     * writer.addClass( 'foo', linkElement );
     * writer.addClass( [ 'foo', 'bar' ], linkElement );
     * ```
     *
     * @see module:engine/view/element~Element#_addClass
     * @param className Single class name or array of class names which will be added.
     * @param element Element for which class will be added.
     */
    addClass(className, element) {
        element._addClass(className);
    }
    /**
     * Removes specified class from the element.
     *
     * ```ts
     * writer.removeClass( 'foo', linkElement );
     * writer.removeClass( [ 'foo', 'bar' ], linkElement );
     * ```
     *
     * @see module:engine/view/element~Element#_removeClass
     * @param className Single class name or array of class names which will be removed.
     * @param element Element from which class will be removed.
     */
    removeClass(className, element) {
        element._removeClass(className);
    }
    setStyle(property, valueOrElement, element) {
        if (isPlainObject(property) && element === undefined) {
            valueOrElement._setStyle(property);
        }
        else {
            element._setStyle(property, valueOrElement);
        }
    }
    /**
     * Removes specified style from the element.
     *
     * ```ts
     * writer.removeStyle( 'color', element );  // Removes 'color' style.
     * writer.removeStyle( [ 'color', 'border-top' ], element ); // Removes both 'color' and 'border-top' styles.
     * ```
     *
     * **Note**: This method can work with normalized style names if
     * {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
     * See {@link module:engine/view/stylesmap~StylesMap#remove `StylesMap#remove()`} for details.
     *
     * @see module:engine/view/element~Element#_removeStyle
     * @param property Style property name or names to be removed.
     * @param element Element from which style will be removed.
     */
    removeStyle(property, element) {
        element._removeStyle(property);
    }
    /**
     * Sets a custom property on element. Unlike attributes, custom properties are not rendered to the DOM,
     * so they can be used to add special data to elements.
     *
     * @see module:engine/view/element~Element#_setCustomProperty
     * @param key Custom property name/key.
     * @param value Custom property value to be stored.
     * @param element Element for which custom property will be set.
     */
    setCustomProperty(key, value, element) {
        element._setCustomProperty(key, value);
    }
    /**
     * Removes a custom property stored under the given key.
     *
     * @see module:engine/view/element~Element#_removeCustomProperty
     * @param key Name/key of the custom property to be removed.
     * @param element Element from which the custom property will be removed.
     * @returns Returns true if property was removed.
     */
    removeCustomProperty(key, element) {
        return element._removeCustomProperty(key);
    }
    /**
     * Creates position at the given location. The location can be specified as:
     *
     * * a {@link module:engine/view/position~Position position},
     * * parent element and offset (offset defaults to `0`),
     * * parent element and `'end'` (sets position at the end of that element),
     * * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
     *
     * This method is a shortcut to other constructors such as:
     *
     * * {@link #createPositionBefore},
     * * {@link #createPositionAfter},
     *
     * @param offset Offset or one of the flags. Used only when first parameter is a {@link module:engine/view/item~Item view item}.
     */
    createPositionAt(itemOrPosition, offset) {
        return Position._createAt(itemOrPosition, offset);
    }
    /**
     * Creates a new position after given view item.
     *
     * @param item View item after which the position should be located.
     */
    createPositionAfter(item) {
        return Position._createAfter(item);
    }
    /**
     * Creates a new position before given view item.
     *
     * @param item View item before which the position should be located.
     */
    createPositionBefore(item) {
        return Position._createBefore(item);
    }
    /**
     * Creates a range spanning from `start` position to `end` position.
     *
     * **Note:** This factory method creates it's own {@link module:engine/view/position~Position} instances basing on passed values.
     *
     * @param start Start position.
     * @param end End position. If not set, range will be collapsed at `start` position.
     */
    createRange(start, end) {
        return new Range(start, end);
    }
    /**
     * Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
     */
    createRangeOn(item) {
        return Range._createOn(item);
    }
    /**
     * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
     * that element and ends after the last child of that element.
     *
     * @param element Element which is a parent for the range.
     */
    createRangeIn(element) {
        return Range._createIn(element);
    }
    createSelection(...args) {
        return new Selection(...args);
    }
}
