/**
 * FolderTree – Shared recursive folder/file tree component.
 *
 * Used on the Files page and in the Workspace chat.
 * Supports:
 * - Alphabetical sorting per level (folders first, then files)
 * - Multi-selection (CTRL+click, SHIFT+click) with visual highlight
 * - Batch drag-and-drop for selected items
 * - Inline CRUD icons for folders
 * - showFiles mode renders files inline under their parent folder
 * - Drag-out: sets application/tree-items on dataTransfer for external drop targets
 */

import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { FaFolder, FaFolderOpen, FaPlus, FaPen, FaTrash, FaChevronRight, FaSyncAlt, FaDownload } from 'react-icons/fa';
import { usePrompt, type PromptOptions } from '../../hooks/usePrompt';
import styles from './FolderTree.module.css';

import { useLanguage } from '../../providers/language/LanguageContext';

/* ── Public types ──────────────────────────────────────────────────────── */

export interface FolderNode {
  id: string;
  name: string;
  parentId: string | null;
  fileCount?: number;
  children?: FolderNode[];
  isProtected?: boolean;
  isReadonly?: boolean;
  icon?: string;
}

export interface FileNode {
  id: string;
  fileName: string;
  mimeType?: string;
  fileSize?: number;
  folderId?: string | null;
  scope?: string;
  neutralize?: boolean;
  sysCreatedBy?: string;
  isReadonly?: boolean;
}

export interface TreeItem {
  id: string;
  type: 'file' | 'folder';
  name: string;
}

export interface FolderTreeProps {
  folders: FolderNode[];
  files?: FileNode[];
  showFiles?: boolean;
  selectedFolderId: string | null;
  onSelect: (folderId: string | null) => void;
  onFileSelect?: (fileId: string) => void;
  selectedItemIds?: Set<string>;
  onSelectionChange?: (selectedIds: Set<string>) => void;
  expandedIds?: Set<string>;
  onToggleExpand?: (id: string) => void;
  onRefresh?: () => void;
  onCreateFolder?: (name: string, parentId: string | null) => Promise<void>;
  onRenameFolder?: (folderId: string, newName: string) => Promise<void>;
  onDeleteFolder?: (folderId: string) => Promise<void>;
  onMoveFolder?: (folderId: string, targetParentId: string | null) => Promise<void>;
  onMoveFolders?: (folderIds: string[], targetParentId: string | null) => Promise<void>;
  onMoveFile?: (fileId: string, targetFolderId: string | null) => Promise<void>;
  onMoveFiles?: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
  onRenameFile?: (fileId: string, newName: string) => Promise<void>;
  onDeleteFile?: (fileId: string) => Promise<void>;
  onDeleteFiles?: (fileIds: string[]) => Promise<void>;
  onDeleteFolders?: (folderIds: string[]) => Promise<void>;
  onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
  onScopeChange?: (fileId: string, newScope: string) => void;
  onNeutralizeToggle?: (fileId: string, newValue: boolean) => void;
}

/* ── Helpers ───────────────────────────────────────────────────────────── */

function _buildTree(folders: FolderNode[]): FolderNode[] {
  const map = new Map<string, FolderNode>();
  const roots: FolderNode[] = [];
  for (const f of folders) map.set(f.id, { ...f, children: [] });
  for (const f of folders) {
    const node = map.get(f.id)!;
    if (f.parentId && map.has(f.parentId)) {
      map.get(f.parentId)!.children!.push(node);
    } else {
      roots.push(node);
    }
  }
  const _sortLevel = (nodes: FolderNode[]) => {
    nodes.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
    for (const n of nodes) {
      if (n.children && n.children.length > 0) _sortLevel(n.children);
    }
  };
  _sortLevel(roots);
  return roots;
}

function _groupFilesByFolder(files: FileNode[]): Map<string, FileNode[]> {
  const map = new Map<string, FileNode[]>();
  for (const f of files) {
    const key = f.folderId || '';
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(f);
  }
  for (const [, arr] of map) {
    arr.sort((a, b) => a.fileName.localeCompare(b.fileName, undefined, { sensitivity: 'base' }));
  }
  return map;
}

function _computeFlatList(
  tree: FolderNode[],
  expandedIds: Set<string>,
  showFiles: boolean,
  filesByFolder: Map<string, FileNode[]>,
): TreeItem[] {
  const result: TreeItem[] = [];
  const _walk = (nodes: FolderNode[]) => {
    for (const node of nodes) {
      result.push({ id: node.id, type: 'folder', name: node.name });
      if (expandedIds.has(node.id)) {
        if (node.children) _walk(node.children);
        if (showFiles) {
          for (const f of (filesByFolder.get(node.id) || [])) {
            result.push({ id: f.id, type: 'file', name: f.fileName });
          }
        }
      }
    }
  };
  _walk(tree);
  if (showFiles) {
    for (const f of (filesByFolder.get('') || [])) {
      result.push({ id: f.id, type: 'file', name: f.fileName });
    }
  }
  return result;
}

function _fileIcon(mime?: string): string {
  if (!mime) return '\uD83D\uDCC4';
  if (mime.startsWith('image/')) return '\uD83D\uDDBC\uFE0F';
  if (mime.includes('pdf')) return '\uD83D\uDCD5';
  if (mime.includes('word') || mime.includes('docx')) return '\uD83D\uDCD8';
  if (mime.includes('sheet') || mime.includes('xlsx') || mime.includes('csv')) return '\uD83D\uDCCA';
  if (mime.includes('presentation') || mime.includes('pptx')) return '\uD83D\uDCD9';
  if (mime.includes('zip') || mime.includes('tar') || mime.includes('gz')) return '\uD83D\uDCE6';
  if (mime.startsWith('text/') || mime.includes('json') || mime.includes('xml')) return '\uD83D\uDCDD';
  if (mime.startsWith('audio/')) return '\uD83C\uDFB5';
  if (mime.startsWith('video/')) return '\uD83C\uDFA5';
  return '\uD83D\uDCC4';
}

/* ── Selection context threaded through the tree ──────────────────────── */

const _SCOPE_ICONS: Record<string, string> = {
  personal: '\uD83D\uDC64',
  featureInstance: '\uD83D\uDC65',
  mandate: '\uD83C\uDFE2',
  global: '\uD83C\uDF10',
};

const _SCOPE_CYCLE: string[] = ['personal', 'featureInstance', 'mandate'];

interface SelectionCtx {
  selectedItemIds: Set<string>;
  selectedFileIds: string[];
  selectedFolderIds: string[];
  onItemClick: (id: string, type: 'file' | 'folder', e: React.MouseEvent) => void;
  onItemDragStart: (e: React.DragEvent, id: string, type: 'file' | 'folder', name: string) => void;
  onRenameFile?: (fileId: string, newName: string) => Promise<void>;
  onDeleteFile?: (fileId: string) => Promise<void>;
  onDeleteFiles?: (fileIds: string[]) => Promise<void>;
  onDeleteFolders?: (folderIds: string[]) => Promise<void>;
  onScopeChange?: (fileId: string, newScope: string) => void;
  onNeutralizeToggle?: (fileId: string, newValue: boolean) => void;
}

/* ── File node (leaf) ─────────────────────────────────────────────────── */

function _FileItem({ file, sel }: { file: FileNode; sel: SelectionCtx }) {
  const { t } = useLanguage();
  const scopeLabels = useMemo((): Record<string, string> => ({
    personal: t('Persönlich'),
    featureInstance: t('Instanz'),
    mandate: t('Mandant'),
    global: t('Global'),
  }), [t]);
  const [dragging, setDragging] = useState(false);
  const [renaming, setRenaming] = useState(false);
  const [renameValue, setRenameValue] = useState('');
  const isSelected = sel.selectedItemIds.has(file.id);
  const multiSelected = sel.selectedItemIds.size > 1;

  const _handleRename = useCallback(async () => {
    const trimmed = renameValue.trim();
    if (trimmed && trimmed !== file.fileName && sel.onRenameFile) {
      await sel.onRenameFile(file.id, trimmed);
    }
    setRenaming(false);
  }, [renameValue, file.id, file.fileName, sel.onRenameFile]);

  const _handleDeleteFiles = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (sel.selectedFileIds.length > 0 && sel.onDeleteFiles) {
      await sel.onDeleteFiles(sel.selectedFileIds);
    }
  }, [sel]);

  const _handleDeleteFolders = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (sel.selectedFolderIds.length > 0 && sel.onDeleteFolders) {
      await sel.onDeleteFolders(sel.selectedFolderIds);
    }
  }, [sel]);

  const _handleDeleteSingle = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (sel.onDeleteFile) await sel.onDeleteFile(file.id);
  }, [file.id, sel]);

  return (
    <div
      className={[
        styles.treeNode,
        styles.fileNode,
        isSelected ? styles.multiSelected : '',
        dragging ? styles.dragging : '',
      ].filter(Boolean).join(' ')}
      onClick={(e) => sel.onItemClick(file.id, 'file', e)}
      draggable
      onDragStart={(e) => {
        sel.onItemDragStart(e, file.id, 'file', file.fileName);
        setDragging(true);
      }}
      onDragEnd={() => setDragging(false)}
    >
      <span className={styles.chevron} style={{ visibility: 'hidden' }}><FaChevronRight /></span>
      <span className={styles.fileIcon}>{_fileIcon(file.mimeType)}</span>
      {renaming ? (
        <input
          autoFocus
          className={styles.renameInput}
          value={renameValue}
          onChange={(e) => setRenameValue(e.target.value)}
          onBlur={_handleRename}
          onKeyDown={(e) => {
            if (e.key === 'Enter') _handleRename();
            if (e.key === 'Escape') setRenaming(false);
          }}
          onClick={(e) => e.stopPropagation()}
        />
      ) : (
        <span className={styles.folderName}>{file.fileName}</span>
      )}
      {!renaming && (
        <span className={styles.rightZone}>
          <span className={styles.actions}>
            {sel.onRenameFile && !multiSelected && (
              <button className={styles.actionBtn} onClick={(e) => { e.stopPropagation(); setRenameValue(file.fileName); setRenaming(true); }} title={t('Umbenennen')}>
                <FaPen />
              </button>
            )}
            {multiSelected && isSelected ? (
              <>
                {sel.selectedFolderIds.length > 0 && sel.onDeleteFolders && (
                  <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteFolders} title={t('{count} Ordner löschen', { count: String(sel.selectedFolderIds.length) })}>
                    <FaFolder style={{ fontSize: 8, marginRight: 1 }} /><FaTrash />
                    <span style={{ fontSize: 9, marginLeft: 2, fontWeight: 600 }}>{sel.selectedFolderIds.length}</span>
                  </button>
                )}
                {sel.selectedFileIds.length > 0 && sel.onDeleteFiles && (
                  <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteFiles} title={`${sel.selectedFileIds.length} ${t('Dateien löschen')}`}>
                    <FaTrash />
                    <span style={{ fontSize: 9, marginLeft: 2, fontWeight: 600 }}>{sel.selectedFileIds.length}</span>
                  </button>
                )}
              </>
            ) : (
              (sel.onDeleteFile || sel.onDeleteFiles) && (
                <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteSingle} title={t('Löschen')}>
                  <FaTrash />
                </button>
              )
            )}
          </span>
          {file.fileSize != null && (
            <span className={styles.fileSize}>
              {(file.fileSize / 1024).toFixed(0)}K
            </span>
          )}
          {file.scope != null && (
            <span className={styles.scopeIcons}>
              <button
                className={styles.actionBtn}
                onClick={(e) => {
                  e.stopPropagation();
                  if (!sel.onScopeChange) return;
                  const idx = _SCOPE_CYCLE.indexOf(file.scope!);
                  const next = _SCOPE_CYCLE[(idx + 1) % _SCOPE_CYCLE.length];
                  sel.onScopeChange(file.id, next);
                }}
                title={`${t('Scope')}: ${scopeLabels[file.scope!] || file.scope} (${t('klicken zum Wechseln')})`}
                style={{ fontSize: 14 }}
              >
                {_SCOPE_ICONS[file.scope!] || '\uD83D\uDC64'}
              </button>
              <button
                className={styles.actionBtn}
                onClick={(e) => {
                  e.stopPropagation();
                  sel.onNeutralizeToggle?.(file.id, !file.neutralize);
                }}
                title={file.neutralize ? t('Neutralisierung aktiv, klicken zum Deaktivieren') : t('Neutralisierung aus, klicken zum Aktivieren')}
                style={{ fontSize: 14, opacity: file.neutralize ? 1 : 0.4 }}
              >
                {'\uD83D\uDD12'}
              </button>
            </span>
          )}
        </span>
      )}
    </div>
  );
}

/* ── Tree node (folder) ───────────────────────────────────────────────── */

interface TreeNodeProps {
  node: FolderNode;
  depth: number;
  selectedFolderId: string | null;
  expandedIds: Set<string>;
  showFiles: boolean;
  filesByFolder: Map<string, FileNode[]>;
  sel: SelectionCtx;
  promptFolderName: (message: string, options?: PromptOptions) => Promise<string | null>;
  onToggle: (id: string) => void;
  onSelect: (id: string | null) => void;
  onCreateFolder?: (name: string, parentId: string | null) => Promise<void>;
  onRenameFolder?: (folderId: string, newName: string) => Promise<void>;
  onDeleteFolder?: (folderId: string) => Promise<void>;
  onMoveFolder?: (folderId: string, targetParentId: string | null) => Promise<void>;
  onMoveFolders?: (folderIds: string[], targetParentId: string | null) => Promise<void>;
  onMoveFile?: (fileId: string, targetFolderId: string | null) => Promise<void>;
  onMoveFiles?: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
  onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
}

function _TreeNode({
  node, depth, selectedFolderId, expandedIds, showFiles, filesByFolder, sel,
  promptFolderName,
  onToggle, onSelect,
  onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
  onDownloadFolder,
}: TreeNodeProps) {
  const { t } = useLanguage();
  const [renaming, setRenaming] = useState(false);
  const [renameValue, setRenameValue] = useState(node.name);
  const [dropOver, setDropOver] = useState(false);
  const [dragging, setDragging] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);
  const isExpanded = expandedIds.has(node.id);
  const isNavSelected = selectedFolderId === node.id;
  const isMultiSelected = sel.selectedItemIds.has(node.id);
  const folderFiles = showFiles ? (filesByFolder.get(node.id) || []) : [];
  const hasChildren = (node.children && node.children.length > 0) || folderFiles.length > 0 || (node.fileCount ?? 0) > 0;

  useEffect(() => {
    if (renaming && inputRef.current) inputRef.current.focus();
  }, [renaming]);

  const _handleRename = useCallback(async () => {
    const trimmed = renameValue.trim();
    if (trimmed && trimmed !== node.name && onRenameFolder) {
      await onRenameFolder(node.id, trimmed);
    }
    setRenaming(false);
  }, [renameValue, node.id, node.name, onRenameFolder]);

  const _handleAdd = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (!onCreateFolder) return;
    const name = await promptFolderName(t('Neuer Ordnername:'), { title: t('Neuer Ordner'), placeholder: t('Ordnername') });
    if (name?.trim()) {
      await onCreateFolder(name.trim(), node.id);
      if (!expandedIds.has(node.id)) onToggle(node.id);
    }
  }, [onCreateFolder, node.id, expandedIds, onToggle, promptFolderName, t]);

  const _handleDeleteSingle = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (onDeleteFolder) await onDeleteFolder(node.id);
  }, [onDeleteFolder, node.id]);

  const _handleDeleteFolders = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (sel.selectedFolderIds.length > 0 && sel.onDeleteFolders) {
      await sel.onDeleteFolders(sel.selectedFolderIds);
    }
  }, [sel]);

  const _handleDeleteFiles = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (sel.selectedFileIds.length > 0 && sel.onDeleteFiles) {
      await sel.onDeleteFiles(sel.selectedFileIds);
    }
  }, [sel]);

  const _handleDragOver = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    setDropOver(true);
  }, []);

  const _handleDragLeave = useCallback(() => setDropOver(false), []);

  const _handleDrop = useCallback(async (e: React.DragEvent) => {
    e.preventDefault();
    setDropOver(false);

    const treeItemsJson = e.dataTransfer.getData('application/tree-items');
    if (treeItemsJson) {
      const items: TreeItem[] = JSON.parse(treeItemsJson);
      const fileIds = items.filter(i => i.type === 'file').map(i => i.id);
      const folderIds = items.filter(i => i.type === 'folder' && i.id !== node.id).map(i => i.id);

      if (folderIds.length > 0 && onMoveFolders) {
        await onMoveFolders(folderIds, node.id);
      } else if (onMoveFolder) {
        for (const fId of folderIds) await onMoveFolder(fId, node.id);
      }
      if (fileIds.length > 0 && onMoveFiles) {
        await onMoveFiles(fileIds, node.id);
      } else if (fileIds.length > 0 && onMoveFile) {
        for (const fId of fileIds) await onMoveFile(fId, node.id);
      }
      return;
    }

    const folderId = e.dataTransfer.getData('application/folder-id');
    const fileIdsJson = e.dataTransfer.getData('application/file-ids');
    const fileId = e.dataTransfer.getData('application/file-id');
    if (folderId && folderId !== node.id && onMoveFolder) {
      await onMoveFolder(folderId, node.id);
    } else if (fileIdsJson && onMoveFiles) {
      await onMoveFiles(JSON.parse(fileIdsJson), node.id);
    } else if (fileId && onMoveFile) {
      await onMoveFile(fileId, node.id);
    }
  }, [node.id, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles]);

  const nodeClasses = [
    styles.treeNode,
    isNavSelected && !isMultiSelected ? styles.selected : '',
    isMultiSelected ? styles.multiSelected : '',
    dropOver ? styles.dropTarget : '',
    dragging ? styles.dragging : '',
  ].filter(Boolean).join(' ');

  const isProtected = node.isProtected === true;
  const isReadonly = node.isReadonly === true;
  const notDraggable = isProtected || isReadonly;
  const notEditable = isProtected || isReadonly;
  const customIcon = node.icon;

  return (
    <div>
      <div
        className={nodeClasses}
        onClick={(e) => sel.onItemClick(node.id, 'folder', e)}
        draggable={!notDraggable}
        onDragStart={notDraggable ? undefined : (e) => {
          sel.onItemDragStart(e, node.id, 'folder', node.name);
          setDragging(true);
        }}
        onDragEnd={notDraggable ? undefined : () => setDragging(false)}
        onDragOver={isProtected ? undefined : _handleDragOver}
        onDragLeave={isProtected ? undefined : _handleDragLeave}
        onDrop={isProtected ? undefined : _handleDrop}
      >
        <span
          className={`${styles.chevron} ${isExpanded ? styles.expanded : ''} ${!hasChildren ? styles.empty : ''}`}
          onClick={(e) => { e.stopPropagation(); if (hasChildren) onToggle(node.id); }}
        >
          <FaChevronRight />
        </span>
        <span className={styles.folderIcon}>
          {customIcon ? (
            <span style={{ fontSize: 14 }}>{customIcon}</span>
          ) : isExpanded ? <FaFolderOpen /> : <FaFolder />}
        </span>
        {renaming && !notEditable ? (
          <input
            ref={inputRef}
            className={styles.renameInput}
            value={renameValue}
            onChange={(e) => setRenameValue(e.target.value)}
            onBlur={_handleRename}
            onKeyDown={(e) => {
              if (e.key === 'Enter') _handleRename();
              if (e.key === 'Escape') setRenaming(false);
            }}
            onClick={(e) => e.stopPropagation()}
          />
        ) : (
          <span className={styles.folderName} style={notEditable ? { fontWeight: 600 } : undefined}>{node.name}</span>
        )}
        {!isProtected && (
          <span className={styles.actions}>
            {!notEditable && onDownloadFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
              <button className={styles.actionBtn} onClick={(e) => { e.stopPropagation(); onDownloadFolder(node.id, node.name); }} title={t('Ordner herunterladen (ZIP)')}>
                <FaDownload />
              </button>
            )}
            {onCreateFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
              <button className={styles.actionBtn} onClick={_handleAdd} title={t('Neuer Unterordner')}>
                <FaPlus />
              </button>
            )}
            {!notEditable && onRenameFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
              <button className={styles.actionBtn} onClick={(e) => { e.stopPropagation(); setRenameValue(node.name); setRenaming(true); }} title={t('Umbenennen')}>
                <FaPen />
              </button>
            )}
            {isMultiSelected && sel.selectedItemIds.size > 1 ? (
              <>
                {sel.selectedFolderIds.length > 0 && sel.onDeleteFolders && (
                  <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteFolders} title={`${sel.selectedFolderIds.length} ${t('Ordner löschen')}`}>
                    <FaFolder style={{ fontSize: 8, marginRight: 1 }} /><FaTrash />
                    <span style={{ fontSize: 9, marginLeft: 2, fontWeight: 600 }}>{sel.selectedFolderIds.length}</span>
                  </button>
                )}
                {sel.selectedFileIds.length > 0 && sel.onDeleteFiles && (
                  <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteFiles} title={`${sel.selectedFileIds.length} ${t('Dateien löschen')}`}>
                    <FaTrash />
                    <span style={{ fontSize: 9, marginLeft: 2, fontWeight: 600 }}>{sel.selectedFileIds.length}</span>
                  </button>
                )}
              </>
            ) : !notEditable && onDeleteFolder && (
              <button className={`${styles.actionBtn} ${styles.danger}`} onClick={_handleDeleteSingle} title={t('Löschen')}>
                <FaTrash />
              </button>
            )}
          </span>
        )}
      </div>
      {isExpanded && hasChildren && (
        <div className={styles.children}>
          {node.children!.map((child) => (
            <_TreeNode
              key={child.id}
              node={child}
              depth={depth + 1}
              selectedFolderId={selectedFolderId}
              expandedIds={expandedIds}
              showFiles={showFiles}
              filesByFolder={filesByFolder}
              sel={sel}
              promptFolderName={promptFolderName}
              onToggle={onToggle}
              onSelect={onSelect}
              onCreateFolder={onCreateFolder}
              onRenameFolder={onRenameFolder}
              onDeleteFolder={onDeleteFolder}
              onMoveFolder={onMoveFolder}
              onMoveFolders={onMoveFolders}
              onMoveFile={onMoveFile}
              onMoveFiles={onMoveFiles}
              onDownloadFolder={onDownloadFolder}
            />
          ))}
          {folderFiles.map((file) => (
            <_FileItem key={file.id} file={file} sel={sel} />
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Root component ────────────────────────────────────────────────────── */

export default function FolderTree({
  folders, files, showFiles = false, selectedFolderId, onSelect, onFileSelect,
  selectedItemIds: externalSelectedIds, onSelectionChange,
  expandedIds: externalExpandedIds, onToggleExpand,
  onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
  onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
  onScopeChange, onNeutralizeToggle,
}: FolderTreeProps) {
  const { t } = useLanguage();

  const [internalExpandedIds, setInternalExpandedIds] = useState<Set<string>>(new Set());
  const [internalSelectedIds, setInternalSelectedIds] = useState<Set<string>>(new Set());
  const lastClickedIdRef = useRef<string | null>(null);
  const { prompt: promptFolderName, PromptDialog } = usePrompt();
  const [rootDropOver, setRootDropOver] = useState(false);

  const expandedIds = externalExpandedIds ?? internalExpandedIds;
  const selectedItemIds = externalSelectedIds ?? internalSelectedIds;

  const realTree = useMemo(() => _buildTree(folders), [folders]);
  const filesByFolder = useMemo(() => _groupFilesByFolder(files || []), [files]);
  const rootFiles = showFiles ? (filesByFolder.get('') || []) : [];

  const knownFolderIds = useMemo(() => {
    const ids = new Set<string>();
    const _collect = (nodes: FolderNode[]) => { for (const n of nodes) { ids.add(n.id); if (n.children) _collect(n.children); } };
    _collect(realTree);
    return ids;
  }, [realTree]);

  const tree = useMemo(() => {
    if (!showFiles) return realTree;
    const orphanFolders: FolderNode[] = [];
    for (const key of filesByFolder.keys()) {
      if (key && !knownFolderIds.has(key)) {
        orphanFolders.push({ id: key, name: key.slice(0, 8) + '…', parentId: null, fileCount: filesByFolder.get(key)?.length ?? 0, isProtected: true });
      }
    }
    if (orphanFolders.length === 0) return realTree;
    return [...realTree, ...orphanFolders.sort((a, b) => a.name.localeCompare(b.name))];
  }, [realTree, showFiles, filesByFolder, knownFolderIds]);

  const flatList = useMemo(
    () => _computeFlatList(tree, expandedIds, showFiles, filesByFolder),
    [tree, expandedIds, showFiles, filesByFolder],
  );

  const _handleToggle = useCallback((id: string) => {
    if (onToggleExpand) {
      onToggleExpand(id);
      return;
    }
    setInternalExpandedIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }, [onToggleExpand]);

  const _setSelection = useCallback((ids: Set<string>) => {
    if (onSelectionChange) {
      onSelectionChange(ids);
    } else {
      setInternalSelectedIds(ids);
    }
  }, [onSelectionChange]);

  const _handleItemClick = useCallback((id: string, type: 'file' | 'folder', e: React.MouseEvent) => {
    if (e.ctrlKey || e.metaKey) {
      const next = new Set(selectedItemIds);
      if (next.has(id)) next.delete(id); else next.add(id);
      _setSelection(next);
      lastClickedIdRef.current = id;
      return;
    }

    if (e.shiftKey && lastClickedIdRef.current) {
      const lastIdx = flatList.findIndex(i => i.id === lastClickedIdRef.current);
      const currIdx = flatList.findIndex(i => i.id === id);
      if (lastIdx >= 0 && currIdx >= 0) {
        const [from, to] = lastIdx < currIdx ? [lastIdx, currIdx] : [currIdx, lastIdx];
        const next = new Set(selectedItemIds);
        for (let i = from; i <= to; i++) next.add(flatList[i].id);
        _setSelection(next);
      }
      return;
    }

    _setSelection(new Set([id]));
    lastClickedIdRef.current = id;
    if (type === 'folder') onSelect(id);
    if (type === 'file') onFileSelect?.(id);
  }, [selectedItemIds, flatList, _setSelection, onSelect, onFileSelect]);

  const _handleItemDragStart = useCallback((e: React.DragEvent, id: string, type: 'file' | 'folder', name: string) => {
    const isInSelection = selectedItemIds.has(id) && selectedItemIds.size > 1;

    if (isInSelection) {
      const items: TreeItem[] = [];
      for (const selId of selectedItemIds) {
        const item = flatList.find(i => i.id === selId);
        if (item) items.push(item);
      }
      e.dataTransfer.setData('application/tree-items', JSON.stringify(items));
      const fileIds = items.filter(i => i.type === 'file').map(i => i.id);
      if (fileIds.length > 0) {
        e.dataTransfer.setData('application/file-ids', JSON.stringify(fileIds));
      }
    } else {
      e.dataTransfer.setData('application/tree-items', JSON.stringify([{ id, type, name }]));
      if (type === 'file') {
        e.dataTransfer.setData('application/file-id', id);
      } else {
        e.dataTransfer.setData('application/folder-id', id);
      }
    }
    e.dataTransfer.effectAllowed = 'copyMove';
  }, [selectedItemIds, flatList]);

  const allFileIds = useMemo(() => {
    const ids = new Set<string>();
    for (const [, arr] of filesByFolder) for (const f of arr) ids.add(f.id);
    return ids;
  }, [filesByFolder]);

  const allFolderIds = useMemo(() => {
    const ids = new Set<string>();
    const _collect = (nodes: FolderNode[]) => { for (const n of nodes) { ids.add(n.id); if (n.children) _collect(n.children); } };
    _collect(tree);
    return ids;
  }, [tree]);

  const sel: SelectionCtx = useMemo(() => {
    const selFileIds = Array.from(selectedItemIds).filter(id => allFileIds.has(id));
    const selFolderIds = Array.from(selectedItemIds).filter(id => allFolderIds.has(id));
    return {
      selectedItemIds,
      selectedFileIds: selFileIds,
      selectedFolderIds: selFolderIds,
      onItemClick: _handleItemClick,
      onItemDragStart: _handleItemDragStart,
      onRenameFile,
      onDeleteFile,
      onDeleteFiles,
      onDeleteFolders,
      onScopeChange,
      onNeutralizeToggle,
    };
  }, [selectedItemIds, allFileIds, allFolderIds, _handleItemClick, _handleItemDragStart, onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onScopeChange, onNeutralizeToggle]);

  // Root drop handler: items dropped on the empty area go to root (null)
  const _handleRootDrop = useCallback(async (e: React.DragEvent) => {
    e.preventDefault();
    setRootDropOver(false);
    const treeItemsJson = e.dataTransfer.getData('application/tree-items');
    if (treeItemsJson) {
      const items: TreeItem[] = JSON.parse(treeItemsJson);
      const fileIds = items.filter(i => i.type === 'file').map(i => i.id);
      const folderIds = items.filter(i => i.type === 'folder').map(i => i.id);
      if (folderIds.length > 0 && onMoveFolders) await onMoveFolders(folderIds, null);
      else if (onMoveFolder) for (const fId of folderIds) await onMoveFolder(fId, null);
      if (fileIds.length > 0 && onMoveFiles) await onMoveFiles(fileIds, null);
      else if (onMoveFile) for (const fId of fileIds) await onMoveFile(fId, null);
      return;
    }

    const folderId = e.dataTransfer.getData('application/folder-id');
    const fileId = e.dataTransfer.getData('application/file-id');
    if (folderId && onMoveFolder) await onMoveFolder(folderId, null);
    else if (fileId && onMoveFile) await onMoveFile(fileId, null);
  }, [onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles]);

  const _handleRootAddFolder = useCallback(async () => {
    if (!onCreateFolder) return;
    const name = await promptFolderName(t('Neuer Ordnername:'), { title: t('Neuer Ordner'), placeholder: t('Ordnername') });
    if (name?.trim()) await onCreateFolder(name.trim(), null);
  }, [onCreateFolder, promptFolderName, t]);

  const isRootSelected = selectedFolderId === null;

  const _handleRootClick = useCallback(() => {
    _setSelection(new Set());
    onSelect(null);
  }, [_setSelection, onSelect]);

  return (
    <div className={styles.folderTree}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '2px 4px' }}>
        <span
          className={`${styles.treeNode} ${isRootSelected ? styles.selected : ''} ${rootDropOver ? styles.dropTarget : ''}`}
          style={{ flex: 1, cursor: 'pointer', fontWeight: 600, paddingLeft: 4 }}
          onClick={_handleRootClick}
          onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setRootDropOver(true); }}
          onDragLeave={() => setRootDropOver(false)}
          onDrop={_handleRootDrop}
        >
          /
        </span>
        <span className={styles.actions}>
          {onCreateFolder && (
            <button className={styles.actionBtn} onClick={_handleRootAddFolder} title={t('Neuer Ordner')}>
              <FaPlus />
            </button>
          )}
          {onRefresh && (
            <button className={styles.actionBtn} onClick={onRefresh} title={t('Aktualisieren')}>
              <FaSyncAlt />
            </button>
          )}
        </span>
      </div>
      <div className={styles.children}>
        {tree.map((node) => (
          <_TreeNode
            key={node.id}
            node={node}
            depth={0}
            selectedFolderId={selectedFolderId}
            expandedIds={expandedIds}
            showFiles={showFiles}
            filesByFolder={filesByFolder}
            sel={sel}
            promptFolderName={promptFolderName}
            onToggle={_handleToggle}
            onSelect={onSelect}
            onCreateFolder={onCreateFolder}
            onRenameFolder={onRenameFolder}
            onDeleteFolder={onDeleteFolder}
            onMoveFolder={onMoveFolder}
            onMoveFolders={onMoveFolders}
            onMoveFile={onMoveFile}
            onMoveFiles={onMoveFiles}
            onDownloadFolder={onDownloadFolder}
          />
        ))}
        {rootFiles.map((file) => (
          <_FileItem key={file.id} file={file} sel={sel} />
        ))}
      </div>
      <PromptDialog />
    </div>
  );
}
