/* ============== Admin Login + Dashboard ============== */
/* Auth is Firebase Google Sign-In, gated by an email whitelist enforced both
   client-side (here) and server-side (Firestore + Storage security rules). */

function AdminPage({ navigate }) {
  const [user, setUser] = useState(() => window.lf && window.lf.auth.currentUser);
  const [ready, setReady] = useState(!!(window.lf && window.lf.auth.currentUser));

  useEffect(() => {
    if (!window.lf) return;
    const unsub = window.lf.onAuthChange((u) => {
      setUser(u);
      setReady(true);
    });
    return unsub;
  }, []);

  if (!ready) {
    return (
      <div style={{ minHeight: "100vh", display: "grid", placeItems: "center", background: "var(--ivory)" }}>
        <div style={{ fontSize: 12, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-mute)" }}>Loading…</div>
      </div>
    );
  }

  if (!user || !window.lf.isAdminUser(user)) {
    return <AdminLogin user={user} navigate={navigate} />;
  }
  return <AdminDashboard user={user} onLogout={() => window.lf.signOut()} navigate={navigate} />;
}

/* ============== Login ============== */
function AdminLogin({ user, navigate }) {
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState(false);

  const deniedEmail = user && user.email && !window.lf.isAdminUser(user) ? user.email : null;

  const signIn = async () => {
    setBusy(true);
    setErr("");
    try {
      await window.lf.signInWithGoogle();
    } catch (e) {
      setErr(e && e.message ? e.message : "Sign-in failed. Please try again.");
    } finally {
      setBusy(false);
    }
  };

  const signOut = () => window.lf.signOut();

  return (
    <div className="page-enter" style={{ minHeight: "100vh", display: "grid", gridTemplateColumns: "1fr 1fr", paddingTop: 0 }}>
      <div style={{
        position: "relative", display: "flex", alignItems: "flex-end", padding: 50,
        background: `linear-gradient(180deg, rgba(46,42,36,0.05) 0%, rgba(46,42,36,0.5) 100%), url("assets/larissa-studio.jpg") center/cover`,
      }}>
        <div style={{ color: "var(--warm-white)" }}>
          <div className="h-script" style={{ fontSize: 56, color: "var(--sunflower-soft)" }}>The Studio Atelier</div>
          <p style={{ marginTop: 8, fontSize: 14, lineHeight: 1.6, maxWidth: 380, opacity: 0.9 }}>
            Private admin for managing the gallery: paintings, prices, photographs and stories.
          </p>
        </div>
      </div>

      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", padding: 40, background: "var(--ivory)" }}>
        <div style={{ width: "100%", maxWidth: 380 }}>
          <div style={{ textAlign: "center", marginBottom: 36 }}>
            <div style={{ fontFamily: "var(--serif)", fontSize: 28, letterSpacing: "0.18em" }}>LARISSA</div>
            <div style={{ fontSize: 9, letterSpacing: "0.4em", color: "var(--ink-mute)", textTransform: "uppercase", marginTop: 2 }}>
              Studio Administration
            </div>
          </div>

          {deniedEmail ? (
            <div style={{ padding: "14px 16px", background: "#FBEDE8", border: "1px solid #E0A89A", color: "#8C4034", fontSize: 13, borderRadius: 4, lineHeight: 1.6 }}>
              <strong>Access denied —</strong> <em>{deniedEmail}</em> is not authorized for the Larissa studio admin. Sign out and sign in with an authorized account.
            </div>
          ) : (
            <p style={{ fontSize: 14, color: "var(--ink-soft)", textAlign: "center", lineHeight: 1.6 }}>
              Sign in with your Google account to manage the studio collection.
            </p>
          )}

          {err && (
            <div style={{ marginTop: 16, padding: "10px 14px", background: "#FBEDE8", border: "1px solid #E0A89A", color: "#8C4034", fontSize: 13, borderRadius: 4 }}>
              {err}
            </div>
          )}

          <button type="button" onClick={signIn} disabled={busy} className="btn btn-primary" style={{ width: "100%", justifyContent: "center", marginTop: 24, opacity: busy ? 0.7 : 1 }}>
            {busy ? "Signing in…" : "Sign in with Google"} <Icon.ArrowR />
          </button>

          {deniedEmail && (
            <button type="button" onClick={signOut} className="btn btn-ghost" style={{ width: "100%", justifyContent: "center", marginTop: 12 }}>
              Sign out of {deniedEmail}
            </button>
          )}

          <button type="button" onClick={() => navigate("home")} style={{
            marginTop: 22, width: "100%", background: "transparent", border: "none",
            color: "var(--ink-mute)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase",
            cursor: "pointer", padding: 8,
          }}>← Back to the public site</button>
        </div>
      </div>
    </div>
  );
}

/* ============== Dashboard ============== */
function AdminDashboard({ user, onLogout, navigate }) {
  const [list, setList] = useState(() => window.PAINTINGS);
  const [editing, setEditing] = useState(null); // painting object being edited
  const [search, setSearch] = useState("");
  const [filterSection, setFilterSection] = useState("all");
  const [savedFlash, setSavedFlash] = useState(false);
  const [error, setError] = useState("");
  const [newId, setNewId] = useState(null); // most recently created painting — highlighted in the list
  const [sortBy, setSortBy] = useState("newest"); // newest | name | price-high | price-low

  // Stay in sync with the Firestore-driven window.PAINTINGS
  useEffect(() => {
    const handler = () => setList([...window.PAINTINGS]);
    window.addEventListener("lf-paintings-updated", handler);
    return () => window.removeEventListener("lf-paintings-updated", handler);
  }, []);

  const filtered = useMemo(() => {
    let l = list;
    if (filterSection !== "all") l = l.filter((p) => p.section === filterSection);
    if (search.trim()) {
      const q = search.toLowerCase();
      l = l.filter((p) => p.title.toLowerCase().includes(q) || (p.short || "").toLowerCase().includes(q));
    }
    // Apply the chosen sort. Copy first so we never mutate the source list.
    const sorted = [...l];
    if (sortBy === "newest") {
      // Paintings created in-app carry a createdAt timestamp; older ones don't,
      // so they keep their existing studio order below the freshly added ones.
      sorted.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
    } else if (sortBy === "name") {
      sorted.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
    } else if (sortBy === "price-high") {
      sorted.sort((a, b) => (b.price || 0) - (a.price || 0));
    } else if (sortBy === "price-low") {
      sorted.sort((a, b) => (a.price || 0) - (b.price || 0));
    }
    return sorted;
  }, [list, search, filterSection, sortBy]);

  const totalAvail = list.filter((p) => p.available).length;
  const totalValue = list.filter((p) => p.available).reduce((s, p) => s + (p.price || 0), 0);

  const flashSaved = () => {
    setSavedFlash(true);
    setTimeout(() => setSavedFlash(false), 1600);
  };

  const updateOne = async (updated) => {
    try {
      await window.lf.savePainting(updated);
      flashSaved();
    } catch (e) {
      console.error(e);
      setError("Could not save: " + (e && e.message ? e.message : "unknown error"));
    }
  };
  const removeOne = async (id) => {
    if (!confirm("Delete this painting? This will also remove its photos. This cannot be undone.")) return;
    try {
      await window.lf.deletePainting(id);
      setEditing(null);
    } catch (e) {
      console.error(e);
      setError("Could not delete: " + (e && e.message ? e.message : "unknown error"));
    }
  };
  const createOne = async () => {
    const newP = {
      id: "p-" + Date.now() + "-" + Math.random().toString(36).slice(2, 7),
      title: "Untitled Painting",
      year: new Date().getFullYear(),
      category: "flowers",
      section: "gallery",
      sectionOrder: 999,
      medium: "Oil on canvas",
      dims: "24 × 36 in",
      price: 1500,
      available: true,
      featured: false,
      grad: window.grads.summerGarden,
      short: "A new work from the studio.",
      story: "Painted in the studio, from life.",
      images: [],
      shipping: 160,
      createdAt: Date.now(),
    };
    try {
      await window.lf.savePainting(newP);
      // Clear any filter/search and switch to "Newest" so the new painting can't
      // be hidden, highlight it, and open the editor right away.
      setSearch("");
      setFilterSection("all");
      setSortBy("newest");
      setNewId(newP.id);
      setEditing(newP);
    } catch (e) {
      console.error(e);
      setError("Could not create: " + (e && e.message ? e.message : "unknown error"));
    }
  };

  return (
    <div className="page-enter" style={{ background: "var(--paper)", minHeight: "100vh", paddingBottom: 100 }}>
      {/* Admin top bar */}
      <header style={{
        background: "var(--ink)", color: "var(--warm-white)",
        padding: "16px 32px", display: "flex", justifyContent: "space-between", alignItems: "center",
        position: "sticky", top: 0, zIndex: 80,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 32 }}>
          <div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 18, letterSpacing: "0.16em" }}>LARISSA · ATELIER</div>
            <div style={{ fontSize: 9, letterSpacing: "0.32em", textTransform: "uppercase", opacity: 0.6, marginTop: 2 }}>
              Studio administration
            </div>
          </div>
          <nav style={{ display: "flex", gap: 16, fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase", opacity: 0.85, alignItems: "center" }}>
            <span style={{ borderBottom: "1px solid var(--sunflower)", paddingBottom: 4 }}>Paintings</span>
            <span style={{ background: "var(--sunflower)", color: "var(--ink)", padding: "3px 8px", borderRadius: 999, fontSize: 10, fontWeight: 600 }}>BUILD 6</span>
          </nav>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
          {savedFlash && <span style={{ fontSize: 11, letterSpacing: "0.18em", color: "var(--sunflower-soft)", textTransform: "uppercase", display: "flex", alignItems: "center", gap: 6 }}><Icon.Check /> Saved</span>}
          <button onClick={() => navigate("home")} style={adminLinkStyle}>View Public Site →</button>
          <button onClick={onLogout} style={adminLinkStyle}>Sign Out</button>
        </div>
      </header>

      {/* Hero / stats */}
      <section style={{ padding: "44px 32px 20px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "end", flexWrap: "wrap", gap: 24 }}>
          <div>
            <div className="eyebrow" style={{ color: "var(--gold-deep)" }}>Welcome back, Larissa</div>
            <h1 className="h-display" style={{ fontSize: "clamp(36px, 4vw, 56px)", marginTop: 10, fontStyle: "italic", color: "var(--ink)" }}>
              Manage the studio collection.
            </h1>
          </div>
          <div style={{ display: "flex", gap: 12 }}>
            <button className="btn btn-primary" onClick={createOne}><Icon.Plus /> Add Painting</button>
          </div>
        </div>

        {error && (
          <div style={{ marginTop: 18, padding: "10px 14px", background: "#FBEDE8", border: "1px solid #E0A89A", color: "#8C4034", fontSize: 13, borderRadius: 4 }}>
            {error} <button onClick={() => setError("")} style={{ background: "transparent", border: "none", color: "#8C4034", cursor: "pointer", marginLeft: 8 }}>dismiss</button>
          </div>
        )}

        <div style={{ marginTop: 36, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16 }}>
          <Stat label="Paintings in collection" value={list.length} />
          <Stat label="Currently available" value={totalAvail} />
          <Stat label="Inventory value (CAD)" value={`$${totalValue.toLocaleString()}`} />
          <Stat label="Featured on homepage" value={list.filter((p) => p.featured).length} />
        </div>
      </section>

      {/* Toolbar */}
      <section style={{ padding: "30px 32px 0" }}>
        <div style={{
          background: "var(--warm-white)", border: "1px solid var(--line)",
          padding: 16, borderRadius: 4,
          display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 16,
        }}>
          <div style={{ display: "flex", gap: 4, flexWrap: "wrap", alignItems: "center" }}>
            <span style={{ fontSize: 10, letterSpacing: "0.22em", color: "var(--ink-mute)", textTransform: "uppercase", marginRight: 8 }}>Section</span>
            {[{ id: "all", label: "All Sections", accent: "var(--ink)" }, ...window.SECTIONS].map((c) => (
              <button key={c.id} onClick={() => setFilterSection(c.id)} style={{
                padding: "6px 14px", background: filterSection === c.id ? "var(--ink)" : "transparent",
                color: filterSection === c.id ? "var(--warm-white)" : "var(--ink)",
                border: "1px solid " + (filterSection === c.id ? "var(--ink)" : "var(--line)"),
                borderRadius: 999, fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", fontWeight: 500, cursor: "pointer",
                display: "inline-flex", alignItems: "center", gap: 7,
              }}>
                {c.id !== "all" && <span style={{ width: 6, height: 6, borderRadius: 999, background: c.accent }} />}
                {c.label.replace(/^Section \d+ · /, "")}
              </button>
            ))}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ fontSize: 10, letterSpacing: "0.22em", color: "var(--ink-mute)", textTransform: "uppercase" }}>Sort</span>
              <select value={sortBy} onChange={(e) => setSortBy(e.target.value)} style={{
                background: "var(--warm-white)", border: "1px solid var(--line)", borderRadius: 999,
                padding: "6px 12px", fontSize: 12, fontFamily: "var(--sans)", color: "var(--ink)", cursor: "pointer", outline: "none",
              }}>
                <option value="newest">Newest first</option>
                <option value="name">Name (A–Z)</option>
                <option value="price-high">Price (high → low)</option>
                <option value="price-low">Price (low → high)</option>
              </select>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <Icon.Search />
              <input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search paintings…"
                style={{ background: "transparent", border: "none", outline: "none", fontSize: 14, fontFamily: "var(--sans)", width: 220, color: "var(--ink)" }} />
            </div>
          </div>
        </div>
      </section>

      {/* Table */}
      <section style={{ padding: "20px 32px 0" }}>
        <div style={{ background: "var(--warm-white)", border: "1px solid var(--line)", borderRadius: 4, overflow: "hidden" }}>
        <div style={{
          display: "grid", gridTemplateColumns: "84px 2fr 1.2fr 1fr 1fr 110px 100px 120px",
          padding: "14px 18px", background: "var(--cream)", borderBottom: "1px solid var(--line)",
          fontSize: 10, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-mute)", fontWeight: 500
        }}>
          <div>Image</div>
          <div>Title</div>
          <div>Section · Where it appears</div>
          <div>Dimensions</div>
          <div>Medium</div>
          <div>Price</div>
          <div>Status</div>
          <div style={{ textAlign: "right" }}>Actions</div>
        </div>
          {filtered.length === 0 ? (
            <div style={{ padding: 40, textAlign: "center", color: "var(--ink-mute)", fontSize: 14 }}>
              No paintings match the current filter.
            </div>
          ) : filtered.map((p) => {
            const sec = window.sectionMeta(p.section);
            return (
            <div key={p.id} style={{
              display: "grid", gridTemplateColumns: "84px 2fr 1.2fr 1fr 1fr 110px 100px 120px",
              padding: "14px 18px", borderBottom: "1px solid var(--line-soft)",
              alignItems: "center", gap: 12,
              background: p.id === newId ? "color-mix(in oklab, var(--sunflower) 14%, var(--warm-white))" : "transparent",
            }}>
              <div style={{ width: 64, height: 64, overflow: "hidden", borderRadius: 2 }}>
                <Painting p={p} aspect="1/1" tag={false} />
              </div>
              <div>
                <div style={{ fontFamily: "var(--serif)", fontSize: 17, lineHeight: 1.2 }}>{p.title}</div>
                <div style={{ fontSize: 11.5, color: "var(--ink-mute)", marginTop: 3 }}>
                  {p.year} · {(p.images || []).length} photo{(p.images || []).length === 1 ? "" : "s"} · <span style={{ textTransform: "capitalize" }}>{p.category}</span>
                  {p.featured && <span style={{ marginLeft: 8, color: "var(--gold-deep)" }}>★ Featured</span>}
                </div>
              </div>
              <div>
                <div style={{
                  display: "inline-flex", alignItems: "center", gap: 8,
                  padding: "5px 10px", borderRadius: 4,
                  background: `color-mix(in oklab, ${sec.accent} 16%, var(--warm-white))`,
                  border: `1px solid color-mix(in oklab, ${sec.accent} 35%, transparent)`,
                  fontSize: 10.5, letterSpacing: "0.14em", textTransform: "uppercase",
                  color: "var(--ink)", fontWeight: 500
                }}>
                  <span style={{ width: 6, height: 6, borderRadius: 999, background: sec.accent }} />
                  {sec.label.replace(/ · .*/, "")}
                </div>
                <div style={{ fontSize: 10.5, color: "var(--ink-mute)", marginTop: 4 }}>{sec.label.replace(/^.*· /, "")}</div>
              </div>
              <div style={{ fontSize: 13, color: "var(--ink-soft)" }}>{p.dims}</div>
              <div style={{ fontSize: 13, color: "var(--ink-soft)" }}>{p.medium}</div>
              <div style={{ fontFamily: "var(--serif)", fontSize: 16 }}>${(p.price || 0).toLocaleString()}</div>
              <div>
                <span style={{
                  fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase",
                  padding: "4px 9px", borderRadius: 999,
                  background: p.available ? "rgba(157,174,146,0.25)" : "rgba(46,42,36,0.08)",
                  color: p.available ? "var(--sage-deep)" : "var(--ink-mute)"
                }}>{p.available ? "Available" : "SOLD"}</span>
              </div>
              <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                <button onClick={() => setEditing(p)} style={smallBtn("var(--ink)")}>Edit</button>
                <button onClick={() => removeOne(p.id)} style={smallBtn("transparent", "var(--ink-mute)")}>Delete</button>
              </div>
            </div>);
          })}
        </div>
        <div style={{ marginTop: 16, fontSize: 12, color: "var(--ink-mute)", textAlign: "center" }}>
          Edits are saved to Firebase and reflected on the public site within a few seconds. Signed in as {user && user.email}.
        </div>
      </section>

      {editing && (
        <PaintingEditor
          painting={editing}
          onClose={() => setEditing(null)}
          onSave={(updated) => { updateOne(updated); setEditing(updated); }}
          onDelete={() => removeOne(editing.id)}
        />
      )}
    </div>
  );
}

const adminLinkStyle = {
  background: "transparent", border: "1px solid rgba(255,255,255,0.2)", color: "var(--warm-white)",
  padding: "8px 16px", borderRadius: 999,
  fontSize: 10.5, letterSpacing: "0.2em", textTransform: "uppercase", fontWeight: 500,
  cursor: "pointer",
};

function smallBtn(bg, color = "var(--warm-white)") {
  return {
    background: bg, color: bg === "transparent" ? color : "var(--warm-white)",
    border: bg === "transparent" ? "1px solid var(--line)" : "none",
    padding: "7px 14px", borderRadius: 999, cursor: "pointer",
    fontSize: 10.5, letterSpacing: "0.18em", textTransform: "uppercase", fontWeight: 500,
  };
}

function Stat({ label, value }) {
  return (
    <div style={{ background: "var(--warm-white)", border: "1px solid var(--line)", padding: 22, borderRadius: 4 }}>
      <div style={{ fontSize: 10, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-mute)" }}>{label}</div>
      <div style={{ fontFamily: "var(--serif)", fontSize: 32, marginTop: 8 }}>{value}</div>
    </div>
  );
}

/* ============== Editor Drawer ============== */
function PaintingEditor({ painting, onClose, onSave, onDelete }) {
  const [draft, setDraft] = useState(painting);
  const [activeTab, setActiveTab] = useState("details");
  const scrollRef = useRef(null);

  useEffect(() => { setDraft(painting); }, [painting.id]);

  // Reset the internal scroll position whenever a different painting is
  // opened or the user switches tabs — otherwise the drawer keeps the
  // previous painting's scroll offset and looks like it opened mid-page.
  // requestAnimationFrame waits for layout, so this reliably scrolls to
  // the very top even on first open.
  useEffect(() => {
    const reset = () => { if (scrollRef.current) scrollRef.current.scrollTop = 0; };
    reset();
    const id = requestAnimationFrame(reset);
    return () => cancelAnimationFrame(id);
  }, [painting.id, activeTab]);

  // Lock the underlying page scroll while the drawer is open. Restores
  // the prior overflow value on close so other admin work keeps working.
  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, []);

  const set = (k, v) => setDraft((d) => ({ ...d, [k]: v }));

  const [uploading, setUploading] = useState(false);
  const [uploadErr, setUploadErr] = useState("");

  const uploadToSlot = async (file, slot) => {
    setUploadErr("");
    setUploading(true);
    try {
      // iPhone Safari delivers .HEIC files by default. Convert HEIC → JPEG
      // before we touch the canvas (HEIC isn't natively decodable by <img>
      // outside Safari, and even Safari's decoded canvas image isn't
      // exportable to JPEG via toBlob on every iOS version).
      const jpegSource = await ensureBrowserDecodable(file);

      // Downscale to JPEG blob to keep storage small (~200-400KB per image)
      const blob = await downscaleToBlob(jpegSource);

      // Generate a unique upload id so each new file lands at a distinct
      // path. This sidesteps CDN caching of old URLs and gives us a stable
      // identifier per upload for auditing duplicates.
      const uploadId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
      const prevUrl = (draft.images || [])[slot];
      const { url, path } = await window.lf.uploadImage(draft.id, slot, blob, "image/jpeg", uploadId);
      // Delete the old file at this slot (best-effort)
      if (prevUrl && prevUrl !== url) {
        window.lf.deleteImageByUrl(prevUrl).catch(() => {});
      }
      console.log(`Uploaded ${file.name} → ${path} (id ${uploadId})`);
      setDraft((d) => {
        const imgs = [...(d.images || [])];
        imgs[slot] = url;
        return { ...d, images: imgs.slice(0, 5) };
      });
    } catch (e) {
      console.error(e);
      setUploadErr(e && e.message ? e.message : "Upload failed");
    } finally {
      setUploading(false);
    }
  };

  const onFiles = async (files, slot = null) => {
    const arr = Array.from(files).slice(0, 5);
    if (slot !== null) {
      if (arr[0]) await uploadToSlot(arr[0], slot);
      return;
    }
    // Append to the next empty slots
    const imgs = [...(draft.images || [])];
    let nextSlot = imgs.findIndex((u) => !u);
    if (nextSlot === -1) nextSlot = imgs.length;
    for (const f of arr) {
      if (nextSlot >= 5) break;
      await uploadToSlot(f, nextSlot);
      nextSlot++;
    }
  };

  const removeImage = async (i) => {
    const url = (draft.images || [])[i];
    if (url) window.lf.deleteImageByUrl(url).catch(() => {});
    setDraft((d) => {
      const imgs = [...(d.images || [])];
      imgs.splice(i, 1);
      return { ...d, images: imgs };
    });
  };

  const save = () => {
    const shipping = draft.shipping === undefined || draft.shipping === null || draft.shipping === ""
      ? 160
      : Math.max(0, Number(draft.shipping) || 0);
    onSave({
      ...draft,
      price: Number(draft.price) || 0,
      year: Number(draft.year) || new Date().getFullYear(),
      shipping,
    });
  };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 250, background: "rgba(46,42,36,0.45)",
      animation: "pageFade 220ms ease both",
    }}>
      <aside onClick={(e) => e.stopPropagation()} style={{
        position: "absolute", top: 0, right: 0, bottom: 0, width: "min(840px, 96vw)",
        background: "var(--ivory)", display: "flex", flexDirection: "column",
        boxShadow: "-30px 0 60px -20px rgba(46,42,36,0.3)",
      }}>
        <div style={{ padding: "22px 32px", borderBottom: "1px solid var(--line)", display: "flex", justifyContent: "space-between", alignItems: "center", background: "var(--warm-white)" }}>
          <div>
            <div className="eyebrow">Editing Painting</div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 22, marginTop: 4, lineHeight: 1.2 }}>{draft.title || "Untitled"}</div>
          </div>
          <div style={{ display: "flex", gap: 10 }}>
            <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
            <button className="btn btn-primary" onClick={save}>Save Changes <Icon.Check /></button>
          </div>
        </div>

        {/* Tabs */}
        <div style={{ display: "flex", borderBottom: "1px solid var(--line)", background: "var(--warm-white)", padding: "0 32px" }}>
          {[["details", "Details"], ["photos", "Photographs (5)"], ["story", "Story"]].map(([k, l]) => (
            <button key={k} onClick={() => setActiveTab(k)} style={{
              padding: "14px 22px", background: "transparent", border: "none",
              borderBottom: activeTab === k ? "2px solid var(--gold)" : "2px solid transparent",
              color: activeTab === k ? "var(--ink)" : "var(--ink-mute)",
              fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase", fontWeight: 500,
              cursor: "pointer", marginBottom: -1,
            }}>{l}</button>
          ))}
        </div>

        <div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: 32 }}>
          {activeTab === "details" && (
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
              <div style={{ gridColumn: "1 / -1" }}>
                <label className="lf-label">Painting Name</label>
                <input className="lf-input" value={draft.title} onChange={(e) => set("title", e.target.value)} />
              </div>

              <div style={{ gridColumn: "1 / -1", padding: "14px 16px", background: "var(--cream)", borderRadius: 4, borderLeft: `3px solid ${window.sectionMeta(draft.section).accent}` }}>
                <label className="lf-label" style={{ marginBottom: 6 }}>Homepage Section · Where it appears</label>
                <select className="lf-input" value={draft.section || "gallery"} onChange={(e) => set("section", e.target.value)} style={{ background: "var(--warm-white)" }}>
                  {window.SECTIONS.map((s) => <option key={s.id} value={s.id}>{s.label} — {s.home}</option>)}
                </select>
                <div style={{ marginTop: 8, fontSize: 11.5, color: "var(--ink-soft)", lineHeight: 1.5 }}>
                  Choose which section of the homepage this painting appears in. "Gallery only" hides it from the homepage but keeps it for sale in the main gallery.
                </div>
              </div>

              <div>
                <label className="lf-label">Category</label>
                <select className="lf-input" value={draft.category} onChange={(e) => set("category", e.target.value)}>
                  {window.CATEGORIES.filter(c => c.id !== "all").map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
                </select>
              </div>
              <div>
                <label className="lf-label">Year</label>
                <input className="lf-input" type="number" value={draft.year} onChange={(e) => set("year", e.target.value)} />
              </div>
              <div>
                <label className="lf-label">Size · Dimensions</label>
                <input className="lf-input" value={draft.dims} onChange={(e) => set("dims", e.target.value)} placeholder="24 × 36 in" />
              </div>
              <div>
                <label className="lf-label">Medium</label>
                <input className="lf-input" value={draft.medium} onChange={(e) => set("medium", e.target.value)} placeholder="Oil on canvas" />
              </div>
              <div>
                <label className="lf-label">Price (CAD)</label>
                <input className="lf-input" type="number" value={draft.price} onChange={(e) => set("price", e.target.value)} />
              </div>
              <div>
                <label className="lf-label">Status</label>
                <select className="lf-input" value={draft.available ? "yes" : "no"} onChange={(e) => set("available", e.target.value === "yes")}>
                  <option value="yes">Available</option>
                  <option value="no">Sold</option>
                </select>
              </div>
              <div>
                <label className="lf-label">Shipping (CAD)</label>
                <input
                  className="lf-input"
                  type="number"
                  min="0"
                  value={draft.shipping === undefined || draft.shipping === null || draft.shipping === "" ? 160 : draft.shipping}
                  onChange={(e) => set("shipping", e.target.value === "" ? 160 : Number(e.target.value))}
                  placeholder="160"
                />
                <div style={{ fontSize: 11, color: "var(--ink-mute)", marginTop: 4 }}>
                  Insured shipping added at checkout. Default $160. Set to 0 for complimentary shipping.
                </div>
              </div>
              <div style={{ gridColumn: "1 / -1", display: "flex", gap: 24, paddingTop: 8 }}>
                <Toggle label="Feature on Homepage" checked={!!draft.featured} onChange={(v) => set("featured", v)} />
              </div>
              <div style={{ gridColumn: "1 / -1" }}>
                <label className="lf-label">Short Description</label>
                <textarea className="lf-input" rows={3} value={draft.short || ""} onChange={(e) => set("short", e.target.value)}
                  placeholder="A single line about this painting — shown on cards and previews." />
                <div style={{ fontSize: 11, color: "var(--ink-mute)", marginTop: 4 }}>{(draft.short || "").length} characters · shown in gallery card hover state.</div>
              </div>

              <div style={{ gridColumn: "1 / -1", padding: "14px 16px", background: "var(--cream)", borderRadius: 4, borderLeft: "3px solid var(--gold)", fontSize: 12.5, color: "var(--ink-soft)", lineHeight: 1.7 }}>
                <strong style={{ color: "var(--ink)" }}>Checkout is automatic.</strong> Stripe checkout opens directly when a buyer clicks "Acquire". The price above plus the shipping above are what buyers pay. The painting is automatically marked "reserved" once a purchase completes.
              </div>

              <div style={{ gridColumn: "1 / -1", paddingTop: 12 }}>
                <button onClick={onDelete} style={{
                  background: "transparent", border: "1px solid #C0625A", color: "#A8453E",
                  padding: "10px 18px", borderRadius: 999, cursor: "pointer",
                  fontSize: 11, letterSpacing: "0.2em", textTransform: "uppercase", fontWeight: 500,
                }}>Delete this painting</button>
              </div>
            </div>
          )}

          {activeTab === "photos" && (
            <div>
              <div style={{ fontSize: 14, color: "var(--ink-soft)", marginBottom: 18, lineHeight: 1.6 }}>
                Up to <strong>five photographs</strong> per painting — the first is the main image used on the gallery, then close-up, brushwork, signed corner, room view. Images are automatically resized and uploaded to Firebase Storage.
              </div>

              {(uploading || uploadErr) && (
                <div style={{ marginBottom: 14, padding: "10px 14px", background: uploadErr ? "#FBEDE8" : "var(--cream)", border: "1px solid " + (uploadErr ? "#E0A89A" : "var(--line)"), color: uploadErr ? "#8C4034" : "var(--ink-soft)", fontSize: 13, borderRadius: 4 }}>
                  {uploadErr ? `Upload error: ${uploadErr}` : "Uploading photo…"}
                </div>
              )}

              <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 14 }}>
                {[0, 1, 2, 3, 4].map((i) => (
                  <ImageSlot
                    key={i}
                    label={["Main", "Close-up", "Brushwork", "Corner", "Detail"][i]}
                    image={(draft.images || [])[i]}
                    onPick={(f) => onFiles([f], i)}
                    onRemove={() => removeImage(i)}
                  />
                ))}
              </div>

              <div style={{ marginTop: 24, padding: 18, background: "var(--cream)", borderRadius: 4, fontSize: 12.5, color: "var(--ink-soft)", lineHeight: 1.7 }}>
                <strong style={{ color: "var(--ink)" }}>Tip —</strong> If no photographs are uploaded, the painting falls back to its colour-tuned placeholder on the public site. You can mix and match: add a Main photo only, or all five.
              </div>
            </div>
          )}

          {activeTab === "story" && (
            <div>
              <label className="lf-label">The Story</label>
              <textarea className="lf-input" rows={10} value={draft.story || ""} onChange={(e) => set("story", e.target.value)}
                placeholder="Where, when, why this painting was made. Two to four sentences in your voice."
                style={{ lineHeight: 1.7, fontSize: 15 }} />
              <div style={{ marginTop: 10, fontSize: 12, color: "var(--ink-mute)" }}>{(draft.story || "").length} characters · shown on the artwork page in quotation.</div>

              <div style={{ marginTop: 32, padding: 24, background: "var(--cream)", borderLeft: "3px solid var(--gold)" }}>
                <div style={{ fontSize: 10, letterSpacing: "0.22em", color: "var(--gold-deep)", textTransform: "uppercase" }}>Preview</div>
                <p style={{ marginTop: 10, fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 18, lineHeight: 1.6 }}>
                  "{draft.story || "Your story will appear here."}"
                </p>
              </div>
            </div>
          )}
        </div>
      </aside>
    </div>
  );
}

function Toggle({ label, checked, onChange }) {
  return (
    <label style={{ display: "inline-flex", alignItems: "center", gap: 10, cursor: "pointer", userSelect: "none" }}>
      <span style={{
        width: 40, height: 22, borderRadius: 999,
        background: checked ? "var(--gold)" : "var(--line)",
        position: "relative", transition: "background 200ms ease",
      }}>
        <span style={{
          position: "absolute", top: 2, left: checked ? 20 : 2,
          width: 18, height: 18, borderRadius: 999, background: "var(--warm-white)",
          transition: "left 200ms ease", boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
        }} />
      </span>
      <span style={{ fontSize: 13, color: "var(--ink)" }}>{label}</span>
      <input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} style={{ display: "none" }} />
    </label>
  );
}

function ImageSlot({ label, image, onPick, onRemove }) {
  const [drag, setDrag] = useState(false);
  const inputRef = useRef(null);
  return (
    <div>
      <div
        onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={(e) => {
          e.preventDefault(); setDrag(false);
          const f = e.dataTransfer.files[0]; if (f) onPick(f);
        }}
        onClick={() => inputRef.current && inputRef.current.click()}
        style={{
          aspectRatio: "4/5", position: "relative",
          border: "1.5px dashed " + (drag ? "var(--gold)" : "var(--line)"),
          background: image ? "transparent" : "var(--warm-white)",
          borderRadius: 4, cursor: "pointer", overflow: "hidden",
          transition: "border 180ms ease, background 180ms ease",
        }}
      >
        {image ? (
          <>
            <img src={image} alt={label} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
            <button onClick={(e) => { e.stopPropagation(); onRemove(); }} style={{
              position: "absolute", top: 6, right: 6,
              width: 26, height: 26, borderRadius: 999,
              background: "rgba(46,42,36,0.8)", color: "var(--warm-white)",
              border: "none", cursor: "pointer",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
            }}><Icon.Close /></button>
          </>
        ) : (
          <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", color: "var(--ink-mute)", gap: 8, padding: 8, textAlign: "center" }}>
            <Icon.Plus />
            <div style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase" }}>Upload</div>
          </div>
        )}
        <input ref={inputRef} type="file" accept="image/*,.heic,.heif,image/heic,image/heif" style={{ display: "none" }}
          onChange={(e) => { const f = e.target.files[0]; if (f) onPick(f); e.target.value = ""; }} />
      </div>
      <div style={{ marginTop: 6, fontSize: 10.5, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--ink-mute)", textAlign: "center" }}>{label}</div>
    </div>
  );
}

/* ============== Helpers ============== */

// Convert HEIC/HEIF files (default iPhone format) to JPEG so the rest of
// the upload pipeline (canvas downscale, Firebase Storage) can handle them.
// JPEG/PNG/WebP files pass through unchanged. Newer iPhone HEIC files
// contain a `tmap` HDR auxiliary image that browser libraries don't
// decode, so the conversion is done server-side in the convertHeic Cloud
// Function which uses a current libheif build.
async function ensureBrowserDecodable(file) {
  const name = (file.name || "").toLowerCase();
  const type = (file.type || "").toLowerCase();
  const isHeic = type.includes("heic") || type.includes("heif")
    || name.endsWith(".heic") || name.endsWith(".heif");
  if (!isHeic) return file;

  const arrayBuf = await file.arrayBuffer();
  const b64 = uint8ToBase64(new Uint8Array(arrayBuf));
  const call = window.lf.functions.httpsCallable("convertHeic");
  const result = await call({ data: b64 });
  const outB64 = result && result.data && result.data.data;
  if (!outB64) throw new Error("Server returned no JPEG data");
  const jpegBytes = base64ToUint8(outB64);
  const baseName = name.replace(/\.heic$|\.heif$/i, "") || "image";
  return new File([jpegBytes], `${baseName}.jpg`, { type: "image/jpeg" });
}

// Browser-safe base64 helpers (atob/btoa can't handle arbitrary binary in
// large strings without chunking — these chunk and stay reasonably fast).
function uint8ToBase64(bytes) {
  let binary = "";
  const chunk = 0x8000;
  for (let i = 0; i < bytes.length; i += chunk) {
    binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
  }
  return btoa(binary);
}
function base64ToUint8(b64) {
  const binary = atob(b64);
  const out = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
  return out;
}

async function downscaleToBlob(file, maxDim = 1600, quality = 0.85) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => {
      const img = new Image();
      img.onload = () => {
        const ratio = Math.min(1, maxDim / Math.max(img.width, img.height));
        const w = Math.round(img.width * ratio);
        const h = Math.round(img.height * ratio);
        const c = document.createElement("canvas");
        c.width = w; c.height = h;
        const ctx = c.getContext("2d");
        ctx.drawImage(img, 0, 0, w, h);
        c.toBlob((blob) => blob ? resolve(blob) : reject(new Error("Encode failed")), "image/jpeg", quality);
      };
      img.onerror = reject;
      img.src = r.result;
    };
    r.onerror = reject;
    r.readAsDataURL(file);
  });
}

window.AdminPage = AdminPage;
