// #region Imports
// #endregion
/**
 * Controller for managing drag and drop operations in the FlowBoard.
 *
 * @public
 */
export class FlowBoardDragController {
    // #region Fields
    _host;
    _config;
    _state;
    _abortController;
    _rafId;
    // #endregion
    // #region Ctor
    /**
     * Creates a new FlowBoardDragController.
     *
     * @public
     * @param host The host element.
     * @param config The controller configuration.
     */
    constructor(host, config) {
        this._host = host;
        this._host.addController(this);
        this._config = config;
        this._state = this.createInitialState();
        this._abortController = null;
        this._rafId = null;
    }
    // #endregion
    // #region Properties
    /**
     * Gets the current drag state.
     *
     * @public
     * @readonly
     */
    get state() {
        return this._state;
    }
    /**
     * Whether a drag operation is in progress.
     *
     * @public
     * @readonly
     */
    get isDragging() {
        return this._state.isDragging;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     */
    hostConnected() {
        // Connection logic if needed
    }
    /**
     * @public
     */
    hostDisconnected() {
        this.cleanup();
    }
    /**
     * Starts a column drag operation.
     *
     * @public
     * @param column The column element to drag.
     * @param event The pointer event that started the drag.
     */
    startColumnDrag(column, event) {
        if (!this._config.isDragEnabled()) {
            return;
        }
        if (column.pinned || !column.reorderable) {
            return;
        }
        const columns = this._config.getColumns();
        const index = columns.indexOf(column);
        if (index === -1) {
            return;
        }
        this._state = {
            isDragging: true,
            type: 'column',
            element: column,
            ghost: this.createColumnGhost(column),
            placeholder: this.createColumnPlaceholder(column),
            fromColumnKey: null,
            key: column.columnKey,
            fromIndex: index,
            toIndex: index,
            targetColumnKey: null,
            startOffset: {
                x: event.clientX - column.getBoundingClientRect().left,
                y: event.clientY - column.getBoundingClientRect().top
            },
            currentPosition: {
                x: event.clientX,
                y: event.clientY
            }
        };
        this.attachGlobalListeners();
        column.classList.add('dragging');
        this._config.onColumnDragStart?.(column.columnKey);
    }
    /**
     * Starts an item drag operation.
     *
     * @public
     * @param item The item element to drag.
     * @param column The column containing the item.
     * @param event The pointer event that started the drag.
     */
    startItemDrag(item, column, event) {
        if (!this._config.isDragEnabled()) {
            return;
        }
        if (item.pinned || !item.draggable || !item.reorderable) {
            return;
        }
        const items = this._config.getColumnItems(column);
        const index = items.indexOf(item);
        if (index === -1) {
            return;
        }
        this._state = {
            isDragging: true,
            type: 'item',
            element: item,
            ghost: this.createItemGhost(item),
            placeholder: this.createItemPlaceholder(item),
            fromColumnKey: column.columnKey,
            key: item.itemKey,
            fromIndex: index,
            toIndex: index,
            targetColumnKey: column.columnKey,
            startOffset: {
                x: event.clientX - item.getBoundingClientRect().left,
                y: event.clientY - item.getBoundingClientRect().top
            },
            currentPosition: {
                x: event.clientX,
                y: event.clientY
            }
        };
        this.attachGlobalListeners();
        item.classList.add('dragging');
        this._config.onItemDragStart?.(item.itemKey, column.columnKey);
    }
    /**
     * Cancels the current drag operation.
     *
     * @public
     */
    cancelDrag() {
        if (!this._state.isDragging) {
            return;
        }
        this.endDrag(false);
    }
    /**
     * Creates the initial drag state.
     *
     * @private
     */
    createInitialState() {
        return {
            isDragging: false,
            type: null,
            element: null,
            ghost: null,
            placeholder: null,
            fromColumnKey: null,
            key: null,
            fromIndex: -1,
            toIndex: -1,
            targetColumnKey: null,
            startOffset: {
                x: 0,
                y: 0
            },
            currentPosition: {
                x: 0,
                y: 0
            }
        };
    }
    /**
     * Creates a ghost element for column dragging.
     *
     * @private
     */
    createColumnGhost(column) {
        const rect = column.getBoundingClientRect();
        const ghost = document.createElement('div');
        ghost.className = 'mosaik-flow-board-column-ghost';
        ghost.style.cssText = `
            position: fixed;
            left: ${rect.left}px;
            top: ${rect.top}px;
            width: ${rect.width}px;
            height: ${rect.height}px;
            pointer-events: none;
            z-index: 10000;
            opacity: 0.8;
            background: var(--flow-board-column-background-color, #fff);
            border-radius: var(--flow-board-column-border-radius, 8px);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
            border: 2px solid var(--flow-board-column-drag-border-color, #3b82f6);
        `;
        document.body.appendChild(ghost);
        return ghost;
    }
    /**
     * Creates a ghost element for item dragging.
     *
     * @private
     */
    createItemGhost(item) {
        const rect = item.getBoundingClientRect();
        const ghost = document.createElement('div');
        ghost.className = 'mosaik-flow-board-item-ghost';
        ghost.style.cssText = `
            position: fixed;
            left: ${rect.left}px;
            top: ${rect.top}px;
            width: ${rect.width}px;
            height: ${rect.height}px;
            pointer-events: none;
            z-index: 10000;
            opacity: 0.9;
            background: var(--flow-board-item-background-color, #fff);
            border-radius: var(--flow-board-item-border-radius, 6px);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
            border: 2px solid var(--flow-board-item-drag-border-color, #3b82f6);
        `;
        document.body.appendChild(ghost);
        return ghost;
    }
    /**
     * Creates a placeholder element for column reordering.
     *
     * @private
     */
    createColumnPlaceholder(column) {
        const rect = column.getBoundingClientRect();
        const placeholder = document.createElement('div');
        placeholder.className = 'mosaik-flow-board-column-placeholder';
        placeholder.style.cssText = `
            width: ${rect.width}px;
            height: ${rect.height}px;
            background: var(--flow-board-placeholder-background, rgba(0, 0, 0, 0.05));
            border-radius: var(--flow-board-column-border-radius, 8px);
            border: 2px dashed var(--flow-board-placeholder-border-color, #94a3b8);
            box-sizing: border-box;
            flex-shrink: 0;
        `;
        return placeholder;
    }
    /**
     * Creates a placeholder element for item reordering.
     *
     * @private
     */
    createItemPlaceholder(item) {
        const rect = item.getBoundingClientRect();
        const placeholder = document.createElement('div');
        placeholder.className = 'mosaik-flow-board-item-placeholder';
        placeholder.style.cssText = `
            width: 100%;
            height: ${rect.height}px;
            background: var(--flow-board-placeholder-background, rgba(0, 0, 0, 0.05));
            border-radius: var(--flow-board-item-border-radius, 6px);
            border: 2px dashed var(--flow-board-placeholder-border-color, #94a3b8);
            box-sizing: border-box;
            margin-block: 4px;
        `;
        return placeholder;
    }
    /**
     * Attaches global pointer event listeners for drag tracking.
     *
     * @private
     */
    attachGlobalListeners() {
        this._abortController = new AbortController();
        const signal = this._abortController.signal;
        document.addEventListener('pointermove', (e) => this.handlePointerMove(e), {
            signal,
            passive: true
        });
        document.addEventListener('pointerup', (e) => this.handlePointerUp(e), { signal });
        document.addEventListener('pointercancel', () => this.cancelDrag(), { signal });
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                this.cancelDrag();
            }
        }, { signal });
    }
    /**
     * Handles pointer move during drag.
     *
     * @private
     */
    handlePointerMove(event) {
        if (!this._state.isDragging) {
            return;
        }
        this._state.currentPosition = {
            x: event.clientX,
            y: event.clientY
        };
        if (this._rafId !== null) {
            cancelAnimationFrame(this._rafId);
        }
        this._rafId = requestAnimationFrame(() => {
            this.updateGhostPosition();
            this.updateDropTarget();
            this._rafId = null;
        });
    }
    /**
     * Handles pointer up to complete drag.
     *
     * @private
     */
    handlePointerUp(_event) {
        if (!this._state.isDragging) {
            return;
        }
        this.endDrag(true);
    }
    /**
     * Updates the ghost element position.
     *
     * @private
     */
    updateGhostPosition() {
        const { ghost, currentPosition, startOffset } = this._state;
        if (!ghost) {
            return;
        }
        ghost.style.left = `${currentPosition.x - startOffset.x}px`;
        ghost.style.top = `${currentPosition.y - startOffset.y}px`;
    }
    /**
     * Updates the drop target based on current pointer position.
     *
     * @private
     */
    updateDropTarget() {
        const { type, currentPosition } = this._state;
        if (type === 'column') {
            this.updateColumnDropTarget(currentPosition);
        }
        else if (type === 'item') {
            this.updateItemDropTarget(currentPosition);
        }
    }
    /**
     * Updates the drop target for column dragging.
     *
     * @private
     */
    updateColumnDropTarget(position) {
        const columns = this._config.getColumns();
        const draggingColumn = this._state.element;
        let newIndex = this._state.fromIndex;
        for (let i = 0; i < columns.length; i++) {
            const column = columns[i];
            if (column === draggingColumn || column.pinned) {
                continue;
            }
            const rect = column.getBoundingClientRect();
            const midX = rect.left + rect.width / 2;
            if (position.x < midX && i < newIndex) {
                newIndex = i;
                break;
            }
            else if (position.x >= midX && i > newIndex) {
                newIndex = i;
            }
        }
        // Ensure we don't move past pinned columns
        const firstNonPinnedIndex = columns.findIndex((c) => !c.pinned);
        if (firstNonPinnedIndex !== -1 && newIndex < firstNonPinnedIndex) {
            newIndex = firstNonPinnedIndex;
        }
        this._state.toIndex = newIndex;
    }
    /**
     * Updates the drop target for item dragging.
     *
     * @private
     */
    updateItemDropTarget(position) {
        const columns = this._config.getColumns();
        let targetColumn = null;
        // Find the column under the cursor
        for (const column of columns) {
            const rect = column.getBoundingClientRect();
            if (position.x >= rect.left && position.x <= rect.right) {
                targetColumn = column;
                break;
            }
        }
        if (!targetColumn) {
            return;
        }
        this._state.targetColumnKey = targetColumn.columnKey;
        const items = this._config.getColumnItems(targetColumn);
        const pinnedCount = items.filter((item) => item.pinned).length;
        let newIndex = pinnedCount;
        for (let i = pinnedCount; i < items.length; i++) {
            const item = items[i];
            if (item === this._state.element) {
                continue;
            }
            const rect = item.getBoundingClientRect();
            const midY = rect.top + rect.height / 2;
            if (position.y < midY) {
                newIndex = i;
                break;
            }
            else {
                newIndex = i + 1;
            }
        }
        this._state.toIndex = newIndex;
    }
    /**
     * Ends the drag operation.
     *
     * @private
     */
    endDrag(dropped) {
        const { type, element, key, fromColumnKey, fromIndex, toIndex, targetColumnKey } = this._state;
        // Clean up visual state
        element?.classList.remove('dragging');
        if (dropped && type === 'column' && key) {
            if (fromIndex !== toIndex) {
                const items = this._config.getColumns();
                const pinnedCount = items.filter((c) => c.pinned).length;
                this._config.onColumnReorder?.(key, fromIndex, Math.max(toIndex, pinnedCount));
            }
            this._config.onColumnDragEnd?.(key, fromIndex !== toIndex);
        }
        else if (dropped && type === 'item' && key && fromColumnKey) {
            if (targetColumnKey && targetColumnKey !== fromColumnKey) {
                // Cross-column move
                const targetColumn = this._config.getColumns().find((c) => c.columnKey === targetColumnKey);
                const pinnedOffset = targetColumn
                    ? this._config.getColumnItems(targetColumn).filter((i) => i.pinned).length
                    : 0;
                this._config.onItemMove?.(key, fromColumnKey, targetColumnKey, fromIndex, toIndex, pinnedOffset);
            }
            else if (fromIndex !== toIndex) {
                // Same column reorder
                const column = this._config.getColumns().find((c) => c.columnKey === fromColumnKey);
                const pinnedOffset = column
                    ? this._config.getColumnItems(column).filter((i) => i.pinned).length
                    : 0;
                this._config.onItemReorder?.(key, fromColumnKey, fromIndex, toIndex, pinnedOffset);
            }
            this._config.onItemDragEnd?.(key, fromColumnKey, fromIndex !== toIndex || targetColumnKey !== fromColumnKey);
        }
        else if (type === 'column' && key) {
            this._config.onColumnDragEnd?.(key, false);
        }
        else if (type === 'item' && key && fromColumnKey) {
            this._config.onItemDragEnd?.(key, fromColumnKey, false);
        }
        this.cleanup();
    }
    /**
     * Cleans up drag state and listeners.
     *
     * @private
     */
    cleanup() {
        if (this._rafId !== null) {
            cancelAnimationFrame(this._rafId);
            this._rafId = null;
        }
        this._abortController?.abort();
        this._abortController = null;
        this._state.ghost?.remove();
        this._state.placeholder?.remove();
        this._state = this.createInitialState();
    }
}
/**
 * Adds a FlowBoardDragController to the given host.
 *
 * @public
 * @param host The host element.
 * @param config The controller configuration.
 * @returns The FlowBoardDragController instance.
 */
export function addFlowBoardDragController(host, config) {
    return new FlowBoardDragController(host, config);
}
//# sourceMappingURL=FlowBoardDragController.js.map