/* ============================================================
   Guided Workshop — item-type view components
   Exports: window.gwViews = { ContentView, PollView, EvidenceView,
                                PollSessionPanel, ItemTypeIcon }
   ============================================================ */

const { useState, useRef, useEffect } = React;

/* ---------- shared icons ---------- */
const gwIcons = {
  content:  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>,
  poll:     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>,
  evidence: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>,
  media:    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>,
  question: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>,
  check:    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>,
  refresh:  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4-4.64 4.36A9 9 0 0 1 3.51 15"/></svg>,
  play:     <svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>,
  copy:     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>,
  close:    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>,
  users:    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
  responded:<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><polyline points="17 11 19 13 23 9"/></svg>,
  upload:   <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>,
};

function ItemTypeIcon({ type }) {
  return <span className="gw-item-icon-svg">{gwIcons[type] || gwIcons.content}</span>;
}

/* ============================================================
   ContentView — rich text + media + Mark as complete
   ============================================================ */
function ContentView({ item, complete, onComplete }) {
  return (
    <article className="gw-view gw-view-content">
      {item.eyebrow && <div className="gw-eyebrow">{item.eyebrow}</div>}
      <h2 className="gw-view-title">{item.num} {item.title}</h2>

      <div className="gw-blocks">
        {item.blocks.map((b, i) => <Block key={i} block={b} />)}
      </div>

      <CompleteBar complete={complete} onComplete={onComplete} />
    </article>
  );
}

function Block({ block }) {
  switch (block.kind) {
    case "heading":
      return <h3 className="gw-block-heading">{block.text}</h3>;
    case "para":
      return <p className="gw-block-para">{block.text}</p>;
    case "image":
      return (
        <figure className="gw-block-figure">
          <img src={block.src} alt={block.caption || ""} loading="lazy" />
          {block.caption && <figcaption>{block.caption}</figcaption>}
        </figure>
      );
    case "video":
      return (
        <div className="gw-block-video" role="button" tabIndex={0}>
          {block.poster && <img src={block.poster} alt="" />}
          <span className="gw-video-play">{gwIcons.play}</span>
          <span className="gw-video-len">4:12</span>
        </div>
      );
    case "bullets":
      return (
        <ul className="gw-block-bullets">
          {block.items.map((it, i) => <li key={i}>{it}</li>)}
        </ul>
      );
    case "callout":
      return (
        <aside className="gw-block-callout">
          <strong>{block.label}</strong>
          <span>{block.text}</span>
        </aside>
      );
    case "gallery":
      return <Gallery images={block.images} mode={block.mode} />;
    default:
      return null;
  }
}

/* ============================================================
   Gallery — renders a creator-uploaded set of images in one of:
     - "grid"   : responsive grid, all visible
     - "carousel": one large image at a time + thumb strip + arrows
     - "hero"   : featured image + thumbnail row, click thumb to swap

   Mode comes from window.__gwGalleryMode (set by Tweaks) — read inside
   each render so flipping the tweak rerenders all galleries.
   ============================================================ */
function Gallery({ images, mode }) {
  const [active, setActive] = useState(0);
  const m = mode || window.__gwGalleryMode || "grid";
  /* Re-read mode when tweak changes */
  const [, rerender] = useState(0);
  useEffect(() => {
    const h = () => rerender(x => x + 1);
    window.addEventListener("gw-gallery-mode", h);
    return () => window.removeEventListener("gw-gallery-mode", h);
  }, []);
  const current = m === "grid" ? null : (window.__gwGalleryMode || "grid");
  const resolved = current || m;

  const openLightbox = (i) => {
    window.dispatchEvent(new CustomEvent("gw-lightbox", { detail: { images, index: i } }));
  };

  if (resolved === "carousel") {
    return (
      <div className="gw-gallery gw-gallery-carousel">
        <div className="gw-carousel-stage">
          <button className="gw-carousel-arrow is-prev" onClick={() => setActive((active - 1 + images.length) % images.length)} aria-label="Previous">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
          </button>
          <figure className="gw-carousel-main" onClick={() => openLightbox(active)}>
            <img src={images[active].src} alt={images[active].caption || ""} loading="lazy"/>
            {images[active].caption && <figcaption>{images[active].caption}</figcaption>}
          </figure>
          <button className="gw-carousel-arrow is-next" onClick={() => setActive((active + 1) % images.length)} aria-label="Next">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
          </button>
        </div>
        <div className="gw-carousel-thumbs">
          {images.map((im, i) => (
            <button key={i} className={`gw-thumb ${i === active ? "is-active" : ""}`} onClick={() => setActive(i)} aria-label={`Image ${i + 1}`}>
              <img src={im.src} alt="" loading="lazy"/>
            </button>
          ))}
        </div>
        <div className="gw-carousel-counter">{active + 1} / {images.length}</div>
      </div>
    );
  }

  if (resolved === "hero") {
    return (
      <div className="gw-gallery gw-gallery-hero">
        <figure className="gw-hero-main" onClick={() => openLightbox(active)}>
          <img src={images[active].src} alt={images[active].caption || ""} loading="lazy"/>
          {images[active].caption && <figcaption>{images[active].caption}</figcaption>}
        </figure>
        <div className="gw-hero-strip">
          {images.map((im, i) => (
            <button key={i} className={`gw-thumb ${i === active ? "is-active" : ""}`} onClick={() => setActive(i)} aria-label={`Show image ${i + 1}`}>
              <img src={im.src} alt="" loading="lazy"/>
            </button>
          ))}
        </div>
      </div>
    );
  }

  /* default — grid */
  return (
    <div className="gw-gallery gw-gallery-grid" style={{ "--gw-gallery-count": images.length }}>
      {images.map((im, i) => (
        <button key={i} className="gw-grid-tile" onClick={() => openLightbox(i)} aria-label={im.caption || `Image ${i + 1}`}>
          <img src={im.src} alt={im.caption || ""} loading="lazy"/>
          {im.caption && <span className="gw-grid-caption">{im.caption}</span>}
        </button>
      ))}
    </div>
  );
}

/* ============================================================
   Lightbox — global overlay, listens for gw-lightbox event
   ============================================================ */
function Lightbox() {
  const [state, setState] = useState(null); // { images, index }

  useEffect(() => {
    const h = (e) => setState(e.detail);
    window.addEventListener("gw-lightbox", h);
    return () => window.removeEventListener("gw-lightbox", h);
  }, []);

  useEffect(() => {
    if (!state) return;
    const onKey = (e) => {
      if (e.key === "Escape") setState(null);
      if (e.key === "ArrowRight") setState(s => s && { ...s, index: (s.index + 1) % s.images.length });
      if (e.key === "ArrowLeft")  setState(s => s && { ...s, index: (s.index - 1 + s.images.length) % s.images.length });
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [state]);

  if (!state) return null;
  const img = state.images[state.index];
  const close = () => setState(null);
  const next  = (e) => { e.stopPropagation(); setState(s => s && { ...s, index: (s.index + 1) % s.images.length }); };
  const prev  = (e) => { e.stopPropagation(); setState(s => s && { ...s, index: (s.index - 1 + s.images.length) % s.images.length }); };

  return (
    <div className="gw-lightbox" onClick={close} role="dialog" aria-label="Image preview">
      <button className="gw-lightbox-close" onClick={close} aria-label="Close">{gwIcons.close}</button>
      <button className="gw-lightbox-arrow is-prev" onClick={prev} aria-label="Previous">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
      </button>
      <figure className="gw-lightbox-figure" onClick={(e) => e.stopPropagation()}>
        <img src={img.src} alt={img.caption || ""}/>
        {img.caption && <figcaption>{img.caption}</figcaption>}
        <div className="gw-lightbox-counter">{state.index + 1} of {state.images.length}</div>
      </figure>
      <button className="gw-lightbox-arrow is-next" onClick={next} aria-label="Next">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
      </button>
    </div>
  );
}

/* ============================================================
   PollView — radio options + Open poll action
   ============================================================ */
function PollView({ item, complete, sessionOpen, onOpenSession, onComplete }) {
  return (
    <article className="gw-view gw-view-poll">
      <div className="gw-tag-row">
        {(item.tags || []).map((t, i) => (
          <span key={i} className={`gw-tag ${t.toLowerCase() === "poll" ? "is-poll" : ""}`}>{t}</span>
        ))}
      </div>
      <h2 className="gw-view-title">{item.num} {item.title}</h2>
      {item.question && <p className="gw-block-para gw-poll-question">{item.question}</p>}

      <fieldset className="gw-poll-options" disabled>
        {item.options.map((opt, i) => (
          <label key={i} className="gw-poll-option">
            <span className="gw-radio" />
            <span className="gw-poll-option-lbl">{opt}</span>
          </label>
        ))}
      </fieldset>

      <div className="gw-poll-helper">
        Polls collect anonymous responses from session participants. Open the
        poll to start collecting answers.
      </div>

      <div className="gw-action-row">
        {!sessionOpen && (
          <button className="gw-btn gw-btn-tonal" onClick={onOpenSession}>
            {gwIcons.play}<span>Open poll</span>
          </button>
        )}
        {sessionOpen && (
          <button className="gw-btn gw-btn-tonal is-on" onClick={onOpenSession}>
            <span className="gw-live-dot" />
            <span>Poll live · {item.session?.responded || 0} responded</span>
          </button>
        )}
        <CompleteBar inline complete={complete} onComplete={onComplete} />
      </div>
    </article>
  );
}

/* ============================================================
   EvidenceView — file upload + notes
   ============================================================ */
function EvidenceView({ item, complete, onComplete }) {
  const [files, setFiles] = useState([]);
  const [notes, setNotes] = useState("");
  const inputRef = useRef(null);
  const onPickFiles = (e) => {
    const list = [...(e.target.files || [])].map(f => ({ name: f.name, size: f.size }));
    setFiles(prev => [...prev, ...list]);
    if (inputRef.current) inputRef.current.value = "";
  };
  const removeFile = (i) => setFiles(prev => prev.filter((_, x) => x !== i));
  const fmt = (b) => b < 1024 ? `${b} B` : b < 1024*1024 ? `${(b/1024).toFixed(0)} KB` : `${(b/1024/1024).toFixed(1)} MB`;

  return (
    <article className="gw-view gw-view-evidence">
      <div className="gw-tag-row">
        <span className="gw-tag is-evidence">Evidence</span>
      </div>
      <h2 className="gw-view-title">{item.num} {item.title}</h2>
      {item.description && <p className="gw-block-para">{item.description}</p>}

      <label className="gw-evidence-drop">
        <input ref={inputRef} type="file" multiple onChange={onPickFiles} hidden />
        <span className="gw-evidence-icon">{gwIcons.upload}</span>
        <span className="gw-evidence-cta"><b>Click to upload</b> or drag and drop</span>
        <span className="gw-evidence-hint">
          {(item.accepts || ["PDF"]).join(", ")} · up to {item.maxSize || "25 MB"}
        </span>
      </label>

      {files.length > 0 && (
        <ul className="gw-evidence-list">
          {files.map((f, i) => (
            <li key={i}>
              <span className="gw-ev-file-icon">{gwIcons.evidence}</span>
              <span className="gw-ev-file-name">{f.name}</span>
              <span className="gw-ev-file-size">{fmt(f.size)}</span>
              <button className="gw-ev-remove" onClick={() => removeFile(i)} title="Remove">
                {gwIcons.close}
              </button>
            </li>
          ))}
        </ul>
      )}

      <label className="gw-field">
        <span className="gw-field-lbl">Notes</span>
        <textarea
          className="gw-textarea"
          rows="3"
          value={notes}
          onChange={(e) => setNotes(e.target.value)}
          placeholder={item.notesPlaceholder || "Add context for reviewers…"}
        />
      </label>

      <CompleteBar complete={complete} onComplete={onComplete} />
    </article>
  );
}

/* ============================================================
   MediaView — single-image or single-video display
   Maps to the "Image Picker" question type in the form builder.
   Layout: optional title/description + large media + optional
   comment box + Mark-as-complete.
   ============================================================ */
function MediaView({ item, complete, onComplete }) {
  const [comment, setComment] = useState("");
  const isVideo = item.mediaKind === "video";
  const isMulti = Array.isArray(item.images) && item.images.length > 1;
  return (
    <article className="gw-view gw-view-media">
      {item.showTitle && (
        <>
          <div className="gw-tag-row">
            <span className={`gw-tag is-media-${isVideo ? "video" : "image"}`}>
              {isVideo ? "Video" : (isMulti ? "Images" : "Image")}
            </span>
          </div>
          <h2 className="gw-view-title">{item.num} {item.title}</h2>
          {item.description && <p className="gw-block-para">{item.description}</p>}
        </>
      )}

      {isMulti ? (
        <Gallery images={item.images} />
      ) : (
        <div className={`gw-media-frame fit-${item.fit || "cover"}`}>
          {isVideo ? (
            <div className="gw-media-video" role="button" tabIndex={0}>
              {item.poster && <img src={item.poster} alt={item.title || ""} />}
              <span className="gw-video-play">{gwIcons.play}</span>
              <span className="gw-video-len">4:12</span>
            </div>
          ) : (
            <img src={item.src} alt={item.title || ""} loading="lazy" />
          )}
        </div>
      )}

      {item.commentBox && (
        <label className="gw-field">
          <span className="gw-field-lbl">Your notes</span>
          <textarea
            className="gw-textarea"
            rows="3"
            value={comment}
            onChange={(e) => setComment(e.target.value)}
            placeholder="Add anything you noticed about this image…"
          />
        </label>
      )}

      <CompleteBar complete={complete} onComplete={onComplete} />
    </article>
  );
}

/* ============================================================
   QuestionView — handles the 5 interactive question sub-types:
   radio, rating, boolean, text, textarea.
   Controlled component: takes `answer` + `onAnswer` from App so the
   value survives navigation.
   ============================================================ */
function QuestionView({ item, answer, onAnswer, complete, pollLive, onComplete, sessionOpen, onOpenSession, onReopenPoll }) {
  const value   = answer?.value ?? null;
  const comment = answer?.comment ?? "";
  const notes   = answer?.notes ?? "";
  const setVal  = (v) => onAnswer({ value: v, comment, notes });
  const setCom  = (c) => onAnswer({ value, comment: c, notes });
  const setNotes = (n) => onAnswer({ value, comment, notes: n });

  const subtype = item.questionType;
  const isPoll  = item.mode === "poll";

  /* In poll mode, the actual responses come from anonymous participants on
     their devices, so the input here is a non-interactive preview. */
  const renderInput = (disabled) => (
    <fieldset className="gw-question-fieldset" disabled={disabled}>
      {subtype === "radio"    && <RadioGroup    item={item} value={disabled ? null : value} onChange={setVal} />}
      {subtype === "rating"   && <RatingScale   item={item} value={disabled ? 0    : value} onChange={setVal} />}
      {subtype === "boolean"  && <BooleanInput  item={item} value={disabled ? null : value} onChange={setVal} />}
      {subtype === "text"     && <SingleLine    item={item} value={disabled ? ""   : value} onChange={setVal} placeholderOverride={disabled ? "Participants type their answer here…" : null} />}
      {subtype === "textarea" && <LongText      item={item} value={disabled ? ""   : value} onChange={setVal} placeholderOverride={disabled ? "Participants type their answer here…" : null} />}
    </fieldset>
  );

  /* Poll lifecycle state — used to pick which action chip to render */
  const pollState = !isPoll ? null
    : pollLive          ? "live"
    : complete          ? "closed"
    : "never";
  const responded = item.session?.responded || 0;
  const dist = (isPoll && pollState === "closed") ? computeDistribution(item) : null;

  return (
    <article className={`gw-view gw-view-question is-${subtype} ${isPoll ? "is-poll-mode" : ""} ${pollState ? "poll-state-" + pollState : ""}`}>
      {item.showTitle && (
        <>
          <div className="gw-tag-row">
            {isPoll && <span className="gw-tag is-poll">Poll</span>}
            <span className="gw-tag is-question">{labelForQuestion(subtype)}</span>
            {item.required && <span className="gw-tag is-required">Required</span>}
            {pollState === "closed" && (
              <span className="gw-tag is-closed">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
                Poll closed · {responded} responded
              </span>
            )}
          </div>
          <h2 className="gw-view-title">{item.num} {item.title}</h2>
          {item.description && <p className="gw-block-para gw-poll-question">{item.description}</p>}
        </>
      )}

      <div className="gw-question-input">
        {pollState === "closed" ? (
          <PollResults
            item={item}
            dist={dist}
            notes={notes}
            onNotesChange={setNotes}
            onReopen={onReopenPoll}
          />
        ) : (
          renderInput(isPoll)
        )}
      </div>

      {isPoll && pollState !== "closed" ? (
        <>
          {pollState === "never" && (
            <div className="gw-poll-helper">
              Polls collect anonymous responses from session participants. Open
              the poll to start collecting answers.
            </div>
          )}
          {pollState === "live" && (
            <div className="gw-poll-helper">
              The poll is live — share the session code or QR with participants
              on the side panel. Mark complete to close it.
            </div>
          )}
          <div className="gw-action-row">
            {pollState === "never" && (
              <button className="gw-btn gw-btn-tonal" onClick={onOpenSession}>
                {gwIcons.play}<span>Open poll</span>
              </button>
            )}
            {pollState === "live" && (
              <button className="gw-btn gw-btn-tonal is-on" onClick={onOpenSession}>
                <span className="gw-live-dot" />
                <span>Poll live · {responded} responded</span>
              </button>
            )}
            <div className="gw-complete-bar is-inline">
              {complete ? (
                <button className="gw-btn gw-btn-completed" onClick={onComplete} title="Mark as not complete">
                  <span className="gw-btn-check">{gwIcons.check}</span>
                  <span>Completed</span>
                </button>
              ) : (
                <button className="gw-btn gw-btn-primary" onClick={onComplete} disabled={pollState === "never"} title={pollState === "never" ? "Open the poll first" : "Closes the poll to new responses"}>
                  <span className="gw-btn-check">{gwIcons.check}</span>
                  <span>Mark complete &amp; close poll</span>
                </button>
              )}
            </div>
          </div>
        </>
      ) : !isPoll ? (
        <>
          {item.commentBox && (
            <label className="gw-field">
              <span className="gw-field-lbl">Add a comment <span className="gw-field-optional">(optional)</span></span>
              <textarea
                className="gw-textarea"
                rows="3"
                value={comment}
                onChange={(e) => setCom(e.target.value)}
                placeholder="Anything to add to your answer above?"
              />
            </label>
          )}
          <CompleteBar complete={complete} onComplete={onComplete} />
        </>
      ) : null}
    </article>
  );
}

function labelForQuestion(s) {
  return { radio: "Multiple choice", rating: "Rating", boolean: "Yes / No", text: "Short answer", textarea: "Long answer" }[s] || "Question";
}

/* ──── Radio group ──── */
function RadioGroup({ item, value, onChange }) {
  return (
    <div className="gw-radio-group">
      {item.options.map((opt, i) => {
        const selected = value === opt;
        return (
          <label key={i} className={`gw-radio-card ${selected ? "is-selected" : ""}`}>
            <input type="radio" name={`r-${item.id}`} checked={selected} onChange={() => onChange(opt)} />
            <span className="gw-radio-dot"><span className="gw-radio-dot-inner"/></span>
            <span className="gw-radio-card-lbl">{opt}</span>
          </label>
        );
      })}
    </div>
  );
}

/* ──── Rating scale (stars or numbers) ──── */
function RatingScale({ item, value, onChange }) {
  const { min = 1, max = 5 } = item.scale || {};
  const labels = item.labels || {};
  const style = item.scaleStyle || "stars";
  const v = Number.isFinite(value) ? value : 0;
  const items = [];
  for (let i = min; i <= max; i++) items.push(i);
  return (
    <div className={`gw-rating gw-rating-${style}`}>
      <div className="gw-rating-row">
        {items.map((n) => {
          const active = n <= v;
          return (
            <button key={n} type="button" className={`gw-rating-cell ${active ? "is-active" : ""}`} onClick={() => onChange(n)} aria-label={`Rate ${n}`}>
              {style === "stars" ? (
                <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="1" strokeLinejoin="round">
                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
                </svg>
              ) : (
                <span className="gw-rating-num">{n}</span>
              )}
            </button>
          );
        })}
      </div>
      {(labels.min || labels.max) && (
        <div className="gw-rating-labels">
          <span>{labels.min}</span>
          <span>{labels.max}</span>
        </div>
      )}
    </div>
  );
}

/* ──── Boolean (Yes / No) ──── */
function BooleanInput({ item, value, onChange }) {
  const t = item.trueLabel || "Yes";
  const f = item.falseLabel || "No";
  return (
    <div className="gw-bool-group">
      <button type="button" className={`gw-bool-btn ${value === true ? "is-yes" : ""}`} onClick={() => onChange(true)}>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
        <span>{t}</span>
      </button>
      <button type="button" className={`gw-bool-btn ${value === false ? "is-no" : ""}`} onClick={() => onChange(false)}>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
        <span>{f}</span>
      </button>
    </div>
  );
}

/* ──── Single line input ──── */
function SingleLine({ item, value, onChange, placeholderOverride }) {
  const max = item.maxLength;
  const v = value || "";
  return (
    <div className="gw-text-wrap">
      <input
        type="text"
        className="gw-text-input"
        value={v}
        onChange={(e) => onChange(e.target.value.slice(0, max || 1000))}
        placeholder={placeholderOverride || item.placeholder || "Type your answer…"}
        maxLength={max}
      />
      {max && <span className="gw-text-counter">{v.length}/{max}</span>}
    </div>
  );
}

/* ──── Long text ──── */
function LongText({ item, value, onChange, placeholderOverride }) {
  const v = value || "";
  return (
    <textarea
      className="gw-textarea gw-textarea-large"
      rows={item.rows || 4}
      value={v}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholderOverride || item.placeholder || "Type your answer…"}
    />
  );
}

/* ============================================================
   PollResults — visualizations shown when a poll has been closed.
   Each questionType gets its own chart treatment.
   ============================================================ */

/* Sample text-poll responses (deterministic by seed) — used only when the
   item itself doesn't ship explicit results. Keeps the prototype seeded. */
const SAMPLE_TEXT_RESPONSES = [
  "Heavy", "Fragmented", "Slow", "Agile", "Stuck", "Reactive", "Improving",
  "Disconnected", "Solid", "Patchy", "Working", "Stretched", "Streamlined",
  "Manual", "Confused", "Aligned",
];
const SAMPLE_TEXTAREA_RESPONSES = [
  "We've made progress in the operations review cadence but our data layer is still patchy. Hard to make real decisions without a single source of truth.",
  "Honestly the operating model feels great in some sites and disjointed in others. We need a clearer set of non-negotiables.",
  "There's a real tension between speed and consistency. Leadership says they want both — we feel forced to pick.",
  "Capability gaps are real. Onboarding works but mid-career upskilling is something we never invest in.",
  "Decision rights are theoretically clear, but in practice escalation happens for everything.",
  "We've been waiting on the tooling consolidation for ~18 months. Lots of duplicate effort still.",
];

/* Deterministic distribution generator based on session code + index */
function poissonish(seed, n) {
  let s = seed * 9301 + 49297;
  const rng = () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
  const out = [];
  for (let i = 0; i < n; i++) out.push(Math.max(1, Math.round(rng() * 100)));
  return out;
}

function computeDistribution(item) {
  const total = item.session?.responded || 0;
  if (total === 0) return null;
  const seed = parseInt((item.session?.code || "0").replace(/\D/g, ""), 10) || 1;
  const t = item.questionType;

  if (t === "radio") {
    const N = item.options.length;
    const raw = poissonish(seed * 7, N);
    const sum = raw.reduce((a, b) => a + b, 0);
    const counts = raw.map((c) => Math.max(1, Math.round((c / sum) * total)));
    return { counts, labels: item.options, total };
  }
  if (t === "rating") {
    const max = item.scale?.max || 5;
    const min = item.scale?.min || 1;
    const N = max - min + 1;
    /* skew toward upper-middle */
    const raw = [];
    let s = seed;
    for (let i = 0; i < N; i++) {
      s = (s * 9301 + 49297) % 233280;
      const r = s / 233280;
      const weight = 0.3 + Math.abs(Math.sin((i / (N - 1)) * Math.PI)) * 1.2;
      raw.push(Math.max(1, Math.round(r * 100 * weight)));
    }
    const sum = raw.reduce((a, b) => a + b, 0);
    const counts = raw.map((c) => Math.max(0, Math.round((c / sum) * total)));
    return { counts, max, min, total };
  }
  if (t === "boolean") {
    const yesPct = 40 + (seed % 35); /* between 40-74% yes */
    const yes = Math.round((yesPct / 100) * total);
    return { yes, no: total - yes, total };
  }
  if (t === "text") {
    const count = Math.min(SAMPLE_TEXT_RESPONSES.length, total);
    return { responses: SAMPLE_TEXT_RESPONSES.slice(0, count), total };
  }
  if (t === "textarea") {
    const count = Math.min(SAMPLE_TEXTAREA_RESPONSES.length, Math.max(3, Math.min(total, 6)));
    return { responses: SAMPLE_TEXTAREA_RESPONSES.slice(0, count), total };
  }
  return null;
}

/* Grade mapping: scale a numeric score (1..max) to a letter grade */
function scoreToGrade(avg, max) {
  if (max <= 1) return "—";
  const norm = (avg - 1) / (max - 1); /* 0..1 */
  if (norm >= 0.85) return "A";
  if (norm >= 0.65) return "B";
  if (norm >= 0.45) return "C";
  if (norm >= 0.25) return "D";
  return "F";
}

function PollResults({ item, dist, notes, onNotesChange, onReopen }) {
  const [grade, setGrade] = useState(false);
  if (!dist) {
    return (
      <div className="gw-pr-empty">
        <p>No responses were collected during the session.</p>
        <button className="gw-btn gw-btn-tonal" onClick={onReopen}>
          {gwIcons.play}<span>Re-open poll</span>
        </button>
      </div>
    );
  }
  const t = item.questionType;
  return (
    <div className="gw-pr">
      <PollStats item={item} dist={dist} grade={grade} onToggleGrade={setGrade} />

      <div className="gw-pr-chart">
        {t === "radio"    && <RadioChart    item={item} dist={dist} />}
        {t === "rating"   && <RatingChart   item={item} dist={dist} />}
        {t === "boolean"  && <BooleanChart  item={item} dist={dist} />}
        {t === "text"     && <TextResponses item={item} dist={dist} />}
        {t === "textarea" && <TextareaResponses item={item} dist={dist} />}
      </div>

      <div className="gw-pr-banner">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h0"/></svg>
        <span>Live results from this session. Use the notes area below to capture clarifications, actions, or follow-up items.</span>
      </div>

      <label className="gw-field">
        <span className="gw-field-lbl">Notes</span>
        <textarea
          className="gw-textarea"
          rows="3"
          value={notes || ""}
          onChange={(e) => onNotesChange(e.target.value)}
          placeholder="Add notes…"
        />
      </label>

      <div className="gw-action-row">
        <button className="gw-btn gw-btn-tonal" onClick={onReopen}>
          {gwIcons.play}<span>Re-open poll</span>
        </button>
      </div>
    </div>
  );
}

/* ──── Stats row ──── */
function PollStats({ item, dist, grade, onToggleGrade }) {
  const t = item.questionType;
  let primary = null, secondary = null;

  if (t === "radio" || t === "rating") {
    const counts = dist.counts;
    const max = t === "rating" ? (dist.max - dist.min + 1) : counts.length;
    const sum = counts.reduce((acc, c, i) => acc + c * (i + 1), 0);
    const total = counts.reduce((a, b) => a + b, 0);
    const avg = sum / total;
    const healthy = counts.slice(Math.ceil(max / 2)).reduce((a, b) => a + b, 0);
    const healthPct = Math.round((healthy / total) * 100);
    primary = grade ? (
      { icon: "grade", label: "Grade", value: scoreToGrade(avg, max) }
    ) : (
      { icon: "score", label: "Average score", value: `${avg.toFixed(2)} out of ${max.toFixed(2)}` }
    );
    secondary = { icon: "health", label: "Health score", value: `${healthPct}%` };
  } else if (t === "boolean") {
    const yesPct = Math.round((dist.yes / dist.total) * 100);
    primary   = { icon: "score",  label: (item.trueLabel  || "Yes"), value: `${dist.yes} · ${yesPct}%` };
    secondary = { icon: "health", label: (item.falseLabel || "No"),  value: `${dist.no} · ${100 - yesPct}%` };
  } else if (t === "text" || t === "textarea") {
    primary   = { icon: "score",  label: "Responses",        value: `${dist.total}` };
    secondary = { icon: "health", label: "Unique answers",   value: `${dist.responses.length}` };
  }

  return (
    <div className="gw-pr-stats">
      {primary && <StatCard {...primary} />}
      {secondary && <StatCard {...secondary} />}
      {(t === "radio" || t === "rating") && (
        <label className="gw-pr-grade-toggle">
          <span className="gw-pr-grade-track" data-on={grade}>
            <input type="checkbox" checked={grade} onChange={(e) => onToggleGrade(e.target.checked)} />
            <span className="gw-pr-grade-knob" />
          </span>
          <span>View results as grade</span>
        </label>
      )}
    </div>
  );
}

function StatCard({ icon, label, value }) {
  const icons = {
    score:  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>,
    health: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>,
    grade:  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>,
  };
  const colorClass = { score: "gw-stat-blue", health: "gw-stat-amber", grade: "gw-stat-purple" }[icon];
  return (
    <div className={`gw-stat-card ${colorClass}`}>
      <span className="gw-stat-icon">{icons[icon]}</span>
      <div className="gw-stat-text">
        <span className="gw-stat-label">{label}</span>
        <span className="gw-stat-value">{value}</span>
      </div>
    </div>
  );
}

/* ──── Radio — stacked horizontal bar ──── */
const RADIO_PALETTE = [
  "#0891b2", "#67e8f9", "#9ca3af", "#c4b5fd", "#7c3aed",
];
function paletteAt(i, n) {
  /* even-spread sampling of the palette regardless of n */
  const idx = Math.round((i / Math.max(1, n - 1)) * (RADIO_PALETTE.length - 1));
  return RADIO_PALETTE[idx];
}

function RadioChart({ item, dist }) {
  const total = dist.counts.reduce((a, b) => a + b, 0);
  const N = dist.counts.length;
  const segments = dist.counts.map((c, i) => ({
    label: dist.labels[i],
    count: c,
    pct: Math.round((c / total) * 100),
    color: paletteAt(i, N),
  }));
  const [hover, setHover] = useState(null);

  return (
    <div className="gw-chart-radio">
      <div className="gw-chart-bar-wrap">
        <div className="gw-chart-bar">
          {segments.map((s, i) => (
            <div
              key={i}
              className="gw-chart-seg"
              style={{ flex: Math.max(s.pct, 1), background: s.color }}
              onMouseEnter={() => setHover(i)}
              onMouseLeave={() => setHover(null)}
            >
              {s.pct >= 6 && <span className="gw-chart-seg-pct">{s.pct}%</span>}
              {hover === i && (
                <span className="gw-chart-tip">
                  {s.label} · {s.count} responses
                </span>
              )}
            </div>
          ))}
        </div>
        <div className="gw-chart-axis">
          <span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span>
        </div>
      </div>
      <ul className="gw-chart-legend">
        {segments.map((s, i) => (
          <li key={i}>
            <span className="gw-legend-dot" style={{ background: s.color }} />
            <span>{s.label}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

/* ──── Rating — vertical histogram with average overlay ──── */
function RatingChart({ item, dist }) {
  const total = dist.counts.reduce((a, b) => a + b, 0);
  const max = Math.max(...dist.counts);
  const sumWeighted = dist.counts.reduce((acc, c, i) => acc + c * (dist.min + i), 0);
  const avg = sumWeighted / total;
  const N = dist.counts.length;

  return (
    <div className="gw-chart-rating">
      <div className="gw-chart-rating-bars">
        {dist.counts.map((c, i) => {
          const star = dist.min + i;
          const h = max > 0 ? (c / max) * 100 : 0;
          const pct = total > 0 ? Math.round((c / total) * 100) : 0;
          return (
            <div key={i} className="gw-chart-rating-col">
              <span className="gw-chart-rating-num">{c}</span>
              <span className="gw-chart-rating-bar" style={{ height: `${h}%` }}>
                <span className="gw-chart-rating-fill" />
              </span>
              <span className="gw-chart-rating-lbl">
                {Array.from({length: star}).map((_, k) => (
                  <svg key={k} viewBox="0 0 24 24" fill="currentColor"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
                ))}
              </span>
              <span className="gw-chart-rating-pct">{pct}%</span>
            </div>
          );
        })}
      </div>
      <div className="gw-chart-rating-avg" style={{ left: `calc(${((avg - dist.min) / (N - 1)) * 100}% )` }}>
        <span className="gw-chart-rating-avg-lbl">Avg {avg.toFixed(1)}</span>
      </div>
    </div>
  );
}

/* ──── Boolean — large split with dominant choice in big type ──── */
function BooleanChart({ item, dist }) {
  const yesPct = Math.round((dist.yes / dist.total) * 100);
  const noPct = 100 - yesPct;
  const dominantYes = yesPct >= noPct;
  return (
    <div className="gw-chart-bool">
      <div className="gw-chart-bool-hero">
        <div className={`gw-chart-bool-big ${dominantYes ? "is-yes" : "is-no"}`}>
          <span className="gw-chart-bool-big-num">{dominantYes ? yesPct : noPct}%</span>
          <span className="gw-chart-bool-big-lbl">said {dominantYes ? (item.trueLabel || "Yes") : (item.falseLabel || "No")}</span>
        </div>
      </div>
      <div className="gw-chart-bool-bar">
        <div className="gw-chart-bool-seg is-yes" style={{ flex: yesPct }}>
          <span>{item.trueLabel || "Yes"}</span>
          <span>{yesPct}%</span>
        </div>
        <div className="gw-chart-bool-seg is-no" style={{ flex: noPct }}>
          <span>{item.falseLabel || "No"}</span>
          <span>{noPct}%</span>
        </div>
      </div>
    </div>
  );
}

/* ──── Text — chips of all responses ──── */
function TextResponses({ item, dist }) {
  return (
    <div className="gw-chart-text">
      <div className="gw-chart-text-chips">
        {dist.responses.map((r, i) => (
          <span key={i} className="gw-chart-chip" style={{ fontSize: `${14 + (i % 3) * 2}px` }}>{r}</span>
        ))}
      </div>
    </div>
  );
}

/* ──── Textarea — cards of all responses ──── */
function TextareaResponses({ item, dist }) {
  const [expanded, setExpanded] = useState(false);
  const visible = expanded ? dist.responses : dist.responses.slice(0, 3);
  return (
    <div className="gw-chart-textarea">
      <ul className="gw-chart-textarea-list">
        {visible.map((r, i) => (
          <li key={i}>
            <span className="gw-chart-quote">"</span>
            <p>{r}</p>
            <span className="gw-chart-resp-meta">Anonymous · response {i + 1}</span>
          </li>
        ))}
      </ul>
      {dist.responses.length > 3 && (
        <button className="gw-btn gw-btn-secondary gw-chart-expand" onClick={() => setExpanded(!expanded)}>
          {expanded ? "Collapse" : `Show all ${dist.responses.length} responses`}
        </button>
      )}
    </div>
  );
}

/* ============================================================
   CompleteBar — Mark as complete / Completed button row
   ============================================================ */
function CompleteBar({ complete, onComplete, inline }) {
  return (
    <div className={`gw-complete-bar ${inline ? "is-inline" : ""}`}>
      {complete ? (
        <button className="gw-btn gw-btn-completed" onClick={onComplete} title="Mark as not complete">
          <span className="gw-btn-check">{gwIcons.check}</span>
          <span>Completed</span>
        </button>
      ) : (
        <button className="gw-btn gw-btn-primary" onClick={onComplete}>
          <span className="gw-btn-check">{gwIcons.check}</span>
          <span>Mark as complete</span>
        </button>
      )}
    </div>
  );
}

/* ============================================================
   PollSessionPanel — slide-in panel with session code + QR
   ============================================================ */
function QRPattern({ seed = 0 }) {
  /* Deterministic, decorative QR-like pattern. Not a scannable QR code —
     just an aesthetic stand-in. 23×23 modules with 3 finder squares. */
  const N = 23;
  const cells = [];
  /* Pseudo-random by seed */
  let s = seed * 9301 + 49297;
  const rng = () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
  for (let y = 0; y < N; y++) {
    for (let x = 0; x < N; x++) {
      const inFinder =
        (x < 7 && y < 7) ||
        (x >= N - 7 && y < 7) ||
        (x < 7 && y >= N - 7);
      if (inFinder) continue;
      if (rng() > 0.5) cells.push({ x, y });
    }
  }
  const finder = (cx, cy) => (
    <>
      <rect x={cx} y={cy} width="7" height="7" fill="currentColor" />
      <rect x={cx + 1} y={cy + 1} width="5" height="5" fill="white" />
      <rect x={cx + 2} y={cy + 2} width="3" height="3" fill="currentColor" />
    </>
  );
  return (
    <svg viewBox={`0 0 ${N} ${N}`} className="gw-qr-svg" shapeRendering="crispEdges">
      {cells.map((c, i) => <rect key={i} x={c.x} y={c.y} width="1" height="1" fill="currentColor"/>)}
      {finder(0, 0)}
      {finder(N - 7, 0)}
      {finder(0, N - 7)}
    </svg>
  );
}

function PollSessionPanel({ item, onClose, onMarkComplete, complete }) {
  const [copied, setCopied] = useState(null);
  const copy = (key, text) => {
    try { navigator.clipboard?.writeText(text); } catch (_) {}
    setCopied(key);
    setTimeout(() => setCopied(null), 1400);
  };

  if (!item || !item.session) return null;
  const s = item.session;
  /* derive a numeric seed from the session code for the QR */
  const seed = parseInt((s.code || "0").replace(/\D/g, ""), 10) || 0;

  return (
    <aside className="gw-poll-panel" role="dialog" aria-label="Workshop invitation">
      <header className="gw-poll-panel-head">
        <div className="gw-poll-panel-title">
          <span className="gw-live-dot" />
          <span>Workshop invitation</span>
        </div>
        <button className="gw-icon-btn" onClick={onClose} aria-label="Close">{gwIcons.close}</button>
      </header>

      <div className="gw-poll-panel-body">
        <div className="gw-session-code-block">
          <div className="gw-session-code-lbl">Session code</div>
          <div className="gw-session-code-row">
            <span className="gw-session-code">{s.code}</span>
            <button className="gw-icon-btn gw-icon-btn-tonal" onClick={() => copy("code", s.code.replace(/\s/g, ""))} title={copied === "code" ? "Copied" : "Copy code"}>
              {copied === "code" ? gwIcons.check : gwIcons.copy}
            </button>
          </div>
        </div>

        <div className="gw-or">Or scan the QR code below</div>

        <div className="gw-qr-frame">
          <QRPattern seed={seed} />
        </div>

        <div className="gw-link-row">
          <span className="gw-link-text" title={s.url}>{s.url}</span>
          <button className="gw-icon-btn gw-icon-btn-tonal" onClick={() => copy("url", s.url)} title={copied === "url" ? "Copied" : "Copy link"}>
            {copied === "url" ? gwIcons.check : gwIcons.copy}
          </button>
        </div>

        <div className="gw-stat-row">
          <span className="gw-stat">
            {gwIcons.users}
            <b>{s.participants}</b><span>participants</span>
          </span>
          <span className="gw-stat-sep">·</span>
          <span className="gw-stat gw-stat-success">
            {gwIcons.responded}
            <b>{s.responded}</b><span>responded</span>
          </span>
        </div>

        <div className="gw-poll-panel-actions">
          {!complete && (
            <button className="gw-btn gw-btn-secondary" onClick={onMarkComplete}>
              Close poll &amp; mark complete
            </button>
          )}
        </div>
      </div>
    </aside>
  );
}

window.gwViews = { ContentView, PollView, EvidenceView, MediaView, QuestionView, PollSessionPanel, ItemTypeIcon, gwIcons, Lightbox, computeDistribution, PollStats, RadioChart, RatingChart, BooleanChart, TextResponses, TextareaResponses };
