// export-utils.jsx — Download visuals as PNG + copy text to clipboard

// Master registry every Asset component registers itself into.
// Keyed by export id, each entry knows its DOM node, group, spec, and download
// filename. The "Download everything" pack walks this map.
window.__ASSETS__ = window.__ASSETS__ || new Map();

const ASSET_LISTENERS = new Set();
function notifyAssetChange() {
  ASSET_LISTENERS.forEach((l) => l());
}

function useAssetCount() {
  const [n, setN] = React.useState(window.__ASSETS__.size);
  React.useEffect(() => {
    const cb = () => setN(window.__ASSETS__.size);
    ASSET_LISTENERS.add(cb);
    return () => { ASSET_LISTENERS.delete(cb); };
  }, []);
  return n;
}

// ───────────────────────────────────────────────────────────────
// Asset — wraps a creative, registers it for export, shows a hover download
// ───────────────────────────────────────────────────────────────
function Asset({ id, group, name, spec, scale = 2, children, style }) {
  const ref = React.useRef(null);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    window.__ASSETS__.set(id, { id, group, name, spec, scale, ref });
    notifyAssetChange();
    return () => {
      window.__ASSETS__.delete(id);
      notifyAssetChange();
    };
  }, [id, group, name, spec, scale]);

  const download = async () => {
    setBusy(true);
    try {
      const blob = await captureNode(ref.current, scale);
      triggerDownload(blob, `${slug(name)}.png`);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="asset-wrap" style={style}>
      <div ref={ref} className="asset-inner">{children}</div>

      <div className="asset-overlay">
        <span className="asset-chip">{spec}</span>
        <button className="asset-dl-btn" onClick={download} disabled={busy}>
          {busy ? "Capturing…" : "↓ PNG"}
        </button>
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────
// CopyText — wraps any text with a copy-to-clipboard affordance
// ───────────────────────────────────────────────────────────────
function CopyButton({ text, label = "Copy", style }) {
  const [done, setDone] = React.useState(false);
  const click = async () => {
    try {
      await navigator.clipboard.writeText(text);
      setDone(true);
      setTimeout(() => setDone(false), 1200);
    } catch (e) {
      console.error("Copy failed", e);
    }
  };
  return (
    <button className={`copy-btn ${done ? "done" : ""}`} onClick={click} style={style} title="Copy to clipboard">
      {done ? "✓ Copied" : label}
    </button>
  );
}

// ───────────────────────────────────────────────────────────────
// captureNode — render a DOM node to a PNG Blob at the right pixel ratio
// ───────────────────────────────────────────────────────────────
async function captureNode(node, scale = 2) {
  if (!node) throw new Error("No node");

  // Wait for fonts so canvas captures real glyphs, not fallback
  if (document.fonts && document.fonts.ready) {
    await document.fonts.ready;
  }

  // Wait for all images inside the node to be decoded
  const imgs = node.querySelectorAll("img");
  await Promise.all(
    Array.from(imgs).map((img) =>
      img.complete && img.naturalWidth
        ? Promise.resolve()
        : new Promise((res) => {
            img.addEventListener("load", res, { once: true });
            img.addEventListener("error", res, { once: true });
          })
    )
  );

  const blob = await htmlToImage.toBlob(node, {
    pixelRatio: scale,
    cacheBust: true,
    backgroundColor: getComputedStyle(node).backgroundColor || "#ffffff",
  });
  return blob;
}

function triggerDownload(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

function slug(s) {
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
}

// ───────────────────────────────────────────────────────────────
// Build the full asset pack as a zip blob.
// Visuals/<group>/<name>.png  +  COPY.txt  +  CHECKLIST.md
// onProgress: ({ stage, current, total }) => void
// ───────────────────────────────────────────────────────────────
async function buildAssetPack({ copyBlocks = [], onProgress } = {}) {
  if (!window.JSZip) throw new Error("JSZip missing");
  const zip = new JSZip();

  const assets = Array.from(window.__ASSETS__.values());
  const total = assets.length;
  let i = 0;
  for (const a of assets) {
    if (onProgress) onProgress({ stage: "capture", current: i, total, name: a.name });
    try {
      const blob = await captureNode(a.ref.current, a.scale);
      const path = `Visuals/${a.group}/${String(++i).padStart(2, "0")}-${slug(a.name)}.png`;
      zip.file(path, blob);
    } catch (e) {
      console.error("Failed to capture", a.id, e);
    }
  }

  // Copy bank text bundle
  let txt = "SCRIBBLY · LITTLE LEAGUE — META ADS COPY BANK\n";
  txt += "===========================================\n\n";
  for (const block of copyBlocks) {
    txt += `# ${block.name}\n\n`;
    for (const l of block.lines) {
      txt += `[${l.role.toUpperCase()}] ${l.text}\n`;
    }
    txt += "\n";
  }
  zip.file("COPY.txt", txt);

  // Checklist
  const checklist = makeChecklist();
  zip.file("README.md", checklist);

  if (onProgress) onProgress({ stage: "zipping", current: total, total });
  const blob = await zip.generateAsync({ type: "blob" });
  return blob;
}

function makeChecklist() {
  return [
    "# Scribbly · Little League — Meta Ads Asset Pack",
    "",
    "Everything you need to ship the campaign live.",
    "",
    "## What's in here",
    "",
    "- `Visuals/Feed/` — 6 feed creatives (1080×1080)",
    "- `Visuals/Story/` — 4 story / reel creatives (1080×1920)",
    "- `Visuals/Carousel/` — 5 carousel cards (1080×1080)",
    "- `Visuals/Storyboard/` — 14 storyboard frames (16:9)",
    "- `COPY.txt` — full copy bank by track",
    "",
    "## Upload order (Meta Ads Manager)",
    "",
    "1. Create campaign · Objective: Sales (or Traffic for early test)",
    "2. Create ad sets per audience (see audience matrix in playbook)",
    "3. Upload visuals from `Visuals/` — match filename to track letter",
    "4. Paste primary / headline / description from `COPY.txt`",
    "5. Set destination URL per audience track",
    "6. Pixel + Conversions API must be live before launch",
    "",
    "## Recommended launch",
    "",
    "- Day 1: A, B, C, F at $50/day each — broad",
    "- Week 2: layer audience splits + S1 + S3 stories",
    "- Week 3: push D (coach bundle) into team-mom interests",
    "- Week 4: scale top 2 creatives, launch E for seasonal",
    "",
    "© Scribbly Books · 2026",
  ].join("\n");
}

Object.assign(window, {
  Asset, CopyButton, useAssetCount,
  captureNode, triggerDownload, buildAssetPack, slug,
});
