/* =================================================================
   Shared primitives + routing helpers
   Exposed on window for the other babel scripts.
   ================================================================= */
const { useState, useEffect, useRef } = React;

/* localized string helper */
const L = (obj, lang) => (obj && typeof obj === 'object' ? (obj[lang] ?? obj.en) : obj);

/* ---- slug + route maps (built once from data) ---- */
const slugify = (s) =>
  String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');

const CAT_BY_ROUTE = {};   // 'video' -> 'film'
const ROUTE_BY_CAT = {};   // 'film'  -> 'video'
(function initRoutes() {
  const data = window.PORTFOLIO;
  data.categories.forEach((c) => { CAT_BY_ROUTE[c.route] = c.id; ROUTE_BY_CAT[c.id] = c.route; });
  Object.keys(data.works).forEach((cat) =>
    data.works[cat].forEach((w) => { w.slug = slugify(w.title); }));
})();

const catMeta = (catId) => window.PORTFOLIO.categories.find((c) => c.id === catId);
const workBySlug = (catId, slug) => window.PORTFOLIO.works[catId].find((w) => w.slug === slug);

const navTo = (path) => {
  const clean = '/' + String(path).replace(/^\/+/, '');
  history.pushState(null, '', clean);
  window.dispatchEvent(new PopStateEvent('popstate', { state: null }));
};

/* ---- reveal-on-scroll hook ---- */
function useReveal(opts = {}) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { el.classList.add('in'); io.unobserve(el); } });
    }, { threshold: opts.threshold ?? 0.16, rootMargin: opts.rootMargin ?? '0px 0px -8% 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return ref;
}

function Reveal({ as = 'div', delay = 0, className = '', children, ...rest }) {
  const ref = useReveal();
  const Tag = as;
  return (
    <Tag ref={ref} className={`reveal ${className}`} style={{ transitionDelay: `${delay}ms` }} {...rest}>
      {children}
    </Tag>
  );
}

/* =================== <T> — typing language swap ====================
   Renders a localized string that RE-TYPES whenever the resolved text
   changes (i.e. on JP/EN switch). A hidden ghost reserves the final box
   so multi-line text never reflows mid-type.
   Usage:  <T t={obj} lang={lang} />   |   <T t="literal" />
=================================================================== */
const _reduceMotion = () =>
  typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function T({ t, lang, as = 'span', block = false, className = '', speed = 20 }) {
  const target = typeof t === 'string' ? t : L(t, lang);
  const [disp, setDisp] = useState(target);
  const [typing, setTyping] = useState(false);
  const first = useRef(true);

  useEffect(() => {
    if (first.current) { first.current = false; setDisp(target); return; }
    if (_reduceMotion() || !target) { setDisp(target); setTyping(false); return; }
    setTyping(true);
    let i = 0;
    const len = target.length;
    // aim for ~1.6–2.4s of typing regardless of string length
    const ticks = Math.min(len, 60 + Math.round(len * 0.25));
    const per = Math.max(1, len / ticks);
    const interval = Math.max(speed, Math.round(2000 / ticks));
    setDisp('');
    const id = setInterval(() => {
      i += per;
      if (i >= len) { setDisp(target); setTyping(false); clearInterval(id); }
      else setDisp(target.slice(0, Math.round(i)));
    }, interval);
    return () => clearInterval(id);
  }, [target]);

  const Tag = as;
  return (
    <Tag className={`tsw ${typing ? 'typing' : ''} ${className}`} data-block={block ? '' : undefined}>
      <span className="tsw-ghost" aria-hidden="true">{target}</span>
      <span className="tsw-live">{disp}<i className="tsw-caret" /></span>
    </Tag>
  );
}

/* =================== Vimeo ==================== */
function VimeoFrame({ id, title }) {
  const [loaded, setLoaded] = useState(false);
  return (
    <div className="vimeo-wrap">
      {!loaded && <div className="vimeo-skel"><span className="mono-label">LOADING · VIMEO {id}</span></div>}
      <iframe
        src={`https://player.vimeo.com/video/${id}?title=0&byline=0&portrait=0&dnt=1&color=ffffff`}
        title={title}
        loading="lazy"
        onLoad={() => setLoaded(true)}
        allow="autoplay; fullscreen; picture-in-picture"
        allowFullScreen
        frameBorder="0"
      />
    </div>
  );
}

/* striped placeholder for app / web visuals */
function Placeholder({ label, tag }) {
  return (
    <div className="ph">
      <div className="ph-stripes" />
      <div className="ph-meta">
        <span className="mono-label">{tag}</span>
        <span className="ph-label">{label}</span>
      </div>
      <div className="ph-corner ph-tl" /><div className="ph-corner ph-tr" />
      <div className="ph-corner ph-bl" /><div className="ph-corner ph-br" />
    </div>
  );
}

/* =================== Tab bar (route-aware) ==================== */
function TabBar({ data, activeCat, lang }) {
  const wrapRef = useRef(null);
  const [ink, setInk] = useState({ left: 0, width: 0 });
  useEffect(() => {
    const el = wrapRef.current?.querySelector(`[data-cat="${activeCat}"]`);
    if (el) setInk({ left: el.offsetLeft, width: el.offsetWidth });
  }, [activeCat, lang]);
  return (
    <div className="tabs" ref={wrapRef}>
      {data.categories.map((c) => (
        <a key={c.id} data-cat={c.id} data-hot href={`/${c.route}`}
          className={`tab ${activeCat === c.id ? 'on' : ''}`}>
          <span className="tab-no mono-label">{c.no}</span>
          <span className="tab-label"><T t={c.label} lang={lang} /></span>
          <span className="tab-count mono-label tnum">{String(data.works[c.id].length).padStart(2, '0')}</span>
        </a>
      ))}
      <span className="tab-ink" style={{ left: ink.left, width: ink.width }} />
    </div>
  );
}

/* =================== Work row (link to detail) ==================== */
function WorkLink({ work, index, lang, routeSlug }) {
  const num = String(index + 1).padStart(2, '0');
  return (
    <div className="row" style={{ '--i': index }}>
      <a className="row-head" data-hot href={`/${routeSlug}/${work.slug}`}>
        <span className="row-num mono-label tnum">{num}</span>
        <span className="row-title">{work.title}</span>
        <span className="row-role"><T t={work.role} lang={lang} /></span>
        <span className="row-year tnum">{work.year}</span>
        <span className="row-mark">↗</span>
      </a>
      <div className="row-rule" />
    </div>
  );
}

Object.assign(window, {
  L, T, slugify, navTo, CAT_BY_ROUTE, ROUTE_BY_CAT, catMeta, workBySlug,
  useReveal, Reveal, VimeoFrame, Placeholder, TabBar, WorkLink,
});
