// Florio Analytics — Labels tab (ported from js/pages/labels.js).
// Browse the labelled plant dataset (`avatar_labels`) and manage the pending
// labeling queue (`labeling_queue`). Two views:
//   • "labels" — grid of labelled photos, filterable per avatar, each card can
//                be reassigned to a different avatar via a Rive-preview modal.
//   • "queue"  — grid of pending queue items with checkbox multi-select,
//                single delete + batch delete.
// All Firestore queries / writes are preserved 1:1 from the old vanilla page,
// re-expressed as React + the new design kit. Real data only.
(function () {
  const { useState, useEffect, useRef, useCallback } = React;

  // ─── Constants (identical to old page) ───────────────────────────────────────
  const AVATAR_ORDER = [
    "monstera", "fern", "peace", "snake", "cactus",
    "palm", "bushy", "hanging", "orchid", "aloevera",
    "succulent", "bonsai", "anthurium", "prayer", "dracaena",
    "ficus", "banana", "clover", "coin",
  ];

  const DISPLAY_NAMES = {
    monstera: "Monstera", fern: "Fern", peace: "Peace Lily",
    snake: "Snake Plant", cactus: "Cactus", palm: "Palm",
    bushy: "Bushy", hanging: "Hanging", orchid: "Orchid",
    aloevera: "Aloe Vera", succulent: "Succulent", bonsai: "Bonsai",
    anthurium: "Anthurium", prayer: "Prayer Plant", dracaena: "Dracaena",
    ficus: "Ficus", banana: "Banana", clover: "Clover", coin: "Coin Plant",
  };

  const AVATAR_COLORS = {
    monstera: "#2d9a4e", fern: "#5bb87a", peace: "#a8b5c8",
    snake: "#d4b83e", cactus: "#c0392b", palm: "#2980b9",
    bushy: "#8e44ad", hanging: "#16a085", orchid: "#c2185b",
    aloevera: "#7cb342", succulent: "#e67e22", bonsai: "#6d4c41",
    anthurium: "#e53935", prayer: "#00838f", dracaena: "#546e7a",
    ficus: "#388e3c", banana: "#f9a825", clover: "#00897b",
    coin: "#9e9d24",
  };

  function fmt(n) {
    return (window.FlorioAPI && window.FlorioAPI.formatNumber) ? window.FlorioAPI.formatNumber(n) : String(n != null ? n : 0);
  }

  // ─── Rive previews (lazy import, cached at module scope) ─────────────────────
  let RiveLib = null;
  async function ensureRiveLib() {
    if (RiveLib) return RiveLib;
    try {
      const mod = await import("https://esm.sh/@rive-app/canvas@2.17.3");
      const Rive = mod.Rive || (mod.default && mod.default.Rive);
      const Layout = mod.Layout || (mod.default && mod.default.Layout);
      const Fit = mod.Fit || (mod.default && mod.default.Fit);
      const Alignment = mod.Alignment || (mod.default && mod.default.Alignment);
      RiveLib = { Rive, Layout, Fit, Alignment };
    } catch (err) {
      console.error("[Labels] Rive import failed:", err);
      RiveLib = { Rive: null };
    }
    return RiveLib;
  }

  // Mounts a Rive canvas (or a letter placeholder) for one avatar.
  // Paths get one extra "../" vs the old page because this file lives in app/.
  function RivePreview({ id, size = 64 }) {
    const slotRef = useRef(null);
    useEffect(() => {
      let cancelled = false;
      let riveInstance = null;
      const name = DISPLAY_NAMES[id] || id;
      const placeholder = () => {
        if (slotRef.current) {
          slotRef.current.innerHTML =
            '<div style="width:100%;height:100%;display:grid;place-items:center;font-family:var(--font-sans);font-weight:700;font-size:20px;color:var(--text-muted);background:var(--surface-sunken);border-radius:10px;">' +
            ((name && name[0]) || "?") + "</div>";
        }
      };
      (async () => {
        const lib = await ensureRiveLib();
        if (cancelled || !slotRef.current) return;
        if (!lib.Rive) { placeholder(); return; }
        const canvas = document.createElement("canvas");
        canvas.width = 160; canvas.height = 160;
        canvas.style.width = "100%"; canvas.style.height = "100%";
        slotRef.current.innerHTML = "";
        slotRef.current.appendChild(canvas);
        const src = "../rive/" + id + "/" + id + "_4.riv";
        try {
          riveInstance = new lib.Rive({
            src, canvas, autoplay: true, stateMachines: "State Machine 1",
            layout: new lib.Layout({ fit: lib.Fit.Contain, alignment: lib.Alignment.Center }),
            onLoadError: () => placeholder(),
          });
        } catch (err) {
          console.error("[Labels] Rive constructor threw for", id, err);
          placeholder();
        }
      })();
      return () => {
        cancelled = true;
        try { if (riveInstance) riveInstance.cleanup(); } catch (err) { /* noop */ }
      };
    }, [id]);
    return <div ref={slotRef} style={{ width: size, height: size, display: "grid", placeItems: "center" }} />;
  }

  // ─── Shared style helpers (match the other ported tabs) ──────────────────────
  const cardStyle = {
    background: "var(--surface-card)", border: "1px solid var(--border-subtle)",
    borderRadius: "var(--radius-2xl)", boxShadow: "var(--shadow-card)", padding: 22,
  };
  const toggleBtn = (active) => ({
    padding: "8px 16px", borderRadius: "var(--radius-md)", cursor: "pointer",
    border: "1px solid " + (active ? "var(--app-accent)" : "var(--border-default)"),
    background: active ? "color-mix(in srgb, var(--app-accent) 12%, transparent)" : "var(--surface-card)",
    color: active ? "var(--app-accent)" : "var(--text-secondary)",
    fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13, whiteSpace: "nowrap",
  });
  const secondaryBtn = (extra) => Object.assign({
    padding: "8px 14px", borderRadius: "var(--radius-md)", border: "1px solid var(--border-default)",
    background: "var(--surface-card)", color: "var(--text-secondary)", cursor: "pointer",
    fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 13, whiteSpace: "nowrap",
  }, extra || {});

  // ─── Empty / loading helpers ─────────────────────────────────────────────────
  function Empty({ note }) {
    const I = window.HyperIcons;
    return (
      <div style={{ gridColumn: "1 / -1", minHeight: 200, display: "grid", placeItems: "center", textAlign: "center", gap: 10, padding: 32 }}>
        <span style={{ color: "var(--text-muted)", display: "flex" }}>{I && I.leaf ? I.leaf({ size: 26 }) : null}</span>
        <span style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-muted)" }}>{note || "No data yet"}</span>
      </div>
    );
  }

  // ─── Reassign modal ──────────────────────────────────────────────────────────
  function ReassignModal({ item, onClose, onReassign }) {
    const [savingId, setSavingId] = useState(null);
    if (!item) return null;
    return (
      <div
        onClick={(ev) => { if (ev.target === ev.currentTarget) onClose(); }}
        style={{
          position: "fixed", inset: 0, zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center",
          background: "rgba(15,20,30,0.55)", padding: 24,
        }}
      >
        <div style={{ ...cardStyle, width: "min(880px, 100%)", maxHeight: "88vh", overflowY: "auto" }}>
          <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, marginBottom: 18 }}>
            <div>
              <div style={{ fontFamily: "var(--font-mono-display)", fontSize: 10, letterSpacing: ".14em", textTransform: "uppercase", color: "var(--text-muted)", marginBottom: 6 }}>Reassign avatar</div>
              <div style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 17, color: "var(--text-primary)" }}>Pick the closest match</div>
            </div>
            <button onClick={onClose} style={secondaryBtn({ padding: "6px 12px", fontSize: 18, lineHeight: 1 })}>&#10005;</button>
          </div>

          <div style={{ display: "flex", gap: 22, flexWrap: "wrap" }}>
            <div style={{ flexShrink: 0 }}>
              <img src={item.photoUrl} alt="" style={{ width: 220, maxWidth: "100%", borderRadius: "var(--radius-lg)", display: "block", objectFit: "cover" }} />
            </div>
            <div style={{
              flex: 1, minWidth: 280, display: "grid",
              gridTemplateColumns: "repeat(auto-fill, minmax(96px, 1fr))", gap: 10, alignContent: "flex-start",
            }}>
              {AVATAR_ORDER.map((id) => {
                const name = DISPLAY_NAMES[id] || id;
                const color = AVATAR_COLORS[id] || "#999";
                const current = item.avatar === id;
                const saving = savingId === id;
                return (
                  <button
                    key={id}
                    disabled={savingId != null}
                    onClick={async () => {
                      if (current) { onClose(); return; }
                      setSavingId(id);
                      await onReassign(item.id, id);
                    }}
                    style={{
                      display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
                      padding: "10px 6px", cursor: savingId != null ? "default" : "pointer",
                      borderRadius: "var(--radius-md)",
                      border: "2px solid " + (current ? color : "var(--border-subtle)"),
                      background: current ? "color-mix(in srgb, " + color + " 10%, var(--surface-card))" : "var(--surface-card)",
                    }}
                  >
                    <RivePreview id={id} size={64} />
                    <span style={{ fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 11.5, color: "var(--text-secondary)", textAlign: "center" }}>
                      {saving ? "Saving…" : name}
                    </span>
                  </button>
                );
              })}
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ─── Main component ──────────────────────────────────────────────────────────
  function Labels({ range }) { // eslint-disable-line no-unused-vars
    const [loading, setLoading] = useState(true);
    const [labelDocs, setLabelDocs] = useState([]);
    const [queueDocs, setQueueDocs] = useState([]);
    const [view, setView] = useState("labels"); // "labels" | "queue"
    const [activeFilter, setActiveFilter] = useState("all");
    const [selectedIds, setSelectedIds] = useState(() => new Set());
    const [reassignItem, setReassignItem] = useState(null);
    const [batchBusy, setBatchBusy] = useState(false);

    const FB = () => window.FlorioDB;

    // ── Data loading (mirror of old loadData) ──
    const loadData = useCallback(async () => {
      const { db, collection, query, where, getDocs } = FB();
      setLoading(true);
      try {
        const [labelsSnap, queueSnap] = await Promise.all([
          getDocs(collection(db, "avatar_labels")),
          getDocs(query(collection(db, "labeling_queue"), where("status", "==", "pending"))),
        ]);

        const labels = [];
        labelsSnap.forEach((d) => {
          const data = d.data();
          if (data.skipped || data.notHouseplant) return;
          if (!data.photoUrl) return;
          // Firestore field is `chosenAvatar`; normalize to `avatar` for display.
          const avatar = data.chosenAvatar || data.avatar || null;
          labels.push(Object.assign({ id: d.id }, data, { avatar }));
        });
        labels.sort((a, b) => {
          const ta = (a.createdAt && a.createdAt.toMillis && a.createdAt.toMillis()) || 0;
          const tb = (b.createdAt && b.createdAt.toMillis && b.createdAt.toMillis()) || 0;
          return tb - ta;
        });

        const queue = [];
        queueSnap.forEach((d) => { queue.push(Object.assign({ id: d.id }, d.data())); });
        queue.sort((a, b) => {
          const ta = (a.createdAt && a.createdAt.toMillis && a.createdAt.toMillis()) || 0;
          const tb = (b.createdAt && b.createdAt.toMillis && b.createdAt.toMillis()) || 0;
          return tb - ta;
        });

        setLabelDocs(labels);
        setQueueDocs(queue);
      } catch (err) {
        console.error("[Labels] loadData failed:", err);
        setLabelDocs([]);
        setQueueDocs([]);
      } finally {
        setLoading(false);
      }
    }, []);

    // ── Boot ──
    useEffect(() => {
      let alive = true;
      (async () => {
        await window.FlorioReady;
        if (!alive) return;
        ensureRiveLib(); // preload in background
        await loadData();
      })();
      return () => { alive = false; };
    }, [loadData]);

    // Clear selection when leaving the queue view.
    useEffect(() => {
      if (view !== "queue") setSelectedIds(new Set());
    }, [view]);

    // ── Reassign (avatar_labels.chosenAvatar) ──
    const reassign = useCallback(async (docId, newAvatar) => {
      const { db, doc, updateDoc } = FB();
      try {
        await updateDoc(doc(db, "avatar_labels", docId), { chosenAvatar: newAvatar });
        setLabelDocs((prev) => prev.map((d) => (d.id === docId ? Object.assign({}, d, { avatar: newAvatar, chosenAvatar: newAvatar }) : d)));
      } catch (err) {
        console.error("[Labels] reassign failed:", err);
        alert("Save failed: " + err.message);
      } finally {
        setReassignItem(null);
      }
    }, []);

    // ── Single queue delete ──
    const deleteSingle = useCallback(async (id) => {
      if (!confirm("Delete this item from the queue?")) return;
      const { db, doc, deleteDoc } = FB();
      try {
        await deleteDoc(doc(db, "labeling_queue", id));
        setQueueDocs((prev) => prev.filter((d) => d.id !== id));
        setSelectedIds((prev) => { const n = new Set(prev); n.delete(id); return n; });
      } catch (err) {
        console.error("[Labels] delete failed:", err);
        alert("Delete failed: " + err.message);
      }
    }, []);

    // ── Batch delete (chunks of 500, like old page) ──
    const batchDelete = useCallback(async () => {
      if (selectedIds.size === 0) return;
      if (!confirm("Delete " + selectedIds.size + " items from the queue?")) return;
      const { db, doc, writeBatch } = FB();
      setBatchBusy(true);
      try {
        const ids = [...selectedIds];
        for (let i = 0; i < ids.length; i += 500) {
          const batch = writeBatch(db);
          ids.slice(i, i + 500).forEach((id) => batch.delete(doc(db, "labeling_queue", id)));
          await batch.commit();
        }
        const removed = new Set(selectedIds);
        setQueueDocs((prev) => prev.filter((d) => !removed.has(d.id)));
        setSelectedIds(new Set());
      } catch (err) {
        console.error("[Labels] batch delete failed:", err);
        alert("Batch delete failed: " + err.message);
      } finally {
        setBatchBusy(false);
      }
    }, [selectedIds]);

    const toggleSelect = useCallback((id) => {
      setSelectedIds((prev) => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; });
    }, []);

    // ── Loading state ──
    if (loading) {
      return (
        <div style={{ display: "grid", placeItems: "center", minHeight: "60vh" }}>
          <div className="pa-spin" style={{ width: 38, height: 38, borderRadius: "50%", border: "3px solid var(--border-default)", borderTopColor: "var(--app-accent)" }} />
        </div>
      );
    }

    const I = window.HyperIcons;

    // ── Filtered label items ──
    const labelItems = activeFilter === "all" ? labelDocs : labelDocs.filter((d) => d.avatar === activeFilter);

    // ── Per-avatar counts (for filter chips) ──
    const perAvatar = {};
    for (const d of labelDocs) { if (d.avatar) perAvatar[d.avatar] = (perAvatar[d.avatar] || 0) + 1; }

    const gridStyle = {
      display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 16,
    };

    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
        {/* ── Header: view toggle + counts ── */}
        <div style={{ ...cardStyle, padding: 16, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
          <div style={{ display: "flex", gap: 10 }}>
            <button style={toggleBtn(view === "labels")} onClick={() => setView("labels")}>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
                {I && I.grid ? I.grid({ size: 15 }) : null}
                Labels · {fmt(labelDocs.length)}
              </span>
            </button>
            <button style={toggleBtn(view === "queue")} onClick={() => setView("queue")}>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
                {I && I.layers ? I.layers({ size: 15 }) : null}
                Queue · {fmt(queueDocs.length)}
              </span>
            </button>
          </div>
        </div>

        {/* ── Filter chips (labels view only) ── */}
        {view === "labels" && (
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <button
              onClick={() => setActiveFilter("all")}
              style={{
                display: "inline-flex", alignItems: "center", gap: 7, padding: "7px 13px",
                borderRadius: 99, cursor: "pointer",
                border: "1px solid " + (activeFilter === "all" ? "var(--app-accent)" : "var(--border-default)"),
                background: activeFilter === "all" ? "color-mix(in srgb, var(--app-accent) 12%, transparent)" : "var(--surface-card)",
                color: activeFilter === "all" ? "var(--app-accent)" : "var(--text-secondary)",
                fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 12.5,
              }}
            >
              All <span style={{ fontFamily: "var(--font-mono)", opacity: 0.8 }}>{fmt(labelDocs.length)}</span>
            </button>
            {AVATAR_ORDER.map((id) => {
              const name = DISPLAY_NAMES[id] || id;
              const color = AVATAR_COLORS[id] || "#999";
              const active = activeFilter === id;
              const count = perAvatar[id] || 0;
              return (
                <button
                  key={id}
                  onClick={() => setActiveFilter(id)}
                  style={{
                    display: "inline-flex", alignItems: "center", gap: 7, padding: "7px 13px",
                    borderRadius: 99, cursor: "pointer",
                    border: "1px solid " + (active ? color : "var(--border-default)"),
                    background: active ? "color-mix(in srgb, " + color + " 14%, var(--surface-card))" : "var(--surface-card)",
                    color: active ? "var(--text-primary)" : "var(--text-secondary)",
                    fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 12.5,
                  }}
                >
                  <span style={{ width: 9, height: 9, borderRadius: "50%", background: color, flexShrink: 0 }} />
                  {name} <span style={{ fontFamily: "var(--font-mono)", opacity: 0.8 }}>{fmt(count)}</span>
                </button>
              );
            })}
          </div>
        )}

        {/* ── Batch toolbar (queue view only) ── */}
        {view === "queue" && (
          <div style={{ ...cardStyle, padding: 14, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
            <span style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 14, color: "var(--text-primary)" }}>
              {fmt(selectedIds.size)} selected
            </span>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <button style={secondaryBtn()} disabled={queueDocs.length === 0} onClick={() => setSelectedIds(new Set(queueDocs.map((d) => d.id)))}>Select all</button>
              <button style={secondaryBtn()} disabled={selectedIds.size === 0} onClick={() => setSelectedIds(new Set())}>Deselect</button>
              <button
                style={secondaryBtn({
                  border: "1px solid var(--negative)", color: "#fff",
                  background: selectedIds.size === 0 || batchBusy ? "color-mix(in srgb, var(--negative) 45%, #fff)" : "var(--negative)",
                  cursor: selectedIds.size === 0 || batchBusy ? "default" : "pointer",
                })}
                disabled={selectedIds.size === 0 || batchBusy}
                onClick={batchDelete}
              >
                {batchBusy ? "Deleting…" : "Delete selected"}
              </button>
            </div>
          </div>
        )}

        {/* ── Grid ── */}
        {view === "labels" ? (
          <div style={gridStyle}>
            {labelItems.length === 0 ? (
              <Empty note={"No labeled photos" + (activeFilter !== "all" ? " for " + (DISPLAY_NAMES[activeFilter] || activeFilter) : "") + "."} />
            ) : labelItems.map((item) => {
              const name = DISPLAY_NAMES[item.avatar] || item.avatar || "?";
              const color = AVATAR_COLORS[item.avatar] || "#999";
              const hasEmbed = item.embeddingVersion === "v2";
              return (
                <div key={item.id} style={{ ...cardStyle, padding: 0, overflow: "hidden", display: "flex", flexDirection: "column" }}>
                  <div style={{ position: "relative", width: "100%", aspectRatio: "1 / 1", background: "var(--surface-sunken)" }}>
                    <img src={item.photoUrl} alt="" loading="lazy" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {hasEmbed && (
                      <span style={{
                        position: "absolute", top: 8, left: 8, padding: "3px 8px", borderRadius: 99,
                        background: "rgba(15,20,30,0.62)", color: "#fff",
                        fontFamily: "var(--font-mono)", fontSize: 9.5, letterSpacing: ".06em", textTransform: "uppercase",
                      }}>embedded</span>
                    )}
                  </div>
                  <div style={{ padding: "10px 12px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
                    <span style={{ display: "inline-flex", alignItems: "center", gap: 7, minWidth: 0 }}>
                      <span style={{ width: 9, height: 9, borderRadius: "50%", background: color, flexShrink: 0 }} />
                      <span style={{ fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 13, color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</span>
                    </span>
                    <button
                      title="Reassign avatar"
                      onClick={() => setReassignItem(item)}
                      style={secondaryBtn({ padding: "5px 9px", fontSize: 15, lineHeight: 1, flexShrink: 0 })}
                    >&#8635;</button>
                  </div>
                </div>
              );
            })}
          </div>
        ) : (
          <div style={gridStyle}>
            {queueDocs.length === 0 ? (
              <Empty note="Queue is empty." />
            ) : queueDocs.map((item) => {
              const selected = selectedIds.has(item.id);
              return (
                <div
                  key={item.id}
                  onClick={() => toggleSelect(item.id)}
                  style={{
                    ...cardStyle, padding: 0, overflow: "hidden", display: "flex", flexDirection: "column", cursor: "pointer",
                    border: "2px solid " + (selected ? "var(--app-accent)" : "var(--border-subtle)"),
                  }}
                >
                  <div style={{ position: "relative", width: "100%", aspectRatio: "1 / 1", background: "var(--surface-sunken)" }}>
                    <img src={item.photoUrl} alt="" loading="lazy" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    <span style={{ position: "absolute", top: 8, left: 8 }}>
                      <input
                        type="checkbox"
                        checked={selected}
                        onClick={(ev) => ev.stopPropagation()}
                        onChange={() => toggleSelect(item.id)}
                        style={{ width: 18, height: 18, cursor: "pointer", accentColor: "var(--app-accent)" }}
                      />
                    </span>
                  </div>
                  <div style={{ padding: "10px 12px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
                    <span style={{ fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 12.5, color: "var(--text-secondary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                      {item.species || "unknown"}
                    </span>
                    <button
                      title="Delete"
                      onClick={(ev) => { ev.stopPropagation(); deleteSingle(item.id); }}
                      style={secondaryBtn({ padding: "5px 9px", fontSize: 14, lineHeight: 1, flexShrink: 0, color: "var(--negative)" })}
                    >&#10005;</button>
                  </div>
                </div>
              );
            })}
          </div>
        )}

        {reassignItem && (
          <ReassignModal item={reassignItem} onClose={() => setReassignItem(null)} onReassign={reassign} />
        )}
      </div>
    );
  }

  (window.FlorioTabs = window.FlorioTabs || {}).labels = Labels;
})();
