// =====================================================================
// shared utilities + hooks
// =====================================================================

const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;

// Color mapping helpers
function roleColor(role) {
  return ({ sheng: "#d4a24c", dan: "#e8c4b8", jing: "#3a5a6e", chou: "#b8945a" })[role] || "#aaa";
}
function genreColor(g) {
  return ({ history: "#c8341c", family: "#d4a24c", court: "#5e8a6a" })[g] || "#aaa";
}
function genreLabel(g) {
  return ({ history: "历史戏", family: "家庭戏", court: "公案戏" })[g] || g;
}
function roleLabel(r) {
  return ({ sheng: "生", dan: "旦", jing: "净", chou: "丑" })[r] || r;
}
function linkClassFromKind(k) {
  if (["foe","deceit"].includes(k)) return "foe";
  if (["family","romantic"].includes(k)) return k === "romantic" ? "rom" : "fam";
  if (["master","loyal","mercy","justice"].includes(k)) return "master";
  return "ally";
}

// hook: container size with ResizeObserver
function useSize() {
  const ref = useRef(null);
  const [size, setSize] = useState({ w: 0, h: 0 });
  useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(entries => {
      for (const e of entries) {
        const r = e.contentRect;
        setSize({ w: r.width, h: r.height });
      }
    });
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  return [ref, size];
}

// simple seeded random for stable layout
function mulberry32(a) {
  return function() {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = a;
    t = Math.imul(t ^ t >>> 15, t | 1);
    t ^= t + Math.imul(t ^ t >>> 7, t | 61);
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

Object.assign(window, {
  roleColor, genreColor, genreLabel, roleLabel, linkClassFromKind,
  useSize, mulberry32,
});
