/* global React, Icons, TEMPLATES, contactList, Photo, SectionBody, sectionLabel, activeSections */
// ============================================================
//  ResumePreview — unified, section-driven template engine
//  A template is a layout descriptor (see data.jsx). The engine
//  renders the résumé's sections in the user's chosen order, routing
//  some to a side column when the template asks for it. All visual
//  identity lives in scoped CSS under .tpl-<id> (see resume.css).
// ============================================================

// identity block (name / title / contact / photo)
function Identity({ d, tpl, layout }) {
  const nameParts = (d.name || "").split(" ");
  return (
    <div className={`rid rid-${layout}`}>
      {tpl.photo && <Photo d={d} className="rphoto-id" />}
      <div className="rid-txt">
        <h1 className="rname" data-edit="name">
          <span className="rname-1">{nameParts[0]}</span>
          {nameParts.length > 1 && <span className="rname-2"> {nameParts.slice(1).join(" ")}</span>}
        </h1>
        {d.title && <div className="rtitle" data-edit="title">{d.title}</div>}
        <div className="rcontact">
          {contactList(d).map(({ k, Ic, val }) => (
            <span key={k} className="rcontact-i"><Ic size={12} /><span className="rcontact-v" data-edit={k}>{val}</span></span>
          ))}
        </div>
      </div>
    </div>
  );
}

function renderSecs(keys, d, tpl) {
  const titles = tpl.titles || {};
  return keys.map((k) => (
    <section className={`rsec rsec-${k}`} key={k} data-sec={k}>
      <div className="rsec-t">{titles[k] || sectionLabel(k)}</div>
      <SectionBody k={k} d={d} variant={tpl.skills || "tags"} tpl={tpl} />
    </section>
  ));
}

function TemplateShell({ d, tpl }) {
  const all = activeSections(d);
  const sideSet = new Set(tpl.side || []);
  const sideKeys = all.filter((k) => sideSet.has(k));
  const mainKeys = all.filter((k) => !sideSet.has(k));

  if (tpl.arch === "sidebar") {
    return (
      <div className="rcols arch-sidebar">
        <aside className="rside">
          <Identity d={d} tpl={tpl} layout="side" />
          {renderSecs(sideKeys, d, tpl)}
        </aside>
        <main className="rmain">
          {renderSecs(mainKeys, d, tpl)}
        </main>
      </div>
    );
  }

  // arch === "stack"
  const hasSide = sideKeys.length > 0;
  return (
    <>
      <header className="rhead">
        <Identity d={d} tpl={tpl} layout="top" />
      </header>
      {hasSide ? (
        <div className="rcols arch-stack">
          <main className="rmain">{renderSecs(mainKeys, d, tpl)}</main>
          <aside className="rside">{renderSecs(sideKeys, d, tpl)}</aside>
        </div>
      ) : (
        <div className="rbody">{renderSecs(mainKeys, d, tpl)}</div>
      )}
    </>
  );
}

function ResumePaper({ data, templateId, accent, accent2, skillStyle }) {
  const base = (TEMPLATES.find((t) => t.id === templateId)) || TEMPLATES[0];
  // a global tweak can override the template's skill variant ("template" = leave as-is)
  const tpl = (skillStyle && skillStyle !== "template") ? { ...base, skills: skillStyle } : base;
  const style = { "--pa": accent || tpl.accent, "--pa2": accent2 || tpl.accent2 };
  return (
    <div className={`paper tpl-${tpl.id} arch-${tpl.arch} head-${tpl.head || "plain"} rail-${tpl.rail || "color"} skills-${tpl.skills || "tags"}`} style={style}>
      <TemplateShell d={data} tpl={tpl} />
    </div>
  );
}

// Scales a 768px paper to fit its container width (gallery thumbnails).
function FitPaper({ data, templateId, designWidth = 768 }) {
  const ref = React.useRef(null);
  const [scale, setScale] = React.useState(0.4);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const fit = () => setScale((el.clientWidth || designWidth * 0.4) / designWidth);
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(el);
    return () => ro.disconnect();
  }, [designWidth]);
  return (
    <div ref={ref} style={{ position: "absolute", inset: 0, overflow: "hidden" }}>
      <div style={{ transform: `scale(${scale})`, transformOrigin: "top left", width: designWidth }}>
        <ResumePaper data={data} templateId={templateId} />
      </div>
    </div>
  );
}

// Splits the résumé into Letter-sized page sheets with uniform print margins.
// Breaking rules:
//  • whole "blocks" (a job, a degree, an entry, a skill bar/ring, a section
//    heading) are never split across pages — breaks land between blocks;
//  • a section heading stays glued to its first item (no orphan heading);
//  • only a single block taller than a page falls back to a clean line break.
// Sidebar rails bleed through the top/bottom margins; every page gets a footer.
function PagedPaper({ data, templateId, accent, accent2, skillStyle, pageH = 994, gap = 26, mTop = 60, mBot = 62 }) {
  const measRef = React.useRef(null);
  const [pages, setPages] = React.useState([{ s: 0, e: pageH - mBot, top: 0 }]);
  const [bg, setBg] = React.useState("#fff");
  const [ink, setInk] = React.useState("#6b6b72");
  const [rail, setRail] = React.useState(null);

  const BLOCK_SEL = ".rjob, .redu, .rproj, .rnamed-i, .rlang, .rpub, .rref, .sbar, .sring, .srow, .rsec-t";
  const FIRST_SEL = ".rjob, .redu, .rproj, .rnamed-i, .rlang, .rpub, .rref, .sbar, .sring, .srow, .rsum, .rtags, .rchips, .rskill-list, .rbars, .rrings, .rscale";

  React.useLayoutEffect(() => {
    const host = measRef.current; if (!host) return;
    const compute = () => {
      const paper = host.querySelector(".paper"); if (!paper) return;
      const pr = paper.getBoundingClientRect();
      const total = pr.height;
      const gs = getComputedStyle(paper);
      if (gs.backgroundColor && gs.backgroundColor !== "rgba(0, 0, 0, 0)") setBg(gs.backgroundColor);
      if (gs.color) setInk(gs.color);

      const rside = paper.querySelector(".rcols.arch-sidebar .rside");
      if (rside) { const rg = getComputedStyle(rside); setRail({ w: rside.offsetWidth, h: rside.offsetHeight, color: rg.backgroundColor, image: rg.backgroundImage }); }
      else setRail(null);

      const relTop = (n) => n.getBoundingClientRect().top - pr.top;
      const relBox = (n) => { const r = n.getBoundingClientRect(); return { top: r.top - pr.top, bottom: r.bottom - pr.top }; };

      // Protect EVERY block in BOTH columns: a page break must never fall inside
      // a block (a job, a degree, an entry, a skill row, a section heading, …).
      // When the two columns don't align this can leave extra whitespace near a
      // break — that's the intended trade-off: each block stays whole on one page.
      const allBlocks = [...paper.querySelectorAll(BLOCK_SEL)].filter((n) => n.getClientRects().length).map(relBox);
      const glue = [];
      paper.querySelectorAll(".rsec").forEach((sec) => {
        const h = sec.querySelector(".rsec-t"); const f = sec.querySelector(FIRST_SEL);
        if (h && f) glue.push({ top: relTop(h), bottom: relBox(f).bottom });
      });
      const straddlers = allBlocks.concat(glue);
      const blockValid = (y) => !straddlers.some((b) => b.top < y - 0.5 && b.bottom > y + 0.5);
      // candidate break points: every block top + every section top (both columns)
      const pcand = new Set([0, total]);
      paper.querySelectorAll(BLOCK_SEL).forEach((n) => { if (n.getClientRects().length) pcand.add(Math.round(relTop(n))); });
      paper.querySelectorAll(".rsec").forEach((s) => pcand.add(Math.round(relTop(s))));
      const pcands = [...pcand].sort((a, b) => a - b);

      // leaf-level fallback (used only when a single block exceeds a page)
      const leaves = [];
      paper.querySelectorAll("*").forEach((n) => {
        if (n.children.length === 0 && n.getClientRects().length) leaves.push(relBox(n));
      });
      const lcand = new Set([0, total]);
      leaves.forEach((l) => { lcand.add(Math.round(l.top)); lcand.add(Math.round(l.bottom)); });
      const lcands = [...lcand].sort((a, b) => a - b);
      const leafValid = (y) => !leaves.some((l) => l.top < y - 0.5 && l.bottom > y + 0.5);

      let out = [];
      let start = 0, i = 0, guard = 0;
      while (start < total - 1 && guard++ < 80) {
        const top = i === 0 ? 0 : mTop;
        const avail = Math.max(120, pageH - top - mBot);
        const target = start + avail;
        if (target >= total - 1) { out.push({ s: start, e: total, top }); break; }
        let cut = 0;
        for (const c of pcands) { if (c > start + 40 && c <= target && blockValid(c)) cut = c; }
        // Normally we take the furthest valid WHOLE-block break so nothing splits.
        // But two-column card layouts (e.g. Prism) often have no break line that
        // clears both columns until far down the page, which would strand a
        // near-empty sheet. When the best block break fills less than ~40% of the
        // page, pack it tighter with a line-level break instead.
        if (!cut || (cut - start) < avail * 0.4) {
          let lcut = 0;
          for (const c of lcands) { if (c > start + 40 && c <= target && leafValid(c)) lcut = c; }
          if (lcut > cut) cut = lcut;
        }
        if (!cut) cut = target;
        out.push({ s: start, e: cut, top }); start = cut; i++;
      }
      if (!out.length) out.push({ s: 0, e: Math.min(total, pageH - mBot), top: 0 });
      // Prefer a two-page résumé: if everything fits on one page yet there's a
      // reasonable amount of content, split at the block break nearest the middle
      // so it reads as two balanced pages (then the cover letter is page 3).
      // The break must land in a sensible middle band — never right under the
      // header — or we'd strand a near-empty first page (the old Prism bug). If no
      // clean mid-page break exists, leave it as one full page.
      if (out.length === 1 && total > (pageH - mTop - mBot) * 0.62) {
        const mid = total * 0.55, lo = total * 0.34, hi = total * 0.72;
        let best = 0;
        for (const c of pcands) {
          if (c > lo && c < hi && blockValid(c) && (best === 0 || Math.abs(c - mid) < Math.abs(best - mid))) best = c;
        }
        if (best > 0) out = [{ s: 0, e: best, top: 0 }, { s: best, e: total, top: mTop }];
      }
      setPages(out);
    };
    compute();
    const ro = new ResizeObserver(compute);
    ro.observe(host);
    return () => ro.disconnect();
  }, [data, templateId, accent, accent2, skillStyle, pageH, mTop, mBot]);

  const fpl = rail ? rail.w + 36 : 54;

  return (
    <div className="paged">
      <div ref={measRef} className="paged-measure" aria-hidden="true">
        <ResumePaper data={data} templateId={templateId} accent={accent} accent2={accent2} skillStyle={skillStyle} />
      </div>
      {pages.map(({ s, e, top }, i) => (
        <div className="pg-sheet" key={i} style={{ height: pageH, marginBottom: i < pages.length - 1 ? gap : 0, background: bg }}>
          {rail && (
            <div className="pg-rail" style={{ width: rail.w, height: rail.h, backgroundColor: rail.color, backgroundImage: rail.image && rail.image !== "none" ? rail.image : undefined, transform: `translateY(${top - s}px)` }} />
          )}
          <div className="pg-clip" style={{ height: Math.max(0, e - s), marginTop: top }}>
            <div style={{ transform: `translateY(${-s}px)` }}>
              <ResumePaper data={data} templateId={templateId} accent={accent} accent2={accent2} skillStyle={skillStyle} />
            </div>
          </div>
          <div className="pg-foot" style={{ color: ink }}>
            <div className="pg-foot-in" style={{ paddingLeft: fpl, paddingRight: 54 }}>
              <span className="pg-foot-name">{data.name}</span>
              <span className="pg-foot-num">Page {i + 1} of {pages.length}</span>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

Object.assign(window, { ResumePaper, FitPaper, PagedPaper, TemplateShell });
