// MostlyQR — shared brand marks, icon set, and the styled QR engine.
// Built on the Mostly Tiny design system. Exposes everything on window.MQR.
const React = window.React;

/* ─────────────────────────────────────────────────────────────
   Icon set — Lucide geometry, stroke 2, round caps (DS standard)
   ───────────────────────────────────────────────────────────── */
const ICONS = {
  refresh: "M3 12a9 9 0 0 1 15-6.7L21 8 M21 3v5h-5 M21 12a9 9 0 0 1-15 6.7L3 16 M3 21v-5h5",
  infinity: "M18.18 8.04C16.31 6.2 13.2 6.6 12 9c-1.2-2.4-4.31-2.8-6.18-.96a3.6 3.6 0 0 0 0 5.16C7.69 15.2 10.8 15 12 12.6c1.2 2.4 4.31 2.6 6.18.6a3.6 3.6 0 0 0 0-5.16Z",
  chart: "M3 3v18h18 M7 14l4-4 3 3 5-6",
  layers: "M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 18l9 5 9-5",
  palette: "M12 2a10 10 0 1 0 0 20 2 2 0 0 0 2-2 2 2 0 0 1 2-2h2a4 4 0 0 0 4-4 10 10 0 0 0-12-10z M8 9h.01 M16 9h.01 M9 15h.01",
  users: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2 M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8 M22 21v-2a4 4 0 0 0-3-3.9 M16 3.1a4 4 0 0 1 0 7.7",
  link: "M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1 M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1",
  arrow: "M5 12h14M13 5l7 7-7 7",
  check: "M20 6 9 17l-5-5",
  x: "M18 6 6 18 M6 6l12 12",
  lock: "M5 11h14v10H5z M8 11V7a4 4 0 0 1 8 0v4",
  shield: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z M9 12l2 2 4-4",
  zap: "M13 2 3 14h9l-1 8 10-12h-9l1-8z",
  globe: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20 M2 12h20 M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20",
  smartphone: "M5 2h14v20H5z M11 18h2",
  wifi: "M5 12.5a10 10 0 0 1 14 0 M8.5 16a5 5 0 0 1 7 0 M12 19.5h.01",
  user: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2 M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8",
  file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M9 13h6 M9 17h6",
  utensils: "M3 2v7a3 3 0 0 0 6 0V2 M6 2v20 M17 2c-2 0-3 2-3 5s1 5 3 5V2z M17 12v10",
  download: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4 M7 10l5 5 5-5 M12 15V3",
  image: "M3 3h18v18H3z M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3 M21 15l-5-5L5 21",
  sliders: "M4 21v-7 M4 10V3 M12 21v-9 M12 8V3 M20 21v-5 M20 12V3 M1 14h6 M9 8h6 M17 16h6",
  scan: "M3 7V5a2 2 0 0 1 2-2h2 M17 3h2a2 2 0 0 1 2 2v2 M21 17v2a2 2 0 0 1-2 2h-2 M7 21H5a2 2 0 0 1-2-2v-2 M7 12h10",
  plus: "M12 5v14M5 12h14",
  qr: "M3 3h7v7H3z M14 3h7v7h-7z M3 14h7v7H3z M14 14h3v3h-3z M20 14v3 M17 20h4 M21 17v4",
  star: "M12 2l3 7 7 .5-5.5 4.5L18 21l-6-4-6 4 1.5-7L2 9.5 9 9z",
  filter: "M22 3H2l8 9.5V19l4 2v-8.5L22 3z",
  share: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8 M16 6l-4-4-4 4 M12 2v13",
  external: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6 M15 3h6v6 M10 14 21 3",
  copy: "M9 9h11v11H9z M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1",
  chevDown: "M6 9l6 6 6-6",
  grid: "M3 3h7v7H3z M14 3h7v7h-7z M14 14h7v7h-7z M3 14h7v7H3z",
  sun: "M12 2v2 M12 20v2 M4.9 4.9l1.4 1.4 M17.7 17.7l1.4 1.4 M2 12h2 M20 12h2 M6.3 17.7l-1.4 1.4 M19.1 4.9l-1.4 1.4 M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z",
  moon: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z",
};

function Icon({ name, d, size = 20, sw = 1.8, style, className }) {
  const path = d || ICONS[name] || "";
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" style={style} className={className}>
      {path.split(" M").map((p, i) => <path key={i} d={(i ? "M" : "") + p} />)}
    </svg>
  );
}

/* ─────────────────────────────────────────────────────────────
   Logo language — MostlyQR mark (QR-finder corner motif + tiny dot)
   Distinct from HyperQR: a single bold finder anchored top-left,
   a descending module run, and the signature tiny dot lower-right.
   ───────────────────────────────────────────────────────────── */
function MQMark({ s = 28, animate = false, tile = "var(--accent)", ink = "#fff" }) {
  return (
    <svg width={s} height={s} viewBox="0 0 100 100" aria-hidden="true">
      <rect x="3" y="3" width="94" height="94" rx="27" fill={tile} />
      {/* one bold QR-finder — concentric rounded square, top-left.
         Inset 17 from the tile edge; the tiny dot mirrors it bottom-right. */}
      <rect x="20" y="20" width="36" height="36" rx="10" fill="none" stroke={ink} strokeWidth="7" />
      <rect x="31" y="31" width="14" height="14" rx="4" fill={ink} />
      {/* signature tiny dot — mirrored inset, lower-right */}
      <circle cx="75" cy="75" r="5" fill={ink} className={animate ? "mq-logo-dot" : undefined} />
    </svg>
  );
}

// Umbrella mark — "mostly" tile carrying the "tiny" dot
function MTMark({ s = 18 }) {
  return (
    <svg width={s} height={s} viewBox="0 0 100 100" aria-hidden="true">
      <rect x="3" y="3" width="94" height="94" rx="27" fill="var(--fg)" />
      <circle cx="74" cy="74" r="5" fill="var(--bg-elev)" />
    </svg>
  );
}

// Wordmark with three accent treatments
function Wordmark({ size = 19, variant = "qr", weight = 600 }) {
  const accent = "var(--accent)";
  const base = { fontFamily: "var(--font-display)", fontWeight: weight, fontSize: size, letterSpacing: "-0.04em", lineHeight: 1 };
  if (variant === "mostly")
    return <span style={base}><span style={{ color: accent }}>Mostly</span>QR</span>;
  if (variant === "ink")
    return <span style={base}>MostlyQR</span>;
  // default: accent on QR
  return <span style={base}>Mostly<span style={{ color: accent }}>QR</span></span>;
}

/* ─────────────────────────────────────────────────────────────
   QR engine — real, scannable matrix via qrcode-generator,
   rendered as styled SVG (body / eye-frame / eye-ball shapes,
   colours, logo, quiet zone). Deterministic fallback texture so
   it never renders blank if the library hasn't loaded.
   ───────────────────────────────────────────────────────────── */
const FINDER = 7;
function buildMatrix(data, ecc = "M", fixedType = 0) {
  if (typeof window.qrcode === "function") {
    try {
      const qr = window.qrcode(fixedType, ecc);
      qr.addData(data || " ");
      qr.make();
      const n = qr.getModuleCount(), m = [];
      for (let r = 0; r < n; r++) { const row = []; for (let c = 0; c < n; c++) row.push(qr.isDark(r, c)); m.push(row); }
      return m;
    } catch (e) { /* fall through */ }
  }
  const n = fixedType ? 29 : 33, m = [];
  let h = 2166136261;
  for (let i = 0; i < (data || "").length; i++) { h ^= data.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; }
  for (let r = 0; r < n; r++) { const row = []; for (let c = 0; c < n; c++) { let x = (h ^ Math.imul(r + 1, 73856093) ^ Math.imul(c + 1, 19349663)) >>> 0; x ^= x >>> 13; row.push((x % 100) / 100 > 0.5); } m.push(row); }
  return m;
}
const inFinder = (r, c, n) =>
  (r < FINDER && c < FINDER) || (r < FINDER && c >= n - FINDER) || (r >= n - FINDER && c < FINDER);

// One body module, shape-aware
function bodyModule(r, c, cell, shape, color, key, animated, n) {
  const x = c * cell, y = r * cell;
  const common = { fill: color, key };
  if (shape === "dots") {
    return <circle cx={x + cell / 2} cy={y + cell / 2} r={cell * 0.42} {...common} className={animated ? "mq-mod" : undefined} />;
  }
  if (shape === "rounded") {
    // Tighter than before (0.9 vs 0.84) so adjacent dark modules nearly touch —
    // big inter-module gaps hurt scanning.
    return <rect x={x + cell * 0.05} y={y + cell * 0.05} width={cell * 0.9} height={cell * 0.9} rx={cell * 0.3} {...common} className={animated ? "mq-mod" : undefined} />;
  }
  if (shape === "classy") {
    return <rect x={x + cell * 0.05} y={y + cell * 0.05} width={cell * 0.9} height={cell * 0.9} rx={cell * 0.5} {...common} className={animated ? "mq-mod" : undefined} />;
  }
  // square (with a hair of rounding so it never looks harsh)
  return <rect x={x} y={y} width={cell * 0.98} height={cell * 0.98} rx={cell * 0.12} {...common} className={animated ? "mq-mod" : undefined} />;
}

// One finder eye. CRITICAL for scanning: the finder MUST keep the 1:1:3:1:1
// proportion — a 7×7 dark square, a 5×5 light gap (1 module ring), and a 3×3 dark
// centre. We draw exactly that as three concentric squares; `shape` only rounds
// the corners (style), it never changes the sizes/positions. `bg` fills the gap so
// it reads as light even on a coloured background.
function finderEye(r, c, cell, frameShape, ballShape, frameColor, ballColor, key, bg) {
  const x = c * cell, y = r * cell, sz = FINDER * cell; // 7 modules
  // Corner radii per style — bounded so the square ratio a scanner needs survives.
  const rOuter = frameShape === "circle" ? sz / 2 : frameShape === "rounded" ? cell * 1.5 : cell * 0.4;
  const rGap = frameShape === "circle" ? (5 * cell) / 2 : frameShape === "rounded" ? cell * 1.1 : cell * 0.3;
  const rBall = ballShape === "circle" ? (3 * cell) / 2 : ballShape === "rounded" ? cell * 1.0 : cell * 0.3;
  return (
    <g key={key}>
      <rect x={x} y={y} width={sz} height={sz} rx={rOuter} fill={frameColor} />
      <rect x={x + cell} y={y + cell} width={5 * cell} height={5 * cell} rx={rGap} fill={bg} />
      <rect x={x + 2 * cell} y={y + 2 * cell} width={3 * cell} height={3 * cell} rx={rBall} fill={ballColor} />
    </g>
  );
}

// Core renderer → returns an <svg> element.
function QR({
  data = "mqr.sh/demo",
  ecc = "M",
  fixedType = 0,
  size = 240,
  fg = "#1d1d1f",
  bg = "#ffffff",
  eyeColor = null,           // null → inherit fg
  ballColor = null,
  bodyShape = "rounded",
  frameShape = "rounded",
  ballShape = "rounded",
  logo = null,               // data URL
  logoSize = 0.28,           // fraction of the matrix the cleared logo zone spans
  logoStyle = "hug",         // "hug" | "card" | "circle" — how the logo sits in the code
  quiet = 3,                 // quiet-zone in modules
  frame = null,              // null | "scanme" | "border"
  frameColor = null,         // null → inherit eyeColor/fg
  frameLabel = "SCAN ME",
  animated = false,
  innerRef = null,
  className = "",
  style = {},
}) {
  const m = React.useMemo(() => buildMatrix(data, ecc, fixedType), [data, ecc, fixedType]);
  const n = m.length;
  const cell = 10;
  const eC = eyeColor || fg;
  const bC = ballColor || fg;
  const grid = n * cell;
  const pad = quiet * cell;
  const total = grid + pad * 2;
  const finders = [[0, 0], [0, n - FINDER], [n - FINDER, 0]];
  const mods = [];
  // Clear the centre for the logo — a square (hug/card) or a disc (circle) of
  // modules, so the chosen shape reads cleanly instead of leaving stray modules.
  const logoModules = logo ? Math.round(n * logoSize) : 0;
  const logoLo = Math.floor((n - logoModules) / 2), logoHi = logoLo + logoModules;
  const ctrM = (n - 1) / 2;
  const inLogo = (r, c) =>
    logoModules &&
    (logoStyle === "circle"
      ? Math.hypot(r - ctrM, c - ctrM) <= logoModules / 2 + 0.001
      : r >= logoLo && r < logoHi && c >= logoLo && c < logoHi);
  for (let r = 0; r < n; r++) {
    for (let c = 0; c < n; c++) {
      if (inFinder(r, c, n)) continue;
      if (logo && inLogo(r, c)) continue;
      if (!m[r][c]) continue;
      mods.push(bodyModule(r, c, cell, bodyShape, fg, r + "-" + c, animated, n));
    }
  }
  const logoPx = logoModules * cell;
  const logoXY = (logoLo) * cell;
  const fC = frameColor || eC;

  // Frame geometry — outer margin + (for scanme) a label strip below.
  const fm = frame ? cell * 1.6 : 0;
  const lh = frame === "scanme" ? cell * 5.2 : 0;
  const outW = total + fm * 2;
  const outH = total + fm * 2 + lh;
  const qrOX = fm, qrOY = fm;

  const qrContent = (
    <g transform={`translate(${qrOX} ${qrOY})`}>
      <rect x="0" y="0" width={total} height={total} fill={bg} />
      <g transform={`translate(${pad} ${pad})`}>
        {mods}
        {finders.map(([r, c], i) => finderEye(r, c, cell, frameShape, ballShape, eC, bC, "f" + i, bg))}
        {logo && (() => {
          // User-chosen integration style. All clip the logo to the cleared shape so
          // it reads as designed-in, not pasted on.
          const clipId = "mqlogo-" + Math.random().toString(36).slice(2, 8);
          // The logo sits INSIDE the cleared zone with a real white ring, so the
          // surrounding modules never crowd ("cut through") it. The white shape only
          // covers the cleared zone (no extra erasing); breathing room comes from
          // insetting the logo within it.
          if (logoStyle === "circle") {
            const c0 = grid / 2, zoneR = logoPx / 2, imgR = zoneR - cell * 0.85;
            return (
              <g>
                <circle cx={c0} cy={c0} r={zoneR + cell * 0.2} fill={bg} />
                <clipPath id={clipId}><circle cx={c0} cy={c0} r={imgR} /></clipPath>
                <image href={logo} x={c0 - imgR} y={c0 - imgR} width={imgR * 2} height={imgR * 2} clipPath={`url(#${clipId})`} preserveAspectRatio="xMidYMid slice" />
              </g>
            );
          }
          // "hug" = ~0.9-module white ring; "card" = a wider ring (smaller logo).
          const inset = (logoStyle === "card" ? cell * 1.7 : cell * 0.9);
          const lx = logoXY + inset, lsz = logoPx - inset * 2;
          return (
            <g>
              <rect x={logoXY - cell * 0.2} y={logoXY - cell * 0.2} width={logoPx + cell * 0.4} height={logoPx + cell * 0.4} rx={cell * 1.6} fill={bg} />
              <clipPath id={clipId}><rect x={lx} y={lx} width={lsz} height={lsz} rx={cell * 1.1} /></clipPath>
              <image href={logo} x={lx} y={lx} width={lsz} height={lsz} clipPath={`url(#${clipId})`} preserveAspectRatio="xMidYMid meet" />
            </g>
          );
        })()}
      </g>
    </g>
  );

  return (
    <svg ref={innerRef} className={className} style={style} width={size} height={size * (outH / outW)}
      viewBox={`0 0 ${outW} ${outH}`} role="img" aria-label="QR code preview"
      xmlns="http://www.w3.org/2000/svg">
      {frame && (
        <rect x={cell * 0.5} y={cell * 0.5} width={outW - cell} height={outH - cell}
          rx={cell * 2.4} fill={frame === "scanme" ? fC : bg}
          stroke={frame === "border" ? fC : "none"} strokeWidth={frame === "border" ? cell * 0.7 : 0} />
      )}
      {frame === "scanme" && (
        <rect x={fm * 0.6} y={fm * 0.6} width={total + fm * 0.8} height={total + fm * 0.8}
          rx={cell * 1.8} fill={bg} />
      )}
      {qrContent}
      {frame === "scanme" && (
        <text x={outW / 2} y={total + fm + lh * 0.62} textAnchor="middle"
          fontFamily="Inter, system-ui, sans-serif" fontWeight="700"
          fontSize={cell * 2.4} letterSpacing={cell * 0.12} fill={bg}>{frameLabel}</text>
      )}
    </svg>
  );
}

/* ── Export helpers ───────────────────────────────────────── */
function svgString(svgEl) {
  const clone = svgEl.cloneNode(true);
  clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
  return '<?xml version="1.0" encoding="UTF-8"?>\n' + new XMLSerializer().serializeToString(clone);
}
function download(filename, blob) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename; document.body.appendChild(a); a.click();
  setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100);
}
function exportSVG(svgEl, name = "mostlyqr") {
  download(name + ".svg", new Blob([svgString(svgEl)], { type: "image/svg+xml" }));
}
function rasterize(svgEl, scale = 4) {
  return new Promise((resolve, reject) => {
    const str = svgString(svgEl);
    const vb = svgEl.viewBox.baseVal;
    const w = (vb && vb.width) || 480, h = (vb && vb.height) || 480;
    const img = new Image();
    const url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(str);
    img.onload = () => {
      const canvas = document.createElement("canvas");
      canvas.width = w * scale; canvas.height = h * scale;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      resolve(canvas);
    };
    img.onerror = reject;
    img.src = url;
  });
}
async function exportPNG(svgEl, name = "mostlyqr") {
  const canvas = await rasterize(svgEl, 4);
  canvas.toBlob((blob) => download(name + ".png", blob), "image/png");
}
// Minimal single-image PDF (embeds a JPEG via DCTDecode — no external lib).
async function exportPDF(svgEl, name = "mostlyqr") {
  const canvas = await rasterize(svgEl, 4);
  const jpegDataUrl = canvas.toDataURL("image/jpeg", 0.95);
  const b64 = jpegDataUrl.split(",")[1];
  const bin = atob(b64);
  const jpeg = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) jpeg[i] = bin.charCodeAt(i);
  const iw = canvas.width, ih = canvas.height;
  // Lay the image on an A6-ish square page at 72dpi
  const pageW = 420, pageH = 420;
  const draw = Math.min(pageW, pageH) - 80;
  const ox = (pageW - draw) / 2, oy = (pageH - draw) / 2;
  const enc = new TextEncoder();
  const parts = [];
  const objs = [];
  let pos = 0;
  const push = (data) => {
    const bytes = typeof data === "string" ? enc.encode(data) : data;
    parts.push(bytes); pos += bytes.length;
  };
  const header = "%PDF-1.4\n";
  push(header);
  const addObj = (body, stream) => {
    objs.push(pos);
    push(`${objs.length} 0 obj\n`);
    push(body);
    if (stream) { push("\nstream\n"); push(stream); push("\nendstream"); }
    push("\nendobj\n");
    return objs.length;
  };
  addObj("<< /Type /Catalog /Pages 2 0 R >>");
  addObj("<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
  addObj(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Resources << /XObject << /Im0 5 0 R >> >> /Contents 4 0 R >>`);
  const content = `q\n${draw} 0 0 ${draw} ${ox} ${oy} cm\n/Im0 Do\nQ\n`;
  addObj(`<< /Length ${enc.encode(content).length} >>`, content);
  addObj(`<< /Type /XObject /Subtype /Image /Width ${iw} /Height ${ih} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpeg.length} >>`, jpeg);
  const xrefPos = pos;
  let xref = `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n`;
  objs.forEach((o) => { xref += String(o).padStart(10, "0") + " 00000 n \n"; });
  push(xref);
  push(`trailer\n<< /Size ${objs.length + 1} /Root 1 0 R >>\nstartxref\n${xrefPos}\n%%EOF`);
  let len = 0; parts.forEach((p) => (len += p.length));
  const out = new Uint8Array(len);
  let off = 0; parts.forEach((p) => { out.set(p, off); off += p.length; });
  download(name + ".pdf", new Blob([out], { type: "application/pdf" }));
}

// Scheme-less form of a short link, for what the QR ENCODES. Dropping "https://"
// shrinks the matrix a whole version (e.g. mqr.sh/<code> ≤14 chars → QR v1, 21×21
// vs v2 25×25). Modern phone cameras prepend the scheme on scan. Only ever applied
// to our own short links — never to vCard/Wi-Fi/free-text payloads.
function bareUrl(u) { return String(u == null ? "" : u).replace(/^https?:\/\//, ""); }

window.MQR = {
  React, Icon, ICONS, MQMark, MTMark, Wordmark,
  QR, buildMatrix, FINDER, inFinder, bareUrl,
  exportSVG, exportPNG, exportPDF, svgString, download,
};
