import { useState, useEffect, useRef, useCallback, memo, type ReactNode } from "react";
import { vfsAsync, onVfsChanged } from "@/lib/vfsDb";
import type { VFSFile } from "@/lib/vfsDb";
import { Z_INDEX } from "@/lib/zindex";
import { parseFile } from "@/lib/fileReader";
import {
  analyzeSync, getImageDimensions, analyzeImageWithAI,
  FileAnalysis, FileCategory,
} from "@/lib/fileAnalyzer";
import { convertFile, downloadBlob, ConvertOptions } from "@/lib/fileConverter";
import { listSnapshots, restoreSnapshot, deleteSnapshot } from "@/lib/workspaceSnapshot"; // S194
import type { StoredSnapshot } from "@/lib/vfsDb"; // S194 — StoredSnapshot is exported from vfsDb

// ─── Helpers ──────────────────────────────────────────────────────────────────

const fmt = {
  size: (b: number) =>
    b < 1024 ? `${b} B` : b < 1_048_576 ? `${(b / 1024).toFixed(1)} KB` : `${(b / 1_048_576).toFixed(2)} MB`,
  date: (ts: number) =>
    new Date(ts).toLocaleString("it-IT", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }),
  num: (n: number) => n.toLocaleString("it-IT"),
};

const CAT_COLOR: Record<FileCategory, string> = {
  image:    "#a78bfa", video: "#f59e0b", audio: "#60a5fa", pdf:    "#ef4444",
  json:     "#f97316", csv:  "#3b82f6", markdown: "#60a5fa", html:  "#facc15",
  css:      "#38bdf8", code: "#4f8ef7", text:  "#9ca3af", archive: "#8b5cf6",
  binary:   "#6b7280",
};

const TYPE_EMOJI: Record<FileCategory, string> = {
  image:   "IMG", video:   "VID", audio:  "AUD", pdf:    "PDF",
  json:    "JSON", csv:    "CSV", markdown:"MD",  html:   "HTML",
  css:     "CSS", code:    "CODE", text:  "TXT", archive:"ZIP",
  binary:  "BIN",
};

function getCatFromFile(f: VFSFile): FileCategory {
  const ext  = f.name.split(".").pop()?.toLowerCase() ?? "";
  const mime = f.type ?? "";
  if (mime.startsWith("image/") || /^(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(ext)) return "image";
  if (mime.startsWith("video/") || /^(mp4|webm|mov)$/.test(ext))                   return "video";
  if (mime.startsWith("audio/") || /^(mp3|wav|ogg|m4a|aac)$/.test(ext))            return "audio";
  if (ext === "pdf")   return "pdf";
  if (ext === "json")  return "json";
  if (ext === "csv")   return "csv";
  if (ext === "md")    return "markdown";
  if (/^(html?)$/.test(ext)) return "html";
  if (/^(css|scss)$/.test(ext)) return "css";
  if (/^(js|jsx|ts|tsx|py|java|c|cpp|go|rs|rb|php|swift|sh|sql)$/.test(ext)) return "code";
  if (mime.startsWith("text/") || /^(txt|log|yaml|yml|toml|xml|ini|env)$/.test(ext)) return "text";
  if (/^(zip|tar|gz|rar)$/.test(ext)) return "archive";
  return "binary";
}

// ─── Stat pill ────────────────────────────────────────────────────────────────

function Pill({ label, value }: { label: string; value: string }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 1, background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.07)", borderRadius: 8, padding: "5px 10px", minWidth: 70 }}>
      <span style={{ fontSize: "0.58rem", color: "#6b6b8a", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 700 }}>{label}</span>
      <span style={{ fontSize: "0.78rem", color: "#d0d0f0", fontWeight: 600, fontFamily: "ui-monospace, monospace" }}>{value}</span>
    </div>
  );
}

// ─── Action card ──────────────────────────────────────────────────────────────

function ActionCard({
  icon, label, desc, color, active, done, loading,
  onClick,
}: {
  icon: string; label: string; desc: string; color: string;
  active?: boolean; done?: boolean; loading?: boolean;
  onClick: () => void;
}) {
  return (
    <button
      onClick={onClick}
      disabled={loading}
      style={{
        all: "unset", cursor: loading ? "default" : "pointer",
        display: "flex", alignItems: "center", gap: 10,
        padding: "0.65rem 0.9rem", borderRadius: 10,
        background: active || done
          ? `${color}18`
          : "rgba(255,255,255,0.025)",
        border: `1px solid ${active || done ? color + "44" : "rgba(255,255,255,0.07)"}`,
        transition: "all 0.15s",
        opacity: loading ? 0.6 : 1,
      }}
      onMouseEnter={e => { if (!loading) { e.currentTarget.style.background = `${color}12`; e.currentTarget.style.borderColor = `${color}33`; } }}
      onMouseLeave={e => { if (!loading && !active && !done) { e.currentTarget.style.background = "rgba(255,255,255,0.025)"; e.currentTarget.style.borderColor = "rgba(255,255,255,0.07)"; } }}
    >
      <span style={{ width: 8, height: 8, borderRadius: "50%", background: done ? "#60a5fa" : loading ? "#fbbf24" : color, display: "inline-block", flexShrink: 0 }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: "0.78rem", fontWeight: 600, color: done ? "#60a5fa" : color }}>{done ? "Completato!" : label}</div>
        {desc && <div style={{ fontSize: "0.66rem", color: "#5a5a78", marginTop: 1 }}>{desc}</div>}
      </div>
    </button>
  );
}

// ─── Main component ───────────────────────────────────────────────────────────

interface FileExplorerProps {
  onClose: () => void;
}

const FileExplorer = memo(function FileExplorer({ onClose }: FileExplorerProps) {

  const [files,        setFiles]        = useState<VFSFile[]>([]);
  const [selected,     setSelected]     = useState<VFSFile | null>(null);
  const [content,      setContent]      = useState<string>("");
  const [search,       setSearch]       = useState("");
  const [contentSearch, setContentSearch] = useState("");
  const [exporting,    setExporting]    = useState(false);
  const [uploading,    setUploading]    = useState(false);
  const [dragOver,     setDragOver]     = useState(false);
  const [analysis,     setAnalysis]     = useState<FileAnalysis | null>(null);
  const [activeAction, setActiveAction] = useState<string | null>(null);
  const [actionDone,   setActionDone]   = useState<string | null>(null);
  const [actionError,  setActionError]  = useState<string | null>(null);
  const [opfsLoading,  setOpfsLoading]  = useState(false);
  const [opfsError,    setOpfsError]    = useState<string | null>(null);
  const [opfsRetry,    setOpfsRetry]    = useState(0);
  const [opfsRetryTrig,setOpfsRetryTrig]= useState(0);
  const [converting,   setConverting]   = useState(false);
  const [convOpts,     setConvOpts]     = useState<ConvertOptions>({ quality: 85, maxW: 1280, maxH: 1280 });
  const [tab,          setTab]          = useState<"files" | "snapshots">("files"); // S194
  const [snapshots,    setSnapshots]    = useState<StoredSnapshot[]>([]); // S194
  const [snapLoading,  setSnapLoading]  = useState(false); // S194
  const [snapMsg,      setSnapMsg]      = useState<string | null>(null); // S194
  const uploadRef    = useRef<HTMLInputElement>(null);
  const opfsAbortRef = useRef<{ cancelled: boolean }>({ cancelled: false });

  // ── File list ──────────────────────────────────────────────────────────────

  const reload = useCallback(() => {
    vfsAsync.list().then(setFiles).catch(() => setFiles([]));
  }, []);
  useEffect(() => { reload(); return onVfsChanged(reload); }, [reload]);

  // S194: load snapshots when switching to snapshots tab
  useEffect(() => {
    if (tab !== "snapshots") return;
    setSnapLoading(true);
    listSnapshots().then(s => { setSnapshots(s); setSnapLoading(false); }).catch(() => setSnapLoading(false));
  }, [tab]);

  // ── Load content when file selected ───────────────────────────────────────

  useEffect(() => {
    if (!selected) { opfsAbortRef.current.cancelled = true; setContent(""); setAnalysis(null); setActiveAction(null); setActionDone(null); setOpfsLoading(false); setOpfsError(null); setOpfsRetry(0); return; }
    setActiveAction(null); setActionDone(null); setActionError(null); setOpfsLoading(false); setOpfsError(null); setOpfsRetry(0);

    if (selected.opfs) {
      opfsAbortRef.current.cancelled = true;          // cancel any in-flight read
      const _opfsToken = { cancelled: false };
      opfsAbortRef.current = _opfsToken;
      setContent("");
      setOpfsLoading(true);
      setOpfsError(null);
      setOpfsRetry(0);
      const MAX_OPFS_RETRIES = 3;
      const OPFS_TIMEOUT_MS   = 8_000;
      const opfsDelay = (ms: number) => new Promise<void>(r => setTimeout(r, ms));
      const withTimeout = <T,>(p: Promise<T>, ms: number): Promise<T> =>
        Promise.race([
          p,
          new Promise<T>((_, reject) =>
            setTimeout(() => reject(new Error('opfs-timeout')), ms),
          ),
        ]);
      (async () => {
        for (let attempt = 0; attempt < MAX_OPFS_RETRIES; attempt++) {
          if (_opfsToken.cancelled) return;           // file changed — abort
          setOpfsRetry(attempt + 1);
          try {
            const c = await withTimeout(vfsAsync.read(selected.path), OPFS_TIMEOUT_MS);
            if (_opfsToken.cancelled) return;         // file changed mid-read
            const c2 = c ?? "";
            setContent(c2);
            setAnalysis(analyzeSync(selected, c2));
            setOpfsLoading(false);
            return;
          } catch (err: unknown) {
            const isTimeout = err instanceof Error && err.message === 'opfs-timeout';
            if (attempt < MAX_OPFS_RETRIES - 1) {
              await opfsDelay(isTimeout ? 200 : 400 * Math.pow(2, attempt));
            }
          }
        }
        if (!_opfsToken.cancelled) {
          setOpfsError(`Impossibile leggere il file dopo ${MAX_OPFS_RETRIES} tentativi (timeout ${OPFS_TIMEOUT_MS / 1000}s/tentativo). Verifica l'archiviazione o ricarica.`);
          setOpfsLoading(false);
        }
      })();
    } else {
      setContent(selected.content);
      setAnalysis(analyzeSync(selected, selected.content));
    }
  }, [selected, opfsRetryTrig]);

  // ── Image dimensions (async after analysis) ────────────────────────────────

  useEffect(() => {
    if (!selected || !analysis) return;
    if (analysis.category !== "image") return;
    const src = content || selected.content;
    if (!src) return;
    getImageDimensions(src).then(({ w, h }) => {
      if (w > 0) setAnalysis(prev => prev ? { ...prev, imgWidth: w, imgHeight: h } : prev);
    });
  }, [selected, analysis?.category, content]); // eslint-disable-line react-hooks/exhaustive-deps

  // ── AI analysis ────────────────────────────────────────────────────────────

  const runAIAnalysis = useCallback(async () => {
    if (!selected || !analysis) return;
    setAnalysis(prev => prev ? { ...prev, aiAnalyzing: true, aiError: undefined } : prev);
    const src = content || selected.content;
    const result = await analyzeImageWithAI(src, selected.name);
    setAnalysis(prev => prev ? { ...prev, aiAnalyzing: false, ...result } : prev);
  }, [selected, analysis, content]);

  // ── Manual OPFS retry ─────────────────────────────────────────────────────

  const retryOpfsRead = useCallback(() => {
    setOpfsRetryTrig(t => t + 1);
  }, []);

  // ── Conversion ─────────────────────────────────────────────────────────────

  const runConvert = useCallback(async (action: string) => {
    if (!selected) return;
    if (opfsLoading) return; // guard: OPFS still loading
    if (action === "ai-analyze") { void runAIAnalysis(); return; }
    if (action === "copy") {
      await navigator.clipboard.writeText(content || selected.content).catch(() => {});
      setActionDone(action);
      setTimeout(() => setActionDone(null), 2000);
      return;
    }
    if (action === "download") {
      const c2 = content || selected.content;
      const blob = new Blob([c2], { type: selected.type || "text/plain" });
      downloadBlob(blob, selected.name);
      return;
    }
    if (action === "delete") {
      await vfsAsync.delete(selected.path);
      setSelected(null);
      reload();
      return;
    }
    setConverting(true);
    setActionError(null);
    try {
      const c2  = content || selected.content;
      const res = await convertFile(selected, c2, action, convOpts);
      downloadBlob(res.blob, res.filename);
      setActionDone(action);
      setTimeout(() => setActionDone(null), 3000);
    } catch (e: unknown) {
      setActionError(e instanceof Error ? e.message : "Errore conversione");
    } finally {
      setConverting(false);
    }
  }, [selected, content, convOpts, reload, runAIAnalysis]);

  // ── Upload ─────────────────────────────────────────────────────────────────

  const handleUpload = useCallback(async (fileList: FileList | null) => {
    if (!fileList || fileList.length === 0) return;
    setUploading(true);
    try {
      for (const file of Array.from(fileList)) {
        const parsed = await parseFile(file);
        if (parsed.error) continue;
        if (parsed.category === "image" && parsed.dataUrl) {
          await vfsAsync.write("/uploads/" + file.name, parsed.dataUrl, file.type);
        } else if (parsed.content) {
          await vfsAsync.write("/uploads/" + file.name, parsed.content, file.type || "text/plain");
          // S459: RAG indexing — popola ragPipeline sui file caricati nel VFS (fire-and-forget)
          import("@/lib/agent/ragPipeline").then(m => m.indexParsedFile(parsed)).catch(() => {});
        }
      }
      reload();
    } finally {
      setUploading(false);
      if (uploadRef.current) uploadRef.current.value = "";
    }
  }, [reload]);

  // ── Export ZIP ────────────────────────────────────────────────────────────

  const exportAllZip = useCallback(async () => {
    if (files.length === 0) return;
    setExporting(true);
    try {
      const JSZip = (await import("jszip")).default;
      const zip   = new JSZip();
      for (const f of files) {
        const c = f.opfs ? (await vfsAsync.read(f.path) ?? "") : f.content;
        zip.file(f.path.startsWith("/") ? f.path.slice(1) : f.path, c);
      }
      const blob = await zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } });
      downloadBlob(blob, "vfs-export.zip");
    } finally {
      setExporting(false);
    }
  }, [files]);

  // ── Download single file ──────────────────────────────────────────────────

  const downloadFile = useCallback(async (f: VFSFile) => {
    const c   = f.opfs ? (await vfsAsync.read(f.path) ?? "") : f.content;
    const blob = new Blob([c], { type: f.type || "text/plain" });
    downloadBlob(blob, f.name);
  }, []);

  // ── Filtered file list ────────────────────────────────────────────────────

  const filtered = search.trim()
    ? files.filter(f =>
        f.path.toLowerCase().includes(search.toLowerCase()) ||
        (!f.opfs && f.content.toLowerCase().includes(search.toLowerCase()))
      )
    : files;

  const totalBytes = files.reduce((s, f) => s + f.size, 0);

  // ── Select file ───────────────────────────────────────────────────────────

  const selectFile = useCallback((f: VFSFile) => {
    setSelected(prev => prev?.path === f.path ? null : f);
    setContentSearch("");
  }, []);

  // ─────────────────────────────────────────────────────────────────────────
  // Render
  // ─────────────────────────────────────────────────────────────────────────

  const showPanel  = !!selected;
  const listWidth  = showPanel ? "min(300px, 42%)" : "100%";

  return (
    <div
      style={{ position: "fixed", inset: 0, zIndex: Z_INDEX.MODAL_OVERLAY, display: "flex", background: "rgba(0,0,0,0.65)", alignItems: "center", justifyContent: "center" }}
      onClick={onClose}
    >
      <div
        style={{ background: "rgba(7,7,18,0.94)", border: "1px solid rgba(255,255,255,0.08)", borderRadius: 18, width: "min(920px, 97vw)", maxHeight: "88vh", display: "flex", flexDirection: "column", overflow: "hidden", boxShadow: "0 24px 80px rgba(0,0,0,0.75)" }}
        onClick={e => e.stopPropagation()}
      >

        {/* ── Header ── */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0.85rem 1.2rem", borderBottom: "1px solid rgba(255,255,255,0.07)", gap: 8, flexWrap: "wrap" }}>
          <div>
            <div style={{ fontWeight: 700, fontSize: "0.97rem", color: "#e2e2f0" }}>File Manager VFS</div>
            <div style={{ fontSize: "0.68rem", color: "#6b6b7b", marginTop: 1 }}>{files.length} file · {fmt.size(totalBytes)}</div>
          </div>
          <div style={{ display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap" }}>
            <input ref={uploadRef} type="file" multiple style={{ display: "none" }} onChange={e => handleUpload(e.target.files)} />
            <Btn color="#4f8ef7" onClick={() => uploadRef.current?.click()} disabled={uploading}>{uploading ? "Carico…" : "Carica"}</Btn>
            {files.length > 0 && <Btn color="#a78bfa" onClick={exportAllZip} disabled={exporting}>{exporting ? "ZIP…" : "Esporta ZIP"}</Btn>}
            {files.length > 0 && <Btn color="#ef4444" onClick={() => { vfsAsync.clear().catch(() => {}); setSelected(null); reload(); }}>Svuota</Btn>}
            <button onClick={onClose} style={{ all: "unset", cursor: "pointer", color: "#6b6b7b", fontSize: "1.15rem", padding: "0 4px" }}>✕</button>
          </div>
        </div>

        {/* ── Tab bar ── */}
        <div style={{ display: "flex", gap: 2, padding: "0.4rem 1.2rem 0", borderBottom: "1px solid rgba(255,255,255,0.07)" }}>
          {(["files", "snapshots"] as const).map(t => (
            <button key={t} onClick={() => setTab(t)} style={{
              all: "unset", cursor: "pointer", padding: "0.3rem 0.8rem", borderRadius: "7px 7px 0 0",
              fontSize: "0.72rem", fontWeight: tab === t ? 700 : 400,
              color: tab === t ? "#3b82f6" : "#6b6b8a",
              background: tab === t ? "rgba(59,130,246,0.1)" : "transparent",
              transition: "all 0.15s",
            }}>
              {t === "files" ? "Files" : "Snapshots"}
            </button>
          ))}
        </div>

        {/* ── Body ── */}
        <div style={{ display: tab === "files" ? "flex" : "none", flex: 1, overflow: "hidden", minHeight: 0 }}>

          {/* ── File list ── */}
          <div
            style={{ width: listWidth, flexShrink: 0, borderRight: showPanel ? "1px solid rgba(255,255,255,0.07)" : "none", display: "flex", flexDirection: "column", overflow: "hidden", transition: "width 0.2s", background: dragOver ? "rgba(79,142,247,0.04)" : "transparent" }}
            onDragOver={e => { e.preventDefault(); setDragOver(true); }}
            onDragLeave={() => setDragOver(false)}
            onDrop={e => { e.preventDefault(); setDragOver(false); handleUpload(e.dataTransfer.files); }}
          >
            <div style={{ padding: "0.5rem 0.8rem", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
              <input
                value={search}
                onChange={e => setSearch(e.target.value)}
                placeholder="Cerca nome o contenuto…"
                style={{ width: "100%", boxSizing: "border-box", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 7, padding: "0.3rem 0.6rem", color: "#e2e2f0", fontSize: "0.76rem", outline: "none" }}
              />
            </div>

            <div style={{ flex: 1, overflowY: "auto" }}>
              {filtered.length === 0 ? (
                <div style={{ textAlign: "center", color: "#4a4a62", padding: "2.5rem 1rem" }}>
                  <div style={{ width: 40, height: 40, borderRadius: 8, background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)", marginBottom: 8, display: "flex", alignItems: "center", justifyContent: "center" }}>
                    <svg width={18} height={18} viewBox="0 0 24 24" fill="none" stroke="#4a4a72" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
                  </div>
                  <div style={{ fontSize: "0.82rem" }}>{search ? "Nessun risultato" : "VFS vuoto"}</div>
                  {!search && <div style={{ fontSize: "0.7rem", marginTop: 5, color: "#3a3a52" }}>Trascina file qui o usa Carica</div>}
                </div>
              ) : filtered.map(f => {
                const cat   = getCatFromFile(f);
                const isSel = selected?.path === f.path;
                const matchesContent = search.trim() && !f.opfs && f.content.toLowerCase().includes(search.toLowerCase()) && !f.path.toLowerCase().includes(search.toLowerCase());
                return (
                  <div
                    key={f.path}
                    onClick={() => selectFile(f)}
                    style={{ display: "flex", alignItems: "center", gap: 8, padding: "0.42rem 0.8rem", cursor: "pointer", background: isSel ? "rgba(79,142,247,0.09)" : "transparent", borderBottom: "1px solid rgba(255,255,255,0.025)", borderLeft: isSel ? "2px solid #4f8ef7" : "2px solid transparent", transition: "all 0.1s" }}
                  >
                    <span style={{ fontSize: "0.58rem", fontWeight: 700, color: CAT_COLOR[cat], background: `${CAT_COLOR[cat]}18`, border: `1px solid ${CAT_COLOR[cat]}33`, borderRadius: 3, padding: "0 3px", letterSpacing: "0.03em", flexShrink: 0 }}>{TYPE_EMOJI[cat]}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: "0.78rem", color: "#c4c4d8", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{f.name}</div>
                      <div style={{ fontSize: "0.63rem", color: "#5a5a72", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                        {matchesContent ? <span style={{ color: "#f59e0b" }}>Match nel contenuto</span> : f.path}
                      </div>
                    </div>
                    <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 1, flexShrink: 0 }}>
                      <span style={{ fontSize: "0.63rem", color: "#6b6b8a", fontFamily: "ui-monospace, monospace" }}>{fmt.size(f.size)}</span>
                      <span style={{ fontSize: "0.58rem", color: CAT_COLOR[cat], background: `${CAT_COLOR[cat]}18`, border: `1px solid ${CAT_COLOR[cat]}33`, borderRadius: 4, padding: "0 4px", lineHeight: 1.5 }}>{cat}</span>
                    </div>
                    <button onClick={e => { e.stopPropagation(); void downloadFile(f); }} style={{ all: "unset", cursor: "pointer", color: "#4a4a72", fontSize: "0.8rem", padding: "2px 3px" }}>⬇</button>
                    <button onClick={e => { e.stopPropagation(); void vfsAsync.delete(f.path).then(reload); if (selected?.path === f.path) setSelected(null); }} style={{ all: "unset", cursor: "pointer", color: "#4a4a72", fontSize: "0.72rem", padding: "2px 3px" }}>X</button>
                  </div>
                );
              })}
            </div>

            {dragOver && (
              <div style={{ padding: "0.5rem", borderTop: "1px solid rgba(79,142,247,0.3)", background: "rgba(79,142,247,0.07)", textAlign: "center", fontSize: "0.73rem", color: "#4f8ef7" }}>
                Rilascia per caricare
              </div>
            )}
          </div>

          {/* ── Detail panel ── */}
          {showPanel && selected && (
            <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
              {/* Detail header */}
              <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "0.6rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.07)", flexShrink: 0 }}>
                <span style={{ fontSize: "0.58rem", fontWeight: 700, color: analysis ? CAT_COLOR[analysis.category] : "#9090c0", background: analysis ? `${CAT_COLOR[analysis.category]}18` : "rgba(144,144,192,0.12)", border: `1px solid ${analysis ? CAT_COLOR[analysis.category] + "33" : "rgba(144,144,192,0.2)"}`, borderRadius: 3, padding: "0 3px", letterSpacing: "0.03em", flexShrink: 0 }}>{analysis ? TYPE_EMOJI[analysis.category] : "FILE"}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: "0.84rem", fontWeight: 600, color: "#e0e0f0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{selected.name}</div>
                  <div style={{ fontSize: "0.64rem", color: "#6b6b8a", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{selected.path} · {fmt.date(selected.updatedAt)}</div>
                </div>
                {analysis && (
                  <span style={{ fontSize: "0.65rem", color: CAT_COLOR[analysis.category], background: `${CAT_COLOR[analysis.category]}18`, border: `1px solid ${CAT_COLOR[analysis.category]}33`, borderRadius: 5, padding: "2px 7px", flexShrink: 0, fontWeight: 700 }}>
                    {analysis.language ?? analysis.category.toUpperCase()}
                  </span>
                )}
                <button onClick={() => setSelected(null)} style={{ all: "unset", cursor: "pointer", color: "#6b6b7b", fontSize: "0.95rem", padding: "2px 4px" }}>✕</button>
              </div>

              {/* Scrollable detail body */}
              <div style={{ flex: 1, overflowY: "auto", display: "flex", flexDirection: "column", gap: 0 }}>

                {/* ── Preview ── */}
                <div style={{ flexShrink: 0 }}>
                  {analysis?.category === "image" && (
                    <div style={{ background: "rgba(255,255,255,0.02)", borderBottom: "1px solid rgba(255,255,255,0.05)", display: "flex", alignItems: "center", justifyContent: "center", padding: "0.8rem", minHeight: 140, maxHeight: 260 }}>
                      <img
                        src={content || selected.content}
                        alt={selected.name}
                        style={{ maxWidth: "100%", maxHeight: 220, objectFit: "contain", borderRadius: 8, boxShadow: "0 4px 20px rgba(0,0,0,0.4)" }}
                      />
                    </div>
                  )}
                  {analysis?.category === "audio" && (
                    <div style={{ padding: "0.8rem 1rem", background: "rgba(96,165,250,0.04)", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
                      <audio controls style={{ width: "100%", height: 40 }} src={content || selected.content} />
                    </div>
                  )}
                  {analysis?.category === "video" && (
                    <div style={{ padding: "0.8rem 1rem", background: "rgba(245,158,11,0.04)", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
                      <video controls style={{ width: "100%", maxHeight: 200, borderRadius: 8 }} src={content || selected.content} />
                    </div>
                  )}
                  {analysis?.category === "csv" && analysis.csvHeaders && (
                    <div style={{ padding: "0.7rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)", overflowX: "auto" }}>
                      <div style={{ fontSize: "0.62rem", color: "#6b6b8a", marginBottom: 4 }}>Anteprima tabella (prime 5 righe)</div>
                      <table style={{ borderCollapse: "collapse", fontSize: "0.71rem", color: "#c0c0d0", minWidth: "100%" }}>
                        <thead>
                          <tr>{analysis.csvHeaders.map(h => (
                            <th key={h} style={{ borderBottom: "1px solid rgba(255,255,255,0.12)", padding: "4px 10px", textAlign: "left", color: "#3b82f6", fontWeight: 700, whiteSpace: "nowrap" }}>{h}</th>
                          ))}</tr>
                        </thead>
                        <tbody>
                          {(analysis.csvPreview ?? []).map((row, i) => (
                            <tr key={i} style={{ background: i % 2 === 0 ? "transparent" : "rgba(255,255,255,0.015)" }}>
                              {row.map((cell, j) => (
                                <td key={j} style={{ padding: "3px 10px", borderBottom: "1px solid rgba(255,255,255,0.04)", whiteSpace: "nowrap", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis" }}>{cell}</td>
                              ))}
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    </div>
                  )}
                  {analysis?.category === "html" && content && (
                    <div style={{ padding: "0.7rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
                      <div style={{ fontSize: "0.62rem", color: "#6b6b8a", marginBottom: 4 }}>Anteprima HTML</div>
                      <iframe
                        srcDoc={content}
                        sandbox="allow-same-origin"
                        style={{ width: "100%", height: 180, border: "1px solid rgba(255,255,255,0.1)", borderRadius: 8, background: "#fff" }}
                        title={selected.name}
                      />
                    </div>
                  )}
                  {(analysis?.category === "text" || analysis?.category === "code" || analysis?.category === "markdown" || analysis?.category === "css" || analysis?.category === "json") && content && (
                    <div style={{ padding: "0.7rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
                        <div style={{ fontSize: "0.62rem", color: "#6b6b8a" }}>Anteprima</div>
                        {analysis.category === "json" && (
                          <span style={{ fontSize: "0.6rem", color: analysis.jsonValid ? "#60a5fa" : "#ef4444", background: analysis.jsonValid ? "rgba(96,165,250,0.1)" : "rgba(239,68,68,0.1)", border: `1px solid ${analysis.jsonValid ? "rgba(96,165,250,0.3)" : "rgba(239,68,68,0.3)"}`, borderRadius: 4, padding: "1px 6px" }}>
                            {analysis.jsonValid ? "ok JSON valido" : "! JSON non valido"}
                          </span>
                        )}
                      </div>
                      <div style={{ position: "relative" }}>
                        {contentSearch && (
                          <div style={{ marginBottom: 4 }}>
                            <input
                              value={contentSearch}
                              onChange={e => setContentSearch(e.target.value)}
                              placeholder="Cerca nel contenuto…"
                              style={{ width: "100%", boxSizing: "border-box", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 5, padding: "0.2rem 0.5rem", color: "#e2e2f0", fontSize: "0.72rem", outline: "none" }}
                            />
                          </div>
                        )}
                        {opfsLoading ? (
                          <div style={{ padding: "1.2rem 0.7rem", background: "rgba(0,0,0,0.25)", borderRadius: 8, border: "1px solid rgba(255,255,255,0.06)", display: "flex", alignItems: "center", gap: 8, color: "#6b7280", fontSize: "0.74rem" }}>
                            <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#4f8ef7", display: "inline-block", animation: "visionPulse 1.4s ease-in-out infinite" }} />
                            Lettura in corso…
                          </div>
                        ) : opfsError ? (
                          <div style={{ padding: "1rem 0.7rem", background: "rgba(239,68,68,0.05)", borderRadius: 8, border: "1px solid rgba(239,68,68,0.12)", color: "#fca5a5", fontSize: "0.73rem" }}>
                            {opfsError}
                          </div>
                        ) : (
                        <pre style={{ margin: 0, padding: "0.7rem", background: "rgba(0,0,0,0.25)", borderRadius: 8, fontSize: "0.71rem", lineHeight: 1.55, color: "#c8c8de", fontFamily: "ui-monospace, monospace", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: 200, overflow: "auto", border: "1px solid rgba(255,255,255,0.06)" }}>
                          {contentSearch.trim()
                            ? content.split("\n").filter(l => l.toLowerCase().includes(contentSearch.toLowerCase())).slice(0, 30).join("\n") || "(Nessun match)"
                            : content.slice(0, 2000) + (content.length > 2000 ? `\n… (${fmt.num(content.length - 2000)} caratteri in più)` : "")
                          }
                        </pre>
                        )}
                        {!opfsLoading && !opfsError && !contentSearch && analysis.category !== "json" && (
                          <button onClick={() => setContentSearch(" ")} style={{ all: "unset", cursor: "pointer", position: "absolute", top: 6, right: 6, fontSize: "0.62rem", color: "#6b6b8a", background: "rgba(0,0,0,0.4)", border: "1px solid rgba(255,255,255,0.08)", borderRadius: 4, padding: "2px 6px" }}>Cerca</button>
                        )}
                      </div>
                    </div>
                  )}
                </div>

                {/* ── OPFS loading banner ── */}
                {opfsLoading && (
                  <div style={{ padding: "0.75rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)", display: "flex", alignItems: "center", gap: 9, background: "rgba(79,142,247,0.06)" }}>
                    <span style={{ width: 10, height: 10, borderRadius: "50%", background: "#4f8ef7", display: "inline-block", animation: "visionPulse 1.4s ease-in-out infinite", flexShrink: 0 }} />
                    <div>
                      <div style={{ fontSize: "0.75rem", color: "#93c5fd", fontWeight: 600 }}>Caricamento in corso… {opfsRetry > 1 ? `(tentativo ${opfsRetry}/3)` : ""}</div>
                      <div style={{ fontSize: "0.66rem", color: "#6b7280", marginTop: 1 }}>File OPFS in lettura — timeout 5s per tentativo. Attendi prima di operare sul file.</div>
                    </div>
                  </div>
                )}

                {/* ── OPFS error banner ── */}
                {opfsError && !opfsLoading && (
                  <div style={{ padding: "0.75rem 1rem", borderBottom: "1px solid rgba(239,68,68,0.15)", display: "flex", alignItems: "flex-start", gap: 9, background: "rgba(239,68,68,0.06)" }}>
                    <span style={{ fontSize: "0.85rem", color: "#fbbf24", fontWeight: 700, flexShrink: 0 }}>!</span>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: "0.75rem", color: "#ef4444", fontWeight: 600 }}>Errore lettura file</div>
                      <div style={{ fontSize: "0.68rem", color: "#fca5a5", marginTop: 1 }}>{opfsError}</div>
                      <button
                        onClick={retryOpfsRead}
                        style={{ all: "unset", cursor: "pointer", marginTop: 6, display: "inline-block", fontSize: "0.7rem", color: "#93c5fd", background: "rgba(79,142,247,0.12)", border: "1px solid rgba(79,142,247,0.25)", borderRadius: 6, padding: "3px 10px", fontWeight: 600 }}
                      >
                        Riprova
                      </button>
                    </div>
                  </div>
                )}

                {/* ── Stats ── */}
                {analysis && !opfsLoading && (
                  <div style={{ padding: "0.7rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)", flexShrink: 0 }}>
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                      <Pill label="Formato" value={analysis.category.toUpperCase()} />
                      <Pill label="Dimensione" value={fmt.size(selected.size)} />
                      {analysis.imgWidth  != null && analysis.imgWidth > 0  && <Pill label="Larghezza" value={`${analysis.imgWidth}px`}   />}
                      {analysis.imgHeight != null && analysis.imgHeight > 0  && <Pill label="Altezza"   value={`${analysis.imgHeight}px`}  />}
                      {analysis.lineCount != null  && <Pill label="Righe"    value={fmt.num(analysis.lineCount)}  />}
                      {analysis.wordCount != null  && <Pill label="Parole"   value={fmt.num(analysis.wordCount)}  />}
                      {analysis.csvRows   != null  && <Pill label="Righe"    value={fmt.num(analysis.csvRows)}    />}
                      {analysis.csvCols   != null  && <Pill label="Colonne"  value={fmt.num(analysis.csvCols)}    />}
                      {analysis.jsonKeyCount != null && <Pill label={analysis.jsonIsArray ? "Elementi" : "Chiavi"} value={fmt.num(analysis.jsonKeyCount)} />}
                      {analysis.jsonDepth != null  && <Pill label="Profondità" value={String(analysis.jsonDepth)} />}
                    </div>
                  </div>
                )}

                {/* ── AI analysis result ── */}
                {(analysis?.aiAnalyzing || analysis?.aiDescription || analysis?.aiError) && (
                  <div style={{ padding: "0.8rem 1rem", borderBottom: "1px solid rgba(255,255,255,0.05)", flexShrink: 0 }}>
                    <div style={{ fontSize: "0.66rem", color: "#a78bfa", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 700, marginBottom: 6 }}>Analisi AI</div>
                    {analysis.aiAnalyzing && (
                      <div style={{ fontSize: "0.76rem", color: "#a78bfa", display: "flex", alignItems: "center", gap: 6 }}>
                        <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#a78bfa", display: "inline-block", animation: "visionPulse 1.4s ease-in-out infinite", flexShrink: 0 }} /> Analisi in corso…
                      </div>
                    )}
                    {analysis.aiError && <div style={{ fontSize: "0.74rem", color: "#ef4444" }}>! {analysis.aiError}</div>}
                    {analysis.aiDescription && (
                      <div style={{ fontSize: "0.76rem", color: "#d0d0f0", lineHeight: 1.5, marginBottom: 6 }}>{analysis.aiDescription}</div>
                    )}
                    {analysis.aiMood && (
                      <div style={{ fontSize: "0.7rem", color: "#a78bfa" }}>Mood: {analysis.aiMood}</div>
                    )}
                    {analysis.aiTextInImage && (
                      <div style={{ fontSize: "0.72rem", color: "#fbbf24", marginTop: 4 }}>Testo rilevato: "{analysis.aiTextInImage}"</div>
                    )}
                    {analysis.aiObjects && analysis.aiObjects.length > 0 && (
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 5 }}>
                        {analysis.aiObjects.map(o => (
                          <span key={o} style={{ fontSize: "0.65rem", color: "#60a5fa", background: "rgba(96,165,250,0.08)", border: "1px solid rgba(96,165,250,0.2)", borderRadius: 5, padding: "2px 7px" }}>{o}</span>
                        ))}
                      </div>
                    )}
                    {analysis.aiTags && analysis.aiTags.length > 0 && (
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 5 }}>
                        {analysis.aiTags.map(t => (
                          <span key={t} style={{ fontSize: "0.62rem", color: "#9ca3af", background: "rgba(156,163,175,0.08)", border: "1px solid rgba(156,163,175,0.15)", borderRadius: 5, padding: "2px 6px" }}>#{t}</span>
                        ))}
                      </div>
                    )}
                  </div>
                )}

                {/* ── Actions ── */}
                {analysis && (
                  <div style={{ padding: "0.8rem 1rem", flexShrink: 0 }}>
                    <div style={{ fontSize: "0.62rem", color: "#6b6b8a", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 700, marginBottom: 8 }}>Cosa vuoi fare?</div>
                    <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
                      {analysis.actions.map(act => (
                        <div key={act.id}>
                          <ActionCard
                            icon={act.icon} label={act.label} desc={act.desc} color={act.color}
                            active={activeAction === act.id}
                            done={actionDone === act.id}
                            loading={converting && activeAction === act.id}
                            onClick={() => {
                              if (opfsLoading) return;
                              if (act.hasOptions) {
                                setActiveAction(prev => prev === act.id ? null : act.id);
                                setActionDone(null);
                              } else {
                                setActiveAction(act.id);
                                void runConvert(act.id);
                              }
                            }}
                          />
                          {/* Inline options for resize/compress */}
                          {activeAction === act.id && act.hasOptions && (
                            <div style={{ marginTop: 5, marginLeft: 12, padding: "0.7rem 0.9rem", background: "rgba(255,255,255,0.03)", border: `1px solid ${act.color}22`, borderRadius: 9, display: "flex", flexDirection: "column", gap: 8 }}>
                              {act.id === "resize" && (
                                <>
                                  <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                                    <label style={{ fontSize: "0.7rem", color: "#9ca3af", width: 70 }}>Max W (px)</label>
                                    <input type="number" min={64} max={4096} value={convOpts.maxW} onChange={e => setConvOpts(p => ({ ...p, maxW: +e.target.value }))}
                                      style={{ flex: 1, background: "rgba(255,255,255,0.07)", border: "1px solid rgba(255,255,255,0.12)", borderRadius: 5, padding: "3px 7px", color: "#e2e2f0", fontSize: "0.75rem", outline: "none" }} />
                                    <label style={{ fontSize: "0.7rem", color: "#9ca3af", width: 70 }}>Max H (px)</label>
                                    <input type="number" min={64} max={4096} value={convOpts.maxH} onChange={e => setConvOpts(p => ({ ...p, maxH: +e.target.value }))}
                                      style={{ flex: 1, background: "rgba(255,255,255,0.07)", border: "1px solid rgba(255,255,255,0.12)", borderRadius: 5, padding: "3px 7px", color: "#e2e2f0", fontSize: "0.75rem", outline: "none" }} />
                                  </div>
                                </>
                              )}
                              {act.id === "compress" && (
                                <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                                  <label style={{ fontSize: "0.7rem", color: "#9ca3af", width: 80 }}>Qualità</label>
                                  <input type="range" min={10} max={95} value={convOpts.quality} onChange={e => setConvOpts(p => ({ ...p, quality: +e.target.value }))} style={{ flex: 1 }} />
                                  <span style={{ fontSize: "0.75rem", color: "#e2e2f0", width: 36, textAlign: "right", fontFamily: "ui-monospace, monospace" }}>{convOpts.quality}%</span>
                                </div>
                              )}
                              <button
                                onClick={() => void runConvert(act.id)}
                                disabled={converting}
                                style={{ all: "unset", cursor: converting ? "default" : "pointer", alignSelf: "flex-start", padding: "0.35rem 1rem", borderRadius: 7, background: act.color, color: "#fff", fontSize: "0.74rem", fontWeight: 700, opacity: converting ? 0.6 : 1, transition: "opacity 0.15s" }}
                              >
                                {converting ? "Conversione…" : "Converti e scarica"}
                              </button>
                              {actionError && <div style={{ fontSize: "0.7rem", color: "#ef4444" }}>! {actionError}</div>}
                            </div>
                          )}
                        </div>
                      ))}
                    </div>
                    {actionError && !activeAction?.match(/resize|compress/) && (
                      <div style={{ marginTop: 6, fontSize: "0.72rem", color: "#ef4444" }}>! {actionError}</div>
                    )}
                  </div>
                )}
              </div>
            </div>
          )}
        </div>


        {/* ── Snapshots panel ── */}
        {tab === "snapshots" && (
          <div style={{ flex: 1, overflow: "auto", padding: "1rem 1.2rem" }}>
            {snapMsg && (
              <div style={{ padding: "0.5rem 0.85rem", marginBottom: "0.8rem", borderRadius: 8,
                background: "rgba(59,130,246,0.12)", color: "#3b82f6", fontSize: "0.78rem" }}>
                {snapMsg}
              </div>
            )}
            {snapLoading ? (
              <div style={{ textAlign: "center", color: "#6b6b8a", padding: "2rem", fontSize: "0.82rem" }}>Caricamento...</div>
            ) : snapshots.length === 0 ? (
              <div style={{ textAlign: "center", color: "#4a4a62", padding: "2.5rem 1rem" }}>
                <div style={{ fontSize: "2.2rem", marginBottom: 8 }}>...</div>
                <div style={{ fontSize: "0.82rem" }}>Nessuno snapshot salvato</div>
                <div style={{ fontSize: "0.7rem", marginTop: 5, color: "#3a3a52" }}>Creati automaticamente ogni 3 step</div>
              </div>
            ) : snapshots.map(s => (
              <div key={s.id} style={{
                display: "flex", alignItems: "center", gap: 10, padding: "0.6rem 0.85rem",
                borderRadius: 10, marginBottom: 6,
                background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.06)",
              }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: "0.8rem", color: "#c0c0d0", fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                    {s.label || "Snapshot"}
                  </div>
                  <div style={{ fontSize: "0.67rem", color: "#6b6b8a", marginTop: 2 }}>
                    {new Date(s.createdAt).toLocaleString("it-IT", { dateStyle: "short", timeStyle: "short" })} · {(s.files as unknown[])?.length ?? 0} file
                  </div>
                </div>
                <Btn color="#3b82f6" onClick={async () => {
                  const ok = await restoreSnapshot(s.id);
                  setSnapMsg(ok ? "Snapshot ripristinato" : "Errore ripristino");
                  setTimeout(() => setSnapMsg(null), 3000);
                }}>Ripristina</Btn>
                <Btn color="#ef4444" onClick={async () => {
                  await deleteSnapshot(s.id);
                  setSnapshots(prev => prev.filter(x => x.id !== s.id));
                }}>X</Btn>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
});

export default FileExplorer;

// ─── Mini button helper ───────────────────────────────────────────────────────

function Btn({ children, color, onClick, disabled }: {
  children: ReactNode; color: string;
  onClick: () => void; disabled?: boolean;
}) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      style={{ all: "unset", cursor: disabled ? "default" : "pointer", fontSize: "0.73rem", color, padding: "3px 9px", borderRadius: 6, background: `${color}12`, border: `1px solid ${color}28`, opacity: disabled ? 0.6 : 1, transition: "opacity 0.15s" }}
    >
      {children}
    </button>
  );
}
