import { Arr } from '@ephox/katamari';
import { Insert, Remove, SugarBody } from '@ephox/sugar';
import * as AlloyTriggers from '../api/events/AlloyTriggers';
import * as SystemEvents from '../api/events/SystemEvents';
import * as GuiTypes from '../api/ui/GuiTypes';
const isConnected = (comp) => comp.getSystem().isConnected();
const fireDetaching = (component) => {
    AlloyTriggers.emit(component, SystemEvents.detachedFromDom());
    const children = component.components();
    Arr.each(children, fireDetaching);
};
const fireAttaching = (component) => {
    const children = component.components();
    Arr.each(children, fireAttaching);
    AlloyTriggers.emit(component, SystemEvents.attachedToDom());
};
// Unlike attach, a virtualAttach makes no actual DOM changes.
// This is because it should only be used in a situation
// where we are patching an existing element.
const virtualAttach = (parent, child) => {
    // So we still add it to the world
    parent.getSystem().addToWorld(child);
    // And we fire attaching ONLY if it's already in the DOM
    if (SugarBody.inBody(parent.element)) {
        fireAttaching(child);
    }
};
// Unlike detach, a virtualDetach makes no actual DOM changes.
// This is because it's used in patching circumstances.
const virtualDetach = (comp) => {
    fireDetaching(comp);
    comp.getSystem().removeFromWorld(comp);
};
const attach = (parent, child) => {
    Insert.append(parent.element, child.element);
};
const detachChildren = (component) => {
    // This will not detach the component, but will detach its children and sync at the end.
    Arr.each(component.components(), (childComp) => Remove.remove(childComp.element));
    // Clear the component also.
    Remove.empty(component.element);
    component.syncComponents();
};
const replaceChildren = (component, newSpecs, buildNewChildren) => {
    // Detach all existing children
    const subs = component.components();
    detachChildren(component);
    const newChildren = buildNewChildren(newSpecs);
    // Determine which components have been deleted and remove them from the world
    const deleted = Arr.difference(subs, newChildren);
    Arr.each(deleted, (comp) => {
        fireDetaching(comp);
        component.getSystem().removeFromWorld(comp);
    });
    // Add all new components
    Arr.each(newChildren, (childComp) => {
        // If the component isn't connected, ie is new, then we also need to add it to the world
        if (!isConnected(childComp)) {
            component.getSystem().addToWorld(childComp);
            attach(component, childComp);
            if (SugarBody.inBody(component.element)) {
                fireAttaching(childComp);
            }
        }
        else {
            attach(component, childComp);
        }
    });
    component.syncComponents();
};
const virtualReplaceChildren = (component, newSpecs, buildNewChildren) => {
    // When replacing we don't want to fire detachedFromDom and attachedToDom again for a premade that has just had its position in the children moved around,
    // so we only detach initially if we aren't a premade. Premades will be detached later, but only if they are no longer in the child list.
    const subs = component.components();
    const existingComps = Arr.bind(newSpecs, (spec) => GuiTypes.getPremade(spec).toArray());
    Arr.each(subs, (childComp) => {
        if (!Arr.contains(existingComps, childComp)) {
            virtualDetach(childComp);
        }
    });
    const newChildren = buildNewChildren(newSpecs);
    // Determine which components have been deleted and remove them from the world
    // It's probable the component has already been detached beforehand so only
    // detach what's still attached to the world (i.e removed premades)
    const deleted = Arr.difference(subs, newChildren);
    Arr.each(deleted, (deletedComp) => {
        if (isConnected(deletedComp)) {
            virtualDetach(deletedComp);
        }
    });
    // Add all new components
    Arr.each(newChildren, (childComp) => {
        // If the component isn't connected, ie is new, then we also need to add it to the world
        if (!isConnected(childComp)) {
            virtualAttach(component, childComp);
        }
        else {
            // Already attached so do nothing
        }
    });
    component.syncComponents();
};
export { fireAttaching, fireDetaching, detachChildren, replaceChildren, virtualAttach, virtualDetach, virtualReplaceChildren };
//# sourceMappingURL=InternalAttachment.js.map