/* Shared hooks: scroll reveal + count-up. Global scope for Babel files. */

// Reveal-on-scroll: attach to any element with className "reveal".
function useRevealOnScroll() {
  React.useEffect(() => {
    const els = Array.from(document.querySelectorAll(".reveal"));
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      els.forEach((el) => el.classList.add("in"));
      return;
    }
    // Rect-based check: reveal any element whose top enters the lower 92% of the viewport.
    // Runs on load, scroll and resize — reliable even where IntersectionObserver
    // doesn't emit an initial callback.
    let raf = 0;
    const check = () => {
      raf = 0;
      const vh = window.innerHeight || document.documentElement.clientHeight;
      let remaining = false;
      els.forEach((el) => {
        if (el.classList.contains("in")) return;
        const top = el.getBoundingClientRect().top;
        if (top < vh * 0.92) el.classList.add("in");
        else remaining = true;
      });
      if (!remaining) {
        window.removeEventListener("scroll", schedule);
        window.removeEventListener("resize", schedule);
      }
    };
    const schedule = () => { if (!raf) raf = requestAnimationFrame(check); };
    window.addEventListener("scroll", schedule, { passive: true });
    window.addEventListener("resize", schedule);
    check();
    return () => {
      if (raf) cancelAnimationFrame(raf);
      window.removeEventListener("scroll", schedule);
      window.removeEventListener("resize", schedule);
    };
  });
}

// Count-up number. Renders formatted value, animates from 0 when scrolled into view.
function CountUp({ end, decimals = 0, duration = 1600, prefix = "", suffix = "" }) {
  const ref = React.useRef(null);
  const [val, setVal] = React.useState(end);
  React.useLayoutEffect(() => { const r = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (!r) setVal(0); }, []);
  const done = React.useRef(false);
  React.useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) { setVal(end); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !done.current) {
          done.current = true;
          const t0 = performance.now();
          const tick = (now) => {
            const p = Math.min(1, (now - t0) / duration);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(end * eased);
            if (p < 1) requestAnimationFrame(tick); else setVal(end);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.5 });
    io.observe(node);
    return () => io.disconnect();
  }, [end, duration]);
  // 애니메이션이 끝난 뒤 end가 바뀌면(예: 실데이터 로드로 4.87→4.94) 즉시 반영.
  React.useEffect(() => { if (done.current) setVal(end); }, [end]);
  const shown = decimals > 0
    ? val.toFixed(decimals)
    : Math.round(val).toLocaleString("ko-KR");
  return React.createElement("span", { ref }, prefix, shown, suffix);
}

/* ---------- 실제 만족도 후기 데이터 (공용: 메인·상세페이지·/reviews 공유) ----------
   예약앱 공개 API에서 실제 후기를 한 번만 불러와 모든 섹션이 공유. 실패하면 각 섹션이 예시로 폴백. */
const REVIEWS_API = "https://book.tidyclean.co.kr/api/public/reviews";
let _rvPromise = null;
function fetchReviewData() {
  if (!_rvPromise) {
    _rvPromise = fetch(REVIEWS_API)
      .then((r) => (r.ok ? r.json() : null))
      .catch(() => null);
  }
  return _rvPromise;
}
function useReviewData() {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    fetchReviewData().then((d) => { if (alive && d && d.ok) setData(d); });
    return () => { alive = false; };
  }, []);
  return data; // null이면 각 섹션이 예시값 사용
}

/* 라이브 후기는 API로 나중에 로드돼 새 DOM으로 그려지므로, 첫 로드 때 한 번 도는 등장
   애니메이션(useRevealOnScroll) 대상에서 빠져 숨김(opacity 0)으로 남는다. 데이터가 들어오면
   화면의 .reveal 요소를 직접 '표시(in)' 처리해 보이게 한다. */
function useRevealLiveContent(active) {
  React.useEffect(() => {
    if (!active) return;
    const id = requestAnimationFrame(() => {
      document.querySelectorAll(".reveal").forEach((el) => el.classList.add("in"));
    });
    return () => cancelAnimationFrame(id);
  }, [active]);
}

/* URL 경로 → 품목 카테고리 (상세페이지별 후기 매칭용). 매칭 없으면 null=전체 */
const PATH_CATEGORY = { "/aircon": "에어컨", "/washer": "세탁기", "/fridge": "냉장고", "/hood": "주방후드", "/screen": "방충망" };
function currentReviewCategory() {
  try {
    const p = (window.location.pathname || "").replace(/\.html$/, "").replace(/(.)\/$/, "$1");
    return PATH_CATEGORY[p] || null;
  } catch (e) { return null; }
}

Object.assign(window, { useRevealOnScroll, CountUp, useReviewData, useRevealLiveContent, currentReviewCategory });


/* 전화·카톡 문의 전환 추적 (광고 유입자의 전화/카톡 클릭을 '문의(lead)' 전환으로) */
function __fireContact(kind){
  try{ var w=window; if(w.wcs && typeof w.wcs.trans==='function'){ if(!w.wcs_add) w.wcs_add={}; w.wcs_add['wa']='s_29fb8c586c38'; w.wcs.trans({ type:'lead' }); } }catch(e){}
  try{ if(window.gtag) window.gtag('event','contact',{ method:kind }); }catch(e){}
}
window.callPhone = function(){ __fireContact('phone'); window.location.href = window.TEL_HREF; };
document.addEventListener('click', function(e){
  var t=e.target; var a=(t && t.closest) ? t.closest('a') : null; if(!a) return;
  var h=a.getAttribute('href')||'';
  if(h.indexOf('tel:')===0) __fireContact('phone');
  else if(h.indexOf('pf.kakao')>-1) __fireContact('kakao');
}, true);
Object.assign(window, { callPhone: window.callPhone });
