const FILL_MS = 2_000;
const DRAIN_MS = 600;

/** @param {number} value */
function clampProgress(value) {
  return Math.max(0, Math.min(1, value));
}

/**
 * @param {HTMLButtonElement} button
 * @param {() => void} onHold
 * @param {number} [fillMs]
 */
export function bindHold(button, onHold, fillMs = FILL_MS) {
  /** @type {number | undefined} */
  let frame;
  let holding = false;
  let progress = 0;
  let previous = 0;

  function render() {
    button.style.setProperty("--hold-progress", String(progress));
    button.style.setProperty("--hold-progress-width", `${progress * 100}%`);
    button.toggleAttribute("data-active", progress > 0);
  }

  function stop() {
    if (frame === undefined) return;

    cancelAnimationFrame(frame);
    frame = undefined;
  }

  /** @param {number} now */
  function tick(now) {
    const elapsed = now - previous;
    const rate = elapsed / (holding ? fillMs : DRAIN_MS);

    previous = now;
    progress = clampProgress(progress + (holding ? rate : -rate));
    render();

    if (holding && progress === 1) {
      stop();
      holding = false;
      progress = 0;
      button.removeAttribute("data-holding");
      render();
      onHold();
      return;
    }

    if (!holding && progress === 0) {
      stop();
      return;
    }

    frame = requestAnimationFrame(tick);
  }

  function run() {
    if (frame !== undefined) return;

    previous = performance.now();
    frame = requestAnimationFrame(tick);
  }

  function release() {
    if (!holding) return;

    holding = false;
    button.removeAttribute("data-holding");
    run();
  }

  function hold() {
    stop();
    holding = true;
    button.dataset.holding = "";
    run();
  }

  render();

  button.addEventListener("pointerdown", (event) => {
    if (event.button !== 0) return;

    button.setPointerCapture(event.pointerId);
    hold();
  });
  button.addEventListener("pointerup", release);
  button.addEventListener("pointercancel", release);
  button.addEventListener("lostpointercapture", release);
  button.addEventListener("keydown", (event) => {
    if (event.repeat || (event.key !== " " && event.key !== "Enter")) return;

    event.preventDefault();
    hold();
  });
  button.addEventListener("keyup", (event) => {
    if (event.key === " " || event.key === "Enter") release();
  });
  button.addEventListener("blur", release);
}
