import { __decorate, __metadata } from "tslib";
import { Directive, ElementRef, EventEmitter, Inject, Injector, Input, NgZone, Output } from '@angular/core';
import { DOCUMENT } from '@angular/common';
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";
/**
 * Directive used for initializing and handling dnd core functionality for layout component tree item
 */
export class DndCoreTreeItemDirective {
    //######################### protected properties #########################
    /**
     * Current metadata for this component
     */
    get metadata() {
        if (!this.dragData.metadata) {
            throw new Error('DndCoreTreeItemDirective: invalid drag data without metadata!');
        }
        return this.dragData.metadata;
    }
    /**
     * Gets indication whether component can accept drop
     */
    get canDrop() {
        var _a, _b;
        return (_b = (_a = this.manager.getComponent(this.metadata.id)) === null || _a === void 0 ? void 0 : _a.canDrop) !== null && _b !== void 0 ? _b : false;
    }
    //######################### constructor #########################
    constructor(dnd, designerElement, draggingSvc, manager, bus, zone, injector, _manager, document) {
        this.dnd = dnd;
        this.designerElement = designerElement;
        this.draggingSvc = draggingSvc;
        this.manager = manager;
        this.bus = bus;
        this.zone = zone;
        this.injector = injector;
        this._manager = _manager;
        this.document = document;
        /**
         * Subscriptions created during initialization
         */
        this.initSubscriptions = new Subscription();
        /**
         * Drop zone target that handles drop of component
         */
        this.placeholderDrop = this.dnd.dropTarget(['TREE_COMPONENT'], {
            canDrop: () => true,
            drop: monitor => {
                var _a;
                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: (_a = this.metadata) === null || _a === void 0 ? void 0 : _a.id,
                };
            },
        }, this.initSubscriptions);
        /**
         * Drop zone target that handles drop of component
         */
        this.containerDrop = this.dnd.dropTarget(['TREE_COMPONENT'], {
            canDrop: monitor => { var _a, _b; return this.canDropAncestors(this.metadata.id, (_b = (_a = monitor.getItem()) === null || _a === void 0 ? void 0 : _a.dragData.metadata) === null || _b === void 0 ? void 0 : _b.id)[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('TREE_COMPONENT', {
            beginDrag: () => {
                this.draggingSvc.setDragging(true);
                this.designerElement.nativeElement.classList.add('is-dragged');
                return {
                    dragData: this.dragData,
                };
            },
            canDrag: () => !this.dragDisabled,
            endDrag: monitor => {
                this.designerElement.nativeElement.classList.remove('is-dragged');
                //dropped outside of any dropzone
                if (monitor.didDrop()) {
                    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(['TREE_COMPONENT'], {
            canDrop: monitor => {
                var _a, _b, _c, _d;
                return ((this.canDrop && !this.hasAncestor(this.metadata.id, (_b = (_a = monitor.getItem()) === null || _a === void 0 ? void 0 : _a.dragData.metadata) === null || _b === void 0 ? void 0 : _b.id)) || this.canDropAncestors(this.metadata.id, (_d = (_c = monitor.getItem()) === null || _c === void 0 ? void 0 : _c.dragData.metadata) === null || _d === void 0 ? void 0 : _d.id)[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 })) {
                    this._manager.dragOverComponent(this.metadata.id);
                    if (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.containerElement) {
            throw new Error('DndCoreDesignerDirective: missing container element!');
        }
        if (!this.dragData) {
            throw new Error('DndCoreDesignerDirective: missing drag data!');
        }
        this.initSubscriptions.add(this.bus
            .dropDataChange
            .pipe(filter(itm => itm.id === this.metadata.id))
            .subscribe(itm => this.dropMetadata.emit(itm.data)));
        this.initSubscriptions.add(this.bus
            .oldDropPlaceholderPreviewChange
            .pipe(filter(itm => itm.parentId === this.metadata.id))
            .subscribe(() => {
            var _a;
            (_a = this.placeholderPreviewElement) === null || _a === void 0 ? void 0 : _a.remove();
            this.placeholderPreviewElement = null;
        }));
        this.initSubscriptions.add(this.bus
            .newDropPlaceholderPreviewChange
            .pipe(filter(itm => itm.parentId === this.metadata.id))
            .subscribe(this.showPlaceholderPreview));
    }
    //######################### public methods - implementation of OnDestroy #########################
    /**
     * Called when component is destroyed
     */
    ngOnDestroy() {
        var _a, _b;
        this.initSubscriptions.unsubscribe();
        (_a = this.placeholderConnection) === null || _a === void 0 ? void 0 : _a.unsubscribe();
        this.placeholderConnection = null;
        (_b = this.containerConnection) === null || _b === void 0 ? void 0 : _b.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) {
        var _a, _b, _c, _d, _e, _f;
        //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.metadata.id, (_b = (_a = monitor.getItem()) === null || _a === void 0 ? void 0 : _a.dragData.metadata) === null || _b === void 0 ? void 0 : _b.id);
        //this should not happen
        if (!canDropAncestor || isBlank(ancestorId)) {
            return [null, null];
        }
        const parentComponent = this.manager.getComponent(ancestorId);
        const componentIndex = (_d = (_c = this.manager.getComponent(id)) === null || _c === void 0 ? void 0 : _c.index) !== null && _d !== void 0 ? _d : 0;
        const item = monitor.getItem();
        if (item && isPresent(item.dragData.index)) {
            //is over itself
            if (((_e = item.dragData.metadata) === null || _e === void 0 ? void 0 : _e.id) === this.metadata.id) {
                return [item.dragData.index, (_f = item.dragData.parentId) !== null && _f !== void 0 ? _f : ''];
            }
        }
        if (!parentComponent) {
            return [null, null];
        }
        return [componentIndex + this.getIndexIncrement(monitor), ancestorId];
    }
    /**
     * Gets coordinates calculated for children of this component
     * @param monitor - Monitor containing information about current drag drop state
     */
    getDropCoordinatesForChildren(monitor) {
        const getHalf = (element) => {
            var _a;
            const rect = (_a = element.children[0]) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
            const position = rect === null || rect === void 0 ? void 0 : rect.y;
            const half = (rect === null || rect === void 0 ? void 0 : 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 = 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) {
        const rect = this.dropzoneElement.getBoundingClientRect();
        const offset = monitor.getClientOffset();
        if (!offset) {
            return 0;
        }
        const position = offset.y - rect.y;
        const half = 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) {
        var _a;
        if (!this.containerElement) {
            return;
        }
        (_a = this.placeholderPreviewElement) !== null && _a !== void 0 ? _a : (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(() => {
            var _a;
            (_a = this.placeholderConnection) === null || _a === void 0 ? void 0 : _a.unsubscribe();
            if (this.placeholderPreviewElement) {
                this.placeholderConnection = this.placeholderDrop.connectDropTarget(this.placeholderPreviewElement);
            }
        });
    }
    /**
     * Connects container element to container drop
     */
    connectDropToContainer() {
        this.zone.runOutsideAngular(() => {
            var _a;
            (_a = this.containerConnection) === null || _a === void 0 ? void 0 : _a.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, ancestorId) {
        if (isBlank(id)) {
            id = this.metadata.id;
        }
        const component = this.manager.getComponentDef(id);
        if (!(component === null || component === void 0 ? void 0 : component.parent) ||
            isPresent(ancestorId) && this.hasAncestor(component.component.id, ancestorId)) {
            return [false, null, id];
        }
        if (component.parent.component.canDrop) {
            return [true, component.parent.component.id, id];
        }
        else {
            return this.canDropAncestors(component.parent.component.id, ancestorId);
        }
    }
    /**
     * Checks whether component has ancestor with specified identifier
     * @param component component to check
     * @param ancestorId ancestor identifier to find
     * @returns
     */
    hasAncestor(id, ancestorId) {
        var _a, _b;
        if (!ancestorId) {
            return false;
        }
        const component = this.manager.getComponentDef(id);
        if (((_a = component === null || component === void 0 ? void 0 : component.component) === null || _a === void 0 ? void 0 : _a.id) === ancestorId) {
            return true;
        }
        if (component === null || component === void 0 ? void 0 : component.parent) {
            return this.hasAncestor((_b = component.parent.component) === null || _b === void 0 ? void 0 : _b.id, ancestorId);
        }
        return false;
    }
}
DndCoreTreeItemDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: DndCoreTreeItemDirective, deps: [{ token: i1.DndService }, { token: i0.ElementRef }, { token: i2.DragActiveService }, { token: i2.LayoutEditorMetadataManager }, { token: i3.DndBusService }, { token: i0.NgZone }, { token: i0.Injector }, { token: i2.LayoutEditorMetadataManager }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
DndCoreTreeItemDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.0.4", type: DndCoreTreeItemDirective, selector: "[dndCoreTreeItem]", inputs: { dropzoneElement: "dropzoneElement", containerElement: "containerElement", dragData: ["dndCoreTreeItem", "dragData"], dragDisabled: "dragDisabled" }, outputs: { dropMetadata: "dropMetadata" }, exportAs: ["dndCoreTreeItem"], ngImport: i0 });
__decorate([
    BindThis,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], DndCoreTreeItemDirective.prototype, "showPlaceholderPreview", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: DndCoreTreeItemDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[dndCoreTreeItem]',
                    exportAs: 'dndCoreTreeItem',
                }]
        }], ctorParameters: () => [{ type: i1.DndService }, { type: i0.ElementRef }, { type: i2.DragActiveService }, { type: i2.LayoutEditorMetadataManager }, { type: i3.DndBusService }, { type: i0.NgZone }, { type: i0.Injector }, { type: i2.LayoutEditorMetadataManager }, { type: Document, decorators: [{
                    type: Inject,
                    args: [DOCUMENT]
                }] }], propDecorators: { dropzoneElement: [{
                type: Input
            }], containerElement: [{
                type: Input
            }], dragData: [{
                type: Input,
                args: ['dndCoreTreeItem']
            }], dragDisabled: [{
                type: Input
            }], dropMetadata: [{
                type: Output
            }], showPlaceholderPreview: [] } });
//# sourceMappingURL=dndCoreTreeItem.directive.js.map