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

export const dragGhost = defineTrait({
  name: 'drag-ghost',
  category: 'motion-positioning',
  description: 'Ghost clone at origin during drag',
  attributes: ['data-drag-ghost-active'],
  events: [],
  config: [],
  setup({ host }) {
    let ghost = null;
    let clickOffsetX = 0;
    let clickOffsetY = 0;

    function onPointerDown(e) {
      // Capture click offset against host's visual box (post-transform).
      const rect = host.getBoundingClientRect();
      clickOffsetX = e.clientX - rect.left;
      clickOffsetY = e.clientY - rect.top;
    }

    function onDragStart(e) {
      // Snapshot is taken synchronously at setDragImage() time. We position
      // the ghost over the host's exact viewport rect so the bitmap is
      // pixel-identical to what the user clicked — no CSS-scope drift, no
      // box-sizing surprises, no transform mismatch. The clone is removed
      // before the browser paints its next frame (drag image is the
      // browser's own translucent overlay on the cursor), so the user
      // never sees a doubled element.
      //
      // The data-drag-ghost-active attribute is set AFTER setDragImage(),
      // so the ghost snapshot doesn't pick up the dragged-state styling
      // (e.g. opacity: 0.4 on the original).
      const rect = host.getBoundingClientRect();
      const cs = getComputedStyle(host);

      ghost = host.cloneNode(true);
      // box-sizing: border-box makes width == rect.width regardless of
      // whether the host was content-box; rect.width/.height already are
      // the host's border-box dimensions (getBoundingClientRect post-
      // transform visual box). margin: 0 prevents collapse-into-body
      // margins from offsetting the ghost.
      ghost.style.cssText = `
        position: fixed;
        left: ${rect.left}px; top: ${rect.top}px;
        width: ${rect.width}px; height: ${rect.height}px;
        box-sizing: border-box;
        margin: 0;
        pointer-events: none;
        opacity: 0.8;
        z-index: 99999;
      `;
      // Preserve the host's transform so a scaled/rotated host produces a
      // matching ghost bitmap.
      if (cs.transform && cs.transform !== 'none') {
        ghost.style.transform = cs.transform;
        ghost.style.transformOrigin = cs.transformOrigin;
      }
      document.body.appendChild(ghost);
      e.dataTransfer?.setDragImage(ghost, clickOffsetX, clickOffsetY);
      host.setAttribute('data-drag-ghost-active', '');
      // Hide the ghost in the next microtask — setDragImage already took
      // its snapshot synchronously. We hide rather than remove so any
      // browser that re-reads the element during the drag cycle (rare,
      // but Safari has historically done this on drop) still finds it.
      // We hide via visibility (paint suppressed) rather than
      // display:none (which would zero the bitmap if the browser ever
      // re-snapshots).
      queueMicrotask(() => {
        if (ghost) ghost.style.visibility = 'hidden';
      });
    }

    function onDragEnd() {
      if (ghost) { ghost.remove(); ghost = null; }
      host.removeAttribute('data-drag-ghost-active');
    }

    host.setAttribute('draggable', 'true');
    host.addEventListener('pointerdown', onPointerDown);
    host.addEventListener('dragstart', onDragStart);
    host.addEventListener('dragend', onDragEnd);

    return () => {
      host.removeEventListener('pointerdown', onPointerDown);
      host.removeEventListener('dragstart', onDragStart);
      host.removeEventListener('dragend', onDragEnd);
      if (ghost) { ghost.remove(); ghost = null; }
      host.removeAttribute('data-drag-ghost-active');
    };
  },
});
