<script lang="ts">
	import { onMount } from 'svelte';
	import { Button } from '$lib/components/ui/button';
	import * as Table from '$lib/components/ui/table';
	import * as Dialog from '$lib/components/ui/dialog';
	import { Input } from '$lib/components/ui/input';
	import { Label } from '$lib/components/ui/label';
	import CodeEditor from '$lib/components/CodeEditor.svelte';
	import ConfirmPopover from '$lib/components/ConfirmPopover.svelte';
	import {
		Folder,
		File,
		Link,
		FileQuestion,
		Download,
		Upload,
		RefreshCw,
		ChevronLeft,
		Home,
		Loader2,
		AlertCircle,
		ChevronRight,
		Pencil,
		X,
		Save,
		Sun,
		Moon,
		Eye,
		Lock,
		Trash2,
		FolderPlus,
		FilePlus,
		EyeOff,
		ArrowUpDown,
		ArrowUp,
		ArrowDown,
		Shield,
		TextCursorInput
	} from 'lucide-svelte';
	import { toast } from 'svelte-sonner';
	import { formatDateTime, appSettings } from '$lib/stores/settings';

	interface FileEntry {
		name: string;
		type: 'file' | 'directory' | 'symlink' | 'other';
		size: number;
		permissions: string;
		owner: string;
		group: string;
		modified: string;
		linkTarget?: string;
		readonly?: boolean;
	}

	type BrowserMode = 'container' | 'volume';
	type SortField = 'name' | 'size' | 'modified' | 'type';
	type SortDirection = 'asc' | 'desc';

	interface VolumeUsageInfo {
		containerId: string;
		containerName: string;
		state: string;
	}

	interface Props {
		containerId?: string;
		volumeName?: string;
		envId?: number;
		initialPath?: string;
		canEdit?: boolean;
		onUsageChange?: (usage: VolumeUsageInfo[], isInUse: boolean) => void;
	}

	let { containerId, volumeName, envId, initialPath = '/', canEdit = true, onUsageChange }: Props = $props();

	// For volume mode, track whether volume is in use (controls editing ability)
	let volumeIsInUse = $state(false);
	let volumeUsage = $state<VolumeUsageInfo[]>([]);
	// Helper container ID for volume file operations (use container endpoints with this ID)
	let volumeHelperId = $state<string | null>(null);

	// Determine mode based on which prop is provided
	const mode: BrowserMode = $derived(volumeName ? 'volume' : 'container');
	const isVolumeMode = $derived(mode === 'volume');

	// Effective canEdit: for containers, use the prop; for volumes, only allow if not in use
	const effectiveCanEdit = $derived(
		isVolumeMode ? (canEdit && !volumeIsInUse) : canEdit
	);

	// Effective container ID for file operations (use helper container for volume mode)
	const effectiveContainerId = $derived(isVolumeMode ? volumeHelperId : containerId);

	// Volume mount path constant (must match VOLUME_MOUNT_PATH in docker.ts)
	const VOLUME_MOUNT_PATH = '/volume';

	// Transform path for container file operations in volume mode
	// The volume is mounted at /volume in the helper container
	function getContainerPath(path: string): string {
		if (!isVolumeMode) return path;
		return `${VOLUME_MOUNT_PATH}${path.startsWith('/') ? path : '/' + path}`;
	}

	let currentPath = $state('/');
	let entries = $state<FileEntry[]>([]);
	let loading = $state(false);
	let error = $state<string | null>(null);
	let uploading = $state(false);
	let fileInput: HTMLInputElement;
	// Track if this container uses busybox (doesn't support --time-style=iso)
	let useSimpleLs = $state(false);

	// Sort state
	let sortField = $state<SortField>('name');
	let sortDirection = $state<SortDirection>('asc');

	// Hidden files toggle
	let showHiddenFiles = $state(true);

	// Editor/Viewer state
	let editingFile = $state<{ name: string; path: string; content: string } | null>(null);
	let viewingFile = $state<{ name: string; path: string; content: string } | null>(null);
	let editorContent = $state('');
	let loadingFile = $state(false);
	let savingFile = $state(false);
	let editorTheme = $state<'light' | 'dark'>('dark');

	// Create modal state
	let showCreateModal = $state(false);
	let createType = $state<'file' | 'directory'>('file');
	let createName = $state('');
	let creating = $state(false);

	// Rename modal state
	let showRenameModal = $state(false);
	let renameEntry = $state<FileEntry | null>(null);
	let renameName = $state('');
	let renaming = $state(false);

	// Chmod modal state
	let showChmodModal = $state(false);
	let chmodEntry = $state<FileEntry | null>(null);
	let chmodMode = $state('644');
	let chmodRecursive = $state(false);
	let changingPerms = $state(false);

	// Permission checkboxes state (owner, group, others - read, write, execute)
	let permOwnerR = $state(true);
	let permOwnerW = $state(true);
	let permOwnerX = $state(false);
	let permGroupR = $state(true);
	let permGroupW = $state(false);
	let permGroupX = $state(false);
	let permOtherR = $state(true);
	let permOtherW = $state(false);
	let permOtherX = $state(false);

	// Update checkboxes from octal mode
	function octalToCheckboxes(mode: string) {
		const octal = mode.padStart(3, '0').slice(-3);
		const owner = parseInt(octal[0]) || 0;
		const group = parseInt(octal[1]) || 0;
		const other = parseInt(octal[2]) || 0;

		permOwnerR = (owner & 4) !== 0;
		permOwnerW = (owner & 2) !== 0;
		permOwnerX = (owner & 1) !== 0;
		permGroupR = (group & 4) !== 0;
		permGroupW = (group & 2) !== 0;
		permGroupX = (group & 1) !== 0;
		permOtherR = (other & 4) !== 0;
		permOtherW = (other & 2) !== 0;
		permOtherX = (other & 1) !== 0;
	}

	// Update octal mode from checkboxes
	function checkboxesToOctal() {
		const owner = (permOwnerR ? 4 : 0) + (permOwnerW ? 2 : 0) + (permOwnerX ? 1 : 0);
		const group = (permGroupR ? 4 : 0) + (permGroupW ? 2 : 0) + (permGroupX ? 1 : 0);
		const other = (permOtherR ? 4 : 0) + (permOtherW ? 2 : 0) + (permOtherX ? 1 : 0);
		chmodMode = `${owner}${group}${other}`;
	}

	// Generate symbolic permission string from checkboxes
	function checkboxesToSymbolic(): string {
		return (permOwnerR ? 'r' : '-') + (permOwnerW ? 'w' : '-') + (permOwnerX ? 'x' : '-') +
			   (permGroupR ? 'r' : '-') + (permGroupW ? 'w' : '-') + (permGroupX ? 'x' : '-') +
			   (permOtherR ? 'r' : '-') + (permOtherW ? 'w' : '-') + (permOtherX ? 'x' : '-');
	}

	// Deleting state
	let deleting = $state<string | null>(null);
	let confirmDeleteEntry = $state<string | null>(null);

	// Load theme preference from localStorage
	onMount(() => {
		const savedTheme = localStorage.getItem('dockhand-editor-theme');
		if (savedTheme === 'dark' || savedTheme === 'light') {
			editorTheme = savedTheme;
		}
		const savedShowHidden = localStorage.getItem('dockhand-filebrowser-show-hidden');
		if (savedShowHidden !== null) {
			showHiddenFiles = savedShowHidden === 'true';
		}
	});

	function toggleEditorTheme() {
		editorTheme = editorTheme === 'light' ? 'dark' : 'light';
		localStorage.setItem('dockhand-editor-theme', editorTheme);
	}

	function toggleHiddenFiles() {
		showHiddenFiles = !showHiddenFiles;
		localStorage.setItem('dockhand-filebrowser-show-hidden', String(showHiddenFiles));
	}

	// Max file size for editing (1MB)
	const MAX_EDITABLE_SIZE = 1024 * 1024;

	// Get language from filename for CodeMirror
	function getLanguageFromFilename(filename: string): string {
		const name = filename.toLowerCase();
		if (name === 'dockerfile') return 'shell';
		if (name === 'makefile' || name === 'rakefile') return 'shell';
		if (name.endsWith('.yml') || name.endsWith('.yaml')) return 'yaml';
		if (name.endsWith('.json')) return 'json';
		if (name.endsWith('.md')) return 'markdown';
		if (name.endsWith('.sh') || name.endsWith('.bash') || name.endsWith('.zsh') ||
			name === '.bashrc' || name === '.zshrc' || name === '.profile' ||
			name === '.bash_profile' || name === '.bash_aliases') return 'shell';
		if (name.endsWith('.js') || name.endsWith('.jsx')) return 'javascript';
		if (name.endsWith('.ts') || name.endsWith('.tsx')) return 'typescript';
		if (name.endsWith('.py')) return 'python';
		if (name.endsWith('.html') || name.endsWith('.htm')) return 'html';
		if (name.endsWith('.css') || name.endsWith('.scss') || name.endsWith('.sass') || name.endsWith('.less')) return 'css';
		if (name.endsWith('.xml')) return 'xml';
		if (name.endsWith('.sql')) return 'sql';
		return 'shell';
	}

	// Check if file is editable
	function isEditable(entry: FileEntry): boolean {
		if (entry.type !== 'file') return false;
		if (entry.size > MAX_EDITABLE_SIZE) return false;
		return true;
	}

	// Check if file is viewable
	function isViewable(entry: FileEntry): boolean {
		if (entry.type !== 'file') return false;
		if (entry.size > MAX_EDITABLE_SIZE) return false;
		return true;
	}

	// Sorted and filtered entries
	const displayEntries = $derived(() => {
		let filtered = entries;

		// Filter hidden files
		if (!showHiddenFiles) {
			filtered = filtered.filter(e => !e.name.startsWith('.'));
		}

		// Sort entries
		return [...filtered].sort((a, b) => {
			// Directories always first
			if (a.type === 'directory' && b.type !== 'directory') return -1;
			if (a.type !== 'directory' && b.type === 'directory') return 1;

			let cmp = 0;
			switch (sortField) {
				case 'name':
					cmp = a.name.localeCompare(b.name);
					break;
				case 'size':
					cmp = a.size - b.size;
					break;
				case 'modified':
					cmp = new Date(a.modified).getTime() - new Date(b.modified).getTime();
					break;
				case 'type':
					cmp = a.type.localeCompare(b.type);
					if (cmp === 0) cmp = a.name.localeCompare(b.name);
					break;
			}

			return sortDirection === 'asc' ? cmp : -cmp;
		});
	});

	// Toggle sort
	function toggleSort(field: SortField) {
		if (sortField === field) {
			sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
		} else {
			sortField = field;
			sortDirection = 'asc';
		}
	}

	// Get sort icon
	function getSortIcon(field: SortField) {
		if (sortField !== field) return ArrowUpDown;
		return sortDirection === 'asc' ? ArrowUp : ArrowDown;
	}

	// Open file for viewing (read-only)
	async function openFileForView(entry: FileEntry) {
		const filePath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
		loadingFile = true;

		try {
			const params = new URLSearchParams({ path: filePath });
			if (envId) params.set('env', envId.toString());

			let res: Response;
			if (isVolumeMode) {
				res = await fetch(`/api/volumes/${encodeURIComponent(volumeName!)}/browse/content?${params}`);
			} else {
				res = await fetch(`/api/containers/${effectiveContainerId}/files/content?${params}`);
			}
			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to read file');
			}

			viewingFile = {
				name: entry.name,
				path: filePath,
				content: data.content
			};
		} catch (err: any) {
			toast.error(err.message || 'Failed to open file');
		} finally {
			loadingFile = false;
		}
	}

	function closeViewer() {
		viewingFile = null;
	}

	// Open file for editing
	async function openFileForEdit(entry: FileEntry) {
		const filePath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
		const containerPath = getContainerPath(filePath);
		loadingFile = true;

		try {
			const params = new URLSearchParams({ path: containerPath });
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/content?${params}`);
			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to read file');
			}

			editorContent = data.content;
			editingFile = {
				name: entry.name,
				path: filePath,
				content: data.content
			};
		} catch (err: any) {
			toast.error(err.message || 'Failed to open file');
		} finally {
			loadingFile = false;
		}
	}

	async function saveFile() {
		if (!editingFile) return;

		savingFile = true;
		try {
			const containerPath = getContainerPath(editingFile.path);
			const params = new URLSearchParams({ path: containerPath });
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/content?${params}`, {
				method: 'PUT',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ content: editorContent })
			});

			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to save file');
			}

			toast.success('File saved');
			closeEditor();
		} catch (err: any) {
			toast.error(err.message || 'Failed to save file');
		} finally {
			savingFile = false;
		}
	}

	function closeEditor() {
		editingFile = null;
		editorContent = '';
	}

	// Create file or directory
	async function handleCreate() {
		if (!createName.trim()) {
			toast.error('Name is required');
			return;
		}

		creating = true;
		try {
			const fullPath = currentPath === '/' ? `/${createName}` : `${currentPath}/${createName}`;
			const containerPath = getContainerPath(fullPath);
			const params = new URLSearchParams();
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/create?${params}`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ path: containerPath, type: createType })
			});

			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to create');
			}

			toast.success(`${createType === 'file' ? 'File' : 'Directory'} created`);
			showCreateModal = false;
			createName = '';
			loadDirectory(currentPath);
		} catch (err: any) {
			toast.error(err.message || 'Failed to create');
		} finally {
			creating = false;
		}
	}

	// Delete file or directory
	async function handleDelete(entry: FileEntry) {
		const fullPath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
		const containerPath = getContainerPath(fullPath);
		deleting = entry.name;

		try {
			const params = new URLSearchParams({ path: containerPath });
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/delete?${params}`, {
				method: 'DELETE'
			});

			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to delete');
			}

			toast.success(`Deleted ${entry.name}`);
			loadDirectory(currentPath);
		} catch (err: any) {
			toast.error(err.message || 'Failed to delete');
		} finally {
			deleting = null;
		}
	}

	// Rename file or directory
	function openRenameModal(entry: FileEntry) {
		renameEntry = entry;
		renameName = entry.name;
		showRenameModal = true;
	}

	async function handleRename() {
		if (!renameEntry || !renameName.trim()) {
			toast.error('Name is required');
			return;
		}

		if (renameName === renameEntry.name) {
			showRenameModal = false;
			return;
		}

		renaming = true;
		try {
			const oldPath = currentPath === '/' ? `/${renameEntry.name}` : `${currentPath}/${renameEntry.name}`;
			const newPath = currentPath === '/' ? `/${renameName}` : `${currentPath}/${renameName}`;
			const containerOldPath = getContainerPath(oldPath);
			const containerNewPath = getContainerPath(newPath);
			const params = new URLSearchParams();
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/rename?${params}`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ oldPath: containerOldPath, newPath: containerNewPath })
			});

			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to rename');
			}

			toast.success('Renamed successfully');
			showRenameModal = false;
			renameEntry = null;
			loadDirectory(currentPath);
		} catch (err: any) {
			toast.error(err.message || 'Failed to rename');
		} finally {
			renaming = false;
		}
	}

	// Change permissions
	function openChmodModal(entry: FileEntry) {
		chmodEntry = entry;
		// Convert permissions string to octal
		chmodMode = permissionsToOctal(entry.permissions);
		// Also update checkboxes from octal
		octalToCheckboxes(chmodMode);
		chmodRecursive = false;
		showChmodModal = true;
	}

	function permissionsToOctal(perms: string): string {
		// perms is like "rwxr-xr-x"
		const parseTriple = (s: string) => {
			let val = 0;
			if (s[0] === 'r') val += 4;
			if (s[1] === 'w') val += 2;
			if (s[2] === 'x' || s[2] === 's' || s[2] === 't') val += 1;
			return val;
		};

		if (perms.length < 9) return '644';

		const owner = parseTriple(perms.slice(0, 3));
		const group = parseTriple(perms.slice(3, 6));
		const other = parseTriple(perms.slice(6, 9));

		return `${owner}${group}${other}`;
	}

	async function handleChmod() {
		if (!chmodEntry || !chmodMode.trim()) {
			toast.error('Mode is required');
			return;
		}

		changingPerms = true;
		try {
			const fullPath = currentPath === '/' ? `/${chmodEntry.name}` : `${currentPath}/${chmodEntry.name}`;
			const containerPath = getContainerPath(fullPath);
			const params = new URLSearchParams();
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/chmod?${params}`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ path: containerPath, mode: chmodMode, recursive: chmodRecursive })
			});

			const data = await res.json();

			if (!res.ok) {
				throw new Error(data.error || 'Failed to change permissions');
			}

			toast.success('Permissions changed');
			showChmodModal = false;
			chmodEntry = null;
			loadDirectory(currentPath);
		} catch (err: any) {
			toast.error(err.message || 'Failed to change permissions');
		} finally {
			changingPerms = false;
		}
	}

	// Format file size
	function formatSize(bytes: number): string {
		if (bytes === 0) return '-';
		const units = ['B', 'KB', 'MB', 'GB'];
		let size = bytes;
		let unitIndex = 0;
		while (size >= 1024 && unitIndex < units.length - 1) {
			size /= 1024;
			unitIndex++;
		}
		return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
	}

	function formatDate(isoDate: string): string {
		try {
			return formatDateTime(isoDate);
		} catch {
			return isoDate;
		}
	}

	function getIcon(entry: FileEntry) {
		switch (entry.type) {
			case 'directory':
				return Folder;
			case 'symlink':
				return Link;
			case 'file':
				return File;
			default:
				return FileQuestion;
		}
	}

	// Load directory contents
	async function loadDirectory(path: string) {
		loading = true;
		error = null;

		try {
			const params = new URLSearchParams({ path });
			if (envId) params.set('env', envId.toString());

			let res: Response;
			let data: any;

			if (isVolumeMode) {
				res = await fetch(`/api/volumes/${encodeURIComponent(volumeName!)}/browse?${params}`);
				data = await res.json();

				// Capture volume usage info and helper container ID from response
				if (data.usage !== undefined) {
					volumeUsage = data.usage;
					volumeIsInUse = data.isInUse ?? false;
					onUsageChange?.(volumeUsage, volumeIsInUse);
				}
				if (data.helperId) {
					volumeHelperId = data.helperId;
				}
			} else {
				if (useSimpleLs) params.set('simpleLs', 'true');

				res = await fetch(`/api/containers/${effectiveContainerId}/files?${params}`);
				data = await res.json();

				if (!res.ok && !useSimpleLs) {
					params.set('simpleLs', 'true');
					res = await fetch(`/api/containers/${effectiveContainerId}/files?${params}`);
					data = await res.json();
					if (res.ok) {
						useSimpleLs = true;
					}
				}
			}

			if (!res.ok) {
				throw new Error(data.error || 'Failed to load directory');
			}

			currentPath = data.path || path;
			entries = data.entries || [];
		} catch (err: any) {
			error = err.message;
			entries = [];
		} finally {
			loading = false;
		}
	}

	function navigateTo(path: string) {
		loadDirectory(path);
	}

	function goUp() {
		if (currentPath === '/') return;
		const parts = currentPath.split('/').filter(Boolean);
		parts.pop();
		navigateTo('/' + parts.join('/') || '/');
	}

	function goHome() {
		navigateTo('/');
	}

	function handleEntryClick(entry: FileEntry) {
		if (entry.type === 'directory') {
			const newPath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
			navigateTo(newPath);
		}
		// Symlinks are not navigable - target path is displayed for reference
	}

	function downloadFile(entry: FileEntry) {
		const filePath =
			currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
		const params = new URLSearchParams({
			path: filePath,
			format: $appSettings.downloadFormat
		});
		if (envId) params.set('env', envId.toString());

		let url: string;
		if (isVolumeMode) {
			url = `/api/volumes/${encodeURIComponent(volumeName!)}/export?${params}`;
		} else {
			url = `/api/containers/${effectiveContainerId}/files/download?${params}`;
		}
		window.open(url, '_blank');
	}

	async function handleFileUpload(event: Event) {
		const input = event.target as HTMLInputElement;
		const files = input.files;
		if (!files || files.length === 0) return;

		uploading = true;

		try {
			const formData = new FormData();
			for (const file of files) {
				formData.append('files', file);
			}

			const containerPath = getContainerPath(currentPath);
			const params = new URLSearchParams({ path: containerPath });
			if (envId) params.set('env', envId.toString());

			const res = await fetch(`/api/containers/${effectiveContainerId}/files/upload?${params}`, {
				method: 'POST',
				body: formData
			});

			const data = await res.json();

			if (!res.ok) {
				const details = data.details?.join('; ') || '';
				throw new Error(details || data.error || 'Upload failed');
			}

			toast.success(`Uploaded ${data.uploaded.length} file(s)`);
			if (data.errors?.length) {
				toast.error(`Failed: ${data.errors.join(', ')}`);
			}

			loadDirectory(currentPath);
		} catch (err: any) {
			toast.error(err.message || 'Upload failed');
		} finally {
			uploading = false;
			input.value = '';
		}
	}

	const pathSegments = $derived(() => {
		if (currentPath === '/') return [];
		return currentPath.split('/').filter(Boolean);
	});

	$effect(() => {
		loadDirectory(initialPath);
	});
</script>

<div class="flex flex-col h-full relative">
	<!-- Header with breadcrumbs and actions -->
	<div class="flex items-center gap-2 p-2 border-b bg-muted/30">
		<Button variant="ghost" size="icon" class="h-7 w-7" onclick={goUp} disabled={currentPath === '/'}>
			<ChevronLeft class="w-3.5 h-3.5" />
		</Button>
		<Button variant="ghost" size="icon" class="h-7 w-7" onclick={goHome}>
			<Home class="w-3.5 h-3.5" />
		</Button>

		<!-- Breadcrumbs -->
		<div class="flex-1 flex items-center gap-1 text-xs overflow-x-auto">
			<button
				type="button"
				class="text-muted-foreground hover:text-foreground px-1"
				onclick={() => navigateTo('/')}
			>
				/
			</button>
			{#each pathSegments() as segment, i}
				<ChevronRight class="w-3 h-3 text-muted-foreground shrink-0" />
				<button
					type="button"
					class="text-muted-foreground hover:text-foreground px-1 truncate max-w-[150px]"
					title={segment}
					onclick={() => navigateTo('/' + pathSegments().slice(0, i + 1).join('/'))}
				>
					{segment}
				</button>
			{/each}
		</div>

		<!-- Actions -->
		{#if effectiveCanEdit}
			<Button
				variant="ghost"
				size="icon"
				class="h-7 w-7"
				onclick={() => { createType = 'file'; createName = ''; showCreateModal = true; }}
				title="New file"
			>
				<FilePlus class="w-3.5 h-3.5" />
			</Button>
			<Button
				variant="ghost"
				size="icon"
				class="h-7 w-7"
				onclick={() => { createType = 'directory'; createName = ''; showCreateModal = true; }}
				title="New directory"
			>
				<FolderPlus class="w-3.5 h-3.5" />
			</Button>
			<input
				bind:this={fileInput}
				type="file"
				multiple
				class="hidden"
				onchange={handleFileUpload}
			/>
			<Button
				variant="ghost"
				size="icon"
				class="h-7 w-7"
				onclick={() => fileInput.click()}
				disabled={uploading || loading}
				title="Upload files"
			>
				{#if uploading}
					<Loader2 class="w-3.5 h-3.5 animate-spin" />
				{:else}
					<Upload class="w-3.5 h-3.5" />
				{/if}
			</Button>
		{/if}
		<Button
			variant="ghost"
			size="icon"
			class="h-7 w-7"
			onclick={toggleHiddenFiles}
			title={showHiddenFiles ? 'Hide hidden files' : 'Show hidden files'}
		>
			{#if showHiddenFiles}
				<Eye class="w-3.5 h-3.5" />
			{:else}
				<EyeOff class="w-3.5 h-3.5" />
			{/if}
		</Button>
		<Button
			variant="ghost"
			size="icon"
			class="h-7 w-7"
			onclick={() => loadDirectory(currentPath)}
			disabled={loading}
			title="Refresh"
		>
			<RefreshCw class="w-3.5 h-3.5 {loading ? 'animate-spin' : ''}" />
		</Button>
	</div>

	<!-- File list -->
	<div class="flex-1 overflow-auto relative">
		{#if loading}
			<div class="absolute inset-0 bg-background/80 flex items-center justify-center z-10">
				<Loader2 class="w-5 h-5 animate-spin mr-2 text-muted-foreground" />
				<span class="text-sm text-muted-foreground">Loading...</span>
			</div>
		{/if}
		{#if error}
			<div class="flex items-center justify-center p-4 h-full">
				<div class="max-w-md bg-destructive/5 border border-destructive/20 rounded-lg p-4 text-center">
					<AlertCircle class="w-6 h-6 text-destructive mx-auto" />
					<p class="text-sm font-medium text-destructive mt-2">Unable to browse files</p>
					<p class="text-xs text-muted-foreground mt-2 break-words font-mono bg-muted/50 rounded px-2 py-1.5">{error}</p>
					<Button variant="outline" size="sm" class="mt-3" onclick={() => loadDirectory(currentPath)}>
						Retry
					</Button>
				</div>
			</div>
		{:else if !loading && displayEntries().length === 0}
			<div class="flex items-center justify-center h-32 text-muted-foreground">
				<span class="text-sm">{showHiddenFiles ? 'Directory is empty' : 'No visible files (hidden files are hidden)'}</span>
			</div>
		{:else if displayEntries().length > 0}
			<Table.Root class="text-xs">
				<Table.Header>
					<Table.Row>
						<Table.Head class="w-[35%] py-1.5 text-xs font-medium">
							<button type="button" class="flex items-center gap-1 hover:text-foreground" onclick={() => toggleSort('name')}>
								Name
								<svelte:component this={getSortIcon('name')} class="w-3 h-3 opacity-50" />
							</button>
						</Table.Head>
						<Table.Head class="w-[8%] py-1.5 text-xs font-medium">
							<button type="button" class="flex items-center gap-1 hover:text-foreground" onclick={() => toggleSort('size')}>
								Size
								<svelte:component this={getSortIcon('size')} class="w-3 h-3 opacity-50" />
							</button>
						</Table.Head>
						<Table.Head class="w-[18%] py-1.5 text-xs font-medium">
							<span class="text-muted-foreground">Permissions</span>
						</Table.Head>
						<Table.Head class="w-[14%] py-1.5 text-xs font-medium">
							<button type="button" class="flex items-center gap-1 hover:text-foreground" onclick={() => toggleSort('modified')}>
								Modified
								<svelte:component this={getSortIcon('modified')} class="w-3 h-3 opacity-50" />
							</button>
						</Table.Head>
						<Table.Head class="w-[25%] py-1.5 text-xs font-medium text-right">Actions</Table.Head>
					</Table.Row>
				</Table.Header>
				<Table.Body>
					{#each displayEntries() as entry (entry.name)}
						{@const Icon = getIcon(entry)}
						{@const isClickable = entry.type === 'directory'}
						<Table.Row class="{isClickable ? 'cursor-pointer' : ''} hover:bg-muted/50 group">
							<Table.Cell class="py-1">
								<button
									type="button"
									class="flex items-center gap-1.5 w-full text-left {isClickable ? '' : 'cursor-default'}"
									onclick={() => handleEntryClick(entry)}
								>
									<Icon
										class="w-3.5 h-3.5 shrink-0 {entry.type === 'directory'
											? 'text-blue-500'
											: entry.type === 'symlink'
												? 'text-purple-500'
												: 'text-muted-foreground'}"
									/>
									<span class="truncate" title={entry.name}>
										{entry.name}
										{#if entry.type === 'symlink' && entry.linkTarget}
											<span class="text-muted-foreground ml-1">
												→ {entry.linkTarget}
											</span>
										{/if}
									</span>
									{#if entry.readonly && entry.type === 'file'}
										<span
											class="inline-flex items-center gap-0.5 ml-1.5 px-1 py-0.5 text-2xs bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded"
											title="Read-only file (no write permission)"
										>
											<Lock class="w-2.5 h-2.5" />
											RO
										</span>
									{/if}
								</button>
							</Table.Cell>
							<Table.Cell class="text-muted-foreground py-1">
								{entry.type === 'directory' ? '-' : formatSize(entry.size)}
							</Table.Cell>
							<Table.Cell class="text-muted-foreground py-1 font-mono text-2xs">
								<span title={entry.permissions}>{permissionsToOctal(entry.permissions)}</span>
								<span class="ml-1 opacity-60">{entry.permissions}</span>
							</Table.Cell>
							<Table.Cell class="text-muted-foreground py-1">
								{formatDate(entry.modified)}
							</Table.Cell>
							<Table.Cell class="text-right py-1">
								<div class="flex items-center justify-end gap-0.5">
									{#if isViewable(entry)}
										<Button
											variant="ghost"
											size="icon"
											class="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
											onclick={(e: MouseEvent) => { e.stopPropagation(); openFileForView(entry); }}
											disabled={loadingFile}
											title="View file"
										>
											<Eye class="w-3 h-3" />
										</Button>
									{/if}
									{#if effectiveCanEdit && isEditable(entry)}
										<Button
											variant="ghost"
											size="icon"
											class="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity {entry.readonly ? 'cursor-not-allowed' : ''}"
											onclick={(e: MouseEvent) => { e.stopPropagation(); if (!entry.readonly) openFileForEdit(entry); }}
											disabled={loadingFile || entry.readonly}
											title={entry.readonly ? "File is read-only" : "Edit file"}
										>
											{#if loadingFile}
												<Loader2 class="w-3 h-3 animate-spin" />
											{:else if entry.readonly}
												<Lock class="w-3 h-3 text-muted-foreground" />
											{:else}
												<Pencil class="w-3 h-3" />
											{/if}
										</Button>
									{/if}
									{#if effectiveCanEdit}
										<Button
											variant="ghost"
											size="icon"
											class="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
											onclick={(e: MouseEvent) => { e.stopPropagation(); openRenameModal(entry); }}
											title="Rename"
										>
											<TextCursorInput class="w-3 h-3" />
										</Button>
										<Button
											variant="ghost"
											size="icon"
											class="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
											onclick={(e: MouseEvent) => { e.stopPropagation(); openChmodModal(entry); }}
											title="Change permissions"
										>
											<Shield class="w-3 h-3" />
										</Button>
										<ConfirmPopover
											open={confirmDeleteEntry === entry.name}
											action="Delete"
											itemType={entry.type === 'directory' ? 'directory' : 'file'}
											itemName={entry.name}
											confirmText="Delete"
											variant="destructive"
											onConfirm={() => handleDelete(entry)}
											onOpenChange={(open) => confirmDeleteEntry = open ? entry.name : null}
										>
											{#snippet children({ open })}
												{#if deleting === entry.name}
													<Loader2 class="w-3 h-3 animate-spin" />
												{:else}
													<Trash2 class="w-3 h-3 {open ? 'text-destructive' : 'text-muted-foreground hover:text-destructive'}" />
												{/if}
											{/snippet}
										</ConfirmPopover>
									{/if}
									<Button
										variant="ghost"
										size="icon"
										class="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
										onclick={(e: MouseEvent) => { e.stopPropagation(); downloadFile(entry); }}
										title="Download"
									>
										<Download class="w-3 h-3" />
									</Button>
								</div>
							</Table.Cell>
						</Table.Row>
					{/each}
				</Table.Body>
			</Table.Root>
		{/if}
	</div>

	<!-- File Editor Overlay -->
	{#if editingFile}
		<div class="absolute inset-0 bg-background flex flex-col z-10">
			<div class="flex items-center justify-between p-2 border-b bg-muted/30">
				<div class="flex items-center gap-2 text-xs">
					<File class="w-3.5 h-3.5 text-muted-foreground" />
					<span class="font-medium">{editingFile.name}</span>
					<span class="text-muted-foreground">{editingFile.path}</span>
				</div>
				<div class="flex items-center gap-1">
					<Button variant="ghost" size="icon" class="h-7 w-7" onclick={toggleEditorTheme} title={editorTheme === 'light' ? 'Switch to dark theme' : 'Switch to light theme'}>
						{#if editorTheme === 'light'}
							<Moon class="w-3.5 h-3.5" />
						{:else}
							<Sun class="w-3.5 h-3.5" />
						{/if}
					</Button>
					<Button variant="outline" size="sm" class="h-7 text-xs" onclick={saveFile} disabled={savingFile}>
						{#if savingFile}
							<Loader2 class="w-3.5 h-3.5 mr-1.5 animate-spin" />
						{:else}
							<Save class="w-3.5 h-3.5 mr-1.5" />
						{/if}
						Save
					</Button>
					<Button variant="ghost" size="icon" class="h-7 w-7" onclick={closeEditor} title="Close editor">
						<X class="w-3.5 h-3.5" />
					</Button>
				</div>
			</div>
			<div class="flex-1 overflow-hidden">
				<CodeEditor
					value={editingFile.content}
					language={getLanguageFromFilename(editingFile.name)}
					theme={editorTheme}
					onchange={(v) => editorContent = v}
				/>
			</div>
		</div>
	{/if}

	<!-- File Viewer Overlay -->
	{#if viewingFile}
		<div class="absolute inset-0 bg-background flex flex-col z-10">
			<div class="flex items-center justify-between p-2 border-b bg-muted/30">
				<div class="flex items-center gap-2 text-xs">
					<Eye class="w-3.5 h-3.5 text-muted-foreground" />
					<span class="font-medium">{viewingFile.name}</span>
					<span class="text-muted-foreground">{viewingFile.path}</span>
					<span class="text-xs text-muted-foreground bg-muted px-1.5 py-0.5 rounded">read-only</span>
				</div>
				<div class="flex items-center gap-1">
					<Button variant="ghost" size="icon" class="h-7 w-7" onclick={toggleEditorTheme} title={editorTheme === 'light' ? 'Switch to dark theme' : 'Switch to light theme'}>
						{#if editorTheme === 'light'}
							<Moon class="w-3.5 h-3.5" />
						{:else}
							<Sun class="w-3.5 h-3.5" />
						{/if}
					</Button>
					<Button variant="ghost" size="icon" class="h-7 w-7" onclick={closeViewer} title="Close viewer">
						<X class="w-3.5 h-3.5" />
					</Button>
				</div>
			</div>
			<div class="flex-1 overflow-hidden">
				<CodeEditor
					value={viewingFile.content}
					language={getLanguageFromFilename(viewingFile.name)}
					theme={editorTheme}
					readonly={true}
				/>
			</div>
		</div>
	{/if}
</div>

<!-- Create File/Directory Modal -->
<Dialog.Root bind:open={showCreateModal}>
	<Dialog.Content class="max-w-sm">
		<Dialog.Header>
			<Dialog.Title>Create {createType === 'file' ? 'File' : 'Directory'}</Dialog.Title>
		</Dialog.Header>
		<div class="space-y-4 py-4">
			<div class="space-y-2">
				<Label for="create-name">Name</Label>
				<Input
					id="create-name"
					bind:value={createName}
					placeholder={createType === 'file' ? 'filename.txt' : 'directory-name'}
					onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter') handleCreate(); }}
				/>
			</div>
			<p class="text-xs text-muted-foreground">
				Will be created in: {currentPath}
			</p>
		</div>
		<Dialog.Footer>
			<Button variant="outline" onclick={() => showCreateModal = false}>Cancel</Button>
			<Button onclick={handleCreate} disabled={creating || !createName.trim()}>
				{#if creating}
					<Loader2 class="w-4 h-4 mr-2 animate-spin" />
				{/if}
				Create
			</Button>
		</Dialog.Footer>
	</Dialog.Content>
</Dialog.Root>

<!-- Rename Modal -->
<Dialog.Root bind:open={showRenameModal}>
	<Dialog.Content class="max-w-sm">
		<Dialog.Header>
			<Dialog.Title>Rename</Dialog.Title>
		</Dialog.Header>
		<div class="space-y-4 py-4">
			<div class="space-y-2">
				<Label for="rename-name">New name</Label>
				<Input
					id="rename-name"
					bind:value={renameName}
					onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter') handleRename(); }}
				/>
			</div>
		</div>
		<Dialog.Footer>
			<Button variant="outline" onclick={() => showRenameModal = false}>Cancel</Button>
			<Button onclick={handleRename} disabled={renaming || !renameName.trim()}>
				{#if renaming}
					<Loader2 class="w-4 h-4 mr-2 animate-spin" />
				{/if}
				Rename
			</Button>
		</Dialog.Footer>
	</Dialog.Content>
</Dialog.Root>

<!-- Chmod Modal -->
<Dialog.Root bind:open={showChmodModal}>
	<Dialog.Content class="max-w-md">
		<Dialog.Header>
			<Dialog.Title>Change permissions</Dialog.Title>
		</Dialog.Header>
		<div class="space-y-4 py-4">
			{#if chmodEntry}
				<p class="text-sm text-muted-foreground">{chmodEntry.name}</p>
				<p class="text-xs text-muted-foreground">Current: {chmodEntry.permissions}</p>
			{/if}

			<!-- Permission checkboxes -->
			<div class="border rounded-lg p-3">
				<table class="w-full text-sm">
					<thead>
						<tr class="text-muted-foreground text-xs">
							<th class="text-left font-normal pb-2"></th>
							<th class="text-center font-normal pb-2 w-16">Read</th>
							<th class="text-center font-normal pb-2 w-16">Write</th>
							<th class="text-center font-normal pb-2 w-16">Execute</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td class="py-1.5 text-muted-foreground">Owner</td>
							<td class="text-center"><input type="checkbox" bind:checked={permOwnerR} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permOwnerW} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permOwnerX} onchange={checkboxesToOctal} class="rounded" /></td>
						</tr>
						<tr>
							<td class="py-1.5 text-muted-foreground">Group</td>
							<td class="text-center"><input type="checkbox" bind:checked={permGroupR} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permGroupW} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permGroupX} onchange={checkboxesToOctal} class="rounded" /></td>
						</tr>
						<tr>
							<td class="py-1.5 text-muted-foreground">Others</td>
							<td class="text-center"><input type="checkbox" bind:checked={permOtherR} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permOtherW} onchange={checkboxesToOctal} class="rounded" /></td>
							<td class="text-center"><input type="checkbox" bind:checked={permOtherX} onchange={checkboxesToOctal} class="rounded" /></td>
						</tr>
					</tbody>
				</table>
			</div>

			<!-- Preview -->
			<div class="flex items-center gap-4 text-sm bg-muted/50 rounded-lg p-3">
				<div>
					<span class="text-muted-foreground text-xs">Octal:</span>
					<span class="font-mono font-medium ml-1">{chmodMode}</span>
				</div>
				<div>
					<span class="text-muted-foreground text-xs">Symbolic:</span>
					<span class="font-mono font-medium ml-1">{checkboxesToSymbolic()}</span>
				</div>
			</div>

			<!-- Manual octal input -->
			<div class="space-y-2">
				<Label for="chmod-mode">Or enter octal mode directly</Label>
				<Input
					id="chmod-mode"
					bind:value={chmodMode}
					placeholder="755"
					maxlength={4}
					oninput={() => octalToCheckboxes(chmodMode)}
					onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter') handleChmod(); }}
				/>
			</div>

			{#if chmodEntry?.type === 'directory'}
				<label class="flex items-center gap-2 text-sm">
					<input type="checkbox" bind:checked={chmodRecursive} class="rounded" />
					Apply recursively
				</label>
			{/if}
		</div>
		<Dialog.Footer>
			<Button variant="outline" onclick={() => showChmodModal = false}>Cancel</Button>
			<Button onclick={handleChmod} disabled={changingPerms || !chmodMode.trim()}>
				{#if changingPerms}
					<Loader2 class="w-4 h-4 mr-2 animate-spin" />
				{/if}
				Apply
			</Button>
		</Dialog.Footer>
	</Dialog.Content>
</Dialog.Root>
