import { __decorate, __metadata } from "tslib";
import { ContentChild, Directive, ElementRef, EventEmitter, Inject, Injector, Input, NgZone, Output } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { LayoutComponentRendererSADirective } from '@anglr/dynamic/layout';
import { BindThis, isBlank, isPresent } from '@jscrpt/common';
import { DndService } from '@ng-dnd/core';
import { filter, Subscription } from 'rxjs';
import { DragActiveService, LayoutEditorMetadataManager } from '../../../../services';
import { DndBusService } from '../../services';
import * as i0 from "@angular/core";
import * as i1 from "@ng-dnd/core";
import * as i2 from "../../../../services";
import * as i3 from "../../services";
// import {registerDropzoneOverlay} from '../../misc/utils';
/**
 * Directive used for initializing and handling dnd core functionality for layout designer
 */
export class DndCoreDesignerDirective {
    /**
     * Current metadata for this component
     */
    get metadata() {
        if (!this.dragData.metadata) {
            throw new Error('DndCoreDesignerDirective: invalid drag data without metadata!');
        }
        return this.dragData.metadata;
    }
    /**
     * Gets indication whether component can accept drop
     */
    get canDrop() {
        return this.manager.getComponent(this.metadata.id)?.canDrop ?? false;
    }
    /**
     * Gets indication whether component children flow is horizontal
     */
    get horizontal() {
        return this.manager.getComponent(this.metadata.id)?.horizontal ?? false;
    }
    /**
     * Gets element that represents container that contains children
     */
    get containerElement() {
        if (!this.componentElement) {
            return;
        }
        const component = this.manager.getComponent(this.metadata.id);
        if (!component?.editorMetadata?.getChildrenContainer) {
            return this.componentElement;
        }
        return component.editorMetadata.getChildrenContainer(this.componentElement) ?? this.componentElement;
    }
    //######################### constructor #########################
    constructor(dnd, designerElement, draggingSvc, manager, bus, zone, injector, document) {
        this.dnd = dnd;
        this.designerElement = designerElement;
        this.draggingSvc = draggingSvc;
        this.manager = manager;
        this.bus = bus;
        this.zone = zone;
        this.injector = injector;
        this.document = document;
        //######################### protected properties #########################
        /**
         * NgZone instance
         */
        this.ngZone = Inject(NgZone);
        /**
         * Subscriptions created during initialization
         */
        this.initSubscriptions = new Subscription();
        /**
         * Drop zone target that handles drop of component
         */
        this.placeholderDrop = this.dnd.dropTarget(['COMPONENT', 'METADATA'], {
            canDrop: () => true,
            drop: monitor => {
                const item = monitor.getItem();
                let index = this.bus.dropPlaceholderPreviewIndex;
                if (item && isPresent(item.dragData.index) && isPresent(index)) {
                    //same parent and higher index
                    if (index > item.dragData.index) {
                        index--;
                    }
                }
                return {
                    index,
                    id: this.metadata?.id,
                };
            },
        }, this.initSubscriptions);
        /**
         * Drop zone target that handles drop of component
         */
        this.containerDrop = this.dnd.dropTarget(['COMPONENT', 'METADATA'], {
            canDrop: monitor => this.canDropAncestors()[0] && monitor.isOver({ shallow: true }),
            drop: monitor => {
                const [index, id] = this.getFixedDropCoordinates(monitor, false);
                return {
                    index,
                    id,
                };
            },
            hover: monitor => {
                if (monitor.isOver({ shallow: true })) {
                    const [index, parentId] = this.getDropCoordinates(monitor, false);
                    if (isBlank(index) || isBlank(parentId)) {
                        return;
                    }
                    this.bus.setDropPlaceholderPreview({
                        index,
                        parentId,
                        placeholder: {
                            height: 0,
                            width: 0
                        }
                    });
                }
            }
        }, this.initSubscriptions);
        //######################### public properties #########################
        /**
         * Drag source used for dragging component
         */
        this.drag = this.dnd.dragSource('COMPONENT', {
            beginDrag: () => {
                this.draggingSvc.setDragging(true);
                this.designerElement.nativeElement.classList.add('is-dragged');
                return {
                    dragData: this.dragData,
                };
            },
            canDrag: () => !this.dragDisabled,
            endDrag: monitor => {
                //dropped outside of any dropzone
                if (!monitor.didDrop()) {
                    this.designerElement.nativeElement.classList.remove('is-dragged');
                }
                //dropped into drop zone
                else {
                    const item = monitor.getItem();
                    const dropResult = monitor.getDropResult();
                    if (!item) {
                        return;
                    }
                    item.dragData.index = dropResult.index;
                    this.bus.setDropData({
                        data: item.dragData,
                        id: dropResult.id,
                    });
                }
                this.bus.setDropPlaceholderPreview(null);
                this.draggingSvc.setDragging(false);
            },
        }, this.initSubscriptions);
        /**
         * Drop zone target that handles drop of component
         */
        this.dropzone = this.dnd.dropTarget(['COMPONENT', 'METADATA'], {
            canDrop: monitor => (this.canDrop || this.canDropAncestors()[0]) && monitor.isOver({ shallow: true }),
            drop: monitor => {
                const [index, id] = this.getFixedDropCoordinates(monitor, this.canDrop);
                return {
                    index,
                    id
                };
            },
            hover: monitor => {
                if (monitor.isOver({ shallow: true }) && monitor.canDrop()) {
                    const [index, parentId] = this.getDropCoordinates(monitor, this.canDrop);
                    if (isBlank(index) || isBlank(parentId)) {
                        return;
                    }
                    this.bus.setDropPlaceholderPreview({
                        index,
                        parentId,
                        placeholder: {
                            height: 0,
                            width: 0
                        }
                    });
                }
            }
        }, this.initSubscriptions);
        /**
         * Indication whether is drag disabled
         */
        this.dragDisabled = false;
        //######################### public properties - outputs #########################
        /**
         * Occurs when metadata are dropped here
         */
        this.dropMetadata = new EventEmitter();
        this.connectDropToContainer();
    }
    //######################### public methods - implementation of OnInit #########################
    /**
     * Initialize component
     */
    ngOnInit() {
        if (!this.dropzoneElement) {
            throw new Error('DndCoreDesignerDirective: missing dropzone element!');
        }
        if (!this.dragData) {
            throw new Error('DndCoreDesignerDirective: missing drag data!');
        }
        this.initSubscriptions.add(this.layoutComponentRendererDirective?.componentElementChange.subscribe(element => {
            if (!element) {
                this.componentElement = null;
                return;
            }
            this.componentElement = element;
        }));
        this.initSubscriptions.add(this.bus
            .dropDataChange
            .pipe(filter(itm => itm.id === this.metadata.id))
            .subscribe(itm => {
            this.ngZone.run(() => this.dropMetadata.emit(itm.data));
        }));
        this.initSubscriptions.add(this.bus
            .oldDropPlaceholderPreviewChange
            .pipe(filter(itm => itm.parentId === this.metadata.id))
            .subscribe(() => {
            this.ngZone.run(() => {
                this.placeholderPreviewElement?.remove();
                this.placeholderPreviewElement = null;
            });
        }));
        this.initSubscriptions.add(this.bus
            .newDropPlaceholderPreviewChange
            .pipe(filter(itm => itm.parentId === this.metadata.id))
            .subscribe((preview) => {
            this.ngZone.run(() => this.showPlaceholderPreview(preview));
        }));
        // this.initSubscriptions.add(registerDropzoneOverlay(this.dropzone, this.dropzoneElement, this.injector, this.dragData));
        // this.initSubscriptions.add(registerDropzoneOverlay(this.containerDrop, this.designerElement.nativeElement, this.injector, this.dragData));
        // this.initSubscriptions.add(registerDropzoneOverlay(this.containerDrop, this.designerElement.nativeElement, this.injector, this.dragData));
    }
    //######################### public methods - implementation of OnDestroy #########################
    /**
     * Called when component is destroyed
     */
    ngOnDestroy() {
        this.initSubscriptions.unsubscribe();
        this.placeholderConnection?.unsubscribe();
        this.placeholderConnection = null;
        this.containerConnection?.unsubscribe();
        this.containerConnection = null;
    }
    //######################### protected methods #########################
    /**
     * Gets drop coordinates
     * @param monitor - Monitor containing information about current drag drop state
     * @param canDrop - Indication whether can drop can occur on monitor itself
     */
    getDropCoordinates(monitor, canDrop) {
        //can drop in itself// for now drop at index 0
        if (canDrop) {
            return this.getDropCoordinatesForChildren(monitor);
        }
        //else get index from descendant
        const [canDropAncestor, ancestorId, id] = this.canDropAncestors();
        //this should not happen
        if (!canDropAncestor || isBlank(ancestorId)) {
            return [null, null];
        }
        const parentComponent = this.manager.getComponent(ancestorId);
        const componentIndex = this.manager.getComponent(id)?.index ?? 0;
        const item = monitor.getItem();
        if (item && isPresent(item.dragData.index)) {
            //is over itself
            if (item.dragData.metadata?.id === this.metadata.id) {
                return [item.dragData.index, item.dragData.parentId ?? ''];
            }
        }
        if (!parentComponent) {
            return [null, null];
        }
        return [componentIndex + this.getIndexIncrement(monitor, parentComponent.horizontal), ancestorId];
    }
    /**
     * Gets coordinates calculated for children of this component
     * @param monitor - Monitor containing information about current drag drop state
     */
    getDropCoordinatesForChildren(monitor) {
        const getHalf = (element) => {
            const rect = element.children[0].getBoundingClientRect();
            const position = this.horizontal ? rect.x : rect.y;
            const half = (this.horizontal ? rect.width : rect.height) / 2;
            return position + half;
        };
        if (!this.containerElement) {
            return [null, null];
        }
        let index = 0;
        const offset = monitor.getClientOffset();
        if (!offset) {
            return [null, null];
        }
        const position = this.horizontal ? offset.x : offset.y;
        for (let x = 0; x < this.containerElement.children.length; x++) {
            const child = this.containerElement.children[x];
            //do nothing for placeholder
            if (child.classList.contains('drag-placeholder')) {
                continue;
            }
            //return index if less than half
            if (position <= getHalf(child)) {
                return [index, this.metadata.id];
            }
            index++;
        }
        return [index, this.metadata.id];
    }
    /**
     * Gets fixed drop coordinates
     * @param monitor - Monitor containing information about current drag drop state
     * @param canDrop - Indication whether can drop can occur on monitor itself
     */
    getFixedDropCoordinates(monitor, canDrop) {
        const [index, id] = this.getDropCoordinates(monitor, canDrop);
        let usedIndex = index;
        const item = monitor.getItem();
        if (item && isPresent(usedIndex) && isPresent(item.dragData.index)) {
            //same parent and higher index
            if (id === item.dragData.parentId &&
                usedIndex > item.dragData.index) {
                usedIndex--;
            }
        }
        return [usedIndex, id];
    }
    /**
     * Gets index increment
     * @param monitor - Monitor to be used for obtaining information about index
     * @param horizontal - Indication whether are items horizontaly oriented
     */
    getIndexIncrement(monitor, horizontal) {
        const rect = this.dropzoneElement.getBoundingClientRect();
        const offset = monitor.getClientOffset();
        if (!offset) {
            return 0;
        }
        const position = horizontal ? offset.x - rect.x : offset.y - rect.y;
        const half = horizontal ? rect.width / 2 : rect.height / 2;
        if (position <= half) {
            return 0;
        }
        else {
            return 1;
        }
    }
    /**
     * Shows placeholder preview at specified location
     * @param preview - Instance of preview data
     */
    showPlaceholderPreview(preview) {
        if (!this.containerElement) {
            return;
        }
        this.placeholderPreviewElement ?? (this.placeholderPreviewElement = this.document.createElement('div'));
        this.placeholderPreviewElement.classList.add('drag-placeholder');
        this.placeholderPreviewElement.remove();
        this.connectDropToPlaceholder();
        this.containerElement.insertBefore(this.placeholderPreviewElement, this.containerElement.children[preview.index]);
    }
    /**
     * Connects placeholder preview element to placeholder drop
     */
    connectDropToPlaceholder() {
        this.zone.runOutsideAngular(() => {
            this.placeholderConnection?.unsubscribe();
            if (this.placeholderPreviewElement) {
                this.placeholderConnection = this.placeholderDrop.connectDropTarget(this.placeholderPreviewElement);
            }
        });
    }
    /**
     * Connects container element to container drop
     */
    connectDropToContainer() {
        this.zone.runOutsideAngular(() => {
            this.containerConnection?.unsubscribe();
            this.containerConnection = this.containerDrop.connectDropTarget(this.designerElement.nativeElement);
        });
    }
    /**
     * Gets indication whether any of ancestors can accept drop, also returns id of that ancestor
     * @param id - Id of component whose parent will be tested, if not specified id of this component will be used
     */
    canDropAncestors(id) {
        if (isBlank(id)) {
            id = this.metadata.id;
        }
        const component = this.manager.getComponentDef(id);
        if (!component?.parent) {
            return [false, null, id];
        }
        if (component.parent.component.canDrop) {
            return [true, component.parent.component.id, id];
        }
        else {
            return this.canDropAncestors(component.parent.component.id);
        }
    }
}
DndCoreDesignerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: DndCoreDesignerDirective, deps: [{ token: i1.DndService }, { token: i0.ElementRef }, { token: i2.DragActiveService }, { token: i2.LayoutEditorMetadataManager }, { token: i3.DndBusService }, { token: i0.NgZone }, { token: i0.Injector }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
DndCoreDesignerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.0.4", type: DndCoreDesignerDirective, selector: "[dndCoreDesigner]", inputs: { dropzoneElement: "dropzoneElement", dragData: ["dndCoreDesigner", "dragData"], dragDisabled: "dragDisabled" }, outputs: { dropMetadata: "dropMetadata" }, queries: [{ propertyName: "layoutComponentRendererDirective", first: true, predicate: LayoutComponentRendererSADirective, descendants: true, static: true }], exportAs: ["dndCoreDesigner"], ngImport: i0 });
__decorate([
    BindThis,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], DndCoreDesignerDirective.prototype, "showPlaceholderPreview", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: DndCoreDesignerDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[dndCoreDesigner]',
                    exportAs: 'dndCoreDesigner',
                }]
        }], ctorParameters: () => [{ type: i1.DndService }, { type: i0.ElementRef }, { type: i2.DragActiveService }, { type: i2.LayoutEditorMetadataManager }, { type: i3.DndBusService }, { type: i0.NgZone }, { type: i0.Injector }, { type: Document, decorators: [{
                    type: Inject,
                    args: [DOCUMENT]
                }] }], propDecorators: { layoutComponentRendererDirective: [{
                type: ContentChild,
                args: [LayoutComponentRendererSADirective, { static: true }]
            }], dropzoneElement: [{
                type: Input
            }], dragData: [{
                type: Input,
                args: ['dndCoreDesigner']
            }], dragDisabled: [{
                type: Input
            }], dropMetadata: [{
                type: Output
            }], showPlaceholderPreview: [] } });
//# sourceMappingURL=dndCoreDesigner.directive.js.map