import * as d3 from 'd3';

export interface Pointer {
  id: number;
  point: [number, number] | null;
  prev: [number, number] | null;
  sourceEvent?: PointerEvent;
}

export interface PointerOptions {
  start?: (tracker: Pointer) => void;
  move?: (tracker: Pointer) => void;
  out?: (tracker: Pointer) => void;
  end?: (tracker: Pointer) => void;
}

export function trackPointer(event: PointerEvent, { start, move, out, end }: PointerOptions): number {
  const tracker: Pointer = {
    id: event.pointerId,
    point: null,
    prev: null,
  };

  const id = event.pointerId;
  const target = event.target as Element;
  tracker.point = d3.pointer(event, target);
  target.setPointerCapture(id);

  const untrack = (sourceEvent: PointerEvent) => {
    tracker.sourceEvent = sourceEvent;
    d3.select(target).on(`.${ id }`, null);
    target.releasePointerCapture(id);
    end?.(tracker);
  };

  d3.select(target)
    .on(`touchstart.${ id }`, (sourceEvent: PointerEvent) => {
      const target = sourceEvent.target as Element;
      const touches = d3.pointers(sourceEvent, target);

      // disable current tracker when entering multi touch mode
      touches.length > 1 && untrack(sourceEvent);
    })
    .on(`pointerup.${ id } pointercancel.${ id } lostpointercapture.${ id }`, (sourceEvent: PointerEvent) => {
      if (sourceEvent.pointerId !== id) {
        return;
      }

      untrack(sourceEvent);
    })
    .on(`pointermove.${ id }`, (sourceEvent) => {
      if (sourceEvent.pointerId !== id) {
        return;
      }
      tracker.sourceEvent = sourceEvent;
      tracker.prev = tracker.point;
      tracker.point = d3.pointer(sourceEvent, target);
      move?.(tracker);
    })
    .on(`pointerout.${ id }`, (e) => {
      if (e.pointerId !== id) {
        return;
      }
      tracker.sourceEvent = e;
      tracker.point = null;
      out?.(tracker);
    });

  start?.(tracker);

  return id;
}
