/* labs-views.jsx — the Labs (tutorial) views.
   Labs is deliberately a DIFFERENT reading mode from the blog: where an article is
   quiet serif long-form, a tutorial is a worked procedure. It borrows the framing
   devices Japanese tech writing uses to make a procedure scannable — tab-labelled
   callout boxes (ポイント / 注意 / 補足), numbered step cards, copyable prompt
   boxes, a 蛍光ペン marker highlight, and a まとめ summary box.

   Content comes from window.LABS (generated by tutorial/raw/build_labs.py).
   Depends on Reveal (views.jsx), i18n.js, Comments. */

const { useEffect: useLE, useMemo: useLMemo, useState: useLState, useRef: useLRef } = React;

/* Labs UI strings live in window.LABS.ui (blog.js owns the shared nav strings). */
function tl(key, lang) {
  const ui = (window.LABS && window.LABS.ui) || {};
  const L = lang || window.SITE_LANG || "en";
  return (ui[L] && ui[L][key]) || (ui.en && ui.en[key]) || key;
}

/* pick the best language block for a tutorial, mirroring blog-views' pick() */
function pickLab(lab, lang) {
  const langs = lab.langs || {};
  const used = langs[lang] ? lang : (langs[lab.origLang] ? lab.origLang : Object.keys(langs)[0]);
  return { ...(langs[used] || {}), usedLang: used, available: Object.keys(langs) };
}

const LAB_LANG_LABEL = { ms: "Bahasa Melayu", en: "English", ja: "日本語" };

/* ---------- Labs index ---------- */
function LabsView({ onOpen }) {
  const lang = useSiteLang();
  const labs = (window.LABS && window.LABS.tutorials) || [];

  return (
    <div className="view labs">
      {/* no kicker here — it would just repeat the title directly beneath it */}
      <Reveal as="header" className="labs-head">
        <h1 className="labs-title">{tl("labs_title", lang)}</h1>
        <p className="labs-sub">{tl("labs_sub", lang)}</p>
      </Reveal>

      <div className="labs-list">
        {labs.map((lab, i) => {
          const v = pickLab(lab, lang);
          return (
            <Reveal key={lab.slug} delay={Math.min(i, 5) * 50}>
              <article className="lab-card" onClick={() => onOpen(lab.slug)}>
                <div className="lab-card-rule" aria-hidden="true" />
                <div className="lab-card-main">
                  <h2 className="lab-card-title">{v.title}</h2>
                  {v.excerpt && <p className="lab-card-dek">{v.excerpt}</p>}
                  <dl className="lab-meta">
                    {v.level && (
                      <div className="lab-meta-pair">
                        <dt>{tl("level", lang)}</dt><dd>{v.level}</dd>
                      </div>
                    )}
                    {v.duration && (
                      <div className="lab-meta-pair">
                        <dt>{tl("duration", lang)}</dt><dd>{v.duration}</dd>
                      </div>
                    )}
                    {v.stepCount > 0 && (
                      <div className="lab-meta-pair">
                        <dt>{tl("contents", lang)}</dt>
                        <dd>{v.stepCount} {tl("steps", lang)}</dd>
                      </div>
                    )}
                  </dl>
                  {v.tools && v.tools.length > 0 && (
                    <ul className="lab-tools">
                      {v.tools.map((t) => <li key={t} className="lab-tool">{t}</li>)}
                    </ul>
                  )}
                </div>
              </article>
            </Reveal>
          );
        })}
        {labs.length === 0 && (
          <p className="labs-sub" style={{ paddingBlock: "2rem" }}>—</p>
        )}
      </div>

      <Reveal as="footer" className="labs-foot"><span className="foot-mark">了</span></Reveal>
    </div>
  );
}

/* ---------- a single tutorial ---------- */
function LabView({ lab, onBack }) {
  const siteLang = useSiteLang();
  const bodyRef = useLRef(null);
  const [readLang, setReadLang] = useLState(null);
  const [activeStep, setActiveStep] = useLState(0);

  useLE(() => {
    setReadLang(null);
    window.scrollTo({ top: 0, behavior: "auto" });
    if (lab) window.track && window.track("lab_opened", {
      slug: lab.slug,
      title: pickLab(lab, siteLang).title,
      language: siteLang,
      is_translation: lab.origLang !== siteLang,
    });
  }, [lab && lab.slug]);
  useLE(() => { setReadLang(null); }, [siteLang]);

  const lang = readLang || siteLang;
  const v = lab ? pickLab(lab, lang) : {};

  /* Copy-to-clipboard for prompt boxes. The prompt HTML is injected via
     dangerouslySetInnerHTML, so the handler is delegated from the container
     rather than bound per-button. */
  useLE(() => {
    const root = bodyRef.current;
    if (!root) return;
    const onClick = (e) => {
      const btn = e.target.closest && e.target.closest(".lab-copy");
      if (!btn || !root.contains(btn)) return;
      const box = btn.closest(".lab-prompt");
      const code = box && box.querySelector(".lab-prompt-body code");
      if (!code) return;
      const text = code.innerText;
      const done = () => {
        const copied = btn.getAttribute("data-copied") || "Copied";
        const idle = btn.getAttribute("data-copy") || "Copy";
        btn.textContent = copied;
        btn.classList.add("is-copied");
        setTimeout(() => { btn.textContent = idle; btn.classList.remove("is-copied"); }, 1700);
      };
      // execCommand first: it is synchronous, so it lands inside the user-gesture
      // window and never stalls on a permission prompt the way the async
      // clipboard API can. navigator.clipboard is the fallback, not the default.
      const legacy = () => {
        const ta = document.createElement("textarea");
        ta.value = text;
        ta.setAttribute("readonly", "");
        ta.style.position = "fixed"; ta.style.top = "0"; ta.style.opacity = "0";
        document.body.appendChild(ta);
        ta.select(); ta.setSelectionRange(0, text.length);
        let ok = false;
        try { ok = document.execCommand("copy"); } catch (err) { ok = false; }
        document.body.removeChild(ta);
        return ok;
      };
      if (legacy()) {
        done();                       // synchronous success — confirm immediately
      } else if (navigator.clipboard && navigator.clipboard.writeText) {
        // Async fallback. Only confirm once the write actually resolves, so the
        // "Copied" label never claims a copy that didn't happen; if the user
        // denies clipboard permission the button simply stays as it was.
        navigator.clipboard.writeText(text).then(done).catch(() => {
          const fail = btn.getAttribute("data-copy") || "Copy";
          btn.textContent = fail;
        });
      }
      window.track && window.track("lab_prompt_copied", {
        slug: lab.slug,
        prompt_index: Number(box.getAttribute("data-prompt-index")) || null,
        prompt_label: (box.querySelector(".lab-prompt-label") || {}).textContent || null,
        language: lang,
      });
    };
    root.addEventListener("click", onClick);
    return () => root.removeEventListener("click", onClick);
  }, [lab && lab.slug, lang]);

  /* Which step is currently on screen — drives the contents rail, and reports
     step-level reach to PostHog once per step per visit. */
  const seenSteps = useLRef({});
  useLE(() => { seenSteps.current = {}; setActiveStep(0); }, [lab && lab.slug, lang]);
  useLE(() => {
    const root = bodyRef.current;
    if (!root) return;
    const steps = Array.from(root.querySelectorAll(".lab-step"));
    if (!steps.length) return;
    const onScroll = () => {
      let idx = 0;
      steps.forEach((s, i) => {
        if (s.getBoundingClientRect().top <= window.innerHeight * 0.42) idx = i;
      });
      setActiveStep(idx);
      const n = idx + 1;
      if (!seenSteps.current[n]) {
        seenSteps.current[n] = 1;
        window.track && window.track("lab_step_reached", {
          slug: lab.slug, step: n, language: lang,
        });
      }
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, [lab && lab.slug, lang]);

  if (!lab) return null;
  const steps = v.steps || [];
  const available = v.available || Object.keys(lab.langs || {});

  const jumpTo = (i) => {
    const root = bodyRef.current;
    if (!root) return;
    const el = root.querySelectorAll(".lab-step")[i];
    if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
  };

  return (
    <div className="view lab-page">
      <Reveal as="nav" className="entry-back">
        <button className="back-link" onClick={onBack}>{tl("back_labs", siteLang)}</button>
      </Reveal>

      <Reveal as="header" className="lab-page-head" delay={60}>
        <p className="lab-page-kicker">{tl("lab_kicker", lang)}</p>
        <h1 className="lab-page-title">{v.title}</h1>
        {v.excerpt && <p className="lab-standfirst">{v.excerpt}</p>}

        <dl className="lab-spec">
          {v.level && (
            <div className="lab-spec-pair">
              <dt>{tl("level", lang)}</dt><dd>{v.level}</dd>
            </div>
          )}
          {v.duration && (
            <div className="lab-spec-pair">
              <dt>{tl("duration", lang)}</dt><dd>{v.duration}</dd>
            </div>
          )}
          {v.tools && v.tools.length > 0 && (
            <div className="lab-spec-pair">
              <dt>{tl("tools", lang)}</dt><dd>{v.tools.join(" · ")}</dd>
            </div>
          )}
        </dl>

        {available.length > 1 && (
          <div className="post-langs" role="group" aria-label="Read in another language">
            {["ms", "en", "ja"].filter((c) => available.includes(c)).map((c) => (
              <button key={c}
                className={`post-lang-btn ${v.usedLang === c ? "is-active" : ""}`}
                onClick={() => setReadLang(c)}>
                {LAB_LANG_LABEL[c]}
              </button>
            ))}
          </div>
        )}
      </Reveal>

      <div className="lab-layout">
        {steps.length > 0 && (
          <aside className="lab-rail" aria-label={tl("contents", lang)}>
            <p className="lab-rail-heading">{tl("contents", lang)}</p>
            <ol className="lab-rail-list">
              {steps.map((s, i) => (
                <li key={i}>
                  <button
                    className={`lab-rail-item ${activeStep === i ? "is-active" : ""}`}
                    onClick={() => jumpTo(i)}>
                    <span className="lab-rail-num">{s.num}</span>
                    <span className="lab-rail-label">{s.label}</span>
                  </button>
                </li>
              ))}
            </ol>
          </aside>
        )}

        <Reveal as="div" className="lab-body" delay={110}>
          <div className="lab-prose" ref={bodyRef}
            dangerouslySetInnerHTML={{ __html: v.html || "" }} />
        </Reveal>
      </div>

      {typeof SubscribeForm !== "undefined" &&
        (!window.HIRE_CONFIG || window.HIRE_CONFIG.enabled !== false) && (
        <Reveal as="div" delay={40}>
          <SubscribeForm source={`lab:${lab.slug}`} />
        </Reveal>
      )}

      {typeof Comments !== "undefined" &&
        (!window.COMMENTS_CONFIG || window.COMMENTS_CONFIG.enabled !== false) && (
        <Reveal as="section" className="comments-wrap" delay={60}>
          <Comments slug={`lab-${lab.slug}`} />
        </Reveal>
      )}
    </div>
  );
}

Object.assign(window, { LabsView, LabView, pickLab });
