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

export const longPress = defineTrait({
  name: 'long-press',
  category: 'input-interaction',
  description: 'Press-and-hold trigger: fires after configurable duration with progress events',
  attributes: [
    'data-long-press-active',
    'data-long-press-progress',
    'data-long-press-fired',
  ],
  events: ['long-press', 'long-press-cancelled', 'long-press-progress'],
  config: [
    'data-long-press-duration',
    'data-long-press-tolerance',
    'data-long-press-progress-interval',
  ],
  setup({ host }) {
    let active = false;
    let fired = false;
    let startX = 0;
    let startY = 0;
    let startedAt = 0;
    let timerId = null;
    let intervalId = null;
    let suppressClick = false;

    function readDuration() {
      const v = parseInt(host.getAttribute('data-long-press-duration'), 10);
      return Number.isFinite(v) && v > 0 ? v : 600;
    }

    function readTolerance() {
      const v = parseInt(host.getAttribute('data-long-press-tolerance'), 10);
      return Number.isFinite(v) && v >= 0 ? v : 8;
    }

    function readProgressInterval() {
      const v = parseInt(host.getAttribute('data-long-press-progress-interval'), 10);
      return Number.isFinite(v) && v > 0 ? v : 50;
    }

    function isDisabled() {
      return host.hasAttribute('disabled');
    }

    function clearTimers() {
      if (timerId !== null) {
        clearTimeout(timerId);
        timerId = null;
      }
      if (intervalId !== null) {
        clearInterval(intervalId);
        intervalId = null;
      }
    }

    function reset() {
      active = false;
      clearTimers();
      host.removeAttribute('data-long-press-active');
      host.removeAttribute('data-long-press-progress');
      host.removeAttribute('data-long-press-fired');
    }

    function cancel(reason) {
      if (!active) return;
      const wasFired = fired;
      reset();
      fired = false;
      // Only the truly-cancelled press emits the cancelled event. A press
      // that already committed (long-press fired) is a successful close,
      // not a cancellation, even though the pointerup handler tears down
      // the same state.
      if (!wasFired) {
        host.dispatchEvent(new CustomEvent('long-press-cancelled', {
          bubbles: true,
          detail: { reason },
        }));
      }
    }

    function commit() {
      if (!active || fired) return;
      fired = true;
      // Final progress = 1 so consumers reading the attribute see the
      // completed state before the fired flag flips.
      host.setAttribute('data-long-press-progress', '1');
      host.setAttribute('data-long-press-fired', '');
      // Suppress the next click so consumers don't double-fire on the
      // pointerup that completes the press.
      suppressClick = true;
      host.dispatchEvent(new CustomEvent('long-press', {
        bubbles: true,
        detail: { duration: performance.now() - startedAt },
      }));
      // Clear interval — duration timer already fired itself.
      if (intervalId !== null) {
        clearInterval(intervalId);
        intervalId = null;
      }
    }

    function onPointerDown(e) {
      if (isDisabled()) return;
      if (active) return;
      active = true;
      fired = false;
      startX = e.clientX ?? 0;
      startY = e.clientY ?? 0;
      startedAt = performance.now();
      const duration = readDuration();
      const interval = readProgressInterval();

      host.setAttribute('data-long-press-active', '');
      host.setAttribute('data-long-press-progress', '0');

      timerId = setTimeout(commit, duration);
      intervalId = setInterval(() => {
        if (!active || fired) return;
        const elapsed = performance.now() - startedAt;
        const progress = Math.min(elapsed / duration, 1);
        host.setAttribute('data-long-press-progress', String(progress));
        host.dispatchEvent(new CustomEvent('long-press-progress', {
          bubbles: true,
          detail: { progress },
        }));
      }, interval);
    }

    function onPointerUp() {
      if (!active) return;
      if (fired) {
        // Successful long-press just completed — reset state silently
        // without firing cancelled.
        reset();
        fired = false;
        return;
      }
      cancel('release');
    }

    function onPointerLeave() {
      if (!active) return;
      if (fired) {
        // Already committed; pointerleave during the post-fire frame is
        // benign — clean up without dispatching cancelled.
        reset();
        fired = false;
        return;
      }
      cancel('leave');
    }

    function onPointerMove(e) {
      if (!active || fired) return;
      const tolerance = readTolerance();
      const dx = (e.clientX ?? 0) - startX;
      const dy = (e.clientY ?? 0) - startY;
      if (Math.hypot(dx, dy) > tolerance) {
        cancel('move');
      }
    }

    function onPointerCancel() {
      if (!active) return;
      cancel('pointer-cancel');
    }

    function onClick(e) {
      // The click that follows a successful long-press is suppressed so
      // consumers wired to both `long-press` and `click` don't double-fire.
      if (suppressClick) {
        suppressClick = false;
        e.stopImmediatePropagation();
        e.preventDefault();
      }
    }

    function onContextMenu(e) {
      // While a long-press is in flight (typical on touch), suppress the
      // browser's native context menu so the trait's commit lands cleanly.
      if (active) {
        e.preventDefault();
      }
    }

    host.addEventListener('pointerdown', onPointerDown);
    host.addEventListener('pointerup', onPointerUp);
    host.addEventListener('pointerleave', onPointerLeave);
    host.addEventListener('pointermove', onPointerMove);
    host.addEventListener('pointercancel', onPointerCancel);
    // `click` runs in capture so we beat any consumer-attached click
    // listener and stop the event before it propagates.
    host.addEventListener('click', onClick, true);
    host.addEventListener('contextmenu', onContextMenu);

    return () => {
      host.removeEventListener('pointerdown', onPointerDown);
      host.removeEventListener('pointerup', onPointerUp);
      host.removeEventListener('pointerleave', onPointerLeave);
      host.removeEventListener('pointermove', onPointerMove);
      host.removeEventListener('pointercancel', onPointerCancel);
      host.removeEventListener('click', onClick, true);
      host.removeEventListener('contextmenu', onContextMenu);
      clearTimers();
      host.removeAttribute('data-long-press-active');
      host.removeAttribute('data-long-press-progress');
      host.removeAttribute('data-long-press-fired');
    };
  },
});
