import {
  createEffect,
  createRenderEffect,
  createSignal,
  onCleanup,
  Show,
  untrack,
  type Component,
  type Signal,
} from "solid-js";
import { css } from "solid-styled";
import { Refresh as RefreshIcon } from "@suid/icons-material";
import { CircularProgress } from "@suid/material";
import {
  createEventListener,
  makeEventListener,
} from "@solid-primitives/event-listener";
import {
  createViewportObserver,
  createVisibilityObserver,
} from "@solid-primitives/intersection-observer";

const PullDownToRefresh: Component<{
  loading?: boolean;
  linkedElement?: HTMLElement;
  onRefresh?: () => void;
}> = (props) => {
  let rootElement: HTMLDivElement;
  const [pullDown, setPullDown] = createSignal(0);

  const pullDownDistance = () => {
    if (props.loading) {
      return 140;
    }
    return Math.max(Math.min(160, pullDown()), 0);
  };

  const obvx = createVisibilityObserver({
    threshold: 0.0001,
  });

  const rootVisible = obvx(() => rootElement);

  let released = true;
  let v = 0;
  let lts = -1;
  let ds = 0;
  let holding = false;
  const M = 1;
  const K = -10;
  const D = -10;
  const updatePullDown = (ts: number) => {
    released = false;
    try {
      const x = untrack(pullDown);
      const dt = lts !== -1 ? ts - lts : 1 / 60;
      const fs = (ds / Math.pow(dt, 2)) * M;
      const fh = (x + ds) * K + D * v;
      const f = fs + fh;
      const a = f / M;
      v += a * dt;
      if (holding && v < 0) {
        v = 0
      }
      setPullDown(x + v * dt);
      if (Math.abs(x) > 1 || Math.abs(v) > 1) {
        requestAnimationFrame(updatePullDown);
      } else {
        v = 0;
        lts = -1;
      }
      if (!holding && untrack(pullDownDistance) >= 160 && !props.loading && props.onRefresh) {
        setTimeout(props.onRefresh, 0)
      }
    } finally {
      ds = 0;
      released = true;
    }
  };
  const handleLinkedWheel = (event: WheelEvent) => {
    const scrollTop = (event.target as HTMLElement).scrollTop;
    if (scrollTop >= 0 && scrollTop < 1) {
      ds = -(event.deltaY / window.devicePixelRatio);
      if (released) {
        released = false;
        requestAnimationFrame(updatePullDown);
      }
    }
  };

  createEffect((cleanup?: () => void) => {
    if (!rootVisible()) {
      return;
    }
    cleanup?.();
    const element = props.linkedElement;
    if (!element) return;
    return makeEventListener(element, "wheel", handleLinkedWheel);
  });

  let lastTouchId: number | undefined = undefined;
  let lastTouchScreenY = 0;
  const handleTouch = (event: TouchEvent) => {
    if (event.targetTouches.length > 1) {
      lastTouchId = 0;
      lastTouchScreenY;
      return;
    }
    const item = event.targetTouches.item(0)!;
    if (lastTouchId && item.identifier !== lastTouchId) {
      lastTouchId = undefined;
      lastTouchScreenY = 0;
      return;
    }
    holding = true;
    if (lastTouchScreenY !== 0) {
      ds = item.screenY - lastTouchScreenY;
    }
    lastTouchScreenY = item.screenY;
    if (released) {
      released = false;
      requestAnimationFrame(updatePullDown);
    }
  };

  const handleTouchEnd = () => {
    lastTouchId = undefined;
    lastTouchScreenY = 0;
    holding = false;
    if (untrack(pullDownDistance) >= 160 && !props.loading && props.onRefresh) {
      setTimeout(props.onRefresh, 0)
    }
  };

  createEffect((cleanup?: () => void) => {
    if (!rootVisible()) {
      return;
    }
    cleanup?.();
    const element = props.linkedElement;
    if (!element) return;
    const cleanup0 = makeEventListener(element, "touchmove", handleTouch);
    const cleanup1 = makeEventListener(element, "touchend", handleTouchEnd);
    return () => (cleanup0(), cleanup1());
  });

  css`
    .pull-down {
      width: 100%;
      display: flex;
      justify-content: center;
      margin-top: -2rem;
      height: calc(1px + 2rem);
    }

    .indicator {
      display: inline-flex;
      justify-content: center;
      align-items: center;
      box-shadow: ${props.loading
        ? "var(--tutu-shadow-e12)"
        : "var(--tutu-shadow-e1)"};
      border-radius: 50%;
      aspect-ratio: 1/1;
      width: 2rem;
      color: var(--tutu-color-primary);
      transform: translateY(${`${pullDownDistance() - 2}px`});
      will-change: transform;
      z-index: var(--tutu-zidx-nav);
      background-color: var(--tutu-color-surface);

      > :global(.refresh-icon) {
        transform: rotate(
          ${`${((pullDownDistance() / 160) * 180).toString()}deg`}
        );
        will-change: transform;
      }

      > :global(.refresh-indicator) {
        width: 1.5rem;
        height: 1.5rem;
        aspect-ratio: 1/1;
      }
    }
  `;
  return (
    <div ref={rootElement!} class="pull-down">
      <span class="indicator">
        <Show
          when={props.loading}
          fallback={<RefreshIcon class="refresh-icon" />}
        >
          <CircularProgress class="refresh-indicator" />
        </Show>
      </span>
    </div>
  );
};

export default PullDownToRefresh;
