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

const FRICTION = 0.92;
const BOUNCE_FACTOR = -0.6;
const MIN_VELOCITY = 0.3;

export const tossable = defineTrait({
  name: 'tossable',
  category: 'motion-positioning',
  description: 'Flick with momentum + viewport bounce',
  attributes: ['data-tossable-flying'],
  events: ['toss-end'],
  config: ['data-tossable-bounds'],
  setup({ host }) {
    let dragging = false;
    let flying = false;
    let rafId = null;
    let startX = 0;
    let startY = 0;
    let originX = 0;
    let originY = 0;
    let currentX = 0;
    let currentY = 0;
    let velocityX = 0;
    let velocityY = 0;
    let lastTime = 0;
    let lastClientX = 0;
    let lastClientY = 0;

    function parseTranslate() {
      // The trait writes to `style.translate` (independent of `transform` in
      // the modern CSS model). Read translate first so subsequent tosses pick
      // up the current position, then fall back to the transform matrix for
      // callers that nudged via `transform`.
      const style = getComputedStyle(host);
      const t = style.translate;
      if (t && t !== 'none') {
        const parts = t.split(/\s+/).map(parseFloat);
        return { x: parts[0] || 0, y: parts[1] || 0 };
      }
      const matrix = new DOMMatrixReadOnly(style.transform);
      return { x: matrix.m41, y: matrix.m42 };
    }

    function getBounds() {
      // Optional bounding: reads data-tossable-bounds="parent" or defaults to viewport
      const boundsAttr = host.getAttribute('data-tossable-bounds');
      if (boundsAttr === 'parent' && host.parentElement) {
        return host.parentElement.getBoundingClientRect();
      }
      return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight, width: window.innerWidth, height: window.innerHeight };
    }

    function applyPosition(x, y) {
      host.style.translate = `${x}px ${y}px`;
      currentX = x;
      currentY = y;
    }

    function animateFling() {
      velocityX *= FRICTION;
      velocityY *= FRICTION;

      let nextX = currentX + velocityX;
      let nextY = currentY + velocityY;

      // Bounce off bounds
      const bounds = getBounds();
      const rect = host.getBoundingClientRect();
      const elW = rect.width;
      const elH = rect.height;

      // Check horizontal bounds (relative to translate origin)
      const baseRect = host.offsetParent
        ? host.offsetParent.getBoundingClientRect()
        : { left: 0, top: 0 };

      const absLeft = baseRect.left + nextX;
      const absRight = absLeft + elW;
      const absTop = baseRect.top + nextY;
      const absBottom = absTop + elH;

      if (absLeft < bounds.left) {
        nextX += bounds.left - absLeft;
        velocityX *= BOUNCE_FACTOR;
      } else if (absRight > bounds.right) {
        nextX -= absRight - bounds.right;
        velocityX *= BOUNCE_FACTOR;
      }

      if (absTop < bounds.top) {
        nextY += bounds.top - absTop;
        velocityY *= BOUNCE_FACTOR;
      } else if (absBottom > bounds.bottom) {
        nextY -= absBottom - bounds.bottom;
        velocityY *= BOUNCE_FACTOR;
      }

      applyPosition(nextX, nextY);

      if (Math.abs(velocityX) < MIN_VELOCITY && Math.abs(velocityY) < MIN_VELOCITY) {
        flying = false;
        host.removeAttribute('data-tossable-flying');
        host.dispatchEvent(new CustomEvent('toss-end', {
          bubbles: true,
          detail: { x: currentX, y: currentY },
        }));
        return;
      }

      rafId = requestAnimationFrame(animateFling);
    }

    function onPointerDown(e) {
      if (host.hasAttribute('disabled')) return;

      if (rafId !== null) {
        cancelAnimationFrame(rafId);
        rafId = null;
      }
      flying = false;
      host.removeAttribute('data-tossable-flying');

      dragging = true;
      startX = e.clientX;
      startY = e.clientY;
      lastClientX = e.clientX;
      lastClientY = e.clientY;
      lastTime = performance.now();
      velocityX = 0;
      velocityY = 0;

      const pos = parseTranslate();
      originX = pos.x;
      originY = pos.y;

      host.setPointerCapture(e.pointerId);
      host.style.touchAction = 'none';
    }

    function onPointerMove(e) {
      if (!dragging) return;
      const now = performance.now();
      const dt = now - lastTime;

      if (dt > 0) {
        velocityX = (e.clientX - lastClientX) / dt * 16;
        velocityY = (e.clientY - lastClientY) / dt * 16;
      }

      lastClientX = e.clientX;
      lastClientY = e.clientY;
      lastTime = now;

      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      applyPosition(originX + dx, originY + dy);
    }

    function onPointerUp() {
      if (!dragging) return;
      dragging = false;

      if (Math.abs(velocityX) > MIN_VELOCITY || Math.abs(velocityY) > MIN_VELOCITY) {
        flying = true;
        host.setAttribute('data-tossable-flying', '');
        rafId = requestAnimationFrame(animateFling);
      } else {
        host.dispatchEvent(new CustomEvent('toss-end', {
          bubbles: true,
          detail: { x: currentX, y: currentY },
        }));
      }
    }

    host.addEventListener('pointerdown', onPointerDown);
    host.addEventListener('pointermove', onPointerMove);
    host.addEventListener('pointerup', onPointerUp);

    return () => {
      host.removeEventListener('pointerdown', onPointerDown);
      host.removeEventListener('pointermove', onPointerMove);
      host.removeEventListener('pointerup', onPointerUp);
      if (rafId !== null) cancelAnimationFrame(rafId);
      host.removeAttribute('data-tossable-flying');
      host.style.touchAction = '';
    };
  },
});
