<script setup lang="ts">
import { computed, onBeforeUnmount, ref, useAttrs, type PropType } from "vue";
import type { RouteLocationRaw } from "vue-router";
import type { NativePresentationName } from "../types";
import { createPointerGesture, sourceRect } from "./gestures";
import { useNativeRouter } from "./lifecycle";

defineOptions({ name: "NativeGestureLink", inheritAttrs: false });

const props = defineProps({
  to: {
    type: [String, Object] as PropType<RouteLocationRaw>,
    required: true,
  },
  presentation: {
    type: String as PropType<NativePresentationName>,
    default: "reveal",
  },
  replace: Boolean,
  direction: {
    type: String as PropType<"left" | "right" | "any">,
    default: "any",
  },
  as: { type: String, default: "div" },
});
const attrs = useAttrs();
const passthroughAttrs = computed(() => {
  const result = { ...attrs };
  delete result.onPointerdown;
  delete result.onPointermove;
  delete result.onPointerup;
  delete result.onPointercancel;
  delete result.onClick;
  return result;
});
const runtime = useNativeRouter();
const root = ref<HTMLElement | null>(null);
let dragDistance = 0;
let suppressClick = false;

const gesture = createPointerGesture(
  () => root.value,
  runtime,
  async (direction) =>
    runtime.beginInteractive("push", props.to, {
      presentation: props.presentation,
      replace: props.replace,
      direction,
      sourceRect: root.value ? sourceRect(root.value) : undefined,
    }),
  (dx) => {
    if (props.direction === "left" && dx >= 0) return null;
    if (props.direction === "right" && dx <= 0) return null;
    return dx < 0 ? "forward" : "back";
  },
);

onBeforeUnmount(() => void runtime.cancelInteractive());

function pointerDown(event: PointerEvent) {
  // Component-owned gestures outrank their containing navigator.
  event.stopPropagation();
  dragDistance = 0;
  suppressClick = false;
  gesture.down(event);
}

function pointerMove(event: PointerEvent) {
  if (event.buttons) {
    dragDistance += Math.abs(event.movementX);
    if (dragDistance > 8) suppressClick = true;
  }
  void gesture.move(event);
}

function click(event: MouseEvent) {
  if (suppressClick) {
    suppressClick = false;
    event.preventDefault();
    event.stopPropagation();
    return;
  }
  void runtime.push(props.to, {
    presentation: props.presentation,
    replace: props.replace,
  });
}
</script>

<template>
  <component
    :is="as"
    ref="root"
    v-bind="passthroughAttrs"
    class="nvr-gesture-link"
    @pointerdown="pointerDown"
    @pointermove="pointerMove"
    @pointerup="gesture.up"
    @pointercancel="gesture.cancel"
    @click="click"
  >
    <slot />
  </component>
</template>
