/* PassStudio — top-level Spoonity admin layout: top bar + builder form + live
   preview, with a Tweaks panel for studio-level presentation controls. */
const PS = window.SpoonityWalletPassDesignSystem_de20b8;
const PS_I18N = window.SpoonityI18n;

const PASS_PRESETS = {
  loyalty: {
    passStyle: "storeCard", backgroundColor: "#7B2FF7", logoSrc: null, iconSrc: null,
    company: "Breakfast Bonanza", name: "Breakfast Bonanza Rewards",
    // `key` is the stable backend field identity; `label` is the default title.
    // A custom title is stored as `labelText` and sent to the backend keyed by `key`.
    headerFields: [{ key: "points", label: "POINTS", value: "240" }],
    primaryFields: [],
    secondaryFields: [{ key: "name", label: "CUSTOMER", value: "Dana Lee" }, { key: "tier", label: "TIER", value: "Gold" }],
    auxiliaryFields: [],
    barcode: { format: "qr", message: "SPN-4821-0093", altText: "4821 0093 5567" },
  },
  coupon: {
    passStyle: "coupon", backgroundColor: "#ff7e3d", logoSrc: null, iconSrc: null,
    company: "Breakfast Bonanza", name: "Free Pastry Coupon",
    headerFields: [], primaryFields: [], secondaryFields: [], auxiliaryFields: [],
    barcode: { format: "aztec", message: "BREW100", altText: "BREW100" },
  },
  boarding: {
    passStyle: "boardingPass", backgroundColor: "#2557cb", logoSrc: null, iconSrc: null,
    company: "Spoon Air", name: "Spoon Air Boarding Pass",
    headerFields: [], primaryFields: [], secondaryFields: [], auxiliaryFields: [],
    barcode: { format: "pdf417", message: "BOARDING-SP218-14A" },
  },
  cinema: {
    passStyle: "eventTicket", backgroundColor: "#111827", logoSrc: null, iconSrc: null,
    company: "Cinema Club", name: "Cinema Club Ticket",
    headerFields: [], primaryFields: [], secondaryFields: [], auxiliaryFields: [],
    barcode: { format: "qr", message: "CINE-DUNE3-H22", altText: "H22" },
  },
};

const GOOGLE_DEFAULT = {
  backgroundColor: "#7B2FF7", logoSrc: null, iconSrc: null,
  programName: "Breakfast Bonanza", cardTitle: "Breakfast Bonanza Rewards", subtitle: "",
  fields: [{ label: "POINTS", value: "240" }, { label: "TIER", value: "Gold" }, { label: "CUSTOMER", value: "Dana Lee" }],
  heroSrc: null, barcode: { format: "qr", message: "SPN-4821-0093", altText: "4821 0093 5567" },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "preset": "loyalty",
  "backdrop": "wallet",
  "device": true,
  "guides": true,
  "previewSide": "right",
  "accent": "#ff7e3d"
}/*EDITMODE-END*/;

/* Live save indicator — communicates state (when it last saved), not an action.
   The studio autosaves on every edit, so this just reflects how fresh that is. */
function SaveStatus({ savedAt }) {
  const [, tick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => tick((n) => n + 1), 20000);
    return () => clearInterval(id);
  }, []);
  const secs = Math.max(0, Math.round((Date.now() - savedAt) / 1000));
  const rel = secs < 60 ? "just now" : secs < 3600 ? Math.round(secs / 60) + "m ago" : Math.round(secs / 3600) + "h ago";
  return (
    <span style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--typography-tertiary)", whiteSpace: "nowrap" }} title="All changes are saved automatically">
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--success, #3f7d58)" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5" /></svg>
      {PS_I18N.T("saved")} {rel}
    </span>
  );
}

/* Load modal — fetches all saved drafts from Supabase and lets the user pick one. */

/* Share button — floats in the preview pane below the backdrop toggle.
   If no draft has been saved yet, prompts the user to save first. */
function ShareButton({ pass, platform }) {
  const [state, setState] = React.useState("idle"); // idle | saving | done | error
  const [url, setUrl] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const storageKey = `spoonity_share_id_${platform}`;

  React.useEffect(() => {
    const saved = localStorage.getItem(storageKey);
    const draftId = localStorage.getItem(`spoonity_draft_id_${platform}`);
    if (saved && draftId) setUrl(`${location.origin}${location.pathname}?share=${saved}`);
  }, [platform]);

  const save = async () => {
    setState("saving");
    try {
      const shareId = await window.saveDraftToSupabase(pass, platform);
      const u = `${location.origin}${location.pathname}?share=${shareId}`;
      setUrl(u);
      setState("done");
    } catch (e) {
      console.error(e);
      setState("error");
      setTimeout(() => setState("idle"), 3000);
    }
  };

  const copy = () => {
    navigator.clipboard.writeText(url);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  const btnBase = {
    display: "flex", alignItems: "center", gap: 6, border: "none", cursor: "pointer",
    padding: "6px 12px", borderRadius: 8, fontFamily: "var(--font-sans)", fontSize: 12,
    fontWeight: 600, transition: "all .15s ease",
  };

  return (
    <div style={{ position: "absolute", top: 66, right: 18, zIndex: 20 }}>
      {!url ? (
        /* Not saved yet — prompt to save */
        <div style={{
          display: "flex", alignItems: "center", gap: 8, padding: "6px 10px 6px 12px",
          borderRadius: 11, background: "var(--surface-secondary)", border: "1px solid var(--border-primary)",
          boxShadow: "0 6px 18px -8px rgba(17,24,39,.35)",
        }}>
          <span style={{ fontSize: 11.5, color: "var(--typography-tertiary)", whiteSpace: "nowrap" }}>
            {state === "saving" ? "Saving…" : state === "error" ? "Save failed" : "Save to share"}
          </span>
          <button type="button" onClick={save} disabled={state === "saving"} style={{
            ...btnBase, background: "var(--surface-base)", color: "var(--typography-primary)",
            boxShadow: "var(--shadow-popup)",
          }}>
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
            Save draft
          </button>
        </div>
      ) : (
        /* Saved — show copy link button */
        <div style={{
          display: "flex", alignItems: "center", gap: 6, padding: "4px 4px 4px 12px",
          borderRadius: 11, background: "var(--surface-secondary)", border: "1px solid var(--border-primary)",
          boxShadow: "0 6px 18px -8px rgba(17,24,39,.35)", maxWidth: 280,
        }}>
          <span style={{ fontSize: 11, color: "var(--typography-tertiary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
            {url.replace(location.origin, "")}
          </span>
          <button type="button" onClick={copy} style={{
            ...btnBase, background: "var(--surface-base)", color: "var(--typography-primary)",
            boxShadow: "var(--shadow-popup)", flexShrink: 0,
          }}>
            {copied
              ? <><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg> Copied</>
              : <><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy link</>
            }
          </button>
        </div>
      )}
    </div>
  );
}

/* Surfaced light/dark control for the preview — what was buried in the Tweaks
   panel, now a first-class toggle floating over the preview pane. */
function BackdropToggle({ value, onChange }) {
  const dark = value === "dark";
  const btn = (active) => ({
    display: "flex", alignItems: "center", gap: 6, border: "none", cursor: "pointer",
    padding: "6px 11px", borderRadius: 8, fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
    background: active ? "var(--surface-base)" : "transparent",
    color: active ? "var(--typography-primary)" : "var(--typography-secondary)",
    boxShadow: active ? "var(--shadow-popup)" : "none", transition: "all .15s ease",
  });
  return (
    <div style={{
      position: "absolute", top: 18, right: 18, zIndex: 20, display: "flex", alignItems: "center", gap: 3, padding: 3,
      borderRadius: 11, background: "var(--surface-secondary)", border: "1px solid var(--border-primary)",
      boxShadow: "0 6px 18px -8px rgba(17,24,39,.35)",
    }}>
      <button type="button" title="Light mode" aria-pressed={!dark} style={btn(!dark)} onClick={() => onChange("wallet")}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></svg>
        Light
      </button>
      <button type="button" title="Dark mode" aria-pressed={dark} style={btn(dark)} onClick={() => onChange("dark")}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z" /></svg>
        Dark
      </button>
    </div>
  );
}

function TopBar({ accent, platform = "apple", savedAt, dark }) {
  return (
    <div style={{
      height: 60, flexShrink: 0, display: "flex", alignItems: "center", gap: 16,
      padding: "0 24px", borderBottom: "1px solid var(--border-primary)", background: "var(--surface-base)",
    }}>
      <img src={dark ? "../../assets/logo-dark.svg" : "../../assets/logo-light.svg"} alt="Spoonity" style={{ height: 26 }} />
      <div style={{ width: 1, height: 24, background: "var(--border-primary)" }} />
      <div style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-sans)", fontSize: 14 }}>
        <span style={{ color: "var(--typography-tertiary)" }}>{PS_I18N.T("loyalty")}</span>
        <span style={{ color: "var(--typography-tertiary)" }}>/</span>
        <span style={{ color: "var(--typography-primary)", fontWeight: 600 }}>{platform === "google" ? "Google Pass Studio" : "Apple Pass Studio"}</span>
      </div>
      <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 14 }}>
        <SaveStatus savedAt={savedAt} />
        <PS.Avatar initials="DL" />
      </div>
    </div>
  );
}

function useHistory(initial) {
  const stackRef = React.useRef([initial]);
  const [idx, setIdx] = React.useState(0);
  const current = stackRef.current[idx];
  const set = (val) => {
    stackRef.current = [...stackRef.current.slice(0, idx + 1), val];
    setIdx(stackRef.current.length - 1);
  };
  const undo = () => setIdx((i) => Math.max(0, i - 1));
  const redo = () => setIdx((i) => Math.min(stackRef.current.length - 1, i + 1));
  const canUndo = idx > 0;
  const canRedo = idx < stackRef.current.length - 1;
  return [current, set, { undo, redo, canUndo, canRedo }];
}

function PassStudio() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [platform, setPlatform] = React.useState("apple");
  const [lang, setLang] = React.useState("en");
  const [pass, setPass, passHistory] = useHistory(PASS_PRESETS[t.preset] || PASS_PRESETS.loyalty);
  const [gpass, setGpass, gpassHistory] = useHistory(GOOGLE_DEFAULT);
  const [sidebarWidth, setSidebarWidth] = React.useState(440);
  const containerRef = React.useRef(null);
  const dragStart = React.useRef({ x: 0, w: 440 });

  // Track whether any modal portal is open so the drag handle can be suppressed.
  const [modalOpen, setModalOpen] = React.useState(false);
  React.useEffect(() => {
    const check = () => {
      const hasModal = [...document.body.children].some(el =>
        !el.dataset.chatbot && getComputedStyle(el).position === 'fixed'
      );
      setModalOpen(hasModal);
    };
    const observer = new MutationObserver(check);
    observer.observe(document.body, { childList: true });
    return () => observer.disconnect();
  }, []);

  const onResizeStart = React.useCallback((e) => {
    // Don't start a drag when a modal portal is open.
    if ([...document.body.children].some(el => !el.dataset.chatbot && getComputedStyle(el).position === 'fixed')) return;
    e.preventDefault();
    const containerW = containerRef.current ? containerRef.current.offsetWidth : window.innerWidth;
    dragStart.current = { x: e.clientX, w: sidebarWidth };
    document.body.style.cursor = "col-resize";
    document.body.style.userSelect = "none";
    const onMove = (ev) => {
      const delta = t.previewSide === "left" ? -(ev.clientX - dragStart.current.x) : (ev.clientX - dragStart.current.x);
      const maxW = containerW - 320; // always leave at least 320px for preview
      const next = Math.min(maxW, Math.max(240, dragStart.current.w + delta));
      setSidebarWidth(next);
    };
    const onUp = () => {
      document.body.style.cursor = "";
      document.body.style.userSelect = "";
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
  }, [sidebarWidth, t.previewSide]);

  // Reflect autosave freshness in the top bar: stamp the moment any edit lands.
  const [savedAt, setSavedAt] = React.useState(() => Date.now());
  React.useEffect(() => { setSavedAt(Date.now()); }, [pass, gpass]);

  // Make T()/PL() across the studio resolve to the selected language this render.
  PS_I18N.setLang(lang);

  // Apply a preset when the tweak changes (Apple side).
  const prevPreset = React.useRef(t.preset);
  React.useEffect(() => {
    if (t.preset !== prevPreset.current) {
      prevPreset.current = t.preset;
      if (PASS_PRESETS[t.preset]) {
        setPass(PASS_PRESETS[t.preset]);
        setGpass((g) => ({ ...g, backgroundColor: PASS_PRESETS[t.preset].backgroundColor }));
      }
    }
  }, [t.preset]);

  const isGoogle = platform === "google";

  // Both platforms stay mounted so switching is a smooth crossfade, not a hard
  // cut. The inactive side fades out and is made non-interactive.
  const appleForm = <BuilderForm pass={pass} setPass={setPass} accent={t.accent} onGoogle={() => setPlatform("google")} lang={lang} onLang={setLang} onUndo={passHistory.undo} onRedo={passHistory.redo} canUndo={passHistory.canUndo} canRedo={passHistory.canRedo} />;
  const googleForm = <GoogleBuilderForm pass={gpass} setPass={setGpass} accent={t.accent} onApple={() => setPlatform("apple")} onUndo={gpassHistory.undo} onRedo={gpassHistory.redo} canUndo={gpassHistory.canUndo} canRedo={gpassHistory.canRedo} />;
  const applePreview = <PhonePreview pass={pass} setPass={setPass} lang={lang} backdrop={t.backdrop} showDevice={t.device} guides={t.guides} />;
  const googlePreview = <GooglePhonePreview pass={gpass} setPass={setGpass} lang={lang} backdrop={t.backdrop} showDevice={t.device} guides={t.guides} />;

  const isDark = t.backdrop === "dark";
  // Dark preview also flips the whole studio UI: toggle the design system's
  // `.dark` theme on <html> so every surface (and any portaled modal) re-themes.
  React.useEffect(() => {
    document.documentElement.classList.toggle("dark", isDark);
    return () => document.documentElement.classList.remove("dark");
  }, [isDark]);

  // The crossfade underlay must match the active backdrop, or in dark mode the
  // light page background flashes through mid-transition. This is the key fix.
  const stageBg = isDark
    ? "radial-gradient(130% 100% at 50% 0%, #1A1026 0%, #120D18 46%, #0F0A16 100%)"
    : "var(--background)";

  // Cinematic platform swap: the leaving side swings back into depth (rotateY +
  // scale down + blur + fade), the entering side swings forward from the other
  // side. `side` is -1 for Apple (exits left), +1 for Google (exits right).
  const stageLayer = (active, side) => ({
    position: "absolute", inset: 0, display: "flex",
    opacity: active ? 1 : 0,
    transform: active
      ? "translateX(0) rotateY(0deg) scale(1)"
      : `translateX(${side * 11}%) rotateY(${side * -12}deg) scale(.9)`,
    filter: active ? "blur(0px)" : "blur(12px)",
    transformOrigin: side < 0 ? "right center" : "left center",
    pointerEvents: active ? "auto" : "none",
    transition: "opacity .55s ease, transform .72s cubic-bezier(.22,1,.32,1), filter .55s ease",
    willChange: "opacity, transform, filter",
    backfaceVisibility: "hidden",
  });
  const formLayer = (active, side) => ({
    position: "absolute", inset: 0, background: "var(--surface-base)",
    opacity: active ? 1 : 0,
    // No transform on the active layer — CSS transforms create a new fixed-
    // positioning context, which would constrain position:fixed popups/modals
    // to the sidebar instead of the full viewport.
    transform: active ? undefined : `translateX(${side * 22}px)`,
    pointerEvents: active ? "auto" : "none",
    transition: "opacity .42s ease, transform .55s cubic-bezier(.22,1,.32,1)",
    willChange: active ? "opacity" : "opacity, transform",
  });

  return (
    <div style={{ height: "100vh", display: "flex", flexDirection: "column", background: "var(--background)", "--border-focused": t.accent }}>
      <TopBar accent={t.accent} platform={platform} savedAt={savedAt} dark={isDark} />
      <div ref={containerRef} style={{ flex: 1, display: "flex", minHeight: 0, flexDirection: t.previewSide === "left" ? "row-reverse" : "row" }}>
        {/* Editing panel — slides + fades between platforms */}
        <div style={{ position: "relative", width: sidebarWidth, flexShrink: 0, height: "100%", background: "var(--surface-base)", overflow: "hidden", zIndex: 1 }}>
          <div style={formLayer(!isGoogle, -1)}>{appleForm}</div>
          <div style={formLayer(isGoogle, 1)}>{googleForm}</div>
        </div>
        {/* Resize handle — hidden and non-interactive when a modal is open */}
        <div
          onMouseDown={onResizeStart}
          style={{
            width: 5, flexShrink: 0, cursor: modalOpen ? "default" : "col-resize",
            position: "relative", zIndex: 2,
            pointerEvents: modalOpen ? "none" : "auto",
            background: "var(--border-primary)", transition: "background .15s",
          }}
          onMouseEnter={e => { if (!modalOpen) e.currentTarget.style.background = "var(--border-focused, #ff7e3d)"; }}
          onMouseLeave={e => e.currentTarget.style.background = "var(--border-primary)"}
        >
          <div style={{
            position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)",
            width: 3, height: 28, borderRadius: 99,
            background: "var(--typography-tertiary)", opacity: modalOpen ? 0 : 0.5, pointerEvents: "none",
            transition: "opacity .15s",
          }} />
        </div>
        {/* Preview stage — cinematic 3D swap */}
        <div style={{ position: "relative", flex: 1, minWidth: 0, display: "flex", overflow: "hidden", perspective: "2000px", background: stageBg, transition: "background .4s ease" }}>
          <div style={stageLayer(!isGoogle, -1)}>{applePreview}</div>
          <div style={stageLayer(isGoogle, 1)}>{googlePreview}</div>
          <BackdropToggle value={t.backdrop} onChange={(v) => setTweak("backdrop", v)} />
        </div>
      </div>

      <ChatBot />
      <TweaksPanel>
        <TweakSection label="Pass" />
        <TweakSelect label="Preset" value={t.preset}
          options={[
            { value: "loyalty", label: "Coffee loyalty" },
            { value: "coupon", label: "Pastry coupon" },
            { value: "boarding", label: "Flight boarding" },
            { value: "cinema", label: "Cinema ticket" },
          ]}
          onChange={(v) => setTweak("preset", v)} />
        <TweakSection label="Preview" />
        <TweakRadio label="Backdrop" value={t.backdrop}
          options={["wallet", "light", "dark"]}
          onChange={(v) => setTweak("backdrop", v)} />
        <TweakToggle label="iPhone frame" value={t.device} onChange={(v) => setTweak("device", v)} />
        <TweakToggle label="Empty-field guides" value={t.guides} onChange={(v) => setTweak("guides", v)} />
        <TweakRadio label="Preview side" value={t.previewSide}
          options={["right", "left"]}
          onChange={(v) => setTweak("previewSide", v)} />
        <TweakSection label="Studio" />
        <TweakColor label="Accent" value={t.accent}
          options={["#ff7e3d", "#640c6f", "#2557cb", "#3f7d58"]}
          onChange={(v) => setTweak("accent", v)} />
      </TweaksPanel>
    </div>
  );
}
/* ── AI Chat Bot ─────────────────────────────────────────────────────────── */
// The Gemini API key lives server-side now; the chatbot calls the backend proxy
// (/api/ai/chat) with the operator's Bearer session. No key in the browser.

function parseBold(text) {
  const parts = text.split(/(\*\*.*?\*\*)/g);
  return parts.map((p, i) =>
    p.startsWith("**") && p.endsWith("**")
      ? React.createElement("strong", { key: i }, p.slice(2, -2))
      : p
  );
}

function BotMessage({ text, animate }) {
  const [shown, setShown] = React.useState(animate ? 0 : text.length);
  React.useEffect(() => {
    if (!animate) { setShown(text.length); return; }
    setShown(0);
    let i = 0;
    const id = setInterval(() => {
      i++;
      setShown(i);
      if (i >= text.length) clearInterval(id);
    }, 18);
    return () => clearInterval(id);
  }, [text, animate]);
  return React.createElement(React.Fragment, null, parseBold(text.slice(0, shown)));
}

async function geminiChat(history, userText) {
  const base = window.SpoonityAuth ? window.SpoonityAuth.getEndpoint() : "";
  const res = await fetch(`${base}/api/ai/chat`, {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ history, userText }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err?.error || `HTTP ${res.status}`);
  }
  const data = await res.json();
  return data.text || "…";
}

function ChatBot() {
  const [open, setOpen] = React.useState(false);
  const [messages, setMessages] = React.useState([
    { role: "bot", text: "Hi! I'm your Spoonity AI assistant. Ask me anything about designing your wallet pass." },
  ]);
  const [input, setInput] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const bottomRef = React.useRef(null);
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages, loading]);

  React.useEffect(() => {
    if (open) setTimeout(() => inputRef.current?.focus(), 320);
  }, [open]);

  const send = async () => {
    const text = input.trim();
    if (!text || loading) return;
    setInput("");
    const next = [...messages, { role: "user", text }];
    setMessages(next);
    setLoading(true);
    try {
      const reply = await geminiChat(messages, text);
      setMessages(m => [...m, { role: "bot", text: reply }]);
    } catch (err) {
      setMessages(m => [...m, { role: "bot", text: `Error: ${err.message}` }]);
    } finally {
      setLoading(false);
    }
  };

  const panelW = 320;

  return ReactDOM.createPortal(
    <div data-chatbot="true">
      {/* Slide-in chat panel */}
      <div style={{
        position: "fixed", bottom: 80, right: 20, width: panelW, zIndex: 2000,
        background: "var(--surface-base, #fff)", borderRadius: 18,
        boxShadow: "0 8px 40px -8px rgba(0,0,0,.22), 0 2px 12px -4px rgba(0,0,0,.12)",
        display: "flex", flexDirection: "column", overflow: "hidden",
        transform: open ? "translateY(0) scale(1)" : "translateY(16px) scale(.96)",
        opacity: open ? 1 : 0, pointerEvents: open ? "auto" : "none",
        transition: "transform .25s cubic-bezier(.2,.8,.2,1), opacity .2s ease",
        maxHeight: "calc(100vh - 120px)",
      }}>
        {/* Header */}
        <div style={{
          padding: "14px 16px", display: "flex", alignItems: "center", gap: 10,
          borderBottom: "1px solid var(--border-primary, #e5e7eb)", flexShrink: 0,
        }}>
          <div style={{
            width: 32, height: 32, borderRadius: 10, background: "linear-gradient(135deg,#ff7e3d,#640c6f)",
            display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
          }}>
            <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <rect x="3" y="11" width="18" height="10" rx="2"/><path d="M12 3v4"/><circle cx="12" cy="3" r="1.5"/>
              <path d="M7 11V9a5 5 0 0 1 10 0v2"/><line x1="8" y1="15" x2="8" y2="15"/><line x1="12" y1="15" x2="12" y2="15"/><line x1="16" y1="15" x2="16" y2="15"/>
            </svg>
          </div>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700, color: "var(--typography-primary,#111)", lineHeight: 1.2 }}>Spoonity AI</div>
            <div style={{ fontSize: 11, color: "var(--typography-tertiary,#888)" }}>Pass design assistant</div>
          </div>
          <button onClick={() => setOpen(false)} style={{
            marginLeft: "auto", border: "none", background: "none", cursor: "pointer",
            fontSize: 17, lineHeight: 1, color: "var(--typography-tertiary,#888)", padding: 4,
          }}>✕</button>
        </div>

        {/* Messages */}
        <div style={{ flex: 1, overflowY: "auto", padding: "14px 14px 8px", display: "flex", flexDirection: "column", gap: 10, minHeight: 0 }}>
          {messages.map((m, i) => (
            <div key={i} style={{ display: "flex", justifyContent: m.role === "user" ? "flex-end" : "flex-start" }}>
              <div style={{
                maxWidth: "82%", padding: "9px 12px", borderRadius: m.role === "user" ? "14px 14px 4px 14px" : "14px 14px 14px 4px",
                background: m.role === "user" ? "linear-gradient(135deg,#ff7e3d,#e05a1a)" : "var(--surface-secondary,#f3f4f6)",
                color: m.role === "user" ? "#fff" : "var(--typography-primary,#111)",
                fontSize: 13, lineHeight: 1.5, whiteSpace: "pre-wrap", wordBreak: "break-word",
              }}>
                {m.role === "user"
                  ? m.text
                  : React.createElement(BotMessage, { text: m.text, animate: i === messages.length - 1 })}
              </div>
            </div>
          ))}
          {loading && (
            <div style={{ display: "flex", justifyContent: "flex-start" }}>
              <div style={{ padding: "9px 14px", borderRadius: "14px 14px 14px 4px", background: "var(--surface-secondary,#f3f4f6)", display: "flex", gap: 4, alignItems: "center" }}>
                {[0,1,2].map(i => (
                  <div key={i} style={{
                    width: 6, height: 6, borderRadius: "50%", background: "var(--typography-tertiary,#aaa)",
                    animation: "vf-bounce .9s ease infinite", animationDelay: `${i * .18}s`,
                  }} />
                ))}
              </div>
            </div>
          )}
          <div ref={bottomRef} />
        </div>

        {/* Input */}
        <div style={{ padding: "10px 12px", borderTop: "1px solid var(--border-primary,#e5e7eb)", display: "flex", gap: 8, flexShrink: 0 }}>
          <input
            ref={inputRef}
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => e.key === "Enter" && !e.shiftKey && (e.preventDefault(), send())}
            placeholder="Ask me anything…"
            style={{
              flex: 1, border: "1.5px solid var(--border-primary,#e5e7eb)", borderRadius: 10,
              padding: "8px 11px", fontSize: 13, outline: "none", fontFamily: "var(--font-sans)",
              background: "var(--surface-base,#fff)", color: "var(--typography-primary,#111)",
              transition: "border-color .15s",
            }}
            onFocus={e => e.target.style.borderColor = "var(--border-focused,#ff7e3d)"}
            onBlur={e => e.target.style.borderColor = "var(--border-primary,#e5e7eb)"}
          />
          <button
            onClick={send}
            disabled={!input.trim() || loading}
            style={{
              width: 36, height: 36, borderRadius: 10, border: "none", cursor: input.trim() && !loading ? "pointer" : "default",
              background: input.trim() && !loading ? "linear-gradient(135deg,#ff7e3d,#e05a1a)" : "var(--surface-secondary,#f3f4f6)",
              display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
              transition: "background .15s",
            }}
          >
            <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke={input.trim() && !loading ? "#fff" : "var(--typography-tertiary,#aaa)"} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
            </svg>
          </button>
        </div>
      </div>

      {/* Floating trigger button */}
      <button
        onClick={() => setOpen(o => !o)}
        title="Spoonity AI assistant"
        style={{
          position: "fixed", bottom: 20, right: 20, width: 52, height: 52, borderRadius: "50%",
          border: "none", cursor: "pointer", zIndex: 2001,
          background: "linear-gradient(135deg,#ff7e3d,#640c6f)",
          boxShadow: "0 4px 18px -4px rgba(255,126,61,.55), 0 2px 8px rgba(0,0,0,.18)",
          display: "flex", alignItems: "center", justifyContent: "center",
          transition: "transform .18s ease, box-shadow .18s ease",
        }}
        onMouseEnter={e => { e.currentTarget.style.transform = "scale(1.08)"; e.currentTarget.style.boxShadow = "0 6px 24px -4px rgba(255,126,61,.7), 0 2px 10px rgba(0,0,0,.2)"; }}
        onMouseLeave={e => { e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.boxShadow = "0 4px 18px -4px rgba(255,126,61,.55), 0 2px 8px rgba(0,0,0,.18)"; }}
      >
        {open
          ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
          : <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="M21 11.5a8.5 8.5 0 0 1-12.6 7.45L3 20.5l1.55-5.4A8.5 8.5 0 1 1 21 11.5z"/>
              <circle cx="8.5" cy="12" r="1" fill="#fff" stroke="none"/>
              <circle cx="12" cy="12" r="1" fill="#fff" stroke="none"/>
              <circle cx="15.5" cy="12" r="1" fill="#fff" stroke="none"/>
            </svg>
        }
      </button>

      {/* Bouncing dots keyframes */}
      <style>{`@keyframes vf-bounce { 0%,80%,100%{transform:translateY(0)} 40%{transform:translateY(-5px)} }`}</style>
    </div>,
    document.body
  );
}

window.PassStudio = PassStudio;
