// App.jsx — orchestrates state, stage scaling, exports

const { useState, useMemo, useRef, useEffect, useCallback } = React;

const DEFAULT_STATE = {
  sizeKey: "A2",
  customW: 420,
  customH: 594,
  layout: "stamped",
  lyricAlign: "left",
  hero: "wildfire",
  blocks: SAMPLE.blocks,
  advanced: false,
  heroFontId: "anton",
  bodyFontId: "inter",
  heroSize: 100,
  bodySize: 4.5,
  padding: 16,
  colorMode: "preset",
  paletteKey: "belter",
  baseColor: "#c8262a",
  manualPalette: { ...PALETTES.belter },
  pattern: "none",
  patternScale: 30,
  patternDensity: 50,
  patternOriginX: 50,
  patternThickness: 0.5,
  tapeCount: 1,
  smartContrast: false,
  photo: null, // dataURL used by the Player artwork block and Vinyl centre label
};

// Quick presets — apply across many fields at once
const QUICK_PRESETS = {
  belter: {
    layout: "stamped", hero: "belter", paletteKey: "belter", colorMode: "preset",
    heroFontId: "archivo", bodyFontId: "inter", pattern: "none", padding: 18, bodySize: 4.5,
  },
  canter: {
    layout: "bleed", hero: "canter", paletteKey: "canter", colorMode: "preset",
    heroFontId: "archivo", bodyFontId: "inter", pattern: "arcs", patternScale: 60, patternDensity: 50, padding: 16, bodySize: 4,
  },
  neon: {
    layout: "tower", hero: "alive", paletteKey: "neon", colorMode: "preset",
    heroFontId: "anton", bodyFontId: "inter", pattern: "grid", patternScale: 8, patternDensity: 30, padding: 16, bodySize: 4,
  },
  repeat: {
    layout: "repeat", hero: "mantra", paletteKey: "bw", colorMode: "preset",
    heroFontId: "alfaslab", bodyFontId: "robotoslab", pattern: "none", padding: 20, bodySize: 5,
  },
  split: {
    layout: "split", hero: "ember", paletteKey: "earth", colorMode: "preset",
    heroFontId: "bigshoulders", bodyFontId: "inter", pattern: "none", padding: 16, bodySize: 4.5,
    smartContrast: true, // the seam trick is the whole point of this preset
  },
  tape: {
    layout: "tape", hero: "echoes", paletteKey: "paper", colorMode: "preset",
    heroFontId: "robotoslab", bodyFontId: "robotoslab", pattern: "dots", patternScale: 10, patternDensity: 35, padding: 18, bodySize: 4.5,
  },
};

function useStageScale(stageRef, w, h, pad = 60) {
  const [scale, setScale] = useState(1);
  useEffect(() => {
    if (!stageRef.current) return;
    const el = stageRef.current;
    const update = () => {
      const rect = el.getBoundingClientRect();
      // mm -> px conversion: 1mm ≈ 3.7795px
      const wPx = w * 3.7795275591;
      const hPx = h * 3.7795275591;
      const aw = rect.width - pad * 2;
      const ah = rect.height - pad * 2;
      const s = Math.min(aw / wPx, ah / hPx, 1.2);
      setScale(Math.max(0.05, s));
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    window.addEventListener("resize", update);
    return () => { ro.disconnect(); window.removeEventListener("resize", update); };
  }, [w, h, pad]);
  return scale;
}

function App() {
  const [state, setState] = useState(() => {
    const saved = (typeof window !== "undefined" && window.loadAutosave && window.loadAutosave());
    return saved ? { ...DEFAULT_STATE, ...saved } : DEFAULT_STATE;
  });
  const set = useCallback((patch) => setState((s) => ({ ...s, ...patch })), []);

  // Track which saved design (if any) is currently loaded
  const [currentDesign, setCurrentDesign] = useState({ id: null, name: null });
  const [orderOpen, setOrderOpen] = useState(false);

  // Autosave state to localStorage on every change
  useEffect(() => {
    if (window.writeAutosave) window.writeAutosave(state);
  }, [state]);

  // Resolve dimensions
  const { width, height } = useMemo(() => {
    if (state.sizeKey === "Custom") return { width: state.customW, height: state.customH };
    const sz = SIZES[state.sizeKey];
    return { width: sz.w, height: sz.h };
  }, [state.sizeKey, state.customW, state.customH]);

  // Resolve palette
  const palette = useMemo(() => {
    if (state.colorMode === "preset") return PALETTES[state.paletteKey] || PALETTES.belter;
    if (state.colorMode === "derive") return derivePalette(state.baseColor);
    return state.manualPalette;
  }, [state.colorMode, state.paletteKey, state.baseColor, state.manualPalette]);

  const heroFont = (FONTS.find((f) => f.id === state.heroFontId) || FONTS[0]).css;
  const bodyFont = (BODY_FONTS.find((f) => f.id === state.bodyFontId) || BODY_FONTS[0]).css;

  const config = {
    width, height, palette, heroFont, bodyFont,
    hero: state.hero,
    blocks: state.blocks,
    layout: state.layout,
    pattern: state.pattern,
    patternScale: state.patternScale,
    patternDensity: state.patternDensity,
    patternOriginX: state.patternOriginX,
    patternThickness: state.patternThickness,
    tapeCount: state.tapeCount,
    heroSize: state.heroSize,
    bodySize: state.bodySize,
    padding: state.padding,
    smartContrast: state.smartContrast,
    lyricAlign: state.lyricAlign,
    photo: state.photo,
  };

  const applyPreset = (key) => {
    const p = QUICK_PRESETS[key];
    if (p) set(p);
  };

  // Stage scaling
  const stageRef = useRef(null);
  const posterRef = useRef(null);
  const scaleWrapRef = useRef(null);
  const scale = useStageScale(stageRef, width, height, 60);

  // --- Exports --------------------------------------------------------------

  // Fetch & inline Google Fonts CSS ourselves (the library's own helper fails
  // on CORS for stylesheets loaded via <link>): grab the CSS text via fetch,
  // then base64-inline every woff2 it references so the export's cloned DOM
  // has the typefaces baked in.
  const buildFontEmbedCSS = async () => {
    try {
      const fontLink = [...document.querySelectorAll("link[rel=stylesheet]")]
        .find((l) => l.href.includes("fonts.googleapis.com"));
      if (!fontLink) return "";
      const cssText = await fetch(fontLink.href, {
        headers: { "User-Agent": navigator.userAgent },
      }).then((r) => r.text());
      const urls = [...cssText.matchAll(/url\((https:\/\/[^)]+)\)/g)].map((m) => m[1]);
      const unique = [...new Set(urls)];
      const fetched = await Promise.all(
        unique.map(async (u) => {
          try {
            const blob = await fetch(u).then((r) => r.blob());
            const dataUrl = await new Promise((res) => {
              const fr = new FileReader();
              fr.onload = () => res(fr.result);
              fr.readAsDataURL(blob);
            });
            return [u, dataUrl];
          } catch {
            return [u, null];
          }
        })
      );
      let inlined = cssText;
      for (const [u, dataUrl] of fetched) {
        if (dataUrl) inlined = inlined.split(u).join(dataUrl);
      }
      return inlined;
    } catch (err) {
      console.warn("[export] could not embed fonts:", err);
      return "";
    }
  };

  // Snapshot every poster on the stage as PNGs.
  // pixelRatio 2 ≈ 192 DPI screen export; PRINT_PIXEL_RATIO ≈ 300 DPI print.
  // Returns [{ name, dataUrl }] (dataUrl only kept when collect), or null on failure.
  const snapshotPosters = async (pixelRatio, nameFor, { download = true, collect = false } = {}) => {
    if (!scaleWrapRef.current || !window.htmlToImage) return null;
    const wrap = scaleWrapRef.current;
    const nodes = [...wrap.querySelectorAll('[data-poster="true"]')];
    if (!nodes.length) return null;
    const prevTransform = wrap.style.transform;
    const prevTransition = wrap.style.transition;
    try {
      if (document.fonts && document.fonts.ready) await document.fonts.ready;
      // Remove the preview scale so posters lay out at full size
      wrap.style.transition = "none";
      wrap.style.transform = "none";
      void wrap.offsetWidth;
      await new Promise((r) => setTimeout(r, 120));
      const fontEmbedCSS = await buildFontEmbedCSS();
      const pxW = width * 3.7795275591;
      const pxH = height * 3.7795275591;
      const results = [];
      for (let i = 0; i < nodes.length; i++) {
        const dataUrl = await window.htmlToImage.toPng(nodes[i], {
          pixelRatio,
          width: pxW,
          height: pxH,
          cacheBust: true,
          fontEmbedCSS,
        });
        const name = nameFor(i, nodes.length);
        if (download) {
          const a = document.createElement("a");
          a.href = dataUrl;
          a.download = name;
          a.click();
          // Give the browser a beat between multi-file downloads
          if (nodes.length > 1) await new Promise((r) => setTimeout(r, 350));
        }
        results.push(collect ? { name, dataUrl } : { name });
      }
      return results;
    } catch (e) {
      console.error("[export] failed:", e);
      alert("Export failed: " + e.message);
      return null;
    } finally {
      wrap.style.transform = prevTransform || "";
      wrap.style.transition = prevTransition || "";
    }
  };

  const exportPNG = async () =>
    !!(await snapshotPosters(2, (i, n) =>
      `poster-${state.hero || "untitled"}-${width}x${height}mm${n > 1 ? `-${i + 1}of${n}` : ""}.png`));

  // Order flow: render 300 DPI print files named by design code, download them
  // as the customer's backup, then push design JSON + PNG to the print queue
  // (Cloudflare Pages Functions + R2). Uploads are best-effort — the downloads
  // remain the fallback if the API is unreachable.
  const PRINT_PIXEL_RATIO = 300 / 96; // css-px → 300 DPI at mm-true size
  const PNG_UPLOAD_LIMIT = 60 * 1024 * 1024;
  const orderPrintFiles = async (code, onStatus = () => {}) => {
    onStatus("render");
    const shots = await snapshotPosters(
      PRINT_PIXEL_RATIO,
      (i, n) => `${code}-${width}x${height}mm${n > 1 ? `-${i + 1}of${n}` : ""}.png`,
      { download: true, collect: true }
    );
    if (!shots || !shots.length) return { ok: false };

    const payload = {
      type: "lyrics-poster-design",
      v: 1,
      name: code,
      savedAt: new Date().toISOString(),
      state,
    };
    const jsonText = JSON.stringify(payload, null, 2);
    const blob = new Blob([jsonText], { type: "application/json" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = `${code}.json`;
    a.click();
    setTimeout(() => URL.revokeObjectURL(a.href), 5000);

    const apiBase = (window.ORDER_CONFIG && window.ORDER_CONFIG.apiBase) || "";
    if (!apiBase) return { ok: true, saved: false, savedPng: false };

    let saved = false;
    let savedPng = false;
    try {
      onStatus("save-design");
      const r = await fetch(`${apiBase}/designs/${code}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: jsonText,
      });
      saved = r.ok;
    } catch (e) {
      console.warn("[order] design upload failed:", e);
    }
    try {
      onStatus("save-print");
      const pngBlob = await (await fetch(shots[0].dataUrl)).blob();
      if (pngBlob.size <= PNG_UPLOAD_LIMIT) {
        const r = await fetch(`${apiBase}/designs/${code}/png`, {
          method: "PUT",
          headers: { "Content-Type": "image/png" },
          body: pngBlob,
        });
        savedPng = r.ok;
      }
    } catch (e) {
      console.warn("[order] print upload failed:", e);
    }
    return { ok: true, saved, savedPng };
  };

  const exportPDF = () => {
    // Use the print stylesheet — write the poster(s) into print-root at exact
    // mm size, set @page size, then call window.print(). In bundle mode each
    // poster gets its own page.
    const printRoot = document.getElementById("print-root");
    if (!printRoot || !scaleWrapRef.current) return;
    const nodes = [...scaleWrapRef.current.querySelectorAll('[data-poster="true"]')];
    if (!nodes.length) return;

    const styleId = "print-page-rule";
    let styleEl = document.getElementById(styleId);
    if (!styleEl) {
      styleEl = document.createElement("style");
      styleEl.id = styleId;
      document.head.appendChild(styleEl);
    }
    styleEl.textContent = `
      @page { size: ${width}mm ${height}mm; margin: 0; }
      @media print {
        .print-root [data-poster="true"] { box-shadow: none !important; }
        .print-root .print-page { page-break-after: always; }
        .print-root .print-page:last-child { page-break-after: auto; }
      }
    `;

    printRoot.innerHTML = "";
    nodes.forEach((node) => {
      const page = document.createElement("div");
      page.className = "print-page";
      const clone = node.cloneNode(true);
      clone.style.transform = "none";
      clone.style.boxShadow = "none";
      page.appendChild(clone);
      printRoot.appendChild(page);
    });

    setTimeout(() => {
      window.print();
      // Don't immediately clear — let print dialog finish reading the DOM
      setTimeout(() => { printRoot.innerHTML = ""; }, 1000);
    }, 100);
  };

  // --- UI -------------------------------------------------------------------

  return (
    <div className="app" style={{ display: "flex", height: "100%", background: "#ececec" }}>
      {/* Stage */}
      <div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
        {/* Toolbar */}
        <div className="stage-toolbar" style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          padding: "12px 18px",
          background: "white",
          borderBottom: "1px solid #e5e5e3",
          gap: 12,
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <div style={{
              width: 26, height: 26, borderRadius: 4,
              background: "#1a1a1a",
              display: "flex", alignItems: "center", justifyContent: "center",
              color: "white", fontWeight: 800, fontFamily: '"Archivo Black", sans-serif', fontSize: 13,
            }}>
              L
            </div>
            <div>
              <div style={{ fontSize: 13, fontWeight: 600 }}>
                {currentDesign.name || "Lyrics Poster Studio"}
              </div>
              <div style={{ fontSize: 10, color: "#888", fontFamily: '"JetBrains Mono", monospace' }}>
                {width} × {height} mm · {state.sizeKey} · {Math.round(scale * 100)}%
              </div>
            </div>
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <DesignsMenu
              currentState={state}
              currentDesignId={currentDesign.id}
              currentDesignName={currentDesign.name}
              onLoad={(savedState) => setState({ ...DEFAULT_STATE, ...savedState })}
              onCurrentDesignChange={(d) => setCurrentDesign(d)}
            />
            <button onClick={exportPNG} style={toolbarBtnStyle}>Download PNG</button>
            <button onClick={exportPDF} style={{ ...toolbarBtnStyle, background: "#1a1a1a", color: "white", borderColor: "#1a1a1a" }}>Print / Save PDF</button>
            <button
              onClick={() => setOrderOpen(true)}
              style={{ ...toolbarBtnStyle, background: "#c8262a", color: "white", borderColor: "#c8262a", fontWeight: 600 }}
            >
              Order a print
            </button>
          </div>
        </div>

        {/* Canvas */}
        <div
          ref={stageRef}
          style={{
            flex: 1,
            position: "relative",
            overflow: "hidden",
            backgroundImage: "radial-gradient(circle, #d8d8d4 1px, transparent 1px)",
            backgroundSize: "20px 20px",
            backgroundColor: "#ececec",
          }}
        >
          <div style={{
            position: "absolute",
            inset: 0,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}>
            <div
              ref={scaleWrapRef}
              style={{
                transform: `scale(${scale})`,
                transformOrigin: "center center",
                transition: "transform 0.18s ease",
              }}
            >
              <Poster config={config} posterRef={posterRef} />
            </div>
          </div>
        </div>
      </div>

      <OrderModal
        open={orderOpen}
        onClose={() => setOrderOpen(false)}
        width={width}
        height={height}
        sizeKey={state.sizeKey}
        layout={state.layout}
        photo={state.photo}
        onOrder={orderPrintFiles}
      />

      {/* Side panel */}
      <div className="panel" style={{
        width: 360,
        flex: "0 0 360px",
        background: "white",
        borderLeft: "1px solid #e5e5e3",
        height: "100%",
        overflow: "hidden",
        display: "flex",
        flexDirection: "column",
      }}>
        <Controls state={state} set={set} applyPreset={applyPreset} />
      </div>
    </div>
  );
}

const toolbarBtnStyle = {
  padding: "7px 14px",
  fontSize: 12,
  fontFamily: "inherit",
  fontWeight: 500,
  border: "1px solid #d8d8d6",
  background: "white",
  color: "#1a1a1a",
  borderRadius: 4,
  cursor: "pointer",
  whiteSpace: "nowrap",
};

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
