/* global React, window */
/* Study result page: MakerChat rail on the left (same pattern as study
   creation), then Intake / Insights / Report tabs over the main area. */
const { useState: stS } = React;

const SRP_SUGS = ["Filter to loyal shoppers", "Which concepts should we cut?", "Compare age segments"];

function SRPChat({ sugs, open, onClose }) {
  const [turns, setTurns] = stS([]);
  const [draft, setDraft] = stS("");
  const send = (text) => {
    const t = (text || "").trim();
    if (!t) return;
    const next = window.AT_RESULTS_TURNS[turns.length];
    setDraft("");
    setTurns(ts => [...ts, next ? { ...next, me: t } : { me: t, steps: [], reply: "I'd need the underlying cuts for that — ask me about the findings, the segments, or what to cut." }]);
  };
  /* Closed renders nothing — the chat icon in the section header is the way
     back in, and a 46px rail saying the same thing was one control too many.
     Returning null rather than unmounting from the parent keeps this
     component's state, so the thread survives a collapse. */
  if (!open) return null;
  return (
    <div className="i3-chat">
      {window.Resz4 ? <window.Resz4 side="left" /> : null}
      <div className="i3-chat__h"><i className="ti ti-sparkles"></i><b>MakerChat</b><em></em>
        <button type="button" className="srp-chatclose" onClick={onClose}
          title="Close MakerChat" aria-label="Close MakerChat"><i className="ti ti-chevrons-left"></i></button>
      </div>
      <div className="i3-log" style={{ gap: 4 }}>
        {turns.length
          ? <window.AgentThread turns={turns} />
          : <div className="i3-msg ai">Results are in. Ask anything about them — the Brand Strategist, Data Scientist and Market Researcher will read them for you.</div>}
      </div>
      {turns.length ? null : <div className="i3-sugs">{(sugs || SRP_SUGS).map(t => <button className="i3-sug" key={t} onClick={() => send(t)}>{t}</button>)}</div>}
      <div className="i3-comp">
        <input placeholder="Ask MakerChat about these results…" value={draft}
          onChange={e => setDraft(e.target.value)}
          onKeyDown={e => { if (e.key === "Enter") send(draft); }} />
        <button onClick={() => send(draft)}><i className="ti ti-arrow-up"></i></button>
      </div>
    </div>
  );
}

/* ---------- Report: the deck this study produces ----------
   Built from the same objectives -> arguments -> findings the Insights tab
   renders, so the deck cannot drift from the data behind it. One slide per
   finding: an argument backed by four tables is four slides, the way it would
   be if a human built it. */
function srpSlides(D) {
  const objs = (D ? D.objectives : []) || [];
  const out = [{ key: "cover", kind: "cover" }];
  if (objs.length) out.push({ key: "agenda", kind: "agenda", objs });
  objs.forEach((o) => {
    out.push({ key: o.slug + "::div", kind: "divider", o });
    out.push({ key: o.slug + "::tk", kind: "takeaways", o });
    let n = 0;
    (o.arguments || []).forEach((a) => {
      (a.findings || []).forEach((f, j) => {
        n += 1;
        out.push({ key: o.slug + "::" + a.id + "::" + j, kind: "claim", o, a, f, i: n, lead: j === 0 });
      });
    });
  });
  return out;
}
function srpNum(n) { return String(n).padStart(2, "0"); }

/* Findings are authored to be read at roughly a browser column width; a slide
   slot is shorter and wider. Render the finding at a fixed logical width, then
   scale it into the slot.

   The logical width has to be FIXED. Deriving it from the current scale (the
   first version set width:(100/sc)% ) makes the width test compare the box
   against a number derived from the box, so w/cw always equals the scale
   already applied — a fixed point that pins whatever value it happens to land
   on. Content that flexes to its container, like a table, never converges. */
const RP_LOGICAL_W = 1160;

function SRPViz({ f }) {
  const box = React.useRef(null), inner = React.useRef(null);
  const [sc, setSc] = stS(1);
  const [off, setOff] = stS(0);
  React.useLayoutEffect(() => {
    const b = box.current, c = inner.current;
    if (!b || !c) return;
    const fit = () => {
      const bs = getComputedStyle(b);
      const h = b.clientHeight - parseFloat(bs.paddingTop) - parseFloat(bs.paddingBottom);
      const w = b.clientWidth - parseFloat(bs.paddingLeft) - parseFloat(bs.paddingRight);
      if (h <= 0 || w <= 0) return;
      const ch = c.scrollHeight;
      // Content narrower than the logical width (a fixed-size chart) is measured
      // at its own width, so it is not shrunk to pay for space it never used.
      const cw = Math.min(RP_LOGICAL_W, Math.max(c.firstChild ? c.firstChild.scrollWidth : 0, 1));
      if (!ch || !cw) return;
      const next = Math.max(0.3, Math.min(1, h / ch, w / cw));
      setSc(next);
      // transform:scale leaves the layout box at full width, so a
      // height-bound finding would sit against the left edge with dead space
      // beside it. Centre what is actually drawn.
      setOff(Math.max(0, (w - cw * next) / 2));
    };
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(b);
    return () => ro.disconnect();
  }, [f]);
  const Viz = window.ApFindingViz;
  return (
    <div className="rp-viz" ref={box}>
      <div className="rp-viz__in" ref={inner}
        style={{ transform: "scale(" + sc + ")", width: RP_LOGICAL_W + "px", marginLeft: off + "px" }}>
        <Viz f={f} />
      </div>
    </div>
  );
}

function SRPSlide({ s, name, base }) {
  const foot = (
    <div className="rp-slide__ft"><b>MAKER/SIGHTS</b>{name}<span>{base}</span></div>
  );
  if (s.kind === "cover") {
    return (
      <div className="rp-slide rp-slide--cover" data-screen-label="Report cover">
        <div className="rp-slide__k"><i className="rule"></i>CONSUMER READ</div>
        <div className="rp-slide__h">{name}</div>
        <div className="rp-slide__s">{base}</div>
        {foot}
      </div>
    );
  }
  if (s.kind === "agenda") {
    return (
      <div className="rp-slide" data-screen-label="Report agenda">
        <div className="rp-slide__k"><i className="rule"></i>AGENDA</div>
        <div className="rp-slide__h">What this study answers</div>
        <div className="rp-slide__body">
          <ol className="rp-agenda">
            {s.objs.map((o) => (
              <li key={o.slug}><b>{o.num}</b><span>{o.objective}</span></li>
            ))}
          </ol>
        </div>
        {foot}
      </div>
    );
  }
  if (s.kind === "divider") {
    return (
      <div className="rp-slide rp-slide--divider" data-screen-label={"Report · section " + s.o.num}>
        <div className="rp-slide__k"><i className="rule"></i>SECTION {s.o.num}</div>
        <div className="rp-slide__h">{s.o.objective}</div>
        <span className="rp-slide__big">{s.o.num}</span>
        {foot}
      </div>
    );
  }
  if (s.kind === "takeaways") {
    const items = (s.o.keyTakeaways || []).slice(0, 3);
    return (
      <div className="rp-slide" data-screen-label={"Report · objective " + s.o.num}>
        <div className="rp-slide__k"><i className="rule"></i>OBJECTIVE {s.o.num} · KEY TAKEAWAYS</div>
        <div className="rp-slide__h">{s.o.objective}</div>
        <div className="rp-slide__body">
          <div className="rp-cards">
            {items.map((t, i) => (
              <div className="rp-card" key={i}>
                <div className="rp-card__n">{srpNum(i + 1)}</div>
                <div className="rp-card__t">{t}</div>
              </div>
            ))}
          </div>
        </div>
        {foot}
      </div>
    );
  }
  /* A finding slide. The first finding under an argument is headlined by the
     argument itself; the rest are headlined by the table and carry the
     argument underneath as the running claim, so four tables backing one point
     do not read as the same slide four times. */
  const f = s.f;
  const Viz = window.ApFindingViz;
  const head = s.lead ? s.a.argument[0] : (f && f.question) || s.a.argument[0];
  const sub = s.lead ? s.a.argument[1] : s.a.argument[0];
  const table = f && f.kind === "matrix";
  return (
    <div className={"rp-slide" + (table ? " rp-slide--table" : "")} data-screen-label={"Report · finding " + s.i}>
      <div className="rp-slide__k"><i className="rule"></i>OBJECTIVE {s.o.num} · FINDING {srpNum(s.i)}</div>
      <div className="rp-slide__h">{head}</div>
      {sub && <div className="rp-slide__s">{sub}</div>}
      <div className="rp-slide__body">
        {f && Viz ? <SRPViz f={f} /> : null}
      </div>
      {table && f.note ? <div className="rp-slide__note">{f.note}</div> : null}
      {foot}
    </div>
  );
}

/* A rail thumbnail is the real slide at 1/5 scale, the way a slide editor
   shows one. Rendering thirty of those at once is wasteful, so each mounts
   only once it has been near the rail viewport. */
function SRPThumb({ s, i, on, name, base, onPick }) {
  const ref = React.useRef(null);
  const [seen, setSeen] = stS(i < 6);
  React.useEffect(() => {
    if (seen || !ref.current) return;
    const io = new IntersectionObserver((es) => {
      if (es.some((e) => e.isIntersecting)) { setSeen(true); io.disconnect(); }
    }, { root: ref.current.closest(".rp-rail"), rootMargin: "400px 0px" });
    io.observe(ref.current);
    return () => io.disconnect();
  }, [seen]);
  return (
    <button ref={ref} className={"rp-thumb" + (on ? " on" : "")} onClick={onPick}
      aria-label={"Slide " + (i + 1)} aria-current={on ? "true" : undefined}>
      <span className="rp-thumb__n">{srpNum(i + 1)}</span>
      <span className="rp-thumb__mini">
        {seen ? <SRPSlide s={s} name={name} base={base} /> : null}
      </span>
    </button>
  );
}

/* A study whose deck has been delivered shows the delivered pages, not a
   re-rendering of them. Same rail-and-stage shape as the generated deck so the
   tab behaves identically either way. */
function SRPDeckThumb({ slide, i, on, onPick }) {
  /* Loaded eagerly, unlike the generated deck's thumbnails. Those mount a whole
     React slide tree each and are worth deferring; this is one <img> out of a
     deck under a megabyte, all of which the stage will want anyway. Deferring
     it only bought blank thumbnails: an IntersectionObserver left the last one
     unmounted at the bottom of the rail, and native loading="lazy" resolves
     against the viewport, so nothing loads at all when the pane is hidden. */
  return (
    <button className={"rp-thumb rp-thumb--img" + (on ? " on" : "")} onClick={onPick}
      aria-label={"Slide " + (i + 1) + " \u2014 " + slide.title} aria-current={on ? "true" : undefined}>
      <span className="rp-thumb__n">{srpNum(i + 1)}</span>
      <span className="rp-thumb__mini">
        <img src={slide.src} alt={slide.title} decoding="async" />
      </span>
    </button>
  );
}

function SRPDeck({ deck }) {
  const slides = deck.slides || [];
  const [sel, setSel] = stS(0);
  const i = Math.min(sel, Math.max(0, slides.length - 1));
  const s = slides[i];
  const rail = React.useRef(null);
  const step = (d) => setSel((v) => Math.max(0, Math.min(slides.length - 1, v + d)));
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "ArrowRight") step(1);
      else if (e.key === "ArrowLeft") step(-1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [slides.length]);
  /* Keep the rail on the slide the stage is showing. At eleven slides the rail
     never scrolled and this was free; a fifty-two page deck paged with the
     arrows leaves the rail stranded at slide one, so the selection has to pull
     it along. Scrolls the rail itself rather than calling scrollIntoView, which
     would also scroll the page behind it. */
  React.useEffect(() => {
    const r = rail.current;
    if (!r) return;
    const t = r.querySelectorAll(".rp-thumb")[i];
    if (!t) return;
    const top = t.offsetTop - r.offsetTop, bot = top + t.offsetHeight;
    if (top < r.scrollTop || bot > r.scrollTop + r.clientHeight) {
      r.scrollTo({ top: top - (r.clientHeight - t.offsetHeight) / 2, behavior: "smooth" });
    }
  }, [i]);
  return (
    <div className="rp" data-screen-label="Report">
      <div className="rp-rail" ref={rail}>
        <div className="rp-rail__k">{slides.length} SLIDES</div>
        {slides.map((x, j) => (
          <SRPDeckThumb key={x.src} slide={x} i={j} on={j === i} onPick={() => setSel(j)} />
        ))}
      </div>
      <div className="rp-stage">
        <div className="rp-bar">
          <span className="rp-bar__t">Report</span>
          <span className="rp-bar__s">{s ? s.kicker + " · " + s.title : deck.fileLabel}</span>
          <span className="rp-bar__sp"></span>
          <span className="rp-bar__pg">
            <button className="rp-nav" onClick={() => step(-1)} disabled={i === 0} aria-label="Previous slide"><i className="ti ti-chevron-left"></i></button>
            <em>{i + 1} / {slides.length}</em>
            <button className="rp-nav" onClick={() => step(1)} disabled={i >= slides.length - 1} aria-label="Next slide"><i className="ti ti-chevron-right"></i></button>
          </span>
          {/* No Export here — the page header already has one, and it now
              carries this deck's file. Two Exports a few pixels apart is a
              choice the reader should not have to make. */}
          <button className="rp-btn"><i className="ti ti-player-play"></i>Present</button>
        </div>
        <div className="rp-canvas rp-canvas--img">
          {s && <img className="rp-page" src={s.src} alt={s.title} />}
        </div>
      </div>
    </div>
  );
}

function SRPReport({ name, base, data }) {
  const D = data || window.APPAREL_INSIGHTS;
  const slides = React.useMemo(() => srpSlides(D), [D]);
  const [sel, setSel] = stS(0);
  const i = Math.min(sel, Math.max(0, slides.length - 1));
  const s = slides[i];
  const stage = React.useRef(null);
  React.useEffect(() => { if (stage.current) stage.current.scrollTop = 0; }, [i]);
  const step = (d) => setSel((v) => Math.max(0, Math.min(slides.length - 1, v + d)));
  return (
    <div className="rp" data-screen-label="Report">
      <div className="rp-rail">
        <div className="rp-rail__k">{slides.length} SLIDES</div>
        {slides.map((x, j) => (
          <SRPThumb key={x.key} s={x} i={j} on={j === i} name={name} base={base}
            onPick={() => setSel(j)} />
        ))}
      </div>
      <div className="rp-stage">
        <div className="rp-bar">
          <span className="rp-bar__t">Report</span>
          <span className="rp-bar__s">Built from the insights you kept · your deck template</span>
          <span className="rp-bar__sp"></span>
          <span className="rp-bar__pg">
            <button className="rp-nav" onClick={() => step(-1)} disabled={i === 0} aria-label="Previous slide"><i className="ti ti-chevron-left"></i></button>
            <em>{i + 1} / {slides.length}</em>
            <button className="rp-nav" onClick={() => step(1)} disabled={i >= slides.length - 1} aria-label="Next slide"><i className="ti ti-chevron-right"></i></button>
          </span>
          <button className="rp-btn"><i className="ti ti-player-play"></i>Present</button>
        </div>
        <div className="rp-canvas" ref={stage}>{s && <SRPSlide s={s} name={name} base={base} />}</div>
      </div>
    </div>
  );
}

/* ---------- the page ---------- */
const SRP_TABS = [
  { id: "intake", label: "Intake", icon: "clipboard-text" },
  { id: "insights", label: "Insights", icon: "sparkles" },
  { id: "results", label: "Results", icon: "chart-bar" },
  { id: "report", label: "Report", icon: "presentation" }
];

/* Per-study wiring. A study id maps to its insights data, its intake screen and
   the respondent file the Results tab reads. window.ACTIVE_STUDY_JSON is set
   before the page mounts and the page is keyed on the study id in sf-app, so a
   study switch remounts everything against the right dataset. */
const SRP_STUDIES = {
  "sv-gsc": {
    json: "gsc-study.json",
    insights: () => window.GSC_INSIGHTS,
    intake: () => (window.GscIntake ? <window.GscIntake /> : null),
    base: () => {
      const D = window.GSC_DESIGN;
      return D ? "n = " + D.n.toLocaleString() + " runners · " + D.markets.join(", ") : "";
    },
    sugs: ["Which colorways belong in wholesale?", "What should we cut from the line?", "Compare the three markets"],
    /* The five shopper segments the range plan is built on, as one-click
       profiles in the Results filter panel. Clicking one reproduces the
       column it came from in the deck. */
    profiles: () => (window.GSC_DESIGN ? window.GSC_DESIGN.profiles : null),
    deck: () => window.GSC_DECK || null,
    cfg: {
      /* Q7 and Q8 are the demographic pair; everything else is a real survey
         question the reader can cut the products by. */
      demoQuestionIds: ["q7", "q8"],
      demoFacets: [
        { field: "gender", label: "Gender" },
        { field: "country", label: "Market" },
        { field: "age", label: "Age group", order: ["18-24", "25-34", "35-44", "45-54", "55+"] },
        { field: "mileage", label: "Weekly distance",
          order: ["Less than 10 miles (16 km)", "10\u201320 miles (16\u201332 km)", "20\u201330 miles (32\u201348 km)",
                  "30\u201340 miles (48\u201364 km)", "40+ miles (64+ km)"] },
        { field: "brand", label: "Preferred brand" }
      ],
      answerFacets: [
        { qid: "q4", label: "Where they shop" },
        { qid: "q2", label: "Purchase motivation" },
        { qid: "q5", label: "How they buy" },
        { qid: "q3", label: "How they discover" }
      ],
      productPlaceholder: "footwear"
    }
  }
};
const SRP_DEFAULT = {
  json: "apparel-study.json",
  insights: () => window.APPAREL_INSIGHTS,
  intake: () => (window.IntakeScreen ? <window.IntakeScreen embedded initialTab="qs" /> : null),
  base: () => "n = 1,240 · UK, FR, DE",
  sugs: SRP_SUGS,
  profiles: () => null,
  deck: () => null,
  cfg: null
};

/* The women's apparel read is the one study with a delivered deck, so its
   Report tab shows those pages rather than re-rendering the findings into
   slides. Both ids are that same read — `sv-apparel` is the closed study in the
   list, `demo-study` the live range read — and nothing else gets the deck: the
   sneaker study would have served an apparel PDF, and a study you just
   submitted has no report to show at all. */
const SRP_APPAREL_DECK_IDS = ["sv-apparel", "demo-study"];
function srpStudyCfg(study) {
  const hit = study && SRP_STUDIES[study.id];
  if (hit) return hit;
  if (study && SRP_APPAREL_DECK_IDS.indexOf(study.id) !== -1) {
    return Object.assign({}, SRP_DEFAULT, { deck: () => window.TECOVAS_DECK || null });
  }
  return SRP_DEFAULT;
}
Object.assign(window, { SRP_STUDIES, SRP_APPAREL_DECK_IDS, srpStudyCfg });

/* The tab is controlled when the shell passes one, so a link can name it and
   the address bar can follow it. Uncontrolled otherwise — the standalone
   pages that mount this without props keep working unchanged. */
function SFStudyPage({ study, tab: tabProp, onTab }) {
  const [tabOwn, setTabOwn] = stS(tabProp || "insights");
  const tab = tabProp || tabOwn;
  const setTab = (id) => { setTabOwn(id); if (onTab) onTab(id); };
  /* One collapse state for the whole page, not one per tab: the rail is the
     same conversation throughout, so closing it on Results and finding it back
     open on Report would read as a bug.

     Starts closed. The results are the point of this page and the rail costs
     ~340px of the read, so it opens on request rather than by default. */
  const [chatOpen, setChatOpen] = stS(false);
  const cfg = srpStudyCfg(study);
  const name = (study && study.name) || "Women's Apparel — FW26 range read";
  const base = cfg.base();
  const data = cfg.insights();
  const deck = cfg.deck && cfg.deck();
  return (
    <div className="sf-work" data-screen-label="Study results">
      <SRPChat sugs={cfg.sugs} open={chatOpen} onClose={() => setChatOpen(false)} />
      <div className="srp-main">
        <div className="srp-tabs">
          {/* Collapsing the rail is worth ~215px, which on the Report tab is the
              difference between 6px and 9px type in a slide's tables. */}
          <button type="button" className={"srp-chat-tgl" + (chatOpen ? " on" : "")}
            onClick={() => setChatOpen(o => !o)} aria-pressed={chatOpen}
            title={chatOpen ? "Hide MakerChat" : "Show MakerChat"}
            aria-label={chatOpen ? "Hide MakerChat" : "Show MakerChat"}>
            <i className="ti ti-message-circle"></i>
          </button>
          <span className="srp-tabs__div" aria-hidden="true"></span>
          {SRP_TABS.map(t => (
            <button key={t.id} className={"srp-tab" + (t.id === tab ? " on" : "")} onClick={() => setTab(t.id)} title={t.label} aria-label={t.label}>
              <i className={"ti ti-" + t.icon}></i><span className="srp-tab__l">{t.label}</span>
            </button>
          ))}
          <span className="srp-tabs__sp"></span>
          {deck && deck.file
            ? <a className="srp-export" href={deck.file} download title={"Download " + (deck.fileLabel || "the report")}>
                <i className="ti ti-file-export"></i>Export</a>
            : <button type="button" className="srp-export"><i className="ti ti-file-export"></i>Export</button>}
        </div>
        <div className="srp-view">
          {tab === "intake" ? (
            cfg.intake()
          ) : tab === "insights" ? (
            window.ApparelInsights && data ? (
              <div className="gl-page od-embed">
                <div className="pf-app"><div className="gl-appbody">
                  <window.ApparelInsights viewMode="client" claimDivider={false} confMode="badge" railMode="none" confAlways
                    data={data} withAppendix={cfg === SRP_DEFAULT} />
                </div></div>
              </div>
            ) : <div style={{ padding: 24, color: "#636c79", fontSize: 13 }}>Loading results…</div>
          ) : tab === "report" ? (
            deck ? <SRPDeck deck={deck} /> : <SRPReport name={name} base={base} data={data} />
          ) : (
            window.RawResultsView ? (
              <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", overflow: "hidden" }}>
                <window.RawResultsView />
              </div>
            ) : <div style={{ padding: 24, color: "#636c79", fontSize: 13 }}>Loading results…</div>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { SFStudyPage });
