/**
 * @name dndSortable
 * @description A directive to make a list of items sortable via drag-and-drop.
 *
 * @attribute {Array} dnd-sortable - The array of items to be sorted.
 * @attribute {String} dnd-sortable-item-selector - A CSS selector to identify the individual draggable items within the list.
 *
 * @example
 * <div dnd-sortable="myItemsArray" dnd-sortable-item-selector=".item">
 *   <div ng-repeat="item in myItemsArray" class="item">{{ item.name }}</div>
 * </div>
 */
llmApp.directive('dndSortable', [function () {
    return {
        restrict: 'A',
        scope: {
            dndList: '=dndSortable' // Two-way bind the array
        },
        link: function (scope, element, attrs) {
            // Get the selector for draggable items from the attribute
            var itemSelector = attrs.dndSortableItemSelector;
            if (!itemSelector) {

                return;
            }

            var container = element[0];
            var draggedItem = null;

            // Helper to get the current index of a DOM element
            function getElementIndex(item) {
                return Array.from(container.querySelectorAll(itemSelector)).indexOf(item);
            }

// Update the dragstart event listener
            container.addEventListener('dragstart', function (e) {
                var targetItem = e.target.closest(itemSelector);
                if (!targetItem) return;

                draggedItem = targetItem;
                var sourceIndex = getElementIndex(draggedItem);

                e.dataTransfer.setData('text/plain', sourceIndex);
                e.dataTransfer.effectAllowed = 'move';

                // Add styling classes after a short delay to ensure drag has started
                setTimeout(function () {
                    draggedItem.classList.add('dragging');
                    draggedItem.style.opacity = '0.5';

                    // Update parent scope with dragging state
                    scope.$apply(function() {
                        if (!scope.$parent.isDragging) {
                            scope.$parent.isDragging = {};
                        }
                        scope.$parent.isDragging[sourceIndex] = true;
                    });
                }, 0);
            });


            // Handle moving over the container
            container.addEventListener('dragover', function (e) {
                e.preventDefault(); // Critical for drop to work
                e.dataTransfer.dropEffect = 'move';

                var targetItem = e.target.closest(itemSelector);
                // Do nothing if we are not over a valid target item or over the item being dragged
                if (!targetItem || targetItem === draggedItem) return;

                // Clear previous indicators
                container.querySelectorAll(itemSelector).forEach(function (item) {
                    item.classList.remove('drop-before', 'drop-after');
                });

                // Add new indicator based on cursor position
                var rect = targetItem.getBoundingClientRect();
                var beforeTarget = e.clientY < rect.top + rect.height / 2;
                targetItem.classList.add(beforeTarget ? 'drop-before' : 'drop-after');
            });

            // Handle the drop event
            container.addEventListener('drop', function (e) {
                e.preventDefault();
                if (!draggedItem) return;

                var sourceIndex = parseInt(e.dataTransfer.getData('text/plain'));
                var targetElement = e.target.closest(itemSelector);
                if (!targetElement) return; // Dropped on a non-item area

                var targetIndex = getElementIndex(targetElement);

                // Determine if dropping before or after the target
                var rect = targetElement.getBoundingClientRect();
                var beforeTarget = e.clientY < rect.top + rect.height / 2;
                var dropIndex = beforeTarget ? targetIndex : targetIndex + 1;

                // Apply the change to the model within a digest cycle
                scope.$apply(function () {
                    moveItem(sourceIndex, dropIndex);
                });
            });

            // Update the dragend event listener
            container.addEventListener('dragend', function (e) {
                cleanupStyling();

                // Reset dragging state in parent scope
                setTimeout(function() {
                    scope.$apply(function() {
                        if (scope.$parent.isDragging) {
                            scope.$parent.isDragging = {};
                        }
                    });
                }, 50); // Small delay to ensure smooth transitions

                draggedItem = null;
            });


            // Moves an item within the array in the directive's scope
            function moveItem(fromIndex, toIndex) {
                if (fromIndex === toIndex) return;

                var list = scope.dndList;
                // Adjust target index if moving an item downwards
                if (toIndex > fromIndex) {
                    toIndex--;
                }

                // Remove from original position and insert into new position
                var itemToMove = list.splice(fromIndex, 1)[0];
                list.splice(toIndex, 0, itemToMove);
            }

            // Resets all drag-and-drop related styling
            function cleanupStyling() {
                container.querySelectorAll(itemSelector).forEach(function (item) {
                    item.classList.remove('drop-before', 'drop-after', 'dragging');
                    item.style.opacity = '1';
                });
            }

            // Ensure all items governed by the directive are draggable
            // This runs once and a MutationObserver handles items added later by ng-repeat
            function initializeDraggableItems() {
                container.querySelectorAll(itemSelector).forEach(function(item) {
                    item.setAttribute('draggable', 'true');
                });
            }

            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    mutation.addedNodes.forEach(function(node) {
                        if (node.nodeType === 1 && node.matches(itemSelector)) {
                            node.setAttribute('draggable', 'true');
                        }
                    });
                });
            });

            observer.observe(container, { childList: true });
            initializeDraggableItems();

            scope.$on('$destroy', function() {
                observer.disconnect();
            });
        }
    };
}]);