/* =========================================================================
 *  CTWCAD — File detail drawer
 * =======================================================================*/
function FileDrawer({ fileId, onClose }) {
  const [file, setFile] = useState(null);
  const [versions, setVersions] = useState([]);
  const [tab, setTab] = useState("info");
  // Local context-menu state so right-click inside the drawer (or the
  // "More" button) opens the file's action list without depending on
  // whoever owns the file browser.
  const [menu, setMenu] = useState(null);
  const M = window.FramerMotion.motion;
  const A = window.FramerMotion.AnimatePresence;

  useEffect(() => {
    if (!fileId) return;
    setFile(null);setVersions([]);setTab("info");
    // Guard against the user clicking file B before file A's getFile
    // resolves — without this flag, A's response would clobber B's
    // state and the drawer would display A while fileId is B.
    let cancelled = false;
    window.api.getFile(fileId).then((f) => { if (!cancelled) setFile(f); });
    window.api.listVersions(fileId).then((v) => { if (!cancelled) setVersions(v); });
    // Wave-3: bump lastOpenedAt so the dashboard's recent list reflects
    // what the user actually pulled up. Fire-and-forget; ignore errors.
    try { window.api.touchFileOpen?.(fileId); } catch {}
    return () => { cancelled = true; };
  }, [fileId]);

  // Helper: dispatch the same item-action event the file browser uses,
  // so all the trash / move / rename / download plumbing in app.jsx
  // applies here too — single source of truth.
  const fireAction = (action) => {
    if (!file) return;
    window.dispatchEvent(new CustomEvent("ctwcad:item-action", {
      detail: { action, kind: "file", file },
    }));
  };

  const openMenuAt = (x, y) => {
    setMenu({
      kind: "file", file,
      x: Math.min(x, window.innerWidth - 240),
      y: Math.min(y, window.innerHeight - 360),
      ox: 0, oy: 0,
    });
  };

  return (
    <A>
      {fileId &&
      <>
          <M.div
          className="ct-backdrop"
          initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
          transition={{ duration: 0.2 }}
          onClick={onClose} />

          <M.aside
          className="ct-drawer"
          initial={{ x: 480, opacity: 0 }}
          animate={{ x: 0, opacity: 1 }}
          exit={{ x: 480, opacity: 0 }}
          transition={{ type: "spring", stiffness: 280, damping: 32 }}
          onContextMenu={(e) => {
            // Right-click anywhere inside the drawer opens the file's
            // action menu. preventDefault keeps the browser menu away.
            e.preventDefault();
            if (file) openMenuAt(e.clientX, e.clientY);
          }}>

            <header className="ct-drawer-head">
              <button className="ct-iconbtn" onClick={onClose} aria-label="Close"><Icon name="x" size={16} /></button>
              <div className="ct-drawer-title">
                <div className="ct-drawer-title-name">{file?.name || "Loading…"}</div>
                {file && <div className="ct-drawer-title-sub ct-mono ct-dim">.{file.kind} · v{file.versionCount} · {fmtBytes(file.sizeBytes)}</div>}
              </div>
              <div className="ct-drawer-tools">
                <button className="ct-iconbtn" title="Download"
                        onClick={() => fireAction("download")}
                        disabled={!file}>
                  <Icon name="download" size={14} />
                </button>
                <button className="ct-iconbtn" title="Share"
                        onClick={() => { setTab("share"); }}
                        disabled={!file}>
                  <Icon name="share-2" size={14} />
                </button>
                <button className="ct-iconbtn" title="More actions"
                        onClick={(e) => {
                          if (!file) return;
                          const r = e.currentTarget.getBoundingClientRect();
                          openMenuAt(r.left, r.bottom + 4);
                        }}
                        disabled={!file}>
                  <Icon name="more-horizontal" size={14} />
                </button>
              </div>
            </header>

            {file ?
          <>
                <div className="ct-drawer-preview">
                  <FilePreview file={file} height={260} />
                </div>

                <nav className="ct-tabs">
                  {["info", "versions", "comments", "activity", "share"].map((t) =>
              <button key={t}
              className={"ct-tab " + (tab === t ? "is-active" : "")}
              onClick={() => setTab(t)}>
                      {t[0].toUpperCase() + t.slice(1)}
                      {tab === t && <window.FramerMotion.motion.span layoutId="tab-underline" className="ct-tab-bar" />}
                    </button>
              )}
                </nav>

                <div className="ct-drawer-body">
                  {tab === "info" && <InfoTab file={file} />}
                  {tab === "versions" && <VersionsTab file={file} versions={versions}
                    onContext={(e, v) => {
                      e.preventDefault();
                      e.stopPropagation();
                      setMenu({
                        kind: "version",
                        version: v, file,
                        x: Math.min(e.clientX, window.innerWidth - 240),
                        y: Math.min(e.clientY, window.innerHeight - 200),
                        ox: 0, oy: 0,
                      });
                    }} />}
                  {tab === "comments" && <CommentsTab file={file} />}
                  {tab === "activity" && <ActivityTab file={file} />}
                  {tab === "share" && <ShareTab file={file} onUpdate={setFile} />}
                </div>

                <footer className="ct-drawer-foot">
                  <OpenInCtwcadButton fileId={file.id} kind={file.kind} />
                </footer>
              </> :

          <div className="ct-skeleton-block" />
          }
          </M.aside>
          <ContextMenu menu={menu} onClose={() => setMenu(null)} />
        </>
      }
    </A>);

}

/* =========================================================================
 *  Info tab — polished file properties panel.
 * =======================================================================*/
function InfoTab({ file }) {
  const [showPath, setShowPath] = useState(false);
  // Owner profile lookup so we can render the name + avatar properly.
  // Best-effort — falls back to the raw uid if the lookup fails.
  const [owner, setOwner] = useState(null);
  useEffect(() => {
    let cancelled = false;
    const ownerId = file.ownerUid || file.ownerId;
    if (!ownerId) return;
    if (!window.firebaseDb || !window.firebaseFns) return;
    const fb = window.firebaseFns;
    fb.getDoc(fb.doc(window.firebaseDb, "users", ownerId))
      .then((snap) => { if (!cancelled && snap.exists()) setOwner({ id: snap.id, ...snap.data() }); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [file.ownerUid, file.ownerId]);

  const kindLabel = (file.kind || "other").toUpperCase();
  // STEP / IGES extras: not in the schema today, but show a graceful em-dash.
  const isStepLike = ["step", "stp", "iges", "igs"].includes(file.kind);

  return (
    <div className="ct-props">
      <div className="ct-prop">
        <div className="ct-prop-key">Format</div>
        <div className="ct-prop-val">
          <span className="ct-prop-kind ct-mono">.{file.kind}</span>
          <span className="ct-dim">{kindLabel}</span>
        </div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">Size</div>
        <div className="ct-prop-val ct-mono">{fmtBytes(file.sizeBytes)}</div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">Versions</div>
        <div className="ct-prop-val ct-mono">{file.versionCount || 1}</div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">Created</div>
        <div className="ct-prop-val" title={fmtDate(file.createdAt)}>{fmtRelative(file.createdAt)}</div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">Modified</div>
        <div className="ct-prop-val" title={fmtDate(file.updatedAt)}>{fmtRelative(file.updatedAt)}</div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">Owner</div>
        <div className="ct-prop-val ct-prop-owner">
          <Avatar user={owner || { initials: "?", avatarHue: 200 }} size={22} />
          <span>{owner?.name || owner?.email || (file.ownerUid || file.ownerId || "—")}</span>
        </div>
      </div>
      {isStepLike && (
        <>
          <div className="ct-prop">
            <div className="ct-prop-key">Triangles</div>
            <div className="ct-prop-val ct-dim">—</div>
          </div>
          <div className="ct-prop">
            <div className="ct-prop-key">Units</div>
            <div className="ct-prop-val ct-dim">—</div>
          </div>
        </>
      )}
      <div className="ct-prop ct-prop-tags-row">
        <div className="ct-prop-key">Tags</div>
        <div className="ct-prop-val">
          <FileTagsEditor file={file} />
        </div>
      </div>
      <div className="ct-prop">
        <div className="ct-prop-key">File ID</div>
        <div className="ct-prop-val ct-mono ct-dim">{file.id}</div>
      </div>
      <div className="ct-prop ct-prop-collapsible">
        <button className="ct-prop-toggle" onClick={() => setShowPath(v => !v)}>
          <Icon name={showPath ? "chevron-down" : "chevron-right"} size={12} />
          <span>{showPath ? "Hide" : "Show"} storage path</span>
        </button>
        {showPath && (
          <div className="ct-prop-path ct-mono">{file.storagePath || "—"}</div>
        )}
      </div>
    </div>
  );
}

/* =========================================================================
 *  Versions tab — polished timeline with restore + commit-style messages.
 * =======================================================================*/
function VersionsTab({ file, versions, onContext }) {
  const isCurrent = (v) => file && v.number === file.currentVersion;
  return (
    <ol className="ct-timeline">
      {versions.map((v, idx) => {
        const sourceLabel = v.source === "ctwcad" ? "CTWCAD desktop"
                          : v.source === "restore" ? "Restored"
                          : v.source === "web" ? "Web upload"
                          : (v.source || "Unknown");
        const sourceIcon = v.source === "ctwcad" ? "monitor"
                         : v.source === "restore" ? "rotate-ccw"
                         : "upload";
        return (
          <li key={v.id} className={"ct-tl-row " + (isCurrent(v) ? "is-current" : "")}
              onContextMenu={(e) => onContext?.(e, v)}>
            <div className="ct-tl-spine">
              <div className={"ct-tl-dot " + (isCurrent(v) ? "is-current" : "")}>
                <Icon name={sourceIcon} size={10} />
              </div>
              {idx < versions.length - 1 && <div className="ct-tl-line" />}
            </div>
            <div className="ct-tl-card">
              <div className="ct-tl-head">
                <span className="ct-tl-num ct-mono">v{v.number}</span>
                <strong className="ct-tl-msg">{v.message || "Snapshot"}</strong>
                {isCurrent(v) && <span className="ct-tl-pill">Current</span>}
              </div>
              <div className="ct-tl-meta ct-dim">
                <span title={fmtDate(v.createdAt)}>{fmtRelative(v.createdAt)}</span>
                <span className="ct-dot" />
                <span>{sourceLabel}</span>
                <span className="ct-dot" />
                <span className="ct-mono">{fmtBytes(v.sizeBytes)}</span>
              </div>
              {!isCurrent(v) && (
                <div className="ct-tl-actions">
                  <button className="ct-ghostbtn" onClick={(e) => {
                    e.stopPropagation();
                    window.dispatchEvent(new CustomEvent("ctwcad:item-action", {
                      detail: { action: "restore-version", kind: "version", version: v, file },
                    }));
                  }}>
                    <Icon name="rotate-ccw" size={12} /> Restore this version
                  </button>
                  {v.downloadURL && (
                    <a className="ct-ghostbtn" href={v.downloadURL} target="_blank" rel="noopener" download>
                      <Icon name="download" size={12} /> Download
                    </a>
                  )}
                </div>
              )}
            </div>
          </li>
        );
      })}
      {(!versions || versions.length === 0) && (
        <li className="ct-tl-empty ct-mono ct-dim">
          No version history yet.
        </li>
      )}
    </ol>
  );
}

/* =========================================================================
 *  Comments tab — live discussion per file.
 * =======================================================================*/
function CommentsTab({ file }) {
  const [rows, setRows] = useState(null);  // null = loading, [] = empty
  const [draft, setDraft] = useState("");
  const [editing, setEditing] = useState(null);  // commentId being edited
  const [editDraft, setEditDraft] = useState("");
  const [busy, setBusy] = useState(false);
  const meUid = window.firebaseAuth?.currentUser?.uid || null;
  const seenIdsRef = useRef(null);

  useEffect(() => {
    setRows(null);
    seenIdsRef.current = null;
    if (!file?.id) return;
    // Live snapshot: any comment a teammate posts shows up immediately,
    // and we toast the user about new ones from somebody else.
    const unsub = window.api.watchComments(file.id, (next) => {
      if (seenIdsRef.current) {
        const knownIds = seenIdsRef.current;
        for (const r of next) {
          if (!knownIds.has(r.id) && r.authorUid !== meUid) {
            // New comment arrived from a teammate — surface a toast so
            // the user knows even if they're scrolled away.
            window.dispatchEvent(new CustomEvent("ctwcad:toast", {
              detail: {
                message: `${r.authorName || "Someone"} commented on ${file.name}`,
                icon: "message-circle",
              },
            }));
          }
        }
      }
      seenIdsRef.current = new Set(next.map(r => r.id));
      setRows(next);
    });
    return () => { try { unsub?.(); } catch {} };
  }, [file?.id, meUid, file?.name]);

  const post = async () => {
    const txt = draft.trim();
    if (!txt) return;
    setBusy(true);
    try {
      await window.api.postComment(file.id, txt);
      setDraft("");
    } catch (e) {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: { message: "Couldn't post comment: " + (e?.code || e?.message || "error"), icon: "alert-circle" },
      }));
    }
    setBusy(false);
  };

  const saveEdit = async (c) => {
    const txt = editDraft.trim();
    if (!txt) return;
    try {
      await window.api.editComment(file.id, c.id, txt);
      setEditing(null);
      setEditDraft("");
    } catch (e) {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: { message: "Couldn't save edit", icon: "alert-circle" },
      }));
    }
  };

  const remove = async (c) => {
    if (!confirm("Delete this comment?")) return;
    try { await window.api.deleteComment(file.id, c.id); }
    catch (e) {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: { message: "Couldn't delete", icon: "alert-circle" },
      }));
    }
  };

  return (
    <div className="ct-comments">
      <div className="ct-comment-compose">
        <textarea
          className="ct-comment-input"
          placeholder="Add a comment…"
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={(e) => {
            // Cmd/Ctrl+Enter posts.
            if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
              e.preventDefault(); post();
            }
          }}
          rows={2}
        />
        <div className="ct-comment-compose-row">
          <span className="ct-mono ct-dim" style={{ fontSize: 11 }}>⌘↵ to post</span>
          <button className="ct-cta-cad ct-comment-post" onClick={post}
                  disabled={busy || !draft.trim()}>
            <Icon name="send" size={12} />
            <span>Post</span>
          </button>
        </div>
      </div>

      {rows === null && <div className="ct-mono ct-dim" style={{ padding: "10px 0" }}>Loading…</div>}
      {rows !== null && rows.length === 0 && (
        <div className="ct-comment-empty ct-dim">
          <Icon name="message-circle" size={16} />
          <span>No comments yet. Start the conversation.</span>
        </div>
      )}
      <ul className="ct-comment-list">
        {(rows || []).map((c) => {
          const mine = c.authorUid && meUid && c.authorUid === meUid;
          const isEditing = editing === c.id;
          return (
            <li key={c.id} className="ct-comment">
              <Avatar
                user={{ initials: (c.authorName || "?")[0]?.toUpperCase() || "?",
                        avatarHue: c.authorAvatarHue ?? 200 }}
                size={28}
              />
              <div className="ct-comment-body">
                <div className="ct-comment-head">
                  <strong>{c.authorName || "Someone"}</strong>
                  <span className="ct-mono ct-dim" title={fmtDate(c.createdAt)}>
                    {fmtRelative(c.createdAt)}
                  </span>
                  {c.editedAt && <span className="ct-dim" style={{ fontSize: 11 }}>(edited)</span>}
                </div>
                {isEditing ? (
                  <>
                    <textarea
                      className="ct-comment-input"
                      value={editDraft}
                      onChange={(e) => setEditDraft(e.target.value)}
                      rows={2}
                    />
                    <div className="ct-comment-actions">
                      <button className="ct-ghostbtn" onClick={() => saveEdit(c)}>Save</button>
                      <button className="ct-ghostbtn" onClick={() => { setEditing(null); setEditDraft(""); }}>Cancel</button>
                    </div>
                  </>
                ) : (
                  <>
                    <div className="ct-comment-text">{c.text}</div>
                    {mine && (
                      <div className="ct-comment-actions">
                        <button className="ct-comment-link" onClick={() => { setEditing(c.id); setEditDraft(c.text); }}>
                          Edit
                        </button>
                        <button className="ct-comment-link is-danger" onClick={() => remove(c)}>
                          Delete
                        </button>
                      </div>
                    )}
                  </>
                )}
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

function ActivityTab({ file }) {
  return (
    <div className="ct-activity-mini">
      <div className="ct-event">
        <Icon name="git-commit" size={14} />
        <div>
          <div>New version saved</div>
          <div className="ct-dim ct-mono">CTWCAD · Seth's PC · {fmtRelative(file.updatedAt)}</div>
        </div>
      </div>
      <div className="ct-event">
        <Icon name="upload" size={14} />
        <div>
          <div>Uploaded via web</div>
          <div className="ct-dim ct-mono">Web · {fmtRelative(file.createdAt)}</div>
        </div>
      </div>
    </div>);

}

/* =========================================================================
 *  Share tab — public link toggle + copy-to-clipboard.
 * =======================================================================*/
function ShareTab({ file, onUpdate }) {
  const isPublic = file.shareState === "public-read" || file.shareState === "link";
  const [busy, setBusy] = useState(false);
  const [copied, setCopied] = useState(false);

  const shareUrl = useMemo(() => {
    if (!isPublic || !file.shareToken) return "";
    const base = window.location.origin;
    return `${base}/?file=${file.id}&share=${file.shareToken}`;
  }, [isPublic, file.id, file.shareToken]);

  const toggle = async (next) => {
    setBusy(true);
    try {
      const updated = await window.api.setFileShareState(
        file.id,
        next ? "public-read" : "private"
      );
      if (updated) onUpdate?.(updated);
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: {
          message: next ? "Share link enabled" : "Share link disabled",
          icon: next ? "link" : "lock",
        },
      }));
    } catch (e) {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: { message: "Couldn't update sharing: " + (e?.code || e?.message || "error"), icon: "alert-circle" },
      }));
    }
    setBusy(false);
  };

  const copy = async () => {
    if (!shareUrl) return;
    try {
      await navigator.clipboard.writeText(shareUrl);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: { message: "Couldn't copy — check clipboard permissions", icon: "alert-circle" },
      }));
    }
  };

  return (
    <div className="ct-share">
      <div className="ct-share-head">
        <Icon name={isPublic ? "globe" : "lock"} size={16} />
        <div className="ct-share-head-text">
          <strong>{isPublic ? "Anyone with the link can view" : "Private"}</strong>
          <div className="ct-dim" style={{ fontSize: 12 }}>
            {isPublic
              ? "Anyone you send the link to can preview the latest version."
              : "Only you (and project members) can access this file."}
          </div>
        </div>
        <label className="ct-toggle">
          <input type="checkbox"
                 checked={isPublic}
                 disabled={busy}
                 onChange={(e) => toggle(e.target.checked)} />
          <span className="ct-toggle-slider" />
        </label>
      </div>

      {isPublic && (
        <div className="ct-share-link-row">
          <input
            type="text"
            readOnly
            className="ct-share-link-field ct-mono"
            value={shareUrl}
            onFocus={(e) => e.target.select()}
          />
          <button className="ct-cta-cad ct-share-copy" onClick={copy} disabled={!shareUrl}>
            <Icon name={copied ? "check" : "copy"} size={12} />
            <span>{copied ? "Copied" : "Copy"}</span>
          </button>
        </div>
      )}

      <div className="ct-share-secondary ct-dim" style={{ fontSize: 12, lineHeight: 1.5 }}>
        Project members always have access regardless of this toggle.
        Disabling sharing rotates the link — old URLs stop working.
      </div>
    </div>
  );
}

/* The "Open in CTWCAD" button. The protocol-handler dialog ("Allow this
 * site to open ctwcad://?") is enforced by the browser on first click —
 * there is no JS API to skip it. After the user picks "Always allow on
 * this site", subsequent clicks open the desktop app silently. We show a
 * one-shot inline hint the first time so they know what to click. */
function OpenInCtwcadButton({ fileId, kind }) {
  const [showedHint, setShowedHint] = useState(
    () => localStorage.getItem("ctwcad.protoHintAck") === "1"
  );
  const [showFallback, setShowFallback] = useState(false);
  const requiresDesktop = kind === "ctwcad" || kind === "ctws";

  const onClick = () => {
    if (!showedHint) {
      // First click — let the navigation happen, then leave a flag so we
      // don't nag again, and arm a 4s "didn't work?" fallback toast.
      localStorage.setItem("ctwcad.protoHintAck", "1");
      setShowedHint(true);
      setTimeout(() => {
        // If the user is still focused on this tab 4s later, the
        // protocol probably didn't fire (app missing, dialog dismissed).
        if (!document.hidden) setShowFallback(true);
      }, 4000);
    }
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6, width: "100%" }}>
      <a
        className="ct-cta-cad"
        href={`ctwcad://open?file_id=${fileId}`}
        onClick={onClick}
      >
        <Icon name="external-link" size={14} />
        <span>{requiresDesktop ? "Open in CTWCAD (required to edit)" : "Open in CTWCAD"}</span>
        <span className="ct-mono ct-dim">⌘E</span>
      </a>
      {!showedHint && (
        <div className="ct-mono ct-dim" style={{ fontSize: 11, lineHeight: 1.4 }}>
          First time? Pick <strong>Always allow on this site</strong> in the
          browser dialog so it stops asking.
        </div>
      )}
      {showFallback && (
        <div className="ct-mono ct-dim" style={{ fontSize: 11, lineHeight: 1.4, color: "var(--ct-warn)" }}>
          Nothing happened? Make sure CTWCAD desktop is installed and signed in.
        </div>
      )}
    </div>
  );
}

/* =========================================================================
 *  Wave-5: per-file tags (Info tab + click-to-filter in browser).
 *  ─────────────────────────────────────────────────────────────
 *  Tags persist as `tags: string[]` on the file doc. Existing /files
 *  rules already permit owner / project-member updates of any field,
 *  so no rule change is needed.
 *
 *  Suggestions come from a localStorage cache of recently-used tags
 *  (`ctwcad.recentTags`, lowercased, max 50 entries). The cache is
 *  cross-file and cross-session so the user gets autocomplete after
 *  typing the same tag twice.
 * =======================================================================*/
const RECENT_TAGS_KEY = "ctwcad.recentTags";
const RECENT_TAGS_MAX = 50;
function loadRecentTags() {
  try {
    const raw = localStorage.getItem(RECENT_TAGS_KEY);
    if (!raw) return [];
    const arr = JSON.parse(raw);
    return Array.isArray(arr) ? arr.filter(t => typeof t === "string") : [];
  } catch { return []; }
}
function rememberTag(tag) {
  if (!tag) return;
  const t = tag.toLowerCase().trim();
  if (!t) return;
  const cur = loadRecentTags().filter(x => x !== t);
  cur.unshift(t);
  try { localStorage.setItem(RECENT_TAGS_KEY, JSON.stringify(cur.slice(0, RECENT_TAGS_MAX))); } catch {}
}

function FileTagsEditor({ file }) {
  const [tags, setTags] = useState(() => Array.isArray(file.tags) ? file.tags : []);
  const [draft, setDraft] = useState("");
  const [adding, setAdding] = useState(false);
  const inputRef = useRef(null);
  const [suggestions, setSuggestions] = useState([]);

  // Re-sync if the upstream file doc changes (folder refresh, etc).
  useEffect(() => {
    setTags(Array.isArray(file.tags) ? file.tags : []);
  }, [file.id, file.tags]);

  useEffect(() => {
    if (!adding) return;
    const recent = loadRecentTags();
    const lower = draft.toLowerCase();
    const filtered = recent
      .filter(t => !tags.includes(t))
      .filter(t => !lower || t.startsWith(lower))
      .slice(0, 6);
    setSuggestions(filtered);
  }, [adding, draft, tags]);

  const persist = (next) => {
    setTags(next);
    window.dispatchEvent(new CustomEvent("ctwcad:item-action", {
      detail: { action: "set-tags", kind: "file", file, tags: next },
    }));
  };

  const addTag = (raw) => {
    const t = (raw || "").trim().toLowerCase();
    if (!t) return;
    if (tags.includes(t)) {
      setDraft(""); setAdding(false);
      return;
    }
    rememberTag(t);
    persist([...tags, t]);
    setDraft("");
    // Stay in add-mode so the user can rapidly add several.
  };

  const removeTag = (t) => {
    persist(tags.filter(x => x !== t));
  };

  return (
    <div className="ct-tags-editor">
      <div className="ct-tags-row">
        {tags.map(t => (
          <span key={t} className="ct-tag-chip" title={`Click to filter, X to remove`}>
            <button
              type="button"
              className="ct-tag-chip-label"
              onClick={() => {
                window.dispatchEvent(new CustomEvent("ctwcad:item-action", {
                  detail: { action: "filter-by-tag", kind: "file", file, tag: t },
                }));
              }}
            >
              #{t}
            </button>
            <button
              type="button"
              className="ct-tag-chip-x"
              onClick={() => removeTag(t)}
              title="Remove tag"
              aria-label={`Remove tag ${t}`}
            >
              <Icon name="x" size={10} />
            </button>
          </span>
        ))}
        {adding ? (
          <span className="ct-tag-chip ct-tag-chip-input">
            <input
              ref={(el) => {
                inputRef.current = el;
                if (el) el.focus();
              }}
              className="ct-tag-input ct-mono"
              value={draft}
              placeholder="tag"
              onChange={(e) => setDraft(e.target.value.replace(/[^a-zA-Z0-9-_ ]/g, ""))}
              onKeyDown={(e) => {
                if (e.key === "Enter") {
                  e.preventDefault();
                  addTag(draft);
                } else if (e.key === "Escape") {
                  e.preventDefault();
                  setAdding(false); setDraft("");
                } else if (e.key === "Backspace" && !draft && tags.length > 0) {
                  e.preventDefault();
                  removeTag(tags[tags.length - 1]);
                }
              }}
              onBlur={() => {
                // Commit on blur so a click outside doesn't lose the draft.
                if (draft.trim()) addTag(draft);
                else { setAdding(false); setDraft(""); }
              }}
              size={Math.max(6, draft.length + 1)}
            />
          </span>
        ) : (
          <button type="button" className="ct-tag-add"
                  onClick={() => { setAdding(true); setDraft(""); }}
                  title="Add tag">
            <Icon name="plus" size={11} />
            <span>{tags.length === 0 ? "Add tag" : "Add"}</span>
          </button>
        )}
      </div>
      {adding && suggestions.length > 0 && (
        <div className="ct-tag-suggestions">
          {suggestions.map(s => (
            <button key={s} type="button" className="ct-tag-suggestion"
                    onMouseDown={(e) => {
                      // mousedown so it fires before the input blur.
                      e.preventDefault();
                      addTag(s);
                    }}>
              #{s}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { FileDrawer, OpenInCtwcadButton, FileTagsEditor });
