import * as k from "react";
import { useState as R, useRef as _, useCallback as S, useMemo as M, useImperativeHandle as q, useContext as O, useLayoutEffect as U, useEffect as j, createContext as G } from "react";
const X = {
  /**
   * A value from 0 to 1, on how much to damp the animation.
   * 0 means no damping, 1 means full damping.
   *
   * @default 0.7
   */
  damping: 0.7,
  /**
   * The stiffness of how fast/slow the animation gets up to speed.
   *
   * @default 0.05
   */
  stiffness: 0.05,
  /**
   * The inertial mass associated with the animation.
   * Higher numbers make the animation slower.
   *
   * @default 1.25
   */
  mass: 1.25
}, J = 70, K = 1e3 / 60, V = 350;
let N = !1;
globalThis.document?.addEventListener("mousedown", () => {
  N = !0;
});
globalThis.document?.addEventListener("mouseup", () => {
  N = !1;
});
globalThis.document?.addEventListener("click", () => {
  N = !1;
});
const W = (s = {}) => {
  const [c, a] = R(!1), [l, d] = R(s.initial !== !1), [p, y] = R(!1), T = _(null);
  T.current = s;
  const w = S(() => {
    if (!N)
      return !1;
    const t = window.getSelection();
    if (!t || !t.rangeCount)
      return !1;
    const o = t.getRangeAt(0);
    return o.commonAncestorContainer.contains(r.current) || r.current?.contains(o.commonAncestorContainer);
  }, []), u = S((t) => {
    e.isAtBottom = t, d(t);
  }, []), f = S((t) => {
    e.escapedFromLock = t, a(t);
  }, []), e = M(() => {
    let t;
    return {
      escapedFromLock: c,
      isAtBottom: l,
      resizeDifference: 0,
      accumulated: 0,
      velocity: 0,
      listeners: /* @__PURE__ */ new Set(),
      get scrollTop() {
        return r.current?.scrollTop ?? 0;
      },
      set scrollTop(o) {
        r.current && (r.current.scrollTop = o, e.ignoreScrollToTop = r.current.scrollTop);
      },
      get targetScrollTop() {
        return !r.current || !h.current ? 0 : r.current.scrollHeight - 1 - r.current.clientHeight;
      },
      get calculatedTargetScrollTop() {
        if (!r.current || !h.current)
          return 0;
        const { targetScrollTop: o } = this;
        if (!s.targetScrollTop)
          return o;
        if (t?.targetScrollTop === o)
          return t.calculatedScrollTop;
        const n = Math.max(Math.min(s.targetScrollTop(o, {
          scrollElement: r.current,
          contentElement: h.current
        }), o), 0);
        return t = { targetScrollTop: o, calculatedScrollTop: n }, requestAnimationFrame(() => {
          t = void 0;
        }), n;
      },
      get scrollDifference() {
        return this.calculatedTargetScrollTop - this.scrollTop;
      },
      get isNearBottom() {
        return this.scrollDifference <= J;
      }
    };
  }, []), b = S((t = {}) => {
    typeof t == "string" && (t = { animation: t }), t.preserveScrollPosition || u(!0);
    const o = Date.now() + (Number(t.wait) || 0), n = L(T.current, t.animation), { ignoreEscapes: i = !1 } = t;
    let m, g = e.calculatedTargetScrollTop;
    t.duration instanceof Promise ? t.duration.finally(() => {
      m = Date.now();
    }) : m = o + (t.duration ?? 0);
    const E = async () => {
      const F = new Promise(requestAnimationFrame).then(() => {
        if (!e.isAtBottom)
          return e.animation = void 0, !1;
        const { scrollTop: D } = e, C = performance.now(), H = (C - (e.lastTick ?? C)) / K;
        if (e.animation || (e.animation = { behavior: n, promise: F, ignoreEscapes: i }), e.animation.behavior === n && (e.lastTick = C), w() || o > Date.now())
          return E();
        if (D < Math.min(g, e.calculatedTargetScrollTop)) {
          if (e.animation?.behavior === n) {
            if (n === "instant")
              return e.scrollTop = e.calculatedTargetScrollTop, E();
            e.velocity = (n.damping * e.velocity + n.stiffness * e.scrollDifference) / n.mass, e.accumulated += e.velocity * H, e.scrollTop += e.accumulated, e.scrollTop !== D && (e.accumulated = 0);
          }
          return E();
        }
        return m > Date.now() ? (g = e.calculatedTargetScrollTop, E()) : (e.animation = void 0, e.scrollTop < e.calculatedTargetScrollTop ? b({
          animation: L(T.current, T.current.resize),
          ignoreEscapes: i,
          duration: Math.max(0, m - Date.now()) || void 0
        }) : e.isAtBottom);
      });
      return F.then((D) => (requestAnimationFrame(() => {
        e.animation || (e.lastTick = void 0, e.velocity = 0);
      }), D));
    };
    return t.wait !== !0 && (e.animation = void 0), e.animation?.behavior === n ? e.animation.promise : E();
  }, [u, w, e]), v = S(() => {
    f(!0), u(!1);
  }, [f, u]), A = S(({ target: t }) => {
    if (t !== r.current)
      return;
    const { scrollTop: o, ignoreScrollToTop: n } = e;
    let { lastScrollTop: i = o } = e;
    e.lastScrollTop = o, e.ignoreScrollToTop = void 0, n && n > o && (i = n), y(e.isNearBottom), setTimeout(() => {
      if (e.resizeDifference || o === n)
        return;
      if (w()) {
        f(!0), u(!1);
        return;
      }
      const m = o > i, g = o < i;
      if (e.animation?.ignoreEscapes) {
        e.scrollTop = i;
        return;
      }
      g && (f(!0), u(!1)), m && f(!1), !e.escapedFromLock && e.isNearBottom && u(!0);
    }, 1);
  }, [f, u, w, e]), B = S(({ target: t, deltaY: o }) => {
    let n = t;
    for (; !["scroll", "auto"].includes(getComputedStyle(n).overflow); ) {
      if (!n.parentElement)
        return;
      n = n.parentElement;
    }
    n === r.current && o < 0 && r.current.scrollHeight > r.current.clientHeight && !e.animation?.ignoreEscapes && (f(!0), u(!1));
  }, [f, u, e]), r = x((t) => {
    r.current?.removeEventListener("scroll", A), r.current?.removeEventListener("wheel", B), t?.addEventListener("scroll", A, { passive: !0 }), t?.addEventListener("wheel", B, { passive: !0 });
  }, []), h = x((t) => {
    if (e.resizeObserver?.disconnect(), !t)
      return;
    let o;
    e.resizeObserver = new ResizeObserver(([n]) => {
      const { height: i } = n.contentRect, m = i - (o ?? i);
      if (e.resizeDifference = m, e.scrollTop > e.targetScrollTop && (e.scrollTop = e.targetScrollTop), y(e.isNearBottom), m >= 0) {
        const g = L(T.current, o ? T.current.resize : T.current.initial);
        b({
          animation: g,
          wait: !0,
          preserveScrollPosition: !0,
          duration: g === "instant" ? void 0 : V
        });
      } else
        e.isNearBottom && (f(!1), u(!0));
      o = i, requestAnimationFrame(() => {
        setTimeout(() => {
          e.resizeDifference === m && (e.resizeDifference = 0);
        }, 1);
      });
    }), e.resizeObserver?.observe(t);
  }, []);
  return {
    contentRef: h,
    scrollRef: r,
    scrollToBottom: b,
    stopScroll: v,
    isAtBottom: l || p,
    isNearBottom: p,
    escapedFromLock: c,
    state: e
  };
};
function x(s, c) {
  const a = S((l) => (a.current = l, s(l)), c);
  return a;
}
const I = /* @__PURE__ */ new Map();
function L(...s) {
  const c = { ...X };
  let a = !1;
  for (const d of s) {
    if (d === "instant") {
      a = !0;
      continue;
    }
    typeof d == "object" && (a = !1, c.damping = d.damping ?? c.damping, c.stiffness = d.stiffness ?? c.stiffness, c.mass = d.mass ?? c.mass);
  }
  const l = JSON.stringify(c);
  return I.has(l) || I.set(l, Object.freeze(c)), a ? "instant" : I.get(l);
}
const P = G(null), Y = typeof window < "u" ? U : j;
function z({ instance: s, children: c, resize: a, initial: l, mass: d, damping: p, stiffness: y, targetScrollTop: T, contextRef: w, ...u }) {
  const f = _(null), e = k.useCallback((i, m) => (n?.targetScrollTop ?? T)?.(i, m) ?? i, [T]), b = W({
    mass: d,
    damping: p,
    stiffness: y,
    resize: a,
    initial: l,
    targetScrollTop: e
  }), { scrollRef: v, contentRef: A, scrollToBottom: B, stopScroll: r, isAtBottom: h, escapedFromLock: t, state: o } = s ?? b, n = M(() => ({
    scrollToBottom: B,
    stopScroll: r,
    scrollRef: v,
    isAtBottom: h,
    escapedFromLock: t,
    contentRef: A,
    state: o,
    get targetScrollTop() {
      return f.current;
    },
    set targetScrollTop(i) {
      f.current = i;
    }
  }), [
    B,
    h,
    A,
    v,
    r,
    t,
    o
  ]);
  return q(w, () => n, [n]), Y(() => {
    v.current && getComputedStyle(v.current).overflow === "visible" && (v.current.style.overflow = "auto");
  }, []), k.createElement(
    P.Provider,
    { value: n },
    k.createElement("div", { ...u }, typeof c == "function" ? c(n) : c)
  );
}
(function(s) {
  function c({ children: a, scrollClassName: l, ...d }) {
    const p = Q();
    return k.createElement(
      "div",
      { ref: p.scrollRef, style: {
        height: "100%",
        width: "100%",
        scrollbarGutter: "stable both-edges"
      }, className: l },
      k.createElement("div", { ...d, ref: p.contentRef }, typeof a == "function" ? a(p) : a)
    );
  }
  s.Content = c;
})(z || (z = {}));
function Q() {
  const s = O(P);
  if (!s)
    throw new Error("use-stick-to-bottom component context must be used within a StickToBottom component");
  return s;
}
export {
  z as S,
  Q as u
};
