import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import JSZip from 'jszip';
import Layout from '../components/Layout';
import PageLoader from '../components/PageLoader';
import SortTh from '../components/SortTh';
import { supabase } from '../lib/supabase';
import { useAuth } from '../context/AuthContext';
import { popupMenuStyle } from '../lib/popupStyles';

const CARD = {
  background: 'var(--card-bg)',
  border: '1px solid var(--border)',
  borderRadius: 8,
  backdropFilter: 'blur(12px)',
  WebkitBackdropFilter: 'blur(12px)',
};

const LABEL = {
  fontSize: 11,
  fontWeight: 500,
  color: 'var(--text-secondary)',
  letterSpacing: 0.8,
  textTransform: 'uppercase',
  marginBottom: 14,
};

const TABLE_TH = {
  fontSize: 10,
  fontWeight: 500,
  color: 'var(--text-muted)',
  letterSpacing: 0.6,
  textTransform: 'uppercase',
  padding: '0 0 12px',
  border: 'none',
  background: 'transparent',
  verticalAlign: 'top',
};

const TABLE_TD = {
  padding: '5px',
  fontSize: 13,
  color: 'var(--text-primary)',
  border: 'none',
  background: 'transparent',
};

function formatBytes(value) {
  if (!value) return '—';
  const units = ['B', 'KB', 'MB', 'GB'];
  const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), 3);
  return `${(value / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
}

function formatDate(value) {
  if (!value) return '—';
  const date = new Date(value);
  const dateLabel = date.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
  });
  const timeLabel = date.toLocaleTimeString('en-US', {
    hour: 'numeric',
    minute: '2-digit',
    hour12: true,
  });
  return `${dateLabel} ${timeLabel}`;
}

function extOf(name) {
  const dot = name.lastIndexOf('.');
  return dot > 0 ? name.slice(dot + 1).toUpperCase() : 'FILE';
}

function fileIconStyle(ext) {
  const kind = ext.toLowerCase();
  if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'heic'].includes(kind)) return { bg: '#16a34a', color: '#fff' };
  if (['mp4', 'mov', 'avi', 'mkv', 'webm'].includes(kind)) return { bg: '#7c3aed', color: '#fff' };
  if (['mp3', 'wav', 'flac', 'aac'].includes(kind)) return { bg: '#db2777', color: '#fff' };
  if (kind === 'pdf') return { bg: '#dc2626', color: '#fff' };
  if (['doc', 'docx'].includes(kind)) return { bg: '#2563eb', color: '#fff' };
  if (['xls', 'xlsx', 'csv'].includes(kind)) return { bg: '#16a34a', color: '#fff' };
  if (['ppt', 'pptx'].includes(kind)) return { bg: '#ea580c', color: '#fff' };
  if (['zip', 'rar', '7z'].includes(kind)) return { bg: '#92400e', color: '#fff' };
  if (['ai', 'eps'].includes(kind)) return { bg: '#ff6c00', color: '#fff' };
  if (['psd', 'psb'].includes(kind)) return { bg: '#001e36', color: '#31a8ff' };
  if (['fig', 'sketch'].includes(kind)) return { bg: '#7c3aed', color: '#fff' };
  return { bg: '#475569', color: '#fff' };
}

function FileIcon({ name, isDir }) {
  if (isDir) {
    return (
      <div style={{ width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
          <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z" />
        </svg>
      </div>
    );
  }

  const ext = extOf(name);
  const { bg, color } = fileIconStyle(ext);
  return (
    <div style={{ width: 24, height: 24, borderRadius: 4, background: bg, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
      <span style={{ color, fontSize: 7, fontWeight: 600, letterSpacing: 0.3, fontFamily: 'monospace' }}>{ext.slice(0, 4)}</span>
    </div>
  );
}

async function getAccessTokenOrThrow() {
  const { data: { session } } = await supabase.auth.getSession();
  if (session?.access_token) return session.access_token;

  const { data: refreshed, error } = await supabase.auth.refreshSession();
  if (error) throw new Error('Session expired. Please sign in again.');
  if (refreshed?.session?.access_token) return refreshed.session.access_token;

  throw new Error('Session expired. Please sign in again.');
}

async function downloadViaLocalFilebrowser(path, session) {
  const accessToken = session?.access_token || await getAccessTokenOrThrow();
  const response = await fetch(`/api/filebrowser?action=download-blob&path=${encodeURIComponent(path)}&sb_access_token=${encodeURIComponent(accessToken)}`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!response.ok) {
    const text = await response.text();
    throw new Error(text || `Error ${response.status}`);
  }
  return response;
}

async function downloadViaApiToken(path, filename) {
  const accessToken = await getAccessTokenOrThrow();
  const metaResponse = await fetch(`/api/filebrowser?action=download&path=${encodeURIComponent(path)}&sb_access_token=${encodeURIComponent(accessToken)}`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!metaResponse.ok) {
    const text = await metaResponse.text();
    throw new Error(text || `Error ${metaResponse.status}`);
  }
  const { url, token } = await metaResponse.json();
  if (!url || !token) throw new Error('Download URL unavailable.');
  const urlObj = new URL(url, window.location.origin);
  urlObj.searchParams.set('auth', token);
  const link = document.createElement('a');
  link.href = urlObj.toString();
  link.download = filename;
  link.target = '_blank';
  link.rel = 'noopener noreferrer';
  link.style.display = 'none';
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

async function downloadBlobViaLocalFilebrowser(path) {
  const accessToken = await getAccessTokenOrThrow();
  const response = await downloadViaLocalFilebrowser(path, { access_token: accessToken });
  return response.blob();
}

async function fbqProxy(op, path, extra = {}) {
  const accessToken = await getAccessTokenOrThrow();
  const authHeaders = { Authorization: `Bearer ${accessToken}` };

  if (op === 'list') {
    const response = await fetch(`/api/filebrowser?action=list&path=${encodeURIComponent(path)}&sb_access_token=${encodeURIComponent(accessToken)}`, {
      method: 'GET',
      headers: authHeaders,
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(text || `Error ${response.status}`);
    }
    const data = await response.json();
    const entries = Array.isArray(data?.entries) ? data.entries : [];
    const folders = entries
      .filter((entry) => entry.type === 'dir' || entry.type === 'directory' || entry.isDir)
      .map((entry) => ({ name: entry.name, filename: entry.name, path: entry.path, type: 'directory', isDir: true, size: 0, modified: entry.mtime || null }));
    const files = entries
      .filter((entry) => !(entry.type === 'dir' || entry.type === 'directory' || entry.isDir))
      .map((entry) => ({ name: entry.name, filename: entry.name, path: entry.path, type: 'file', isDir: false, size: entry.size || 0, modified: entry.mtime || null }));
    return new Response(JSON.stringify({ folders, files }), { status: 200, headers: { 'Content-Type': 'application/json' } });
  }

  if (op === 'download') {
    return downloadViaLocalFilebrowser(path, { access_token: accessToken });
  }

  if (op === 'mkdir') {
    const normalized = String(path || '/');
    const parts = normalized.split('/').filter(Boolean);
    const name = parts.pop() || '';
    const parentPath = `/${parts.join('/')}`;
    const response = await fetch(`/api/filebrowser?action=mkdir&sb_access_token=${encodeURIComponent(accessToken)}`, {
      method: 'POST',
      headers: { ...authHeaders, 'Content-Type': 'application/json' },
      body: JSON.stringify({ path: parentPath || '/', name }),
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(text || `Error ${response.status}`);
    }
    return response;
  }

  if (op === 'upload') {
    const file = extra.body?.get?.('file');
    if (!file) throw new Error('No file provided.');
    const uploadResponse = await fetch(`/api/filebrowser?action=upload&path=${encodeURIComponent(path)}&sb_access_token=${encodeURIComponent(accessToken)}`, {
      method: 'POST',
      headers: { ...authHeaders, 'Content-Type': 'application/octet-stream' },
      body: file,
    });
    if (!uploadResponse.ok) {
      const text = await uploadResponse.text();
      throw new Error(text || `Error ${uploadResponse.status}`);
    }
    return uploadResponse;
  }

  if (op === 'delete') {
    const files = JSON.parse(extra.headers?.['X-Files'] || '[]');
    for (const filePath of files) {
      const response = await fetch(`/api/filebrowser?action=delete&path=${encodeURIComponent(filePath)}&sb_access_token=${encodeURIComponent(accessToken)}`, {
        method: 'DELETE',
        headers: authHeaders,
      });
      if (!response.ok) {
        const text = await response.text();
        throw new Error(text || `Error ${response.status}`);
      }
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { 'Content-Type': 'application/json' } });
  }

  if (op === 'move' || op === 'copy') {
    const files = JSON.parse(extra.headers?.['X-Files'] || '[]');
    for (const srcPath of files) {
      const response = await fetch(`/api/filebrowser?action=move&sb_access_token=${encodeURIComponent(accessToken)}`, {
        method: 'POST',
        headers: { ...authHeaders, 'Content-Type': 'application/json' },
        body: JSON.stringify({ srcPath, dstPath: path, mode: op }),
      });
      if (!response.ok) {
        const text = await response.text();
        throw new Error(text || `Error ${response.status}`);
      }
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { 'Content-Type': 'application/json' } });
  }

  throw new Error(`Unsupported operation: ${op}`);
}

function safeName(v) {
  return String(v || '').trim().replace(/[\\/:*?"<>|#%{}^~[\]`]+/g, '-').replace(/\s+/g, ' ').replace(/^-+|-+$/g, '');
}

function rootPath(user) {
  if (!user || user.role === 'team' || user.role === 'external') return '/';
  const companies = user.companies?.filter(Boolean) ?? [];
  if (companies.length > 1) return '/';
  const name = companies[0]?.name ?? user.company?.name;
  return name ? `/${safeName(name)}` : '/';
}

function parsePath(path) {
  return path.split('/').filter(Boolean);
}

function buildPath(parts) {
  return `/${parts.join('/')}`;
}

const TREE_TOGGLE_W = 18;

export default function FileSharing() {
  const { currentUser } = useAuth();
  const accessScope = useMemo(() => ({
    home: rootPath(currentUser),
    canManage: currentUser?.role === 'team',
  }), [currentUser]);
  const home = accessScope.home;
  const pinsStorageKey = useMemo(() => `fbq_pins:${currentUser?.id ?? 'anon'}`, [currentUser?.id]);

  const [path, setPath] = useState(home);
  const [entries, setEntries] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [pins, setPins] = useState([]);
  const [selected, setSelected] = useState(new Set());
  const [uploading, setUploading] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(null);
  const [downloadingSelection, setDownloadingSelection] = useState(false);
  const [downloadProgress, setDownloadProgress] = useState(null);
  const [ctxMenu, setCtxMenu] = useState(null);
  const [clipboard, setClipboard] = useState(null);
  const [hoveredNavPath, setHoveredNavPath] = useState(null);
  const [dragActive, setDragActive] = useState(false);
  const [sortKey, setSortKey] = useState('name');
  const [sortDir, setSortDir] = useState('asc');
  const [expanded, setExpanded] = useState(() => new Set([home]));
  const [, setNavVersion] = useState(0);
  const navCache = useRef(new Map());
  const dragDepthRef = useRef(0);
  const rootRef = useRef(null);
  const tableScrollRef = useRef(null);
  const tableHeadRef = useRef(null);
  const firstBodyRowRef = useRef(null);
  const [contentHeight, setContentHeight] = useState(null);
  const [tableHeaderHeight, setTableHeaderHeight] = useState(0);
  const [tableRowHeight, setTableRowHeight] = useState(0);
  const [tableViewportHeight, setTableViewportHeight] = useState(0);

  useEffect(() => {
    if (!ctxMenu) return undefined;
    const close = () => setCtxMenu(null);
    const onKey = (event) => {
      if (event.key === 'Escape') setCtxMenu(null);
    };
    window.addEventListener('click', close);
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('click', close);
      window.removeEventListener('keydown', onKey);
    };
  }, [ctxMenu]);

  const load = useCallback(async (nextPath) => {
    setLoading(true);
    setError('');
    setSelected(new Set());
    try {
      const response = await fbqProxy('list', nextPath);
      const data = await response.json();
      setEntries(Array.isArray(data) ? data : [...(data?.folders ?? []), ...(data?.files ?? [])]);
    } catch (err) {
      setError(err?.message || 'Failed to load files.');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    load(path);
  }, [path, load]);

  useEffect(() => {
    try {
      setPins(JSON.parse(localStorage.getItem(pinsStorageKey) || '[]'));
    } catch {
      setPins([]);
    }
  }, [pinsStorageKey]);

  async function loadNavPath(nextPath) {
    if (navCache.current.has(nextPath)) return;
    navCache.current.set(nextPath, []);
    try {
      const response = await fbqProxy('list', nextPath);
      const data = await response.json();
      const folders = (data?.folders ?? []).map((folder) => folder.name ?? folder.filename ?? '').filter(Boolean);
      navCache.current.set(nextPath, folders);
      setNavVersion((value) => value + 1);
    } catch {
      navCache.current.delete(nextPath);
    }
  }

  useEffect(() => {
    loadNavPath(home);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    function measure() {
      const root = rootRef.current;
      if (!root) return;
      const main = root.closest('.main-content');
      const header = main?.querySelector('.site-header');
      if (!main || !header) return;
      const styles = window.getComputedStyle(main);
      const paddingTop = parseFloat(styles.paddingTop || '0');
      const paddingBottom = parseFloat(styles.paddingBottom || '0');
      setContentHeight(Math.max(320, main.clientHeight - paddingTop - paddingBottom - header.offsetHeight));
    }

    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);

  useEffect(() => {
    function measureTableHeader() {
      setTableHeaderHeight(tableHeadRef.current?.offsetHeight ?? 0);
      setTableRowHeight(firstBodyRowRef.current?.offsetHeight ?? 0);
    }

    measureTableHeader();
    window.addEventListener('resize', measureTableHeader);
    return () => window.removeEventListener('resize', measureTableHeader);
  }, [entries.length, loading, sortDir, sortKey]);

  useEffect(() => {
    const node = tableScrollRef.current;
    if (!node) return undefined;

    const measureViewport = () => setTableViewportHeight(node.clientHeight);
    measureViewport();

    const observer = new ResizeObserver(measureViewport);
    observer.observe(node);
    window.addEventListener('resize', measureViewport);

    return () => {
      observer.disconnect();
      window.removeEventListener('resize', measureViewport);
    };
  }, [contentHeight, entries.length, loading]);

  useEffect(() => {
    const segments = parsePath(path);
    const homeParts = parsePath(home);
    const toExpand = [home];
    for (let index = homeParts.length; index < segments.length; index += 1) {
      toExpand.push(buildPath(segments.slice(0, index + 1)));
    }
    setExpanded((prev) => {
      const next = new Set(prev);
      toExpand.forEach((value) => next.add(value));
      return next;
    });
    toExpand.forEach(loadNavPath);
    loadNavPath(path);
  }, [path]); // eslint-disable-line react-hooks/exhaustive-deps

  function toggleNav(nextPath) {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(nextPath)) {
        next.delete(nextPath);
      } else {
        next.add(nextPath);
        loadNavPath(nextPath);
      }
      return next;
    });
  }

  function renderNavTree(nodePath, depth) { // eslint-disable-line no-unused-vars
    const folders = navCache.current.get(nodePath) ?? [];
    if (!folders.length) return null;
    return folders.map((folderName) => {
      const childPath = nodePath === '/' ? `/${folderName}` : `${nodePath}/${folderName}`;
      const isExpanded = expanded.has(childPath);
      return (
        <div key={childPath}>
          <div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
            <button
              type="button"
              onClick={(event) => {
                event.stopPropagation();
                toggleNav(childPath);
              }}
              style={{ width: 18, height: 28, flexShrink: 0, background: 'none', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', padding: 0 }}
            >
              <svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor" style={{ transform: isExpanded ? 'rotate(90deg)' : 'none', transition: 'transform 0.15s' }}>
                <path d="M2 1l4 3-4 3V1z" />
              </svg>
            </button>
            <button
              type="button"
              onClick={() => navigate(childPath)}
              onContextMenu={(event) => {
                event.preventDefault();
                event.stopPropagation();
                pin(folderName, childPath);
              }}
              onMouseEnter={() => setHoveredNavPath(childPath)}
              onMouseLeave={() => setHoveredNavPath((prev) => (prev === childPath ? null : prev))}
              style={{
                flex: 1,
                background: 'none',
                border: 'none',
                cursor: 'pointer',
                textAlign: 'left',
                padding: '4px 6px 4px 2px',
                borderRadius: 4,
                fontSize: 13,
                fontWeight: 500,
                color: path === childPath || hoveredNavPath === childPath ? 'var(--accent)' : 'var(--text-primary)',
                display: 'flex',
                alignItems: 'center',
                overflow: 'hidden',
                transition: 'color 140ms ease',
              }}
            >
              <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{folderName}</span>
            </button>
          </div>
          {isExpanded && (
            <div style={{ marginLeft: TREE_TOGGLE_W, position: 'relative' }}>
              <span aria-hidden style={{ position: 'absolute', left: -(TREE_TOGGLE_W / 2), top: 0, bottom: 0, width: 1, background: 'var(--border)' }} />
              {renderNavTree(childPath, depth + 1)}
            </div>
          )}
        </div>
      );
    });
  }

  function navigate(nextPath) {
    setPath(nextPath);
  }

  function breadcrumbs() {
    const segments = parsePath(path);
    const homeParts = parsePath(home);
    const crumbs = [{ label: 'Home', path: home }];
    for (let index = homeParts.length; index < segments.length; index += 1) {
      crumbs.push({ label: segments[index], path: buildPath(segments.slice(0, index + 1)) });
    }
    return crumbs;
  }

  function toggleSelect(name) {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name);
      else next.add(name);
      return next;
    });
  }

  function toggleSort(column) {
    if (sortKey === column) {
      setSortDir((prev) => (prev === 'asc' ? 'desc' : 'asc'));
      return;
    }
    setSortKey(column);
    setSortDir(column === 'modified' ? 'desc' : 'asc');
  }

  function pin(label, nextPath) {
    const next = [...pins.filter((item) => item.path !== nextPath), { label, path: nextPath }];
    setPins(next);
    localStorage.setItem(pinsStorageKey, JSON.stringify(next));
  }

  function unpin(nextPath) {
    const next = pins.filter((item) => item.path !== nextPath);
    setPins(next);
    localStorage.setItem(pinsStorageKey, JSON.stringify(next));
  }

  function triggerDownload(url, filename) {
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  }

  async function handleDownload(entry) {
    const nextPath = entry.path ?? (path === '/' ? `/${entry.name}` : `${path}/${entry.name}`);
    try {
      await downloadViaApiToken(nextPath, entry.name);
    } catch (err) {
      setError(err?.message || 'Download failed.');
    }
  }

  async function addPathToZip(zip, targetPath, zipPath = '') {
    const response = await fbqProxy('list', targetPath);
    const data = await response.json();
    const folders = data?.folders ?? [];
    const files = data?.files ?? [];

    for (const folder of folders) {
      const folderName = folder.name ?? folder.filename ?? '';
      if (!folderName) continue;
      const childPath = targetPath === '/' ? `/${folderName}` : `${targetPath}/${folderName}`;
      const childZipPath = zipPath ? `${zipPath}/${folderName}` : folderName;
      await addPathToZip(zip, childPath, childZipPath);
    }

    for (const file of files) {
      const fileName = file.name ?? file.filename ?? '';
      if (!fileName) continue;
      const filePath = file.path ?? (targetPath === '/' ? `/${fileName}` : `${targetPath}/${fileName}`);
      const blob = await downloadBlobViaLocalFilebrowser(filePath);
      zip.file(zipPath ? `${zipPath}/${fileName}` : fileName, blob);
    }
  }

  async function handleDownloadSelected() {
    if (!selected.size) return;
    setDownloadingSelection(true);
    setDownloadProgress(0);
    setError('');
    try {
      const selectedEntries = [...selected]
        .map((name) => entries.find((entry) => (entry.name ?? entry.filename) === name))
        .filter(Boolean);

      if (selectedEntries.length === 1) {
        const [entry] = selectedEntries;
        const name = entry.name ?? entry.filename ?? '';
        const targetPath = entry.path ?? (path === '/' ? `/${name}` : `${path}/${name}`);
        const isDir = entry.type === 'directory' || entry.isDir;

        if (!isDir) {
          const blob = await downloadBlobViaLocalFilebrowser(targetPath);
          setDownloadProgress(100);
          triggerDownload(URL.createObjectURL(blob), name);
          return;
        }

        const zip = new JSZip();
        setDownloadProgress(50);
        await addPathToZip(zip, targetPath, name);
        setDownloadProgress(100);
        triggerDownload(URL.createObjectURL(await zip.generateAsync({ type: 'blob' })), `${name}.zip`);
        return;
      }

      const zip = new JSZip();

      for (let i = 0; i < selectedEntries.length; i++) {
        const entry = selectedEntries[i];
        const name = entry.name ?? entry.filename ?? '';
        const targetPath = entry.path ?? (path === '/' ? `/${name}` : `${path}/${name}`);
        const isDir = entry.type === 'directory' || entry.isDir;
        if (isDir) {
          await addPathToZip(zip, targetPath, name);
        } else {
          const blob = await downloadBlobViaLocalFilebrowser(targetPath);
          zip.file(name, blob);
        }
        setDownloadProgress(Math.round(((i + 1) / selectedEntries.length) * 100));
      }

      const folderName = parsePath(path).at(-1) || 'files';
      triggerDownload(URL.createObjectURL(await zip.generateAsync({ type: 'blob' })), `${folderName}-selection.zip`);
    } catch (err) {
      setError(err?.message || 'Download failed.');
    } finally {
      setDownloadingSelection(false);
      setDownloadProgress(null);
    }
  }

  async function handlePaste(destPath) {
    if (!clipboard) return;
    try {
      await fbqProxy(clipboard.mode, destPath, {
        headers: { 'X-Files': JSON.stringify(clipboard.items.map((i) => i.path)) },
      });
      if (clipboard.mode === 'move') setClipboard(null);
      load(path);
    } catch {
      setError('Paste failed.');
    }
  }

  async function handleDelete() {
    if (!selected.size) return;
    if (!window.confirm(`Delete ${selected.size} item(s)?`)) return;
    const filePaths = [...selected].map((name) => (
      entries.find((entry) => (entry.name ?? entry.filename) === name)?.path
      ?? (path === '/' ? `/${name}` : `${path}/${name}`)
    ));
    try {
      await fbqProxy('delete', path, { headers: { 'X-Files': JSON.stringify(filePaths) } });
      load(path);
    } catch {
      setError('Delete failed.');
    }
  }

  async function uploadFiles(files) {
    if (!files.length) return;
    setUploading(true);
    setUploadProgress(0);
    try {
      const dirSet = new Set();
      files.forEach(({ relativePath }) => {
        const segments = relativePath.split('/').filter(Boolean);
        for (let index = 1; index < segments.length; index += 1) {
          dirSet.add(segments.slice(0, index).join('/'));
        }
      });

      const dirs = [...dirSet].sort((left, right) => left.split('/').length - right.split('/').length);
      for (const dir of dirs) {
        const dirPath = `${path === '/' ? '' : path}/${dir}/`;
        try {
          await fbqProxy('mkdir', dirPath);
        } catch {}
      }

      for (let index = 0; index < files.length; index += 1) {
        const { file, relativePath } = files[index];
        const formData = new FormData();
        formData.append('file', file);
        await fbqProxy('upload', path === '/' ? `/${relativePath}` : `${path}/${relativePath}`, { body: formData });
        setUploadProgress(Math.round(((index + 1) / files.length) * 100));
      }
      load(path);
    } catch {
      setError('Upload failed.');
    } finally {
      setUploading(false);
      setUploadProgress(null);
    }
  }

  async function collectEntry(entry, parent = '') {
    if (entry.isFile) {
      return [await new Promise((resolve, reject) => entry.file((file) => resolve({ file, relativePath: parent ? `${parent}/${file.name}` : file.name }), reject))];
    }
    if (!entry.isDirectory) return [];
    const nextParent = parent ? `${parent}/${entry.name}` : entry.name;
    const reader = entry.createReader();
    const children = [];
    while (true) {
      const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
      if (!batch.length) break;
      children.push(...batch);
    }
    return (await Promise.all(children.map((child) => collectEntry(child, nextParent)))).flat();
  }

  async function handleDrop(event) {
    event.preventDefault();
    event.stopPropagation();
    dragDepthRef.current = 0;
    setDragActive(false);

    const entriesToRead = [...(event.dataTransfer?.items ?? [])]
      .filter((item) => item.kind === 'file')
      .map((item) => item.webkitGetAsEntry?.())
      .filter(Boolean);

    if (entriesToRead.length) {
      await uploadFiles((await Promise.all(entriesToRead.map((entry) => collectEntry(entry)))).flat());
      return;
    }

    await uploadFiles(
      [...(event.dataTransfer?.files ?? [])].map((file) => ({
        file,
        relativePath: file.webkitRelativePath || file.name,
      })),
    );
  }

  function handleDragEnter(event) {
    event.preventDefault();
    event.stopPropagation();
    dragDepthRef.current += 1;
    setDragActive(true);
  }

  function handleDragOver(event) {
    event.preventDefault();
    event.stopPropagation();
    event.dataTransfer.dropEffect = 'copy';
  }

  function handleDragLeave(event) {
    event.preventDefault();
    event.stopPropagation();
    dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
    if (dragDepthRef.current === 0) setDragActive(false);
  }

  function openCtxMenu(event, entry, childPath) {
    event.preventDefault();
    event.stopPropagation();
    setCtxMenu({ x: event.clientX, y: event.clientY, entry, childPath });
  }

  const crumbs = breadcrumbs();

  const sortedEntries = useMemo(() => {
    function valueOf(entry, column) {
      if (column === 'name') return (entry.name ?? entry.filename ?? '').toLowerCase();
      if (column === 'size') return Number(entry.size ?? -1);
      if (column === 'modified') return new Date(entry.modified ?? entry.lastModified ?? 0).getTime();
      return '';
    }

    return [...entries].sort((left, right) => {
      const leftIsDir = left.type === 'directory' || left.isDir;
      const rightIsDir = right.type === 'directory' || right.isDir;
      if (leftIsDir !== rightIsDir) return leftIsDir ? -1 : 1;

      let result = 0;
      const leftValue = valueOf(left, sortKey);
      const rightValue = valueOf(right, sortKey);
      if (typeof leftValue === 'string' && typeof rightValue === 'string') {
        result = leftValue.localeCompare(rightValue);
      } else if (leftValue === rightValue) {
        result = 0;
      } else {
        result = leftValue > rightValue ? 1 : -1;
      }

      if (result === 0) {
        const leftName = (left.name ?? left.filename ?? '').toLowerCase();
        const rightName = (right.name ?? right.filename ?? '').toLowerCase();
        result = leftName.localeCompare(rightName);
      }

      return sortDir === 'asc' ? result : -result;
    });
  }, [entries, sortDir, sortKey]);

  const fillerRowCount = useMemo(() => {
    if (!entries.length || !tableRowHeight || !tableViewportHeight) return 0;
    const availableHeight = tableViewportHeight - tableHeaderHeight;
    if (availableHeight <= 0) return 0;
    const visibleRowCount = Math.ceil(availableHeight / tableRowHeight) + 1;
    return Math.max(0, visibleRowCount - sortedEntries.length);
  }, [entries.length, sortedEntries.length, tableHeaderHeight, tableRowHeight, tableViewportHeight]);

  return (
    <Layout>
      <div
        ref={rootRef}
        style={{
          display: 'grid',
          gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 3fr)',
          gap: 24,
          height: contentHeight ?? 'calc(100vh - 160px)',
          alignItems: 'stretch',
        }}
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 24, minHeight: 0 }}>
          <div style={{ ...CARD, padding: '14px 16px', flexShrink: 0 }}>
            <div style={LABEL}>Pinned</div>
            {pins.length === 0 ? (
              <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>Right-click a folder to pin it.</div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                {pins.map((pinItem) => (
                  <div key={pinItem.path} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                    <button
                      type="button"
                      onClick={() => navigate(pinItem.path)}
                      onMouseEnter={() => setHoveredNavPath(pinItem.path)}
                      onMouseLeave={() => setHoveredNavPath((prev) => (prev === pinItem.path ? null : prev))}
                      style={{
                        flex: 1,
                        background: 'none',
                        border: 'none',
                        cursor: 'pointer',
                        textAlign: 'left',
                        padding: '4px 6px',
                        borderRadius: 4,
                        fontSize: 13,
                        fontWeight: 500,
                        color: path === pinItem.path || hoveredNavPath === pinItem.path ? 'var(--accent)' : 'var(--text-primary)',
                        display: 'flex',
                        alignItems: 'center',
                        overflow: 'hidden',
                        transition: 'color 140ms ease',
                      }}
                    >
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{pinItem.label}</span>
                    </button>
                    <button type="button" onClick={() => unpin(pinItem.path)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, color: 'var(--text-muted)', flexShrink: 0 }}>
                      <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
                        <line x1="18" y1="6" x2="6" y2="18" />
                        <line x1="6" y1="6" x2="18" y2="18" />
                      </svg>
                    </button>
                  </div>
                ))}
              </div>
            )}
          </div>

          <div style={{ ...CARD, padding: '14px 16px', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
            <div style={{ ...LABEL, flexShrink: 0 }}>Navigation</div>
            <div className="table-scroll-hidden" style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
              <button
                type="button"
                onClick={() => navigate(home)}
                onMouseEnter={() => setHoveredNavPath(home)}
                onMouseLeave={() => setHoveredNavPath((prev) => (prev === home ? null : prev))}
                style={{ width: '100%', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'left', padding: '4px 6px', borderRadius: 4, fontSize: 13, fontWeight: 500, color: path === home || hoveredNavPath === home ? 'var(--accent)' : 'var(--text-primary)', display: 'flex', alignItems: 'center', marginBottom: 2, transition: 'color 140ms ease' }}
              >
                Home
              </button>
              {renderNavTree(home, 0)}
            </div>
          </div>
        </div>

        <div
          style={{
            ...CARD,
            padding: 0,
            position: 'relative',
            display: 'flex',
            flexDirection: 'column',
            overflow: 'hidden',
            borderColor: dragActive ? 'var(--accent)' : 'var(--border)',
            boxShadow: dragActive ? '0 0 0 1px rgba(245,165,35,0.25) inset' : 'none',
            transition: 'border-color 140ms ease, box-shadow 140ms ease',
          }}
          onDragEnter={handleDragEnter}
          onDragOver={handleDragOver}
          onDragLeave={handleDragLeave}
          onDrop={handleDrop}
        >
          <div style={{ padding: '18px 21px', borderBottom: '1px solid var(--border)', flexShrink: 0, position: 'relative' }}>
            {selected.size > 0 && (
              <div style={{ position: 'absolute', top: 18, right: 21, display: 'flex', gap: 8 }}>
                <button
                  type="button"
                  className="btn btn-outline"
                  onClick={handleDownloadSelected}
                  disabled={downloadingSelection}
                >
                  {downloadingSelection ? 'Downloading...' : `Download (${selected.size})`}
                </button>
                <button type="button" className="btn btn-danger" onClick={handleDelete}>
                  Delete ({selected.size})
                </button>
              </div>
            )}

            <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', paddingRight: selected.size > 0 ? 286 : 0 }}>
              <div>
                <div style={LABEL}>File Browser</div>
                <div style={{ fontSize: 12, color: dragActive ? 'var(--accent)' : 'var(--text-muted)', marginTop: -2, marginBottom: 14, transition: 'color 140ms ease' }}>
                  Drag and drop files or folders here.
                </div>
                <div style={{ ...LABEL, marginBottom: 0 }}>
                  {crumbs.map((crumb, index) => (
                    <span key={crumb.path}>
                      {index > 0 && <span style={{ margin: '0 4px', opacity: 0.4 }}>/</span>}
                      <button
                        type="button"
                        className="dashboard-inline-link"
                        onClick={() => navigate(crumb.path)}
                        style={{
                          background: 'none',
                          border: 'none',
                          cursor: index < crumbs.length - 1 ? 'pointer' : 'default',
                          padding: 0,
                          color: index === crumbs.length - 1 ? 'var(--accent)' : 'var(--text-primary)',
                          fontSize: 'inherit',
                          fontWeight: 'inherit',
                          letterSpacing: 'inherit',
                          textTransform: 'inherit',
                          fontFamily: 'inherit',
                          lineHeight: 'inherit',
                        }}
                      >
                        {crumb.label}
                      </button>
                    </span>
                  ))}
                </div>
              </div>
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                {uploading && <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Uploading {uploadProgress}%</div>}
              </div>
            </div>
          </div>

          <div
            className="table-scroll-hidden"
            ref={tableScrollRef}
            style={{
              flex: 1,
              overflowY: 'auto',
              minHeight: 0,
              padding: '18px 21px',
            }}
            onContextMenu={(e) => {
              if (e.target === e.currentTarget || e.target.tagName === 'TABLE' || e.target.closest('table')?.parentElement === e.currentTarget) {
                e.preventDefault();
                if (clipboard) setCtxMenu({ x: e.clientX, y: e.clientY, entry: null, childPath: null });
              }
            }}
          >
            {error && <div style={{ fontSize: 13, color: 'var(--danger)', paddingBottom: 12 }}>{error}</div>}
            {loading ? (
              <div className="card-empty-center">Loading...</div>
            ) : entries.length === 0 ? (
              <div className="card-empty-center">This folder is empty.</div>
            ) : (
              <table className="table-sticky-head" style={{ width: '100%', tableLayout: 'fixed', borderCollapse: 'collapse' }}>
                <thead ref={tableHeadRef}>
                  <tr>
                    <th style={{ ...TABLE_TH, width: 32, textAlign: 'center', padding: '0 5px 12px 5px', cursor: 'default' }}>
                      <input type="checkbox" checked={selected.size === entries.length && entries.length > 0} onChange={(event) => setSelected(event.target.checked ? new Set(entries.map((entry) => entry.name ?? entry.filename)) : new Set())} />
                    </th>
                    <SortTh col="name" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} style={{ ...TABLE_TH, textAlign: 'left', paddingLeft: 5 }}>
                      Name
                    </SortTh>
                    <SortTh col="size" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} style={{ ...TABLE_TH, textAlign: 'right', width: 100, padding: '0 5px 12px 5px' }}>
                      Size
                    </SortTh>
                    <SortTh col="modified" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} style={{ ...TABLE_TH, textAlign: 'right', width: 185, padding: '0 5px 12px 5px' }}>
                      Modified
                    </SortTh>
                  </tr>
                </thead>
                <tbody>
                  {sortedEntries.map((entry, index) => {
                    const name = entry.name ?? entry.filename ?? '';
                    const isDir = entry.type === 'directory' || entry.isDir;
                    const childPath = entry.path ?? (path === '/' ? `/${name}` : `${path}/${name}`);
                    const rowBg = index % 2 === 0 ? 'var(--file-row-alt-bg)' : 'transparent';

                    return (
                      <tr
                        key={name}
                        ref={index === 0 ? firstBodyRowRef : undefined}
                        style={{ cursor: isDir ? 'pointer' : 'default' }}
                        onClick={() => isDir && navigate(childPath)}
                        onContextMenu={(event) => openCtxMenu(event, entry, childPath)}
                      >
                        <td style={{ ...TABLE_TD, width: 32, textAlign: 'center', background: rowBg }} onClick={(event) => { event.stopPropagation(); toggleSelect(name); }}>
                          <input type="checkbox" checked={selected.has(name)} onChange={() => toggleSelect(name)} onClick={(event) => event.stopPropagation()} />
                        </td>
                        <td style={{ ...TABLE_TD, textAlign: 'left', background: rowBg }}>
                          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                            <FileIcon name={name} isDir={isDir} />
                            {isDir ? (
                              <button type="button" className="dashboard-inline-link" onClick={(event) => { event.stopPropagation(); navigate(childPath); }} style={{ fontSize: 13, fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'left' }}>
                                {name}
                              </button>
                            ) : (
                              <span style={{ fontSize: 13, color: 'var(--text-primary)', fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</span>
                            )}
                          </div>
                        </td>
                        <td style={{ ...TABLE_TD, textAlign: 'right', fontSize: 12, color: 'var(--text-primary)', background: rowBg }}>{isDir ? '—' : formatBytes(entry.size ?? null)}</td>
                        <td style={{ ...TABLE_TD, textAlign: 'right', fontSize: 12, color: 'var(--text-primary)', background: rowBg }}>{formatDate(entry.modified ?? entry.lastModified ?? null)}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>

          {(loading || uploading || downloadingSelection) && (
            <PageLoader
              scope="container"
              label={uploading ? 'Uploading' : downloadingSelection ? 'Downloading' : 'Loading'}
              progress={uploading ? uploadProgress : downloadingSelection ? downloadProgress : null}
            />
          )}
        </div>
      </div>

      {ctxMenu && (() => {
        const { x, y, entry, childPath } = ctxMenu;
        const isDir = entry ? (entry.type === 'directory' || entry.isDir) : false;
        const name = entry ? (entry.name ?? entry.filename ?? '') : '';
        const isPinned = entry ? pins.some((pinItem) => pinItem.path === childPath) : false;
        const clipboardItems = entry
          ? (selected.has(name)
            ? [...selected].map((n) => {
                const e = entries.find((en) => (en.name ?? en.filename) === n);
                return { name: n, path: e?.path ?? (path === '/' ? `/${n}` : `${path}/${n}`) };
              })
            : [{ name, path: childPath }])
          : [];
        const itemStyle = {
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          padding: '7px 14px',
          fontSize: 13,
          cursor: 'pointer',
          color: 'var(--text-primary)',
          background: 'none',
          border: 'none',
          width: '100%',
          textAlign: 'left',
          borderRadius: 4,
          whiteSpace: 'nowrap',
        };
        const separatorStyle = { height: 1, background: 'var(--border)', margin: '4px 0' };

        return (
          <div onClick={(event) => event.stopPropagation()} style={{ ...popupMenuStyle, position: 'fixed', left: x, top: y, zIndex: 1000, padding: '4px 0', minWidth: 160 }}>
            {entry && isDir && (
              <button
                style={itemStyle}
                onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                onClick={() => {
                  navigate(childPath);
                  setCtxMenu(null);
                }}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z" />
                </svg>
                Open
              </button>
            )}

            {entry && !isDir && (
              <button
                style={itemStyle}
                onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                onClick={() => {
                  handleDownload(entry);
                  setCtxMenu(null);
                }}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" />
                  <polyline points="7,10 12,15 17,10" />
                  <line x1="12" y1="15" x2="12" y2="3" />
                </svg>
                Download
              </button>
            )}

            {entry && isDir && (
              <>
                <div style={separatorStyle} />
                <button
                  style={itemStyle}
                  onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                  onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                  onClick={() => {
                    if (isPinned) unpin(childPath);
                    else pin(name, childPath);
                    setCtxMenu(null);
                  }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
                  </svg>
                  {isPinned ? 'Unpin' : 'Pin folder'}
                </button>
              </>
            )}

            {entry && (
              <>
                <div style={separatorStyle} />
                <button
                  style={itemStyle}
                  onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                  onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                  onClick={() => {
                    setClipboard({ items: clipboardItems, mode: 'copy' });
                    setCtxMenu(null);
                  }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                    <rect x="9" y="9" width="13" height="13" rx="2" />
                    <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
                  </svg>
                  Copy{clipboardItems.length > 1 ? ` (${clipboardItems.length})` : ''}
                </button>
                <button
                  style={itemStyle}
                  onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                  onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                  onClick={() => {
                    setClipboard({ items: clipboardItems, mode: 'move' });
                    setCtxMenu(null);
                  }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                    <circle cx="6" cy="6" r="3" /><circle cx="6" cy="18" r="3" />
                    <line x1="20" y1="4" x2="8.12" y2="15.88" /><line x1="14.47" y1="14.48" x2="20" y2="20" /><line x1="8.12" y1="8.12" x2="12" y2="12" />
                  </svg>
                  Cut{clipboardItems.length > 1 ? ` (${clipboardItems.length})` : ''}
                </button>
              </>
            )}

            {clipboard && (
              <>
                <div style={separatorStyle} />
                <button
                  style={itemStyle}
                  onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--interactive-row-hover)'; }}
                  onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                  onClick={() => {
                    handlePaste(path);
                    setCtxMenu(null);
                  }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" />
                    <rect x="8" y="2" width="8" height="4" rx="1" />
                  </svg>
                  Paste here{clipboard.items.length > 1 ? ` (${clipboard.items.length})` : ''}
                </button>
              </>
            )}

            {accessScope.canManage && entry && (
              <>
                <div style={separatorStyle} />
                <button
                  style={{ ...itemStyle, color: 'var(--danger)' }}
                  onMouseEnter={(event) => { event.currentTarget.style.background = 'rgba(255,80,80,0.08)'; }}
                  onMouseLeave={(event) => { event.currentTarget.style.background = 'none'; }}
                  onClick={() => {
                    setCtxMenu(null);
                    if (!window.confirm(`Delete "${name}"?`)) return;
                    fbqProxy('delete', path, { headers: { 'X-Files': JSON.stringify([childPath]) } })
                      .then(() => load(path))
                      .catch(() => setError('Delete failed.'));
                  }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="3,6 5,6 21,6" />
                    <path d="M19 6l-1 14H6L5 6" />
                    <path d="M10 11v6" />
                    <path d="M14 11v6" />
                    <path d="M9 6V4h6v2" />
                  </svg>
                  Delete
                </button>
              </>
            )}
          </div>
        );
      })()}
    </Layout>
  );
}
