/**
 * スクロールに連動してコールバック関数に渡る値変化します。
 * コールバック関数に渡る値は整数値に収束します。
 * The value passed to the callback function changes in conjunction with the scroll.
 * The value passed to the callback function converges to an integer value.
 */
import { world, hook, gui, Scroller } from "negl";
import { MathUtils } from "three";

function damping(current: number, target: number, damp: number) {
  const x = MathUtils.damp(current, target, damp, world.delta);
  return x;
}
/**
 * スクロールに連動してコールバック関数に渡る値変化します。
 * コールバック関数に渡る値は整数値に収束します。
 * The value passed to the callback function changes in conjunction with the scroll.
 * The value passed to the callback function converges to an integer value.
 *
 * スクロール量に合せてスライドを切り替えたい場合などに使用してください。
 * Please use this when you want to switch slides according to the scroll amount.
 *
 * @param {(idx: number, delta: number) => void}
 *        {idx} スクロールに連動した値が渡ります。この値は時間と共に整数値に収束します。A value linked to the scroll is passed. This value converges to an integer over time.
 *        {delta} スクロールの変化量が渡ります。 The change in scroll is passed.
 * @param options 下記のオプションを指定することができます。You can specify the following options.
 * @param options.speed  スクロール量が変化した際のスピード。スクロール変化量に掛け合わせる数値。The speed when the scroll amount changes. A value that is multiplied by the scroll change amount.
 * @param options.damping スクロール量が変化した際のdamping（減衰）。小さいほどスクロールの減衰が大きくなります。（ゆっくり動きます。）The damping (attenuation) when the scroll amount changes. The smaller it is, the greater the scroll attenuation (it moves slowly).
 * @param defaultIdx コールバック関数の idx に渡る値のデフォルト値。The default value for idx passed to the callback function.
 * @returns idx をコードから変更するための関数（goTo）を返します。Returns a function (goTo) to change idx from the code.
 */
function scrollSnap(
  callback: (idx: number, delta: number) => void,
  options: {
    speed?: number;
    damping?: number;
  } = {},
  defaultIdx = 0
) {
  const _options = {
    speed: 5,
    damping: 3,
  };
  // オプションをデフォルトオプション（_options）とマージします。
  // Merge the options with the default options (_options).
  Object.assign(_options, options);

  // スクロールの変化量の合計値を保持します。
  // Keep the total value of the scroll change amount.
  let deltaY = 0;

  // スクロールした際のコールバック関数
  // Callback function when scrolling
  function _scroll({ delta: { y } }: Scroller) {
    // delta.y にはスクロールの変化量が渡ってきます。
    // delta.y receives the scroll change amount.
    // deltaY にスクロールの変化量 delta.y を足し合わせます。
    // Add the scroll change amount delta.y to deltaY.
    deltaY += y * _options.speed * 1e-3;
  }

  // コールバック関数に渡す値（整数値に収束する値）
  // The value passed to the callback function (a value that converges to an integer)
  let slideIdx = defaultIdx;
  // 前回の slideIdx の値（下の計算で使用）
  // The previous value of slideIdx (used in the calculation below)
  let prevSlideIdx = defaultIdx;
  // slideIdx を四捨五入した値を保持します。
  // Keep the rounded value of slideIdx.
  let intSlideIdx = defaultIdx;

  // コールバック関数
  // Callback function
  function _scrollSnap() {
    slideIdx += deltaY; //
    deltaY *= 0.8; // deltaYの値を少し小さくします。
    // Make the value of deltaY a little smaller.

    // 現在の slideIdx から一番近い整数値を intSlideIdx に格納します。
    // Store the closest integer value to the current slideIdx in intSlideIdx.
    intSlideIdx = Math.round(slideIdx);
    // 現在の slideIdx と 一番近い整数値がどの程度離れているか取得します。
    // Get how far the current slideIdx is from the closest integer value.
    const distance = intSlideIdx - slideIdx;
    // 整数値に slideIdx の値を近づけます。
    // Bring the value of slideIdx closer to an integer value.
    slideIdx += Math.sign(distance) * Math.abs(distance);
    // 前回の値 prevSlideIdx と現在の値 slideIdx のダンピング値を取得します。
    // Get the damping value between the previous value prevSlideIdx and the current value slideIdx.
    slideIdx = damping(prevSlideIdx, slideIdx, _options.damping);

    // コールバック関数を実行します。
    // Execute the callback function.
    callback(slideIdx, deltaY);

    // 現在の値を前回の値とします。
    // Set the current value as the previous value.
    prevSlideIdx = slideIdx;
  }

  // lilGUIにGUIを追加します。
  // Add GUI to lilGUI.
  gui.add((lilGUI) => {
    const folder = lilGUI.addFolder("scroll snap");
    folder.close();
    folder.add(_options, "speed", 1, 10, 0.1);
    folder.add(_options, "damping", 1, 6, 0.1);
  });

  /**
   * hook - https://docs.not-equal.dev/reference/classes/Hook/#hook
   */
  // hook.RENDER（requestAnimationFrameのループ）に追加します。
  // これによってrequestAnimationFrameのループで _scrollSnap が実行されます。
  // Add to hook.RENDER (the loop of requestAnimationFrame).
  // This will execute _scrollSnap in the loop of requestAnimationFrame.
  hook.on(hook.RENDER, _scrollSnap);

  // hook.SCROLL（スクロール）に追加します。
  // これによってSCROLLが行われたタイミングで_scrollが実行されます。
  // Add to hook.SCROLL (scroll).
  // This will execute _scroll at the time of SCROLL.
  hook.on(hook.SCROLL, _scroll);

  // 上記のフックを破棄するための関数です。
  // This is a function to destroy the above hooks.
  function _destroyScrollSlot() {
    hook.off(hook.RENDER, _scrollSnap);
    hook.off(hook.SCROLL, _scroll);
  }

  // トランジションが開始されるタイミングでフックを削除
  // 補足）他のページに遷移する際にフックが残っていると、不要な処理が動いたり、意図せぬ挙動となる可能性があるため、他ページに遷移する開始時点（T_BEGIN）でフックを削除する。
  // Remove hooks when the transition begins.
  // Note: Hooks left over when transitioning to other pages may cause unnecessary processes to run or unintended behavior, so remove hooks at the start of the transition to other pages (T_BEGIN).
  hook.on(hook.T_BEGIN, _destroyScrollSlot, { once: true });

  // JSから数値を変更するための関数を戻り値として返します。
  // Return a function to change the value from JS as a return value.
  function goTo(idx: number) {
    deltaY += idx;
  }
  return goTo;
}

export { scrollSnap };
export default scrollSnap;
