// screens-library.jsx

function Library({ navigate, onOpenAsset, openProject, onClearProject }) {
  const PROJECTS = window.ZAMPP_DATA?.PROJECTS || [];
  const [bins, setBins] = React.useState(window.ZAMPP_DATA?.BINS || []);
  const BINS = bins; // legacy local name; keep so the rest of the function reads unchanged

  // Re-fetch bins on mount + whenever the open project changes; surfaces
  // every-project bins when the global view is on, project-scoped otherwise.
  var refreshBins = React.useCallback(function() {
    var qs2 = openProject ? '?project_id=' + openProject.id : '';
    window.ZAMPP_API.fetch('/bins' + qs2)
      .then(function(list) {
        var normalized = (list || []).map(function(b) { return { ...b, count: b.asset_count != null ? b.asset_count : (b.count || 0), icon: b.type || 'grid' }; });
        if (!openProject) window.ZAMPP_DATA.BINS = normalized;
        setBins(normalized);
      })
      .catch(function() {});
  }, [openProject]);

  React.useEffect(function() {
    refreshBins();
    var onBinsChanged = function() { refreshBins(); };
    window.addEventListener('df:bins-changed', onBinsChanged);
    return function() { window.removeEventListener('df:bins-changed', onBinsChanged); };
  }, [refreshBins]);

  const createBin = () => {
    if (!openProject) { window.alert('Open a project first (Projects → click a project), then create a bin inside it.'); return; }
    setNewBinName(''); setCreatingBin(true);
  };

  const submitBin = (name) => {
    if (!name || !name.trim()) { setCreatingBin(false); return; }
    setCreatingBin(false);
    window.ZAMPP_API.fetch('/bins', {
      method: 'POST',
      body: JSON.stringify({ project_id: openProject.id, name: name.trim() }),
    })
      .then(() => window.ZAMPP_API.fetch('/bins?project_id=' + openProject.id))
      .then(list => setBins((list || []).map(b => ({ ...b, count: b.asset_count || 0, icon: b.type || 'grid' }))))
      .catch(e => window.alert('Could not create bin: ' + e.message));
  };
  const [view, setView] = React.useState('grid');
  const [filter, setFilter] = React.useState('all'); // 'all', 'ready', 'processing', 'live', 'error', 'recent'
  const [search, setSearch] = React.useState(window._dfPendingSearch || '');
  React.useEffect(() => { delete window._dfPendingSearch; }, []);
  // Local state lets us re-render after delete / move-to-bin without forcing
  // a full app reload - keeps ZAMPP_DATA in sync as the cache of record.
  const [allAssets, setAllAssets] = React.useState(window.ZAMPP_DATA.ASSETS || []);
  const [ctxMenu, setCtxMenu] = React.useState(null); // { asset, x, y }
  const [renamingAsset, setRenamingAsset] = React.useState(null);
  const [confirm, confirmModal] = window.useConfirm();
  // Asset queued for hi-res download. Null means no modal showing. Set when
  // the user clicks Download and has NOT dismissed the "are you sure" warning.
  const [pendingDownload, setPendingDownload] = React.useState(null);

  // Single entry point for hi-res downloads from the library. If the user
  // ticked "Don't show again" in the warning modal, skip straight to the
  // download; otherwise pop the modal with this asset queued. The modal's
  // confirm handler calls runDownload directly.
  const requestDownload = function(asset) {
    if (!asset || !asset.original_s3_key) return;
    try {
      if (localStorage.getItem('df.lib.download.warnDismissed') === '1') {
        runDownload(asset);
        return;
      }
    } catch (_) { /* localStorage unavailable: show modal to be safe */ }
    setPendingDownload(asset);
  };
  const [creatingBin, setCreatingBin] = React.useState(false);
  const [newBinName, setNewBinName] = React.useState('');
  const [draggingAssetId, setDraggingAssetId] = React.useState(null);
  const [dragOverBinId, setDragOverBinId] = React.useState(null);
  const [recentlyMovedId, setRecentlyMovedId] = React.useState(null);
  // Rename project state
  const [renamingProject, setRenamingProject] = React.useState(null);
  const [projVersion, setProjVersion] = React.useState(0);
  // Multi-select state
  const [selectedAssets, setSelectedAssets] = React.useState(new Set());
  const [selectionMode, setSelectionMode] = React.useState(false);

  const refreshAssets = React.useCallback(() => {
    window.ZAMPP_API.refreshAssets()
      .then(function(normalized) {
        setAllAssets(normalized);
      })
      .catch(() => {});
  }, []);

  const toggleSelection = function(assetId, e) {
    if (e) e.stopPropagation();
    setSelectedAssets(function(prev) {
      var next = new Set(prev);
      if (next.has(assetId)) next.delete(assetId);
      else next.add(assetId);
      return next;
    });
  };

  const selectAll = function() {
    setSelectedAssets(new Set(assets.map(function(a) { return a.id; })));
  };

  const clearSelection = function() {
    setSelectedAssets(new Set());
    setSelectionMode(false);
  };

  const bulkMoveToBin = async function(binId) {
    var ids = Array.from(selectedAssets);
    if (ids.length === 0) return;
    var targetBin = bins.find(function(b) { return b.id === binId; });
    for (var i = 0; i < ids.length; i++) {
      var asset = allAssets.find(function(a) { return a.id === ids[i]; });
      if (asset && targetBin && asset.project_id && targetBin.project_id && asset.project_id !== targetBin.project_id) {
        alert('Cannot move assets to a bin in a different project.');
        return;
      }
    }
    try {
      await Promise.all(ids.map(function(id) {
        return window.ZAMPP_API.fetch('/assets/' + id, { method: 'PATCH', body: JSON.stringify({ bin_id: binId }) });
      }));
      refreshAssets();
      window.dispatchEvent(new Event('df:bins-changed'));
      clearSelection();
    } catch (e) {
      alert('Bulk move failed: ' + e.message);
    }
  };

  const bulkDelete = async function() {
    var ids = Array.from(selectedAssets);
    if (ids.length === 0) return;
    if (!(await confirm({
      title: 'Delete ' + ids.length + ' assets?',
      message: 'Delete ' + ids.length + ' assets permanently?\nThis removes the database rows and S3 objects.\nThis cannot be undone.',
    }))) return;
    try {
      await Promise.all(ids.map(function(id) {
        return window.ZAMPP_API.fetch('/assets/' + id + '?hard=true', { method: 'DELETE' });
      }));
      refreshAssets();
      clearSelection();
    } catch (e) {
      alert('Bulk delete failed: ' + e.message);
    }
  };

  const deleteAsset = React.useCallback(async (asset) => {
    if (!(await confirm({
      title: 'Delete asset?',
      message: 'Delete "' + (asset.display_name || asset.name) + '" permanently?\nThis removes the database row and the S3 objects.\nThis cannot be undone.',
    }))) return;
    window.ZAMPP_API.fetch('/assets/' + asset.id + '?hard=true', { method: 'DELETE' })
      .then(refreshAssets)
      .catch(function(e) { alert('Delete failed: ' + e.message); });
  }, [confirm, refreshAssets]);

  // Auto-refresh: poll the library while it's open so live recordings flip
  // to 'ready' (with thumbnail) without a manual reload. Also pull once on
  // mount so uploads/imports created on other screens appear immediately.
  const hasLive = React.useMemo(
    () => allAssets.some(a => a.status === 'live' || a.status === 'processing' || a.status === 'ingesting'),
    [allAssets]
  );
  React.useEffect(() => {
    refreshAssets();
    const tick = hasLive ? 4000 : 15000;
    const id = setInterval(refreshAssets, tick);
    const onAssetsChanged = () => refreshAssets();
    window.addEventListener('df:assets-changed', onAssetsChanged);
    return () => {
      clearInterval(id);
      window.removeEventListener('df:assets-changed', onAssetsChanged);
    };
  }, [hasLive, refreshAssets]);

  // Dismiss the context menu on any outside click (capture phase so clicking
  // a menu item still fires before the menu unmounts).
  React.useEffect(() => {
    if (!ctxMenu) return;
    const close = () => setCtxMenu(null);
    window.addEventListener('click', close);
    window.addEventListener('contextmenu', close);
    window.addEventListener('scroll', close, true);
    return () => {
      window.removeEventListener('click', close);
      window.removeEventListener('contextmenu', close);
      window.removeEventListener('scroll', close, true);
    };
  }, [ctxMenu]);

  const openCtx = (asset, e) => {
    e.preventDefault();
    e.stopPropagation();
    setCtxMenu({ asset, x: e.clientX, y: e.clientY });
  };

  // Drag-and-drop: asset → bin
  const onAssetDragStart = function(assetId, e) {
    e.dataTransfer.setData('text/plain', assetId);
    e.dataTransfer.effectAllowed = 'move';
    setDraggingAssetId(assetId);
  };

  const onBinDragOver = function(binId, e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    if (dragOverBinId !== binId) setDragOverBinId(binId);
  };

  const onBinDragLeave = function() {
    setDragOverBinId(null);
  };

  const onBinDrop = function(binId, e) {
    e.preventDefault();
    setDraggingAssetId(null);
    var assetId = e.dataTransfer.getData('text/plain');
    if (!assetId) return;
    // Guard against cross-project moves
    var asset = allAssets.find(function(a) { return a.id === assetId; });
    var targetBin = bins.find(function(b) { return b.id === binId; });
    if (asset && targetBin && asset.project_id && targetBin.project_id && asset.project_id !== targetBin.project_id) {
      alert('Cannot move asset to a bin in a different project.');
      return;
    }
    window.ZAMPP_API.fetch('/assets/' + assetId, { method: 'PATCH', body: JSON.stringify({ bin_id: binId }) })
      .then(function() {
        setRecentlyMovedId(assetId);
        refreshAssets();
        window.dispatchEvent(new Event('df:bins-changed'));
        setTimeout(function() { setRecentlyMovedId(null); }, 2000);
      })
      .catch(function(e2) { alert('Move failed: ' + e2.message); });
  };

  // Project rename
  const renameProject = function(p) { setRenamingProject(p); };

  // Close drag state when drag ends anywhere
  React.useEffect(function() {
    if (!draggingAssetId) return;
    var onEnd = function() { setDraggingAssetId(null); };
    window.addEventListener('dragend', onEnd);
    return function() { window.removeEventListener('dragend', onEnd); };
  }, [draggingAssetId]);

  // Project context menu state
  var [projectCtx, setProjectCtx] = React.useState(null);
  React.useEffect(function() {
    if (!projectCtx) return;
    var close = function() { setProjectCtx(null); };
    // #49: add contextmenu + scroll dismiss so right-clicking another project
    // or scrolling away doesn't leave the menu orphaned
    window.addEventListener('click', close);
    window.addEventListener('contextmenu', close);
    window.addEventListener('scroll', close, true);
    return function() {
      window.removeEventListener('click', close);
      window.removeEventListener('contextmenu', close);
      window.removeEventListener('scroll', close, true);
    };
  }, [projectCtx]);

  var openProjectCtx = function(p, e) {
    e.preventDefault();
    e.stopPropagation();
    setProjectCtx({ project: p, x: e.clientX, y: e.clientY });
  };

  const [selectedBinId, setSelectedBinId] = React.useState(null);
  // Clear bin filter on project change so a stale id doesn't hide everything.
  React.useEffect(() => { setSelectedBinId(null); }, [openProject?.id]);
  let assets = openProject
    ? allAssets.filter(function(a) { return a.project_id === openProject.id; })
    : allAssets;
  const ALL_ASSETS = allAssets;
  if (filter === 'recent') {
    assets = assets.filter(function(a) { return (Date.now() - new Date(a.created_at)) < 86400000; });
  } else if (filter !== 'all') {
    assets = assets.filter(function(a) { return a.status === filter; });
  }
  if (search) assets = assets.filter(function(a) { return a.name.toLowerCase().includes(search.toLowerCase()); });
  if (selectedBinId) assets = assets.filter(function(a) { return a.bin_id === selectedBinId; });

  const activeBin = selectedBinId ? BINS.find(b => b.id === selectedBinId) : null;
  const displayTitle = activeBin
    ? (openProject ? openProject.name + ' · ' : '') + activeBin.name
    : (openProject ? openProject.name : 'All Assets');
  const errorCount = ALL_ASSETS.filter(function(a) { return a.status === 'error'; }).length;
  const recentCount = ALL_ASSETS.filter(function(a) { return (Date.now() - new Date(a.created_at)) < 86400000; }).length;

  return (
    <div className="library-layout">
      {confirmModal}
      <aside className="library-rail">
        <div>
          <h4>Projects</h4>
          <div className="rail-list">
            <div className={`rail-item ${!openProject ? 'active' : ''}`} onClick={function() { if (onClearProject) onClearProject(); navigate('library'); }} style={{ cursor: 'pointer' }}>
              <Icon name="library" size={13} className="rail-icon" />
              <span>All projects</span>
              <span className="rail-count">{ALL_ASSETS.length}</span>
            </div>
            {PROJECTS.slice(0, 8).map(function(p) {
              return (
                <div key={p.id} className={`rail-item ${openProject && openProject.id === p.id ? 'active' : ''}`} style={{ cursor: 'pointer' }}
                  onClick={function() { navigate('projects'); }}
                  onContextMenu={function(e) { openProjectCtx(p, e); }}>
                  <span className="rail-color-dot" style={{ background: p.color }} />
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
                  <span className="rail-count">{p.assets}</span>
                </div>
              );
            })}
          </div>
        </div>
        <div>
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <h4 style={{ flex: 1, margin: 0 }}>Bins</h4>
            <button className="icon-btn" aria-label="Create bin" onClick={createBin}
              title={openProject ? 'Create bin in this project' : 'Open a project to create a bin'}
              style={{ opacity: openProject ? 1 : 0.5 }}>
              <Icon name="plus" size={11} />
            </button>
          </div>
          <div className="rail-list">
            {creatingBin && (
              <div style={{ padding: '4px 6px', display: 'flex', gap: 4, alignItems: 'center' }}>
                <input
                  className="field-input"
                  autoFocus
                  value={newBinName}
                  onChange={function(e) { setNewBinName(e.target.value); }}
                  onKeyDown={function(e) {
                    if (e.key === 'Enter') submitBin(newBinName);
                    if (e.key === 'Escape') { setCreatingBin(false); }
                  }}
                  onBlur={function() { submitBin(newBinName); }}
                  placeholder="Bin name"
                  style={{ fontSize: 12, height: 26, padding: '0 6px', flex: 1 }}
                />
              </div>
            )}
            {!creatingBin && BINS.length === 0 ? (
              <div style={{ fontSize: 11, color: 'var(--text-3)', padding: '6px 8px', fontStyle: 'italic' }}>
                {openProject ? 'No bins yet: click + to create one.' : 'Open a project to manage bins.'}
              </div>
            ) : BINS.map(function(b) {
              const isActive = selectedBinId === b.id;
              const isDragTarget = draggingAssetId !== null && dragOverBinId === b.id;
              return (
                <div key={b.id}
                     className={'rail-item' + (isActive ? ' active' : '') + (isDragTarget ? ' droppable' : '')}
                     onClick={function() { setSelectedBinId(isActive ? null : b.id); }}
                     onDragOver={function(e) { onBinDragOver(b.id, e); }}
                     onDrop={function(e) { onBinDrop(b.id, e); }}
                     onDragLeave={onBinDragLeave}
                     style={{ cursor: 'pointer' }}
                     title={isActive ? 'Click to clear bin filter' : 'Filter to this bin'}>
                   <Icon name={binIcon(b.icon)} size={13} className="rail-icon" />
                  <span>{b.name}</span>
                  <span className="rail-count">{b.count}</span>
                </div>
              );
            })}
          </div>
        </div>
        <div>
          <h4>Smart filters</h4>
          <div className="rail-list">
            {errorCount > 0 && <div className="rail-item" onClick={function() { setFilter('error'); }} style={{ cursor: 'pointer' }}><Icon name="alert" size={13} className="rail-icon" /><span>Errors</span><span className="rail-count">{errorCount}</span></div>}
            <div className={'rail-item' + (filter === 'recent' ? ' active' : '')} onClick={function() { setFilter(filter === 'recent' ? 'all' : 'recent'); }} style={{ cursor: 'pointer' }}><Icon name="clock" size={13} className="rail-icon" /><span>Last 24h</span><span className="rail-count">{recentCount}</span></div>
            <div className="rail-item" onClick={function() { setFilter('ready'); }} style={{ cursor: 'pointer' }}><Icon name="check" size={13} className="rail-icon" /><span>Ready</span><span className="rail-count">{ALL_ASSETS.filter(function(a) { return a.status === 'ready'; }).length}</span></div>
          </div>
        </div>
      </aside>

      <div className="library-main">
        <div className="library-toolbar">
          <h1 className="toolbar-title">{displayTitle}</h1>
          <span className="count">· {assets.length} assets</span>
          <div style={{ flex: 1 }} />
          {selectionMode && selectedAssets.size > 0 && (
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginRight: 12 }}>
              <span style={{ fontSize: 13, color: 'var(--text-2)' }}>{selectedAssets.size} selected</span>
              <div className="tab-group">
                <button onClick={selectAll} title="Select all"><Icon name="check" size={12} /></button>
                <button onClick={clearSelection} title="Clear selection"><Icon name="x" size={12} /></button>
              </div>
              {BINS.length > 0 && (
                <select className="field-input" style={{ height: 32, fontSize: 12, padding: '0 8px' }}
                  onChange={function(e) { if (e.target.value) bulkMoveToBin(e.target.value); e.target.value = ''; }}
                  value="">
                  <option value="">Move to bin…</option>
                  {BINS.filter(function(b) { return !openProject || b.project_id === openProject.id; }).map(function(b) {
                    return <option key={b.id} value={b.id}>{b.name}</option>;
                  })}
                </select>
              )}
              <button className="btn danger sm" onClick={bulkDelete}><Icon name="trash" size={12} />Delete</button>
            </div>
          )}
          <button className={'btn ghost sm' + (selectionMode ? ' active' : '')} onClick={function() { setSelectionMode(!selectionMode); if (selectionMode) clearSelection(); }} title="Select multiple">
            <Icon name="check" size={12} />Select
          </button>
          <div className="search" style={{ width: 220 }}>
            <Icon name="search" className="search-icon" />
            <input value={search} onChange={function(e) { setSearch(e.target.value); }} placeholder="Filter assets…" />
          </div>
          <div className="tab-group">
            {['all', 'ready', 'processing', 'live', 'error'].map(function(f) {
              return (
                <button key={f} className={filter === f ? 'active' : ''} onClick={function() { setFilter(f); }}>
                  {f === 'all' ? 'All' : f[0].toUpperCase() + f.slice(1)}
                </button>
              );
            })}
          </div>
          <div className="tab-group">
              <button className={view === 'grid' ? 'active' : ''} onClick={function() { setView('grid'); }} aria-label="Grid view" title="Grid view"><Icon name="grid" size={12} /></button>
              <button className={view === 'list' ? 'active' : ''} onClick={function() { setView('list'); }} aria-label="List view" title="List view"><Icon name="list" size={12} /></button>
          </div>
          <button className="btn primary" onClick={function() { navigate('upload'); }}><Icon name="upload" />Upload</button>
        </div>

        {assets.length === 0 ? (
          <div style={{ padding: 60, textAlign: 'center', color: 'var(--text-3)' }}>No assets match this filter.</div>
        ) : view === 'grid' ? (
          <div className="library-grid">
            {assets.map(function(a) {
              return <AssetCard key={a.id} asset={a}
                       onOpen={function() { onOpenAsset(a); }}
                       onContextMenu={function(e) { openCtx(a, e); }}
                       onDownload={function() { requestDownload(a); }}
                       onDragStart={function(e) { onAssetDragStart(a.id, e); }}
                       draggable={true}
                       selectionMode={selectionMode}
                       isSelected={selectedAssets.has(a.id)}
                       onToggleSelect={function(e) { toggleSelection(a.id, e); }} />;
            })}
          </div>
        ) : (
          <div className="library-list">
            <div className="list-row head">
              {selectionMode && <div style={{ width: 32 }}></div>}
              <div></div><div>Name</div><div>Duration</div><div>Resolution</div><div>Codec</div><div>Size</div><div>Updated</div><div></div>
            </div>
            {assets.map(function(a) {
              return (
                <div key={a.id} className="list-row" onClick={function() { if (!selectionMode) onOpenAsset(a); }} onContextMenu={function(e) { openCtx(a, e); }} style={{ cursor: 'pointer' }}
                     draggable="true" onDragStart={function(e) { onAssetDragStart(a.id, e); }}>
                  {selectionMode && (
                    <div style={{ width: 32, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                      <input type="checkbox" checked={selectedAssets.has(a.id)} onChange={function(e) { toggleSelection(a.id, e); }} onClick={function(e) { e.stopPropagation(); }} style={{ cursor: 'pointer' }} />
                    </div>
                  )}
                  <div className="thumb"><AssetThumb asset={a} /></div>
                  <div>
                    <div className="name">{a.name}</div>
                    <div style={{ display: 'flex', gap: 6, marginTop: 2 }}>
                      <StatusDot status={a.status} />
                      <span style={{ fontSize: 11, color: 'var(--text-3)' }}>{a.status}</span>
                    </div>
                  </div>
                  <div className="col-sub">{a.duration}</div>
                  <div className="col-sub">{a.res}</div>
                  <div className="col-sub">{a.codec || '·'}</div>
                  <div className="col-sub">{a.size}</div>
                  <div className="col-sub">{a.updated}</div>
                  <button className="icon-btn" aria-label="Asset actions" onClick={function(e) { e.stopPropagation(); openCtx(a, e); }}><Icon name="more" /></button>
                </div>
              );
            })}
          </div>
        )}
      </div>
      {ctxMenu && (
        <AssetContextMenu
          asset={ctxMenu.asset}
          x={ctxMenu.x}
          y={ctxMenu.y}
          bins={BINS}
          onClose={function() { setCtxMenu(null); }}
          onChanged={refreshAssets}
          onOpen={function() { onOpenAsset(ctxMenu.asset); }}
          onRename={function(a) { setCtxMenu(null); setRenamingAsset(a); }}
          onDownload={function(a) { setCtxMenu(null); requestDownload(a); }}
          onDelete={function(a) { setCtxMenu(null); deleteAsset(a); }}
        />
      )}
      {pendingDownload && (
        <DownloadWarningModal
          asset={pendingDownload}
          onClose={function() { setPendingDownload(null); }}
          onConfirm={function(dismissForever) {
            const a = pendingDownload;
            setPendingDownload(null);
            if (dismissForever) {
              try { localStorage.setItem('df.lib.download.warnDismissed', '1'); } catch (_) {}
            }
            runDownload(a);
          }}
        />
      )}
      {renamingAsset && (
        <RenameAssetModal
          asset={renamingAsset}
          onClose={function() { setRenamingAsset(null); }}
          onSaved={function() { setRenamingAsset(null); refreshAssets(); }}
        />
      )}
      {projectCtx && (
        <ProjectContextMenu
          project={projectCtx.project}
          x={projectCtx.x}
          y={projectCtx.y}
          onClose={function() { setProjectCtx(null); }}
          onRename={function(p) { setProjectCtx(null); setRenamingProject(p); }}
        />
      )}
      {renamingProject && window.RenameProjectModal && (
        React.createElement(window.RenameProjectModal, {
          project: renamingProject,
          onClose: function() { setRenamingProject(null); },
          onSaved: function() {
            setRenamingProject(null);
            // Re-fetch projects and update ZAMPP_DATA so the rail refreshes
            window.ZAMPP_API.fetch('/projects').then(function(list) {
              if (Array.isArray(list)) {
                window.ZAMPP_DATA.PROJECTS = list.map(function(p, i) {
                  return {
                    ...p,
                    color: (window.ZAMPP_DATA.PROJECTS.find(function(x) { return x.id === p.id; }) || {}).color
                           || window.PROJECT_COLORS?.[i % (window.PROJECT_COLORS?.length || 1)]
                           || 'var(--accent)',
                    assets: (window.ZAMPP_DATA?.ASSETS || []).filter(function(a) { return a.project_id === p.id; }).length,
                    updated: window.ZAMPP_API.fmtRelative(p.updated_at),
                  };
                });
                setProjVersion(function(v) { return v + 1; });
              }
            }).catch(function() {});
          }
        })
      )}
    </div>
  );
}

// Hi-res download trigger shared by the card and the context menu. Resolves
// a presigned S3 URL via /assets/:id/hires (returns { url, filename, ext })
// and clicks a hidden anchor so the browser does the download. The download
// itself is direct S3 - never proxied through mam-api - so big files don't
// touch the API container.
function runDownload(asset) {
  if (!asset || !asset.id) return;
  return window.ZAMPP_API.fetch('/assets/' + asset.id + '/hires')
    .then(function(r) {
      if (!r || !r.url) { alert('No hi-res source available for this asset.'); return; }
      const a = document.createElement('a');
      a.href = r.url;
      a.download = r.filename || ((asset.display_name || asset.name || asset.id) + '.' + (r.ext || 'mov'));
      a.target = '_blank';
      a.rel    = 'noopener';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    })
    .catch(function(e) { alert('Download failed: ' + (e.message || 'unknown error')); });
}

function AssetContextMenu({ asset, x, y, bins, onClose, onChanged, onOpen, onRename, onDownload, onDelete }) {
  const ref = React.useRef(null);
  // Pin the menu inside the viewport even if the user right-clicked near
  // the bottom-right edge of the grid.
  const [pos, setPos] = React.useState({ left: x, top: y });
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const r = ref.current.getBoundingClientRect();
    const margin = 8;
    let nx = x, ny = y;
    if (x + r.width  + margin > window.innerWidth)  nx = window.innerWidth  - r.width  - margin;
    if (y + r.height + margin > window.innerHeight) ny = window.innerHeight - r.height - margin;
    setPos({ left: Math.max(margin, nx), top: Math.max(margin, ny) });
  }, [x, y]);

  const rename = function() { if (onRename) onRename(asset); else onClose(); };

  const moveToBin = function(binId) {
    onClose();
    window.ZAMPP_API.fetch('/assets/' + asset.id, { method: 'PATCH', body: JSON.stringify({ bin_id: binId }) })
      .then(function() { onChanged(); window.dispatchEvent(new Event('df:bins-changed')); })
      .catch(function(e) { alert('Move failed: ' + e.message); });
  };

  const copyId = function() {
    onClose();
    if (navigator.clipboard) navigator.clipboard.writeText(asset.id).catch(function() {});
  };

  const remove = function() {
    if (onDelete) { onDelete(asset); return; }
    onClose();
  };

  return (
    <div ref={ref} className="ctx-menu" style={{ left: pos.left, top: pos.top }}
         onClick={function(e) { e.stopPropagation(); }}
         onContextMenu={function(e) { e.preventDefault(); e.stopPropagation(); }}>
      <div className="ctx-header">{asset.display_name || asset.name}</div>
      <button onClick={function() { onClose(); onOpen(); }}><Icon name="play" size={11} />Open</button>
      <button onClick={rename}><Icon name="edit" size={11} />Rename…</button>
      {asset.original_s3_key && onDownload && (
        <button onClick={function() { onDownload(asset); }}><Icon name="download" size={11} />Download original…</button>
      )}
      <div className="ctx-divider" />
      {(bins && bins.length > 0) ? (
        <>
          <div className="ctx-section-label">Move to bin</div>
          {bins
            .filter(function(b) { return !asset.project_id || b.project_id === asset.project_id; })
            .slice(0, 10)
            .map(function(b) {
              const isCurrent = asset.bin_id === b.id;
              return (
                <button key={b.id} onClick={function() { moveToBin(b.id); }} disabled={isCurrent}
                  title={b.project_name ? 'in ' + b.project_name : ''}>
                  <Icon name="folder" size={11} />{b.name}{isCurrent && <span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--text-3)' }}>current</span>}
                </button>
              );
            })}
          {asset.bin_id && (
            <button onClick={function() { moveToBin(null); }}>
              <Icon name="x" size={11} />Remove from bin
            </button>
          )}
        </>
      ) : (
        <div className="ctx-empty">No bins: create one inside a project</div>
      )}
      <div className="ctx-divider" />
      <button onClick={copyId}><Icon name="library" size={11} />Copy asset ID</button>
      <button className="danger" onClick={remove}><Icon name="trash" size={11} />Delete permanently</button>
    </div>
  );
}

function AssetCard({ asset, onOpen, onContextMenu, onDownload, onDragStart, draggable, selectionMode, isSelected, onToggleSelect }) {
  const [hoverStream, setHoverStream] = React.useState(null);
  const [hovered, setHovered] = React.useState(false);
  const timerRef = React.useRef(null);
  const hlsRef = React.useRef(null);
  const videoRef = React.useRef(null);

  const handleMouseEnter = function() {
    timerRef.current = setTimeout(function() {
      setHovered(true);
      if (!hoverStream) {
        window.ZAMPP_API.fetch('/assets/' + asset.id + '/stream')
          .then(function(r) { if (r && r.url) setHoverStream(r); })
          .catch(function() {});
      }
    }, 350);
  };

  const handleMouseLeave = function() {
    clearTimeout(timerRef.current);
    setHovered(false);
  };

  // HLS wiring
  React.useEffect(function() {
    if (!hovered || !hoverStream || hoverStream.type !== 'hls' || !videoRef.current) return;
    if (!window.Hls) return;
    hlsRef.current = new window.Hls({ maxBufferLength: 10 });
    hlsRef.current.loadSource(hoverStream.url);
    hlsRef.current.attachMedia(videoRef.current);
    return function() {
      if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; }
    };
  }, [hovered, hoverStream]);

  const showVideo = hovered && hoverStream;

  return (
    <div className="asset-card" onClick={selectionMode ? onToggleSelect : onOpen} onContextMenu={onContextMenu} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}
         draggable={draggable} onDragStart={onDragStart}>
      <div style={{ position: 'relative' }}>
        {selectionMode && (
          <div style={{ position: 'absolute', top: 8, left: 8, zIndex: 10 }}>
            <input type="checkbox" checked={isSelected} onChange={onToggleSelect} onClick={function(e) { e.stopPropagation(); }} style={{ cursor: 'pointer', width: 18, height: 18 }} />
          </div>
        )}
        <AssetThumb asset={asset} />
        {showVideo && (
          <video
            key={hoverStream.url}
            ref={videoRef}
            src={hoverStream.type !== 'hls' ? hoverStream.url : undefined}
            autoPlay
            muted
            loop
            playsInline
            style={{
              position: 'absolute',
              inset: 0,
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              borderRadius: 6,
            }}
          />
        )}
        {/* Status badges and duration: inside the relative wrapper so
            position:absolute is anchored to the thumbnail, not the card (#52) */}
        <div className="thumb-status">
          {asset.status === 'live' && <span className="badge live">LIVE</span>}
          {asset.status === 'processing' && <span className="badge warning">Processing</span>}
          {asset.status === 'error' && <span className="badge danger">Error</span>}
        </div>
        {/* Hi-res download trigger: only shown when the asset has an
            original_s3_key (everything queued through ingest / conform).
            Hidden until card hover, lives in top-right of the thumb. */}
        {asset.original_s3_key && onDownload && (
          <button
            className="thumb-download-btn"
            aria-label="Download original"
            title="Download original"
            onClick={function(e) { e.stopPropagation(); onDownload(); }}>
            <Icon name="download" size={12} />
          </button>
        )}
        {(asset.type === 'video' || !asset.type) && asset.duration !== '·' && <div className="thumb-duration">{asset.duration}</div>}
      </div>
      <div className="meta">
        <div className="name">{asset.name}</div>
        <div className="sub">
          <span>{asset.res}</span>
          <span>·</span>
          <span>{asset.size}</span>
        </div>
      </div>
    </div>
  );
}

function ProjectContextMenu({ project, x, y, onClose, onRename }) {
  var ref = React.useRef(null);
  var [pos, setPos] = React.useState({ left: x, top: y });
  React.useLayoutEffect(function() {
    if (!ref.current) return;
    var r = ref.current.getBoundingClientRect();
    var margin = 8;
    var nx = x, ny = y;
    if (x + r.width  + margin > window.innerWidth)  nx = window.innerWidth  - r.width  - margin;
    if (y + r.height + margin > window.innerHeight) ny = window.innerHeight - r.height - margin;
    setPos({ left: Math.max(margin, nx), top: Math.max(margin, ny) });
  }, [x, y]);

  return (
    <div ref={ref} className="ctx-menu" style={{ left: pos.left, top: pos.top }}
         onClick={function(e) { e.stopPropagation(); }}
         onContextMenu={function(e) { e.preventDefault(); e.stopPropagation(); }}>
      <div className="ctx-header">{project.name}</div>
      <button onClick={function() { onClose(); onRename(project); }}><Icon name="edit" size={11} />Rename project…</button>
      <button onClick={function() { onClose(); window.ZAMPP_API.fetch('/projects/' + project.id, { method: 'DELETE' }).then(function() { window.location.reload(); }).catch(function(e) { alert('Delete failed: ' + e.message); }); }} className="danger"><Icon name="trash" size={11} />Delete project</button>
    </div>
  );
}

function binIcon(name) {
  return { grid: 'library', live: 'record', film: 'film', proxy: 'proxy', audio: 'audio', package: 'package' }[name] || 'folder';
}

function RenameAssetModal({ asset, onClose, onSaved }) {
  const [name, setName] = React.useState(asset.display_name || asset.name || '');
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const original = asset.display_name || asset.name || '';
  const submit = function() {
    const trimmed = name.trim();
    if (!trimmed || trimmed === original) { onClose(); return; }
    setSaving(true); setErr(null);
    window.ZAMPP_API.fetch('/assets/' + asset.id, { method: 'PATCH', body: JSON.stringify({ display_name: trimmed }) })
      .then(onSaved)
      .catch(function(e) { setSaving(false); setErr(e.message); });
  };
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ width: 420 }} onClick={function(e) { e.stopPropagation(); }}>
        <div className="modal-head">
          <div style={{ fontSize: 15, fontWeight: 600 }}>Rename asset</div>
          <button className="icon-btn" aria-label="Close" onClick={onClose}><Icon name="x" /></button>
        </div>
        <div className="modal-body">
          <div className="field">
            <label className="field-label">Display name</label>
            <input className="field-input" autoFocus value={name}
                   onChange={function(e) { setName(e.target.value); }}
                   onKeyDown={function(e) { if (e.key === 'Enter') submit(); if (e.key === 'Escape') onClose(); }} />
          </div>
          {err && <div style={{ fontSize: 12, color: 'var(--danger)', marginTop: 4 }}>{err}</div>}
        </div>
        <div className="modal-foot">
          <button className="btn ghost sm" onClick={onClose}>Cancel</button>
          <button className="btn primary sm" onClick={submit} disabled={saving || !name.trim()}>{saving ? 'Saving…' : 'Rename'}</button>
        </div>
      </div>
    </div>
  );
}

// First-run-style warning before a hi-res download. Tells the operator the
// file is full-length, may be multi-GB, and that speed depends on their
// connection. "Don't show again on this device" persists the dismissal so
// subsequent downloads (and every Download click after dismissal) skip
// straight to the file. Settings → Account exposes a control to re-enable.
function DownloadWarningModal({ asset, onClose, onConfirm }) {
  const [dismiss, setDismiss] = React.useState(false);
  const name = asset.display_name || asset.name || asset.id;
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ width: 460 }} onClick={function(e) { e.stopPropagation(); }}>
        <div className="modal-head">
          <div style={{ fontSize: 15, fontWeight: 600 }}>Download original ingest</div>
          <button className="icon-btn" aria-label="Close" onClick={onClose}><Icon name="x" /></button>
        </div>
        <div className="modal-body" style={{ fontSize: 12.5, color: 'var(--text-2)', lineHeight: 1.45 }}>
          <div style={{ marginBottom: 8 }}>
            You're about to download the full-length original ingest for
            <span style={{ color: 'var(--text-1)', fontFamily: 'var(--font-mono)', marginLeft: 4 }}>{name}</span>.
          </div>
          <div style={{ marginBottom: 12, color: 'var(--text-3)' }}>
            Originals are often multi-gigabyte. Download speed depends on
            your network connection; don't start this on a metered link
            or a busy uplink.
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', userSelect: 'none' }}>
            <input
              type="checkbox"
              checked={dismiss}
              onChange={function(e) { setDismiss(e.target.checked); }}
              style={{ accentColor: 'var(--accent)' }}
            />
            <span style={{ fontSize: 12 }}>Don't show this again on this device</span>
          </label>
        </div>
        <div className="modal-foot">
          <button className="btn ghost sm" onClick={onClose}>Cancel</button>
          <button className="btn primary sm" onClick={function() { onConfirm(dismiss); }}>Download</button>
        </div>
      </div>
    </div>
  );
}

window.Library = Library;
window.AssetCard = AssetCard;
