/* TraitName — clickable/hoverable trait name that shows a hint window with description.
   Stays open as long as the mouse is over the trait name OR the hint window itself. */

const TraitName = ({ trait, detriment = false, suffix = ".", className = "", label = null }) => {
  const [open, setOpen] = React.useState(false);
  const [pos, setPos] = React.useState({ left: 0, top: 0, arrowLeft: 18 });
  const triggerRef = React.useRef(null);
  const hintRef = React.useRef(null);
  const closeTimerRef = React.useRef(null);

  // We allow a tiny grace period so moving the cursor from the trigger to the
  // hint window doesn't cause a flicker-close. Both trigger and hint cancel
  // the timer on enter and start it on leave.
  const cancelClose = () => {
    if (closeTimerRef.current) {
      clearTimeout(closeTimerRef.current);
      closeTimerRef.current = null;
    }
  };
  const scheduleClose = () => {
    cancelClose();
    closeTimerRef.current = setTimeout(() => setOpen(false), 80);
  };

  // Re-measure & clamp. Wrapped so we can call from the layout effect AND
  // from scroll/resize. Stored in a ref to avoid re-creating the listener.
  const measure = React.useCallback(() => {
    if (!hintRef.current || !triggerRef.current) return;
    const PAD = 8;
    const trig = triggerRef.current.getBoundingClientRect();
    const hint = hintRef.current.getBoundingClientRect();
    // documentElement.clientWidth excludes the vertical scrollbar gutter,
    // which window.innerWidth sometimes includes. Using clientWidth keeps
    // the popover from poking into the scrollbar lane.
    const vw = document.documentElement.clientWidth || window.innerWidth;
    const vh = document.documentElement.clientHeight || window.innerHeight;

    // Default: under trigger, left-aligned with trigger.
    let left = trig.left;
    let top = trig.bottom + 6;
    let placement = "below";

    // Flip above if there's no room below and there's room above.
    if (top + hint.height + PAD > vh && trig.top - 6 - hint.height >= PAD) {
      top = trig.top - 6 - hint.height;
      placement = "above";
    }

    // Clamp horizontally inside the viewport
    if (left + hint.width + PAD > vw) left = vw - hint.width - PAD;
    if (left < PAD) left = PAD;

    // Clamp vertically (in case neither above nor below quite fits — better
    // to overlap the trigger than escape the viewport).
    if (top + hint.height + PAD > vh) top = vh - hint.height - PAD;
    if (top < PAD) top = PAD;

    // Arrow follows trigger center, clamped within the popover's edges.
    const triggerCenter = trig.left + trig.width / 2;
    const arrowLeft = Math.max(12, Math.min(hint.width - 24, triggerCenter - left - 6));

    setPos({
      left: left + window.scrollX,
      top: top + window.scrollY,
      arrowLeft,
      placement,
    });
  }, []);

  // After the hint mounts, measure & clamp.
  React.useLayoutEffect(() => {
    if (!open) return;
    measure();
  }, [open, measure]);

  React.useEffect(() => () => cancelClose(), []);

  // Recalculate on scroll/resize while open.
  React.useEffect(() => {
    if (!open) return;
    window.addEventListener("scroll", measure, true);
    window.addEventListener("resize", measure);
    return () => {
      window.removeEventListener("scroll", measure, true);
      window.removeEventListener("resize", measure);
    };
  }, [open, measure]);

  const onEnter = () => {
    cancelClose();
    setOpen(true);
  };

  const reading = TRAIT_READING[trait.id];

  return (
    <>
      <span
        ref={triggerRef}
        className={"t-name trait-trigger " + className + (detriment ? " detriment" : "")}
        onMouseEnter={onEnter}
        onMouseLeave={scheduleClose}
      >{label != null ? label : (trait.name + suffix)}</span>
      {open && ReactDOM.createPortal(
        <div
          ref={hintRef}
          className={"trait-hint" + (detriment ? " detriment" : "") + (pos.placement === "above" ? " above" : "")}
          style={{
            left: pos.left,
            top: pos.top,
            "--arrow-left": pos.arrowLeft + "px",
          }}
          onMouseEnter={cancelClose}
          onMouseLeave={scheduleClose}
        >
          <div className="trait-hint-name">
            {trait.name}
            {trait.tag && <span className="trait-hint-tag">{trait.tag}</span>}
            {detriment && <span className="trait-hint-tag">detriment</span>}
          </div>
          <div className="trait-hint-desc">{trait.description}</div>
          {reading && (
            <div className="trait-hint-reading">{reading}</div>
          )}
        </div>,
        document.body,
      )}
    </>
  );
};

window.TraitName = TraitName;

/* LanguageChip — same hover-popover pattern but for a language id. */
const LanguageChip = ({ id }) => {
  const lang = LANGUAGES[id] || { name: id, description: "Unknown tongue." };
  // Re-use TraitName by giving it a trait-shaped object.
  const trait = { id: "lang_" + id, name: lang.name, description: lang.description };
  return <TraitName trait={trait} suffix="" />;
};

window.LanguageChip = LanguageChip;

/* SkillChip — hover-popover for a skill id. Shows label, governing ability,
   and the codex-flavored description. */
const SkillChip = ({ id, suffix = "", className = "", labelOverride = null }) => {
  const name = SKILL_LABEL[id] || id;
  const desc = SKILL_DESC[id] || "A skill of Theriapolis.";
  const ab = SKILL_ABILITY[id];
  const trait = {
    id: "skill_" + id,
    name,
    description: desc,
    tag: ab,
  };
  return <TraitName trait={trait} suffix={suffix} className={className} label={labelOverride} />;
};

window.SkillChip = SkillChip;

/* BonusPill — small badge showing a numeric bonus, with a hover popover
   that lists the sources of the bonus (clade, species, etc.) */
const BonusPill = ({ total, sources, ability }) => {
  if (!sources || sources.length === 0) return null;
  const breakdown = sources.map(s => `${signed(s.value)} from ${s.source}`).join(" · ");
  const trait = {
    id: "bonus_" + ability,
    name: ability + " modifier",
    description: breakdown,
  };
  return (
    <TraitName
      trait={trait}
      label={signed(total)}
      className={"bonus-pill " + (total >= 0 ? "pos" : "neg")}
    />
  );
};

window.BonusPill = BonusPill;
