/*
 * 공개 트랙 비주얼·모션 오케스트레이터 (design.md §3C-V V-6·V-7).
 * PublicShell 하위(.kxp)만 스코프. 선언형 클래스/속성을 스캔해 CSS-우선 모션을 배선:
 *   ① .pub-reveal        → IntersectionObserver(threshold 0.15·once) → .is-visible
 *   ② .pub-countup       → IO 진입 1회 카운트업(data-count-to·data-count-suffix)
 *   ③ [data-parallax]    → rAF passive scroll 라이트 파랄랙스(데스크톱만)
 *   ④ .pub-rail--auto    → 오토스크롤(hover/focus/visibility 정지, data-interval)
 *   ⑤ .pub-hero          → 마운트 인트로 .is-in
 * prefers-reduced-motion: 파랄랙스·오토스크롤·카운트업 애니 전면 비활성(최종 상태 즉시).
 * transform/opacity·scrollLeft만 사용(레이아웃 프로퍼티 애니 금지). AppShell/모바일 미주입.
 */
import { useEffect, type RefObject } from 'react';

function fmt(n: number): string {
  return Math.round(n).toLocaleString();
}

export function usePublicMotion(rootRef: RefObject<HTMLElement | null>): void {
  useEffect(() => {
    const root = rootRef.current;
    if (!root || typeof window === 'undefined') return;

    const reduce =
      typeof window.matchMedia === 'function' &&
      window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const desktop =
      typeof window.matchMedia === 'function' &&
      window.matchMedia('(min-width: 1024px)').matches;

    const cleanups: Array<() => void> = [];
    const supportsIO = typeof IntersectionObserver !== 'undefined';

    // ── ① 스크롤 리빌 ────────────────────────────────────────────────────────
    if (reduce || !supportsIO) {
      root.querySelectorAll('.pub-reveal').forEach((n) => n.classList.add('is-visible'));
    } else {
      const revealIO = new IntersectionObserver(
        (entries) => {
          entries.forEach((e) => {
            if (e.isIntersecting) {
              e.target.classList.add('is-visible');
              revealIO.unobserve(e.target);
            }
          });
        },
        { threshold: 0.15 },
      );
      const observeReveals = () =>
        root
          .querySelectorAll('.pub-reveal:not(.is-visible)')
          .forEach((n) => revealIO.observe(n));
      observeReveals();
      cleanups.push(() => revealIO.disconnect());

      // 비동기(행사·공지 등)로 뒤늦게 마운트되는 .pub-reveal 노드 포착.
      let rafScan = 0;
      const mo = new MutationObserver(() => {
        if (rafScan) return;
        rafScan = requestAnimationFrame(() => {
          rafScan = 0;
          observeReveals();
        });
      });
      mo.observe(root, { childList: true, subtree: true });
      cleanups.push(() => {
        if (rafScan) cancelAnimationFrame(rafScan);
        mo.disconnect();
      });
    }

    // ── ② 카운트업 ──────────────────────────────────────────────────────────
    const counts = Array.from(root.querySelectorAll<HTMLElement>('.pub-countup'));
    const finalize = (node: HTMLElement) => {
      const to = parseFloat(node.dataset.countTo || '0');
      node.textContent = fmt(to) + (node.dataset.countSuffix || '');
    };
    if (reduce || !supportsIO) {
      counts.forEach(finalize);
    } else {
      const runCount = (node: HTMLElement) => {
        const to = parseFloat(node.dataset.countTo || '0');
        const suffix = node.dataset.countSuffix || '';
        const dur = 1200;
        const start = performance.now();
        const tick = (now: number) => {
          const p = Math.min(1, (now - start) / dur);
          const eased = 1 - Math.pow(1 - p, 3); // ease-decelerate
          node.textContent = fmt(to * eased) + suffix;
          if (p < 1) requestAnimationFrame(tick);
        };
        requestAnimationFrame(tick);
      };
      const countIO = new IntersectionObserver(
        (entries) => {
          entries.forEach((e) => {
            if (e.isIntersecting) {
              runCount(e.target as HTMLElement);
              countIO.unobserve(e.target);
            }
          });
        },
        { threshold: 0.4 },
      );
      counts.forEach((n) => countIO.observe(n));
      cleanups.push(() => countIO.disconnect());
    }

    // ── ③ 라이트 파랄랙스(데스크톱·모션 허용 시만) ──────────────────────────────
    if (!reduce && desktop) {
      const layers = Array.from(root.querySelectorAll<HTMLElement>('[data-parallax]'));
      if (layers.length) {
        let raf = 0;
        const apply = () => {
          raf = 0;
          const vh = window.innerHeight || document.documentElement.clientHeight;
          layers.forEach((l) => {
            const rect = l.getBoundingClientRect();
            if (rect.bottom < -200 || rect.top > vh + 200) return; // 오프스크린 무시
            const factor = parseFloat(l.dataset.parallax || '0');
            const y = (rect.top + rect.height / 2 - vh / 2) * factor;
            l.style.transform = `translate3d(0, ${y.toFixed(1)}px, 0)`;
          });
        };
        const onScroll = () => {
          if (raf) return;
          raf = requestAnimationFrame(apply);
        };
        window.addEventListener('scroll', onScroll, { passive: true });
        window.addEventListener('resize', onScroll, { passive: true });
        apply();
        cleanups.push(() => {
          window.removeEventListener('scroll', onScroll);
          window.removeEventListener('resize', onScroll);
          if (raf) cancelAnimationFrame(raf);
          layers.forEach((l) => {
            l.style.transform = '';
          });
        });
      }
    }

    // ── ④ 오토스크롤 레일 ──────────────────────────────────────────────────────
    if (!reduce) {
      root.querySelectorAll<HTMLElement>('.pub-rail--auto').forEach((rail) => {
        const interval = Math.max(1500, parseInt(rail.dataset.interval || '4500', 10) || 4500);
        let paused = false;
        const step = () => {
          if (paused) return;
          const max = rail.scrollWidth - rail.clientWidth;
          if (max <= 4) return;
          const card = rail.querySelector<HTMLElement>('[data-rail-item]');
          const delta = card ? card.offsetWidth + 20 : Math.round(rail.clientWidth * 0.8);
          let next = rail.scrollLeft + delta;
          if (next >= max - 4) next = 0;
          rail.scrollTo({ left: next, behavior: 'smooth' });
        };
        const timer = window.setInterval(step, interval);
        const pause = () => {
          paused = true;
        };
        const resume = () => {
          paused = false;
        };
        const onVis = () => {
          paused = document.hidden;
        };
        rail.addEventListener('mouseenter', pause);
        rail.addEventListener('mouseleave', resume);
        rail.addEventListener('focusin', pause);
        rail.addEventListener('focusout', resume);
        document.addEventListener('visibilitychange', onVis);
        cleanups.push(() => {
          window.clearInterval(timer);
          rail.removeEventListener('mouseenter', pause);
          rail.removeEventListener('mouseleave', resume);
          rail.removeEventListener('focusin', pause);
          rail.removeEventListener('focusout', resume);
          document.removeEventListener('visibilitychange', onVis);
        });
      });
    }

    // ── ⑤ 히어로 인트로 ────────────────────────────────────────────────────────
    const heroes = Array.from(root.querySelectorAll<HTMLElement>('.pub-hero'));
    if (reduce) {
      heroes.forEach((h) => h.classList.add('is-in'));
    } else {
      const rafId = requestAnimationFrame(() =>
        requestAnimationFrame(() => heroes.forEach((h) => h.classList.add('is-in'))),
      );
      cleanups.push(() => cancelAnimationFrame(rafId));
    }

    // ── ⑥ 히어로 배경 회전 슬라이드(크로스페이드) ──────────────────────────────────
    // prefers-reduced-motion 시 첫 장 고정(회전 미배선). 정지: hover/focus·문서 숨김·오프스크린.
    if (!reduce) {
      root.querySelectorAll<HTMLElement>('[data-hero-slides]').forEach((box) => {
        const slides = Array.from(box.querySelectorAll<HTMLElement>('img[data-slide]'));
        if (slides.length < 2) return; // 폴백/단일 이미지는 회전 없음

        const interval = 5500; // ~5.5s 노출 후 다음 장
        let idx = slides.findIndex((s) => s.classList.contains('is-active'));
        if (idx < 0) {
          idx = 0;
          slides[0].classList.add('is-active');
        }

        let hovered = false;
        let offscreen = false;
        const isPaused = () => hovered || offscreen || document.hidden;

        const nextVisible = (from: number): number => {
          for (let step = 1; step <= slides.length; step++) {
            const j = (from + step) % slides.length;
            if (slides[j].dataset.failed !== '1') return j;
          }
          return from;
        };
        const tick = () => {
          if (isPaused()) return;
          const nx = nextVisible(idx);
          if (nx === idx) return; // 표시 가능한 다음 장 없음
          slides[idx].classList.remove('is-active');
          slides[nx].classList.add('is-active');
          idx = nx;
        };
        const timer = window.setInterval(tick, interval);

        const hero =
          (box.closest('.pub-hero, .kxp-hero, .kxp-vhero') as HTMLElement | null) ??
          (box.closest('section') as HTMLElement | null) ??
          box;
        const onEnter = () => {
          hovered = true;
        };
        const onLeave = () => {
          hovered = false;
        };
        hero.addEventListener('mouseenter', onEnter);
        hero.addEventListener('mouseleave', onLeave);
        hero.addEventListener('focusin', onEnter);
        hero.addEventListener('focusout', onLeave);

        let io: IntersectionObserver | null = null;
        if (supportsIO) {
          io = new IntersectionObserver(
            (entries) => {
              entries.forEach((e) => {
                offscreen = !e.isIntersecting;
              });
            },
            { threshold: 0 },
          );
          io.observe(box);
        }

        cleanups.push(() => {
          window.clearInterval(timer);
          hero.removeEventListener('mouseenter', onEnter);
          hero.removeEventListener('mouseleave', onLeave);
          hero.removeEventListener('focusin', onEnter);
          hero.removeEventListener('focusout', onLeave);
          io?.disconnect();
        });
      });
    }

    return () => cleanups.forEach((fn) => fn());
  }, [rootRef]);
}
