import { defineTrait } from '../define.js';

/**
 * `droppable-collection` — coordinator over a tree of `droppable` children.
 *
 * Authored for the Tasks UI playground (apps/tasks/spec/).
 * Per SPEC §6.2 + ARCHITECTURE-REVIEW H2 (category: 'input-interaction').
 *
 * Pairs with `droppable` (per-target) and `draggable-list-item` (motion-
 * positioning, the lifter). Represents a board / multi-column container
 * whose immediate descendant droppables form a coordinated set:
 *
 *   <task-board-ui [droppable-collection]>
 *     <task-column-ui [droppable] data-droppable-id="todo">…</task-column-ui>
 *     <task-column-ui [droppable] data-droppable-id="doing">…</task-column-ui>
 *     <task-column-ui [droppable] data-droppable-id="done">…</task-column-ui>
 *   </task-board-ui>
 *
 * Responsibilities (pure DOM coordination — no shared module state):
 *  1. Listen for `dnd-drop` events bubbling up from any droppable child.
 *  2. Re-emit a single `dnd-collection-drop` on the collection host that
 *     view code can subscribe to once instead of N column listeners.
 *  3. Reflect the dragging state to the collection host so the board can
 *     dim non-target columns or render group affordances.
 *
 * Coordination is via DOM events only (light-DOM, bubbles: true) so it
 * remains observable from authoring code, no shared registry needed.
 */

const ATTR_ACTIVE = 'data-droppable-collection-active';
const ATTR_DRAGGING = 'data-droppable-collection-dragging';

export const droppableCollection = defineTrait({
  name: 'droppable-collection',
  category: 'input-interaction',
  description: 'Coordinates droppable children; re-emits drops as dnd-collection-drop',
  attributes: [ATTR_ACTIVE, ATTR_DRAGGING],
  events: ['dnd-collection-drop'],
  config: [],
  setup({ host }) {
    host.setAttribute(ATTR_ACTIVE, '');

    function onLift() {
      host.setAttribute(ATTR_DRAGGING, '');
    }
    function onDragEnd() {
      host.removeAttribute(ATTR_DRAGGING);
    }

    /** @param {Event} e */
    function onChildDrop(e) {
      // Only react to drops that landed inside a child droppable of THIS
      // collection — don't double-handle drops from a nested collection.
      const target = /** @type {Element} */ (e.target);
      if (!target || !host.contains(target)) return;
      const detail = /** @type {CustomEvent} */ (e).detail;
      if (!detail) return;

      host.removeAttribute(ATTR_DRAGGING);
      host.dispatchEvent(new CustomEvent('dnd-collection-drop', {
        bubbles: true,
        composed: false,
        detail: {
          source_id: detail.source_id,
          source_index: detail.source_index,
          source_container_id: detail.source_container_id,
          target_container_id: detail.target_container_id,
          target_index: detail.target_index,
        },
      }));
    }

    host.addEventListener('dnd-lift', onLift);
    host.addEventListener('dnd-drop-cancel', onDragEnd);
    // Listen for the TARGET-side event from the [droppable] trait, not the
    // source-side `dnd-drop` from the lifter — those would arrive too late
    // (the lifter dispatches on document, only the host re-emit reaches us)
    // AND match this collection's own outbound `dnd-collection-drop` shape.
    host.addEventListener('dnd-drop-receive', onChildDrop);

    return () => {
      host.removeEventListener('dnd-lift', onLift);
      host.removeEventListener('dnd-drop-cancel', onDragEnd);
      host.removeEventListener('dnd-drop-receive', onChildDrop);
      host.removeAttribute(ATTR_ACTIVE);
      host.removeAttribute(ATTR_DRAGGING);
    };
  },
});
