import type { DirectiveBinding, ObjectDirective } from 'vue';

export const START_HOLD_EVENT = 'start-hold';

export interface StartDirectiveValue {
  threshold: number;
  fn?: () => void;
  /** 默认派发 `start-hold`；`false` 时不派发 */
  emit?: boolean;
}

type StartBinding = DirectiveBinding<StartDirectiveValue>;

type Entry = {
  binding: StartBinding;
};

const entries = new WeakMap<HTMLElement, Entry>();

const listenOpts: AddEventListenerOptions = { capture: true };

function resolveMode(modifiers: StartBinding['modifiers']): 'duration' | 'distance' {
  if (modifiers.distance && modifiers.duration) {
    console.warn('[v-start] Both .duration and .distance are set; using .distance.');
    return 'distance';
  }
  if (modifiers.distance) return 'distance';
  return 'duration';
}

function readValue(binding: StartBinding): StartDirectiveValue | null {
  const v = binding.value;
  if (v == null || typeof v !== 'object') return null;
  if (typeof v.threshold !== 'number' || Number.isNaN(v.threshold)) return null;
  return v;
}

function mount(el: HTMLElement, binding: StartBinding) {
  let down: { timeStamp: number; clientX: number; clientY: number; pointerId: number } | null =
    null;

  const onPointerDown = (e: PointerEvent) => {
    down = {
      timeStamp: e.timeStamp,
      clientX: e.clientX,
      clientY: e.clientY,
      pointerId: e.pointerId,
    };
  };

  const onPointerUp = (e: PointerEvent) => {
    if (!down || e.pointerId !== down.pointerId) return;

    const entry = entries.get(el);
    if (!entry) return;

    const value = readValue(entry.binding);
    const mode = resolveMode(entry.binding.modifiers);

    const start = down;
    down = null;

    if (!value) return;

    const delta =
      mode === 'duration'
        ? e.timeStamp - start.timeStamp
        : Math.hypot(e.clientX - start.clientX, e.clientY - start.clientY);

    if (delta <= value.threshold) return;

    value.fn?.();

    if (value.emit !== false) {
      const detail =
        mode === 'duration' ? { durationMs: delta } : { distancePx: delta };
      el.dispatchEvent(
        new CustomEvent(START_HOLD_EVENT, {
          detail,
          bubbles: true,
        }),
      );
    }
  };

  el.addEventListener('pointerdown', onPointerDown, listenOpts);
  el.addEventListener('pointerup', onPointerUp, listenOpts);

  entries.set(el, { binding });

  return () => {
    el.removeEventListener('pointerdown', onPointerDown, listenOpts);
    el.removeEventListener('pointerup', onPointerUp, listenOpts);
    entries.delete(el);
  };
}

export const startDirective: ObjectDirective<HTMLElement, StartDirectiveValue> = {
  mounted(el, binding) {
    (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup = mount(el, binding);
  },
  updated(el, binding) {
    const e = entries.get(el);
    if (e) e.binding = binding;
  },
  unmounted(el) {
    (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup?.();
    (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup = undefined;
  },
};
