/* Interactive figures for blog posts. Each is configured from JSON in the post source
   (content/blog/posts/*.html) so later comparisons reuse them with their own facts.
   Product-UI fragments (IlCard, IlPill, IlIcon, IlNum, CAvatar, ilFont, Icon) come from mocks.jsx.
   Wrapped in an IIFE because every text/babel script shares one global scope. */
(() => {
  const { useState, useEffect, useRef, useLayoutEffect } = React;
  const prefersReduced = () => !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);

  /* Runs only while the figure is on screen */
  function useInView(ref, margin = "0px") {
    const [seen, setSeen] = useState(false);
    useEffect(() => {
      if (!ref.current || !("IntersectionObserver" in window)) { setSeen(true); return; }
      const io = new IntersectionObserver(([e]) => setSeen(e.isIntersecting), { rootMargin: margin, threshold: 0.2 });
      io.observe(ref.current);
      return () => io.disconnect();
    }, []);
    return seen;
  }

  /* A step counter that loops while visible; with reduced motion it rests on the final state */
  function useLoop(steps, ms, active, rest) {
    const [t, setT] = useState(rest);
    useEffect(() => {
      if (prefersReduced()) { setT(rest); return; }
      if (!active) return;
      const id = setInterval(() => setT(x => (x + 1) % steps), ms);
      return () => clearInterval(id);
    }, [active, steps, ms]);
    return t;
  }

  /* Lays a fixed-size drawing out at any width by scaling it */
  function Stage({ w, h, children }) {
    const ref = useRef(null);
    const [s, setS] = useState(1);
    useLayoutEffect(() => {
      const el = ref.current;
      const fit = () => setS(el.getBoundingClientRect().width / w);
      fit();
      const ro = new ResizeObserver(fit);
      ro.observe(el);
      return () => ro.disconnect();
    }, [w]);
    return <div ref={ref} style={{ position: "relative", width: "100%", height: h * s }}>
      <div style={{ position: "absolute", left: 0, top: 0, width: w, height: h, transform: `scale(${s})`, transformOrigin: "0 0" }}>{children}</div>
    </div>;
  }

  const curve = (x1, y1, x2, y2) => { const mx = (x1 + x2) / 2; return `M${x1} ${y1} C${mx} ${y1} ${mx} ${y2} ${x2} ${y2}`; };
  const SEND_BLUE = "#0948B6";

  /* ---------------------------------------------------------------------------
     1. TwoModels: the competitor's working model beside Duologue's, animated.
     --------------------------------------------------------------------------- */
  function StatusPill({ sent }) {
    return <span style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 9px", borderRadius: 999, flex: "0 0 auto", transition: "background .3s, color .3s",
      background: sent ? "var(--green-50)" : "var(--n-75)", color: sent ? "var(--green-700)" : "var(--text-muted)", ...ilFont(600, 12, 14) }}>
      <Icon name={sent ? "check" : "clock"} size={12} color={sent ? "var(--green-700)" : "var(--n-500)"} />{sent ? "Sent" : "Queued"}
    </span>;
  }

  function WideModel({ steps, leads, pill }) {
    const ref = useRef(null);
    const on = useInView(ref);
    const n = leads.length;
    const t = useLoop(n + 4, 480, on, n);
    const sent = Math.min(t, n);
    const rowY = i => 26 + i * 56;
    return <div ref={ref}><Stage w={560} h={490}>
      <svg aria-hidden="true" width="560" height="490" style={{ position: "absolute", inset: 0 }}>
        {leads.map((_, i) => {
          const live = i === sent - 1;
          return <path key={i} d={curve(240, 150, 316, rowY(i) + 22)} fill="none" strokeWidth="2"
            stroke={live ? SEND_BLUE : i < sent ? "var(--blue-200)" : "var(--n-300)"} strokeDasharray={i < sent ? "0" : "3 4"} style={{ transition: "stroke .3s" }} />;
        })}
      </svg>
      <IlCard style={{ left: 22, top: 40, width: 218, padding: "16px 16px 10px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, paddingBottom: 12, borderBottom: "1px solid var(--n-150)" }}>
          <IlIcon name="workflow" size={30} icon={15} radius={8} tone="neutral" />
          <span style={{ ...ilFont(600, 15, 18), color: "var(--text-primary)" }}>Sequence</span>
          <span style={{ flex: 1 }} /><span style={{ ...ilFont(500, 12, 14), color: "var(--text-muted)" }}>{steps.length} steps</span>
        </div>
        {steps.map(([icon, label], i) => <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderBottom: i < steps.length - 1 ? "1px dashed var(--n-150)" : 0 }}>
          <IlNum n={i + 1} size={22} />
          <Icon name={icon} size={15} color="var(--n-600)" />
          <span style={{ ...ilFont(500, 14, 18), color: "var(--text-body)" }}>{label}</span>
        </div>)}
      </IlCard>
      {leads.map((name, i) => <IlCard key={i} style={{ left: 316, top: rowY(i), width: 222, height: 44, padding: "0 8px 0 10px", display: "flex", alignItems: "center", gap: 10,
        border: i === sent - 1 ? "1px solid var(--blue-200)" : "1px solid var(--n-150)", transition: "border-color .3s" }}>
        <CAvatar name={name} size={24} />
        <span style={{ ...ilFont(500, 14, 18), color: "var(--text-primary)", flex: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</span>
        <StatusPill sent={i < sent} />
      </IlCard>)}
      <IlPill solid style={{ left: 22, top: 424, background: "var(--n-900)", borderColor: "var(--n-900)", boxShadow: "0 16px 32px -12px rgba(16,24,40,.5)" }}>
        <Icon name="calendar-clock" size={17} color="#fff" /><span>{pill}</span>
      </IlPill>
    </Stage></div>;
  }

  /* An email client's model: a split inbox, each email sorted, answered or cleared in turn */
  const INBOX_ACTIONS = {
    done: ["check", "Done", "var(--green-50)", "var(--green-700)"],
    reply: ["zap", "Instant Reply", "var(--blue-50)", "var(--blue-700)"],
    remind: ["alarm-clock", "Remind me", "var(--amber-50)", "var(--amber-700)"],
    archive: ["archive", "Auto archived", "var(--n-75)", "var(--text-secondary)"]
  };
  function InboxModel({ tabs, emails, pill }) {
    const ref = useRef(null);
    const on = useInView(ref);
    const n = emails.length;
    const t = useLoop(n + 4, 700, on, n);
    const done = Math.min(t, n);
    return <div ref={ref}><Stage w={560} h={490}>
      <IlCard style={{ left: 22, top: 26, width: 516, height: 44, padding: "0 8px", display: "flex", alignItems: "center", gap: 4 }}>
        {tabs.map((tab, i) => <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 10px", borderRadius: 7,
          background: i === 0 ? "var(--n-100)" : "transparent", color: i === 0 ? "var(--text-primary)" : "var(--text-muted)", ...ilFont(i === 0 ? 600 : 500, 13, 16) }}>
          {tab}{i === 0 ? <span style={{ minWidth: 18, padding: "1px 6px", borderRadius: 999, textAlign: "center", background: n - done ? SEND_BLUE : "var(--green-600)", color: "#fff", ...ilFont(600, 11, 14), transition: "background .3s" }}>{n - done}</span> : null}
        </span>)}
      </IlCard>
      {emails.map(([who, subject, action], i) => {
        const processed = i < done;
        const live = i === done - 1;
        const [icon, label, bg, fg] = INBOX_ACTIONS[action];
        return <IlCard key={i} style={{ left: 22, top: 86 + i * 46, width: 516, height: 38, padding: "0 8px 0 10px", display: "flex", alignItems: "center", gap: 10,
          opacity: processed && !live ? .55 : 1, border: live ? "1px solid var(--blue-200)" : "1px solid var(--n-150)", boxShadow: processed && !live ? "none" : undefined, transition: "opacity .4s, border-color .3s" }}>
          <CAvatar name={who} size={22} />
          <span style={{ width: 118, flex: "0 0 auto", ...ilFont(processed ? 500 : 600, 13, 16), color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{who}</span>
          <span style={{ flex: 1, minWidth: 0, ...ilFont(400, 13, 16), color: "var(--text-secondary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{subject}</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "3px 9px", borderRadius: 999, flex: "0 0 auto", background: bg, color: fg, ...ilFont(600, 11.5, 14),
            opacity: processed ? 1 : 0, transform: processed ? "none" : "translateX(6px)", transition: "opacity .3s, transform .3s" }}>
            <Icon name={icon} size={11} color={fg} />{label}
          </span>
        </IlCard>;
      })}
      <IlPill solid style={{ left: 22, top: 424, background: "var(--n-900)", borderColor: "var(--n-900)", boxShadow: "0 16px 32px -12px rgba(16,24,40,.5)" }}>
        <Icon name={done === n ? "circle-check" : "inbox"} size={17} color="#fff" /><span>{done === n ? pill.done : pill.working}</span>
      </IlPill>
    </Stage></div>;
  }

  function DeepModel({ account, sources, brief, pill }) {
    const ref = useRef(null);
    const on = useInView(ref);
    const n = sources.length;
    const t = useLoop(n + 4, 950, on, n);
    const k = Math.min(t, n);
    const rowY = i => 30 + i * 62;
    const Cite = ({ c, shown }) => <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", minWidth: 16, height: 16, marginLeft: 3, borderRadius: 5, verticalAlign: 1,
      background: shown ? "var(--blue-50)" : "transparent", color: shown ? "var(--blue-700)" : "transparent", ...ilFont(600, 10, 10), transition: "all .4s" }}>{c}</span>;
    return <div ref={ref}><Stage w={560} h={490}>
      <svg aria-hidden="true" width="560" height="490" style={{ position: "absolute", inset: 0 }}>
        {sources.map((_, i) => <path key={i} d={curve(234, rowY(i) + 24, 272, 190)} fill="none" strokeWidth="2"
          stroke={i === k - 1 ? SEND_BLUE : i < k ? "var(--blue-200)" : "var(--n-300)"} strokeDasharray={i < k ? "0" : "3 4"} style={{ transition: "stroke .3s" }} />)}
      </svg>
      {sources.map(([icon, label, sub], i) => <IlCard key={i} style={{ left: 18, top: rowY(i), width: 216, height: 48, padding: "0 10px", display: "flex", alignItems: "center", gap: 10,
        border: i === k - 1 ? "1px solid var(--blue-200)" : "1px solid var(--n-150)", transition: "border-color .3s" }}>
        <IlIcon name={icon} size={28} icon={14} radius={7} tone={i < k ? "blue" : "neutral"} />
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: "block", ...ilFont(600, 13, 16), color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{label}</span>
          <span style={{ display: "block", ...ilFont(400, 12, 15), color: "var(--text-muted)", whiteSpace: "nowrap" }}>{sub}</span>
        </span>
        <span style={{ opacity: i < k ? 1 : 0, transition: "opacity .3s" }}><IlNum n={i + 1} size={20} /></span>
      </IlCard>)}
      <IlCard style={{ left: 272, top: 30, width: 270, padding: "14px 16px 12px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, paddingBottom: 11, borderBottom: "1px solid var(--n-150)" }}>
          <IlIcon name="file-search" size={26} icon={13} radius={7} tone="blue" />
          <span style={{ minWidth: 0 }}>
            <span style={{ display: "block", ...ilFont(600, 13.5, 16), color: "var(--text-primary)" }}>Account brief</span>
            <span style={{ display: "block", ...ilFont(400, 12, 15), color: "var(--text-muted)" }}>{account}</span>
          </span>
        </div>
        {brief.map(([label, text, cites], i) => {
          const shown = Math.max(...cites) <= k;
          return <div key={i} style={{ marginTop: 11 }}>
            <span style={{ display: "block", ...ilFont(600, 10.5, 13), letterSpacing: ".08em", textTransform: "uppercase", color: shown ? "var(--blue-700)" : "var(--text-disabled)", transition: "color .4s" }}>{label}</span>
            <span style={{ display: "block", marginTop: 3, ...ilFont(400, 13, 19), color: "var(--text-body)" }}>
              <span style={{ color: shown ? "var(--text-body)" : "transparent", background: shown ? "transparent" : "var(--n-100)", borderRadius: 4, WebkitBoxDecorationBreak: "clone", boxDecorationBreak: "clone", transition: "color .4s, background .4s" }}>{text}</span>
              {cites.map(c => <Cite key={c} c={c} shown={shown} />)}
            </span>
          </div>;
        })}
        <div style={{ display: "flex", alignItems: "center", gap: 7, marginTop: 12, paddingTop: 10, borderTop: "1px solid var(--n-150)", ...ilFont(500, 12.5, 16), color: k === n ? "var(--green-700)" : "var(--text-muted)" }}>
          <Icon name={k === n ? "circle-check" : "loader"} size={14} color={k === n ? "var(--green-700)" : "var(--n-500)"} style={k === n ? null : { animation: "du-spin 1.6s linear infinite" }} />
          Built from {k} of {n} sources
        </div>
      </IlCard>
      <IlPill solid style={{ right: 18, top: 424 }}>
        <Icon name="refresh-cw" size={16} color="#fff" /><span>{pill}</span>
      </IlPill>
    </Stage></div>;
  }

  function TwoModels({ them, us }) {
    return <div className="bv-models">
      {[[them, "them"], [us, "us"]].map(([side, key]) => <div key={key} className={"bv-model bv-model--" + key}>
        <div className="bv-model-head">
          <span className="bv-model-name">{key === "us" ? <img src="/assets/duologue-mark.svg" alt="" width="16" height="16" /> : <span className="bv-ring" />}{side.name}</span>
          <h3>{side.title}</h3>
          <p>{side.body}</p>
        </div>
        <div className="duo-cmp bv-model-canvas">
          {key === "us" ? <DeepModel {...side} /> : side.model === "inbox" ? <InboxModel {...side} /> : <WideModel {...side} />}
        </div>
      </div>)}
    </div>;
  }

  /* ---------------------------------------------------------------------------
     2. Spectrum: where each product sits between two design poles. Editorial, not measured.
     --------------------------------------------------------------------------- */
  function Spectrum({ them, us, rows }) {
    const ref = useRef(null);
    const on = useInView(ref);
    const [placed, setPlaced] = useState(prefersReduced());
    const [tip, setTip] = useState(null);
    useEffect(() => { if (on) { const id = setTimeout(() => setPlaced(true), 150); return () => clearTimeout(id); } }, [on]);
    const Marker = ({ row, who, i }) => {
      const isUs = who === "us";
      const pos = placed ? row[who].at : 50;
      const id = `${i}-${who}`;
      const open = tip === id;
      return <button type="button" className={"bv-mark bv-mark--" + who + (open ? " is-open" : "")}
        style={{ left: pos + "%" }} aria-label={`${isUs ? us : them}: ${row[who].note}`}
        onMouseEnter={() => setTip(id)} onMouseLeave={() => setTip(null)} onFocus={() => setTip(id)} onBlur={() => setTip(null)} onClick={() => setTip(open ? null : id)}>
        <span className="bv-mark-label">{isUs ? us : them}</span>
        <span className="bv-mark-dot" />
        {open ? <span className={"bv-tip" + (row[who].at > 60 ? " bv-tip--left" : "")} role="tooltip">
          <b>{isUs ? us : them}</b>{row[who].note}
        </span> : null}
      </button>;
    };
    return <div className="bv-spec" ref={ref}>
      <div className="bv-spec-legend">
        <span><i className="bv-key bv-key--them" />{them}</span>
        <span><i className="bv-key bv-key--us" />{us}</span>
        <em>Hover or tap a marker for the reasoning</em>
      </div>
      {rows.map((row, i) => <div className="bv-spec-row" key={i}>
        <div className="bv-spec-q">{row.label}</div>
        <div className="bv-spec-line">
          <span className="bv-pole">{row.left}</span>
          <div className="bv-track">
            <span className="bv-track-bar" />
            <span className="bv-track-mid" />
            <Marker row={row} who="them" i={i} />
            <Marker row={row} who="us" i={i} />
          </div>
          <span className="bv-pole bv-pole--right">{row.right}</span>
        </div>
      </div>)}
    </div>;
  }

  /* ---------------------------------------------------------------------------
     3. FitFinder: a short self-check that leans one way, the other, or both.
     --------------------------------------------------------------------------- */
  function FitFinder({ them, us, questions, verdicts }) {
    const [answers, setAnswers] = useState({});
    const answered = Object.keys(answers).length;
    const score = Object.entries(answers).reduce((a, [qi, oi]) => a + questions[qi].options[oi].score, 0);
    const max = questions.reduce((a, q) => a + Math.max(...q.options.map(o => Math.abs(o.score))), 0);
    const pos = 50 + (score / max) * 50;
    const v = !answered ? null : score <= -3 ? verdicts.them : score >= 3 ? verdicts.us : verdicts.both;
    return <div className="bv-fit">
      <div className="bv-fit-qs">
        {questions.map((q, qi) => <fieldset key={qi} className="bv-fit-q">
          <legend><span>{String(qi + 1).padStart(2, "0")}</span>{q.q}</legend>
          <div className="bv-fit-opts">
            {q.options.map((o, oi) => <button type="button" key={oi} aria-pressed={answers[qi] === oi}
              className={"bv-opt" + (answers[qi] === oi ? " is-on" : "")}
              onClick={() => setAnswers(a => { const b = { ...a }; if (b[qi] === oi) delete b[qi]; else b[qi] = oi; return b; })}>{o.label}</button>)}
          </div>
        </fieldset>)}
      </div>
      <div className="bv-fit-result" aria-live="polite">
        <span className="b-eyebrow">Where you lean</span>
        <div className="bv-meter">
          <div className="bv-meter-track"><span className="bv-meter-needle" style={{ left: pos + "%", opacity: answered ? 1 : .35 }} /></div>
          <div className="bv-meter-ends"><span>{them}</span><span>Both</span><span>{us}</span></div>
        </div>
        <h3>{v ? v.title : "Answer a question to start"}</h3>
        <p>{v ? v.body : "Five questions about how your team sells. There are no wrong answers, and both tools are good at what they are built for."}</p>
        <div className="bv-fit-foot">
          <span>{answered} of {questions.length} answered</span>
          {answered ? <button type="button" onClick={() => setAnswers({})}>Reset</button> : null}
        </div>
      </div>
    </div>;
  }

  /* ---------------------------------------------------------------------------
     4. ProductTiles: the homepage USP illustrations, scaled to the column.
     --------------------------------------------------------------------------- */
  function MockTile({ mock, title, body }) {
    const ref = useRef(null);
    useLayoutEffect(() => {
      const el = ref.current;
      const fit = () => { const tile = el.querySelector(".du-ill"); if (tile) tile.style.setProperty("--z", tile.getBoundingClientRect().width / 640); };
      fit();
      const ro = new ResizeObserver(fit);
      ro.observe(el);
      return () => ro.disconnect();
    }, []);
    const M = window[mock];
    return <div className="bv-tile" ref={ref}>
      {M ? <M /> : null}
      <div className="bv-tile-copy"><h3>{title}</h3><p>{body}</p></div>
    </div>;
  }
  function ProductTiles({ tiles }) {
    return <div className="bv-tiles">{tiles.map((t, i) => <MockTile key={i} {...t} />)}</div>;
  }

  window.BlogVisuals = { TwoModels, Spectrum, FitFinder, ProductTiles };
})();
