/* =========================================================
   primitives.jsx — shared interaction components.
   Functional icons only (1.5px stroke). Restrained motion.
   ========================================================= */

/* ---------- functional icons ---------- */
function Icon({ name, size = 18, stroke = 1.5, style }) {
  const common = {
    width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round",
    strokeLinejoin: "round", style,
  };
  const paths = {
    arrow:   <><line x1="5" y1="12" x2="19" y2="12" /><polyline points="13 6 19 12 13 18" /></>,
    arrowL:  <><line x1="19" y1="12" x2="5" y2="12" /><polyline points="11 18 5 12 11 6" /></>,
    check:   <polyline points="4 12 10 18 20 6" />,
    x:       <><line x1="6" y1="6" x2="18" y2="18" /><line x1="18" y1="6" x2="6" y2="18" /></>,
    sun:     <><circle cx="12" cy="12" r="4.2" /><line x1="12" y1="2.5" x2="12" y2="5" /><line x1="12" y1="19" x2="12" y2="21.5" /><line x1="2.5" y1="12" x2="5" y2="12" /><line x1="19" y1="12" x2="21.5" y2="12" /><line x1="5.2" y1="5.2" x2="6.9" y2="6.9" /><line x1="17.1" y1="17.1" x2="18.8" y2="18.8" /><line x1="5.2" y1="18.8" x2="6.9" y2="17.1" /><line x1="17.1" y1="6.9" x2="18.8" y2="5.2" /></>,
    moon:    <path d="M21 12.8A8.5 8.5 0 1 1 11.2 3a6.6 6.6 0 0 0 9.8 9.8z" />,
    ledger:  <><path d="M5 4h11l3 3v13H5z" /><line x1="8" y1="9" x2="15" y2="9" /><line x1="8" y1="12.5" x2="15" y2="12.5" /><line x1="8" y1="16" x2="12" y2="16" /></>,
    map:     <><polygon points="3 6 9 4 15 6 21 4 21 18 15 20 9 18 3 20" /><line x1="9" y1="4" x2="9" y2="18" /><line x1="15" y1="6" x2="15" y2="20" /></>,
    flag:    <><line x1="5" y1="21" x2="5" y2="4" /><path d="M5 4h11l-2 4 2 4H5" /></>,
    print:   <><polyline points="6 9 6 3 18 3 18 9" /><path d="M6 18H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2" /><rect x="6" y="14" width="12" height="7" /></>,
    rotate:  <><polyline points="3 4 3 9 8 9" /><path d="M4 13a8 8 0 1 0 2-7l-3 3" /></>,
    chevron: <polyline points="6 9 12 15 18 9" />,
    plus:    <><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></>,
  };
  return <svg {...common} aria-hidden="true">{paths[name] || null}</svg>;
}

/* ---------- Reveal: quiet fade-and-rise on scroll-in ---------- */
function Reveal({ children, delay = 0, as = "div", className = "", style }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) { setShown(true); return; }
    // fallback: never let content stay hidden if the observer never fires
    const fallback = setTimeout(() => setShown(true), 1200 + delay);
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            const t = setTimeout(() => setShown(true), delay);
            io.unobserve(el);
            return () => clearTimeout(t);
          }
        });
      },
      { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
    );
    io.observe(el);
    return () => { io.disconnect(); clearTimeout(fallback); };
  }, [delay]);
  const Tag = as;
  return (
    <Tag ref={ref} className={`reveal ${shown ? "in" : ""} ${className}`} style={style}>
      {children}
    </Tag>
  );
}

/* ---------- three-question header strip ---------- */
function Questions({ items }) {
  return (
    <div className="questions">
      {items.map((q, i) => (
        <div className="q" key={i}>
          <div className="q__i">{String(i + 1).padStart(2, "0")}</div>
          <div className="q__t">{q}</div>
        </div>
      ))}
    </div>
  );
}

/* ---------- section header w/ eyebrow ---------- */
function SectionHead({ children }) {
  return (
    <div className="section__eyebrow">
      <span>{children}</span>
      <span className="dash" />
    </div>
  );
}

/* ---------- SIGNAL CARD (Listening) — click to open for context ---------- */
function SignalCard({ data, marks, onToggle }) {
  const set = marks || [];
  const has = (k) => set.includes(k);
  const [open, setOpen] = React.useState(false);
  const canOpen = !!data.more;
  return (
    <div className={`signal ${set.length ? "is-marked" : ""} ${open ? "is-open" : ""}`} data-kind={data.kind}>
      <button className="signal__open" onClick={() => canOpen && setOpen((o) => !o)} aria-expanded={open} disabled={!canOpen}>
        <span className="signal__src">
          <span>{data.src}</span>
          {canOpen && <Icon name="chevron" size={13} style={{ transition: "transform var(--dur) var(--ease)", transform: open ? "rotate(180deg)" : "none" }} />}
        </span>
        <span className="signal__t">{data.t}</span>
      </button>
      {canOpen && <div className="signal__more" hidden={!open}>{data.more}</div>}
      <div className="signal__marks">
        <button
          className={`mark important ${has("important") ? "on" : ""}`}
          onClick={() => onToggle("important")}
          aria-pressed={has("important")}
        >
          <span className="glyph" /> Important
        </button>
        <button
          className={`mark surprising ${has("surprising") ? "on" : ""}`}
          onClick={() => onToggle("surprising")}
          aria-pressed={has("surprising")}
        >
          <span className="glyph" /> Surprised us
        </button>
      </div>
    </div>
  );
}

/* ---------- CHOICE — select control + click-to-expand context ---------- */
function Choice({ on, radio, title, desc, rank, group, more, onSelect }) {
  const [open, setOpen] = React.useState(false);
  const canOpen = !!more;
  return (
    <div className={`choice ${radio ? "choice--radio" : ""} ${on ? "is-on" : ""} ${open ? "is-open" : ""} ${canOpen ? "can-open" : ""}`}>
      <div className="choice__row">
        <button className="choice__select" onClick={onSelect} aria-pressed={on}
          aria-label={on ? "Remove mark" : "Mark this"} title={on ? "Marked — click to remove" : "Mark this"}>
          <span className="choice__box" />
        </button>
        <button className="choice__main" onClick={() => canOpen && setOpen((o) => !o)} aria-expanded={open}>
          <span className="choice__t">{title}</span>
          {desc && <span className="choice__d">{desc}</span>}
          {(rank || group) && <span className="choice__rank">{rank || group}</span>}
        </button>
        {canOpen && (
          <button className="choice__exp" onClick={() => setOpen((o) => !o)}
            aria-label={open ? "Collapse" : "Read more"} aria-expanded={open}>
            <Icon name="chevron" size={16} style={{ transition: "transform var(--dur) var(--ease)", transform: open ? "rotate(180deg)" : "none" }} />
          </button>
        )}
      </div>
      {canOpen && <div className="choice__more" hidden={!open}>{more}</div>}
    </div>
  );
}

/* ---------- TEXT MARK (writes into the ledger) ---------- */
function TextMark({ label, placeholder, value, onChange, hint }) {
  return (
    <div className="textmark">
      <label className="textmark__label">{label}</label>
      <textarea
        value={value || ""}
        placeholder={placeholder}
        onChange={(e) => onChange(e.target.value)}
        spellCheck="false"
      />
      {hint && <div className="textmark__hint">{hint}</div>}
    </div>
  );
}

/* ---------- SCALE ROW (Transformation: today vs tomorrow) ---------- */
function ScaleRow({ data, value, onChange }) {
  const trackRef = React.useRef(null);
  const today = value?.today ?? data.today;
  const tomorrow = value?.tomorrow ?? data.tomorrow;
  const MIN = 1, MAX = 7;
  const pct = (v) => ((v - MIN) / (MAX - MIN)) * 100;

  const drag = (which) => (e) => {
    e.preventDefault();
    const track = trackRef.current;
    const move = (clientX) => {
      const r = track.getBoundingClientRect();
      let p = (clientX - r.left) / r.width;
      p = Math.max(0, Math.min(1, p));
      const v = Math.round(MIN + p * (MAX - MIN));
      onChange({ today: which === "today" ? v : today, tomorrow: which === "tomorrow" ? v : tomorrow });
    };
    move(e.clientX);
    const onMove = (ev) => move(ev.clientX);
    const onUp = () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
    };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
  };

  const key = (which) => (e) => {
    const cur = which === "today" ? today : tomorrow;
    let v = cur;
    if (e.key === "ArrowRight" || e.key === "ArrowUp") v = Math.min(MAX, cur + 1);
    if (e.key === "ArrowLeft" || e.key === "ArrowDown") v = Math.max(MIN, cur - 1);
    if (v !== cur) {
      e.preventDefault();
      onChange({ today: which === "today" ? v : today, tomorrow: which === "tomorrow" ? v : tomorrow });
    }
  };

  const lo = Math.min(today, tomorrow), hi = Math.max(today, tomorrow);
  const gap = Math.abs(tomorrow - today);

  return (
    <div className="scale">
      <div className="scale__head">
        <div className="scale__name">{data.name}</div>
        <div className="scale__attr">{data.attr}</div>
      </div>
      <div className="scale__track" ref={trackRef}>
        <div className="scale__rail" />
        <div className="scale__gap" style={{ left: pct(lo) + "%", width: (pct(hi) - pct(lo)) + "%" }} />
        <div className="scale__stops">
          {Array.from({ length: 7 }).map((_, i) => <span className="scale__stop" key={i} />)}
        </div>
        <div
          className="handle today" style={{ left: pct(today) + "%" }}
          onPointerDown={drag("today")} onKeyDown={key("today")}
          tabIndex={0} role="slider" aria-label={`${data.name} — today`}
          aria-valuemin={MIN} aria-valuemax={MAX} aria-valuenow={today}
        >
          <span className="handle__dot" />
          <span className="handle__lbl">today</span>
        </div>
        <div
          className="handle tomorrow" style={{ left: pct(tomorrow) + "%" }}
          onPointerDown={drag("tomorrow")} onKeyDown={key("tomorrow")}
          tabIndex={0} role="slider" aria-label={`${data.name} — tomorrow`}
          aria-valuemin={MIN} aria-valuemax={MAX} aria-valuenow={tomorrow}
        >
          <span className="handle__dot" />
          <span className="handle__lbl">tomorrow</span>
        </div>
      </div>
      <div className="scale__ends">
        <div className="scale__end lo">{data.low}</div>
        <div className="scale__end hi">{data.high}</div>
      </div>
      <div className="scale__gapread">
        {gap === 0 ? "No distance set — drag the markers apart" : `Distance to cross · ${gap} ${gap === 1 ? "level" : "levels"}`}
      </div>
    </div>
  );
}

Object.assign(window, {
  Icon, Reveal, Questions, SectionHead,
  SignalCard, Choice, TextMark, ScaleRow,
});
