<?php

$CONFIG = [
    'script_dir'      => __DIR__ . DIRECTORY_SEPARATOR,
    'base_dir'        => DIRECTORY_SEPARATOR,
    'allowed_types'   => ['zip', 'php', 'txt', 'jpg', 'png', 'pdf', 'html', 'css', 'js'],
    'max_size'        => 10 * 1024 * 1024,
    'text_extensions' => ['txt', 'php', 'html', 'css', 'js', 'log', 'ini', 'md', 'csv', 'xml', 'json', 'htaccess'],
    'max_view_size'   => 5 * 1024 * 1024,
];

$AUTH = [
    'user_hash'    => '$2y$11$vloemvW2RzZ4QvgBc0eaEOcVGfyFeqkqgR23avtYsVcc6jlIprYsu',
    'pass_hash'    => '$2y$11$lL6ABKLcizNyk66UsEFPBOFtB93mW0P/9zmvl8dKAp2iXktfj9BF.',
    'session_name' => 'fm_session',
    'timeout'      => 30 * 60,
    'max_attempts' => 5,
    'lockout'      => 5 * 60,
];

$CONFIG['base_dir']   = rtrim(str_replace(['\\', '/'], DIRECTORY_SEPARATOR, realpath($CONFIG['base_dir']) ?: $CONFIG['base_dir']), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$CONFIG['script_dir'] = realpath($CONFIG['script_dir']) . DIRECTORY_SEPARATOR;
$CONFIG['temp_dir']   = $CONFIG['script_dir'] . 'temp' . DIRECTORY_SEPARATOR;

if (!is_dir($CONFIG['temp_dir'])) {
    @mkdir($CONFIG['temp_dir'], 0755, true);
}

if (is_dir($CONFIG['temp_dir']) && !is_file($CONFIG['temp_dir'] . '.htaccess')) {
    @file_put_contents(
        $CONFIG['temp_dir'] . '.htaccess',
        "Require all denied\n<IfModule !mod_authz_core.c>\nOrder allow,deny\nDeny from all\n</IfModule>\n"
    );
}

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

function formatSize($bytes) {
    $bytes = (int) $bytes;
    if ($bytes <= 0) return '0 B';
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = (int) floor(log($bytes, 1024));
    $i = min($i, count($units) - 1);
    return round($bytes / pow(1024, $i), 1) . ' ' . $units[$i];
}

function fileExt($name) {
    return strtolower(pathinfo($name, PATHINFO_EXTENSION));
}

function normalizeSeparators($path) {
    return str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
}

function relativeTo($absolute, $base_dir) {
    $rel = strpos($absolute, $base_dir) === 0
        ? substr($absolute, strlen($base_dir))
        : $absolute;

    return trim(str_replace(DIRECTORY_SEPARATOR, '/', $rel), '/');
}

function securePath($user_path, $base_dir) {
    $user_path = normalizeSeparators($user_path);
    $user_path = preg_replace('/\.\.+/', '', $user_path);
    $full_path = $base_dir . ltrim($user_path, DIRECTORY_SEPARATOR);
    $real_path = realpath($full_path);

    if ($real_path === false) {
        return realpath(dirname($full_path)) === false ? false : $full_path;
    }
    if (strpos($real_path . DIRECTORY_SEPARATOR, $base_dir) !== 0) {
        return false;
    }
    return $real_path . (is_dir($real_path) ? DIRECTORY_SEPARATOR : '');
}

function insideBase($path, $base_dir) {
    $real = realpath($path);
    return $real !== false && strpos($real . DIRECTORY_SEPARATOR, $base_dir) === 0;
}

function postedItems() {
    $raw = $_POST['items'] ?? [];
    if (!is_array($raw)) {
        $raw = array_filter(array_map('trim', explode(',', (string) $raw)), 'strlen');
    }
    $items = [];
    foreach ($raw as $item) {
        $name = basename(trim((string) $item));
        if ($name !== '' && $name !== '.' && $name !== '..') {
            $items[] = $name;
        }
    }
    return array_values(array_unique($items));
}

function itemIcon($name, $is_dir = false) {
    if ($is_dir) {
        return ['fi-rr-folder', 'ico-folder'];
    }

    $map = [
        'zip'  => ['fi-rr-file-zipper', 'ico-archive'],
        'rar'  => ['fi-rr-file-zipper', 'ico-archive'],
        '7z'   => ['fi-rr-file-zipper', 'ico-archive'],
        'tar'  => ['fi-rr-file-zipper', 'ico-archive'],
        'gz'   => ['fi-rr-file-zipper', 'ico-archive'],
        'jpg'  => ['fi-rr-picture', 'ico-image'],
        'jpeg' => ['fi-rr-picture', 'ico-image'],
        'png'  => ['fi-rr-picture', 'ico-image'],
        'gif'  => ['fi-rr-picture', 'ico-image'],
        'webp' => ['fi-rr-picture', 'ico-image'],
        'svg'  => ['fi-rr-picture', 'ico-image'],
        'bmp'  => ['fi-rr-picture', 'ico-image'],
        'ico'  => ['fi-rr-picture', 'ico-image'],
        'pdf'  => ['fi-rr-file-pdf', 'ico-pdf'],
        'doc'  => ['fi-rr-file-word', 'ico-doc'],
        'docx' => ['fi-rr-file-word', 'ico-doc'],
        'xls'  => ['fi-rr-file-excel', 'ico-sheet'],
        'xlsx' => ['fi-rr-file-excel', 'ico-sheet'],
        'csv'  => ['fi-rr-file-excel', 'ico-sheet'],
        'php'  => ['fi-rr-file-code', 'ico-code'],
        'js'   => ['fi-rr-file-code', 'ico-code'],
        'ts'   => ['fi-rr-file-code', 'ico-code'],
        'json' => ['fi-rr-file-code', 'ico-code'],
        'xml'  => ['fi-rr-file-code', 'ico-code'],
        'css'  => ['fi-rr-file-code', 'ico-code'],
        'py'   => ['fi-rr-file-code', 'ico-code'],
        'html' => ['fi-rr-browser', 'ico-code'],
        'sql'  => ['fi-rr-database', 'ico-code'],
        'sh'   => ['fi-rr-terminal', 'ico-code'],
        'bat'  => ['fi-rr-terminal', 'ico-code'],
        'mp3'  => ['fi-rr-music-alt', 'ico-media'],
        'wav'  => ['fi-rr-music-alt', 'ico-media'],
        'ogg'  => ['fi-rr-music-alt', 'ico-media'],
        'mp4'  => ['fi-rr-video-camera', 'ico-media'],
        'avi'  => ['fi-rr-video-camera', 'ico-media'],
        'mkv'  => ['fi-rr-video-camera', 'ico-media'],
        'mov'  => ['fi-rr-video-camera', 'ico-media'],
        'txt'  => ['fi-rr-document', 'ico-text'],
        'md'   => ['fi-rr-document', 'ico-text'],
        'log'  => ['fi-rr-time-past', 'ico-text'],
        'ini'  => ['fi-rr-settings-sliders', 'ico-text'],
    ];

    return $map[fileExt($name)] ?? ['fi-rr-file', 'ico-file'];
}

function result($errors = [], $success = '') {
    return ['errors' => (array) $errors, 'success' => $success];
}

function generateHashesCli(array $argv) {
    $user = $argv[2] ?? '';
    $pass = $argv[3] ?? '';

    if ($user === '' || $pass === '') {
        echo "Uso: php " . basename(__FILE__) . " --hash \"usuario\" \"senha\"\n";
        exit(1);
    }

    echo "\nCole as duas linhas abaixo no array \$AUTH, no topo deste arquivo:\n\n";
    echo "    'user_hash'    => '" . password_hash($user, PASSWORD_BCRYPT, ['cost' => 11]) . "',\n";
    echo "    'pass_hash'    => '" . password_hash($pass, PASSWORD_BCRYPT, ['cost' => 11]) . "',\n\n";
    exit(0);
}

if (PHP_SAPI === 'cli') {
    if (($argv[1] ?? '') === '--hash') {
        generateHashesCli($argv);
    }
    exit("Abra este arquivo pelo navegador.\n"
       . "Para gerar novas credenciais: php " . basename(__FILE__) . " --hash \"usuario\" \"senha\"\n");
}

function startSecureSession(array $auth) {
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    $https = (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
          || (int) ($_SERVER['SERVER_PORT'] ?? 0) === 443
          || strtolower($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';

    session_name($auth['session_name']);
    session_set_cookie_params([
        'lifetime' => 0,
        'path'     => '/',
        'httponly' => true,
        'secure'   => $https,
        'samesite' => 'Strict',
    ]);
    session_start();
}

function agentFingerprint() {
    return sha1($_SERVER['HTTP_USER_AGENT'] ?? '');
}

function csrfToken() {
    if (empty($_SESSION['fm_csrf'])) {
        $_SESSION['fm_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['fm_csrf'];
}

function csrfValid() {
    return !empty($_SESSION['fm_csrf'])
        && is_string($_POST['csrf'] ?? null)
        && hash_equals($_SESSION['fm_csrf'], $_POST['csrf']);
}

function csrfField() {
    return '<input type="hidden" name="csrf" value="' . e(csrfToken()) . '">';
}

function lockoutFile(array $auth, $temp_dir) {
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
    return $temp_dir . '.login_' . substr(sha1($ip . '|' . $auth['session_name']), 0, 16) . '.json';
}

function loginState(array $auth, $temp_dir) {
    $file  = lockoutFile($auth, $temp_dir);
    $state = is_file($file) ? json_decode((string) @file_get_contents($file), true) : null;

    if (!is_array($state)) {
        $state = ['count' => 0, 'until' => 0];
    }

    if (($state['until'] ?? 0) > 0 && $state['until'] <= time()) {
        $state = ['count' => 0, 'until' => 0];
        @unlink($file);
    }
    return $state;
}

function registerLoginFailure(array $auth, $temp_dir) {
    $state = loginState($auth, $temp_dir);
    $state['count'] = ($state['count'] ?? 0) + 1;

    if ($state['count'] >= $auth['max_attempts']) {
        $state['until'] = time() + $auth['lockout'];
    }
    @file_put_contents(lockoutFile($auth, $temp_dir), json_encode($state));

    return $state;
}

function clearLoginFailures(array $auth, $temp_dir) {
    @unlink(lockoutFile($auth, $temp_dir));
}

function lockoutRemaining(array $state) {
    return max(0, (int) ($state['until'] ?? 0) - time());
}

function isLoggedIn(array $auth) {
    if (empty($_SESSION['fm_auth'])) {
        return false;
    }
    if (($_SESSION['fm_agent'] ?? '') !== agentFingerprint()) {
        return false;
    }
    if ($auth['timeout'] > 0 && (time() - (int) ($_SESSION['fm_seen'] ?? 0)) > $auth['timeout']) {
        return false;
    }

    $_SESSION['fm_seen'] = time();
    return true;
}

function doLogout() {
    $_SESSION = [];

    if (ini_get('session.use_cookies')) {
        $p = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
    }
    session_destroy();
}

function renderLoginPage($error = '', $locked_for = 0) {
    $csrf = csrfToken();
    ?>
<!DOCTYPE html>
<html lang="pt-BR" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Acesso restrito</title>
<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-regular-rounded/css/uicons-regular-rounded.css">
<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-solid-rounded/css/uicons-solid-rounded.css">
<style>
:root {
    --bg: #f4f6fb; --surface: #ffffff; --surface-alt: #f8fafc; --border: #e4e8f0;
    --text: #16202c; --text-soft: #64748b; --accent: #4f46e5; --accent-soft: #eef2ff;
    --accent-text: #ffffff; --red: #dc2626; --red-soft: #fdecec; --amber: #d97706;
    --amber-soft: #fff5e6; --violet: #7c3aed; --shadow: 0 20px 50px rgba(15, 23, 42, .14);
}
[data-theme="dark"] {
    --bg: #0e131b; --surface: #161d28; --surface-alt: #1c2431; --border: #27313f;
    --text: #e8edf5; --text-soft: #94a3b8; --accent: #7c83ff; --accent-soft: #212a45;
    --accent-text: #0e131b; --red: #f87171; --red-soft: #2c1a1c; --amber: #fbbf24;
    --amber-soft: #2c2313; --violet: #a78bfa; --shadow: 0 20px 50px rgba(0, 0, 0, .55);
}
* { box-sizing: border-box; }
body {
    margin: 0; min-height: 100vh;
    display: grid; place-items: center;
    padding: 24px;
    background: var(--bg);
    color: var(--text);
    font-family: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
    font-size: 14px;
}
body::before {
    content: ''; position: fixed; inset: 0; z-index: 0;
    background:
        radial-gradient(680px 380px at 12% -8%, var(--accent-soft), transparent 70%),
        radial-gradient(620px 340px at 108% 108%, var(--accent-soft), transparent 70%);
    opacity: .85; pointer-events: none;
}
[class^="fi-"], [class*=" fi-"] { display: inline-flex; line-height: 0; vertical-align: -.14em; }
.login {
    position: relative; z-index: 1;
    width: 100%; max-width: 396px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 18px;
    box-shadow: var(--shadow);
    overflow: hidden;
}
.login-head { padding: 30px 30px 22px; text-align: center; }
.login-mark {
    width: 56px; height: 56px; margin: 0 auto 14px;
    display: grid; place-items: center;
    border-radius: 17px;
    background: linear-gradient(135deg, var(--accent), var(--violet));
    color: #fff; font-size: 24px;
    box-shadow: 0 10px 24px rgba(79, 70, 229, .35);
}
.login-head h1 { margin: 0; font-size: 19px; font-weight: 650; letter-spacing: -.2px; }
.login-head p  { margin: 6px 0 0; font-size: 13px; color: var(--text-soft); }
.login-body { padding: 0 30px 26px; }
.field { margin-bottom: 15px; }
.field label {
    display: block; margin-bottom: 6px;
    font-size: 11.5px; font-weight: 650; letter-spacing: .5px;
    text-transform: uppercase; color: var(--text-soft);
}
.control { position: relative; }
.control > .fi {
    position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
    color: var(--text-soft); font-size: 14px; pointer-events: none;
}
.control input {
    width: 100%; padding: 12px 14px 12px 40px;
    border: 1px solid var(--border); border-radius: 11px;
    background: var(--surface-alt); color: var(--text);
    font-size: 14px; font-family: inherit;
    transition: border-color .18s, box-shadow .18s, background .18s;
}
.control input:focus {
    outline: none; background: var(--surface);
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.control input:disabled { opacity: .6; cursor: not-allowed; }
.peek {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    width: 30px; height: 30px; display: grid; place-items: center;
    border: none; border-radius: 8px; background: transparent;
    color: var(--text-soft); font-size: 13px; cursor: pointer;
}
.peek:hover { color: var(--accent); background: var(--accent-soft); }
.submit {
    width: 100%; margin-top: 6px; padding: 13px 18px;
    display: inline-flex; align-items: center; justify-content: center; gap: 9px;
    border: none; border-radius: 11px;
    background: var(--accent); color: var(--accent-text);
    font-size: 14px; font-weight: 620; font-family: inherit;
    cursor: pointer;
    transition: filter .18s, transform .12s, box-shadow .2s;
}
.submit:hover:not(:disabled) { box-shadow: 0 10px 24px rgba(79, 70, 229, .32); filter: brightness(1.05); }
.submit:active:not(:disabled) { transform: translateY(1px); }
.submit:disabled { opacity: .55; cursor: not-allowed; }
.alert {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 12px 14px; margin-bottom: 17px;
    border-radius: 11px; font-size: 13px;
    background: var(--red-soft); border: 1px solid var(--red); color: var(--red);
}
.alert.warn { background: var(--amber-soft); border-color: var(--amber); color: var(--amber); }
.alert .fi { font-size: 15px; margin-top: 1px; }
.login-foot {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 14px 30px;
    border-top: 1px solid var(--border);
    background: var(--surface-alt);
    color: var(--text-soft); font-size: 12px;
}
.theme-btn {
    position: fixed; top: 18px; right: 18px; z-index: 2;
    width: 38px; height: 38px; display: grid; place-items: center;
    border: 1px solid var(--border); border-radius: 11px;
    background: var(--surface); color: var(--text);
    cursor: pointer; font-size: 14px;
}
</style>
</head>
<body>
<button type="button" class="theme-btn" id="theme-toggle" title="Alternar tema"><i class="fi fi-rr-moon"></i></button>

<main class="login">
    <div class="login-head">
        <div class="login-mark"><i class="fi fi-sr-lock"></i></div>
        <h1>Acesso restrito</h1>
        <p>Entre com suas credenciais para abrir o gerenciador</p>
    </div>

    <form method="post" class="login-body" autocomplete="off">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">

        <?php if ($locked_for > 0): ?>
        <div class="alert warn">
            <i class="fi fi-rr-triangle-warning"></i>
            <div>Muitas tentativas erradas. Novo acesso liberado em
                <b><span id="countdown"><?= (int) ceil($locked_for / 60) ?></span> minuto(s)</b>.
            </div>
        </div>
        <?php elseif ($error !== ''): ?>
        <div class="alert">
            <i class="fi fi-rr-cross-circle"></i>
            <div><?= e($error) ?></div>
        </div>
        <?php endif; ?>

        <div class="field">
            <label for="login_user">Usuário</label>
            <div class="control">
                <i class="fi fi-rr-user"></i>
                <input type="text" id="login_user" name="login_user" placeholder="seu usuário"
                       autocomplete="username" required autofocus
                       <?= $locked_for > 0 ? 'disabled' : '' ?>>
            </div>
        </div>

        <div class="field">
            <label for="login_pass">Senha</label>
            <div class="control">
                <i class="fi fi-rr-key"></i>
                <input type="password" id="login_pass" name="login_pass" placeholder="sua senha"
                       autocomplete="current-password" required
                       <?= $locked_for > 0 ? 'disabled' : '' ?>>
                <button type="button" class="peek" id="peek" title="Mostrar senha" tabindex="-1">
                    <i class="fi fi-rr-eye"></i>
                </button>
            </div>
        </div>

        <button type="submit" class="submit" <?= $locked_for > 0 ? 'disabled' : '' ?>>
            <i class="fi fi-rr-sign-in-alt"></i> Entrar
        </button>
    </form>

    <div class="login-foot">
        <i class="fi fi-rr-shield-check"></i> Sessão protegida &middot; credenciais em bcrypt
    </div>
</main>

<script>
(function () {
    var toggle = document.getElementById('theme-toggle');

    function applyTheme(theme) {
        document.documentElement.setAttribute('data-theme', theme);
        toggle.innerHTML = theme === 'dark' ? '<i class="fi fi-rr-sun"></i>' : '<i class="fi fi-rr-moon"></i>';
        try { localStorage.setItem('fm-theme', theme); } catch (err) {}
    }

    var saved = null;
    try { saved = localStorage.getItem('fm-theme'); } catch (err) {}
    applyTheme(saved || (window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));

    toggle.addEventListener('click', function () {
        applyTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
    });

    var peek = document.getElementById('peek');
    var pass = document.getElementById('login_pass');
    if (peek && pass) {
        peek.addEventListener('click', function () {
            var showing = pass.type === 'text';
            pass.type = showing ? 'password' : 'text';
            peek.innerHTML = showing ? '<i class="fi fi-rr-eye"></i>' : '<i class="fi fi-rr-eye-crossed"></i>';
            pass.focus();
        });
    }

    var left = <?= (int) $locked_for ?>;
    if (left > 0) {
        setTimeout(function () { location.reload(); }, left * 1000 + 1200);
    }
})();
</script>
</body>
</html>
    <?php
    exit;
}

startSecureSession($AUTH);

if (isset($_GET['logout'])) {
    if (!empty($_SESSION['fm_csrf']) && hash_equals($_SESSION['fm_csrf'], (string) $_GET['logout'])) {
        doLogout();
    }
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}

if (!isLoggedIn($AUTH)) {
    $login_error = '';
    $state       = loginState($AUTH, $CONFIG['temp_dir']);

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_user'])) {
        if (lockoutRemaining($state) > 0) {
            $login_error = 'Acesso bloqueado temporariamente.';
        } elseif (!csrfValid()) {
            $login_error = 'Sessão expirada. Tente novamente.';
        } else {
            $user_ok = password_verify((string) $_POST['login_user'], $AUTH['user_hash']);
            $pass_ok = password_verify((string) ($_POST['login_pass'] ?? ''), $AUTH['pass_hash']);

            if ($user_ok && $pass_ok) {
                clearLoginFailures($AUTH, $CONFIG['temp_dir']);
                session_regenerate_id(true);

                $_SESSION['fm_auth']  = true;
                $_SESSION['fm_user']  = substr(trim((string) $_POST['login_user']), 0, 64);
                $_SESSION['fm_seen']  = time();
                $_SESSION['fm_agent'] = agentFingerprint();
                $_SESSION['fm_csrf']  = bin2hex(random_bytes(32));

                header('Location: ' . $_SERVER['REQUEST_URI']);
                exit;
            }

            $state = registerLoginFailure($AUTH, $CONFIG['temp_dir']);
            $left  = max(0, $AUTH['max_attempts'] - $state['count']);

            $login_error = 'Usuário ou senha inválidos.'
                . ($left > 0 ? ' Tentativas restantes: ' . $left . '.' : '');
        }
    }

    renderLoginPage($login_error, lockoutRemaining($state));
}

function sendDownloadHeaders($filename, $length, $mime = 'application/octet-stream') {
    $safe = str_replace(['"', "\r", "\n"], '', $filename);
    header('Content-Description: File Transfer');
    header('Content-Type: ' . $mime);
    header('Content-Disposition: attachment; filename="' . $safe . '"; filename*=UTF-8\'\'' . rawurlencode($filename));
    header('Content-Length: ' . $length);
    header('Cache-Control: no-cache, must-revalidate');
    header('Pragma: public');
}

function streamFile($path, $filename, $mime = 'application/octet-stream', $delete_after = false) {
    while (ob_get_level()) {
        ob_end_clean();
    }
    sendDownloadHeaders($filename, filesize($path), $mime);
    readfile($path);
    if ($delete_after) {
        @unlink($path);
    }
    exit;
}

function zipAddPath(ZipArchive $zip, $real_path, $entry_name) {
    if (is_file($real_path)) {
        return $zip->addFile($real_path, $entry_name) ? 1 : 0;
    }

    $count    = $zip->addEmptyDir($entry_name) ? 1 : 0;
    $prefix   = strlen($real_path) + 1;
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($real_path, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $file) {
        $sub   = str_replace(DIRECTORY_SEPARATOR, '/', substr($file->getPathname(), $prefix));
        $entry = $entry_name . '/' . $sub;
        $ok    = $file->isDir() ? $zip->addEmptyDir($entry) : $zip->addFile($file->getPathname(), $entry);
        if ($ok) $count++;
    }

    return $count;
}

function buildZip(array $items, $zip_path, $current_dir, $base_dir) {
    if (empty($items)) {
        return ['error' => 'Nenhum item selecionado.'];
    }
    if (!class_exists('ZipArchive')) {
        return ['error' => 'Extensão ZIP não disponível no servidor.'];
    }

    $zip = new ZipArchive();
    if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
        return ['error' => 'Não foi possível criar o arquivo ZIP.'];
    }

    $count    = 0;
    $warnings = [];

    foreach ($items as $item) {
        $item_path = $current_dir . $item;

        if (!file_exists($item_path)) {
            $warnings[] = 'Item não encontrado: ' . $item;
            continue;
        }
        if (!insideBase($item_path, $base_dir)) {
            $warnings[] = 'Acesso negado: ' . $item;
            continue;
        }
        $count += zipAddPath($zip, realpath($item_path), $item);
    }

    $zip->close();

    if ($count === 0) {
        @unlink($zip_path);
        return ['error' => 'Nenhum item pôde ser adicionado ao ZIP.'];
    }

    return ['count' => $count, 'warnings' => $warnings];
}

function actionCompress(array $items, array $ctx) {
    $zip_name = 'backup_' . date('Y-m-d_H-i-s') . '_' . substr(md5(uniqid('', true)), 0, 8) . '.zip';
    $zip_path = $ctx['temp_dir'] . $zip_name;

    $zip = buildZip($items, $zip_path, $ctx['current_dir'], $ctx['base_dir']);
    if (isset($zip['error'])) {
        return result([$zip['error']]);
    }

    $final_path = $ctx['current_dir'] . $zip_name;
    if (!@rename($zip_path, $final_path)) {
        @unlink($zip_path);
        return result(['Erro ao mover o arquivo ZIP para o diretório atual. Verifique as permissões.']);
    }
    @chmod($final_path, 0644);

    $msg = 'Compactação concluída: <strong>' . e($zip_name) . '</strong><br>'
         . 'Itens adicionados: ' . (int) $zip['count'] . ' &middot; Tamanho: ' . formatSize(filesize($final_path));

    if (!empty($zip['warnings'])) {
        $msg .= '<br><small>Avisos: ' . e(implode('; ', $zip['warnings'])) . '</small>';
    }

    return result([], $msg);
}

function actionDownloadSelected(array $items, array $ctx) {
    if (empty($items)) {
        return result(['Nenhum item selecionado.']);
    }

    if (count($items) === 1) {
        $path = securePath(($ctx['rel_dir'] ? $ctx['rel_dir'] . '/' : '') . $items[0], $ctx['base_dir']);
        if ($path !== false && is_file($path)) {
            streamFile($path, $items[0]);
        }
    }

    $zip_name = count($items) === 1
        ? $items[0] . '.zip'
        : 'arquivos_selecionados_' . date('Y-m-d_H-i-s') . '.zip';
    $zip_path = $ctx['temp_dir'] . 'download_' . uniqid('', true) . '.zip';

    $zip = buildZip($items, $zip_path, $ctx['current_dir'], $ctx['base_dir']);
    if (isset($zip['error'])) {
        return result([$zip['error']]);
    }

    streamFile($zip_path, $zip_name, 'application/zip', true);
}

function actionDownload($name, array $ctx) {
    $filename = basename($name);
    $filepath = securePath(($ctx['rel_dir'] ? $ctx['rel_dir'] . '/' : '') . $filename, $ctx['base_dir']);

    if ($filepath === false || !is_file($filepath)) {
        return result(['Arquivo não encontrado ou acesso negado.']);
    }
    streamFile($filepath, $filename);
}

function actionView($name, array $ctx) {
    $filename = basename($name);
    $filepath = securePath(($ctx['rel_dir'] ? $ctx['rel_dir'] . '/' : '') . $filename, $ctx['base_dir']);

    if ($filepath === false || !is_file($filepath) || !in_array(fileExt($filename), $ctx['text_extensions'], true)) {
        return result(['Arquivo não encontrado, acesso negado ou não é um arquivo de texto.']);
    }

    while (ob_get_level()) {
        ob_end_clean();
    }
    header('Content-Type: text/plain; charset=utf-8');
    header('Content-Disposition: inline; filename="' . str_replace('"', '', $filename) . '"');

    if (filesize($filepath) > $ctx['max_view_size']) {
        echo 'Arquivo muito grande para visualização. Faça o download.';
        exit;
    }
    readfile($filepath);
    exit;
}

function actionCopy(array $post, array $ctx) {
    $source_name = basename($post['source'] ?? '');
    $target_name = basename($post['target'] ?? '');
    $target_dir  = trim($post['target_dir'] ?? '', '/\\');

    if ($source_name === '' || $target_name === '') {
        return result(['Nome de arquivo inválido.']);
    }

    $source_path   = securePath(($ctx['rel_dir'] ? $ctx['rel_dir'] . '/' : '') . $source_name, $ctx['base_dir']);
    $dest_dir_path = securePath($target_dir !== '' ? $target_dir : $ctx['rel_dir'], $ctx['base_dir']);

    if ($source_path === false || !is_file($source_path) || $dest_dir_path === false || !is_dir($dest_dir_path)) {
        return result(['Origem ou destino inválido.']);
    }

    $target_path = rtrim($dest_dir_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $target_name;

    if (file_exists($target_path)) {
        return result(['O arquivo de destino já existe: ' . $target_name]);
    }
    if (!@copy($source_path, $target_path)) {
        return result(['Erro ao copiar o arquivo. Verifique as permissões.']);
    }
    @chmod($target_path, 0644);

    return result([], 'Arquivo copiado: <strong>' . e($source_name) . '</strong> &rarr; <strong>' . e($target_name) . '</strong>');
}

function actionMkdir($name, array $ctx) {
    $folder_name = basename(trim((string) $name));

    if ($folder_name === '' || $folder_name === '.' || $folder_name === '..') {
        return result(['Nome de pasta inválido.']);
    }

    $new_folder = $ctx['current_dir'] . $folder_name;
    if (file_exists($new_folder)) {
        return result(['Esta pasta já existe.']);
    }
    if (!@mkdir($new_folder, 0755, true)) {
        return result(['Erro ao criar a pasta. Verifique as permissões.']);
    }

    return result([], 'Pasta criada: <strong>' . e($folder_name) . '</strong>');
}

function actionDelete($name, array $ctx) {
    $item_name = basename((string) $name);
    $item_path = securePath(($ctx['rel_dir'] ? $ctx['rel_dir'] . '/' : '') . $item_name, $ctx['base_dir']);

    if ($item_path === false || rtrim($item_path, DIRECTORY_SEPARATOR) === rtrim($ctx['base_dir'], DIRECTORY_SEPARATOR)) {
        return result(['Operação não permitida.']);
    }

    if (is_dir($item_path)) {
        if (count(scandir($item_path)) > 2) {
            return result(['A pasta não está vazia. Remova os arquivos primeiro.']);
        }
        return @rmdir($item_path)
            ? result([], 'Pasta removida: <strong>' . e($item_name) . '</strong>')
            : result(['Erro ao remover a pasta.']);
    }

    if (is_file($item_path)) {
        return @unlink($item_path)
            ? result([], 'Arquivo removido: <strong>' . e($item_name) . '</strong>')
            : result(['Erro ao remover o arquivo.']);
    }

    return result(['Item não encontrado.']);
}

function uploadTarget($upload_dir, array $ctx) {
    $dest = securePath(trim((string) $upload_dir, '/\\'), $ctx['base_dir']);
    return ($dest === false || !is_dir($dest)) ? $ctx['base_dir'] : $dest;
}

function actionUrlUpload(array $post, array $ctx) {
    $url  = trim($post['url'] ?? '');
    $dest = uploadTarget($post['upload_dir'] ?? '', $ctx);

    if ($url === '') {
        return result(['A URL não pode estar vazia.']);
    }
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return result(['URL inválida.']);
    }
    if (!function_exists('curl_init')) {
        return result(['cURL não está disponível no servidor.']);
    }

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; FileManager/1.0)',
    ]);

    $content      = curl_exec($ch);
    $http_code    = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    $curl_error   = curl_error($ch);
    curl_close($ch);

    if ($curl_error !== '')   return result(['Erro cURL: ' . $curl_error]);
    if ($http_code !== 200)   return result(['Erro HTTP: ' . $http_code]);
    if ($content === '' || $content === false) return result(['Arquivo vazio ou não pôde ser baixado.']);
    if (strlen($content) > $ctx['max_size']) {
        return result(['Arquivo muito grande. Máximo: ' . formatSize($ctx['max_size'])]);
    }

    $path_parts    = pathinfo(parse_url($url, PHP_URL_PATH) ?? '');
    $original_name = $path_parts['filename'] ?? 'arquivo';
    $file_ext      = strtolower($path_parts['extension'] ?? '');

    if ($file_ext === '' && $content_type) {
        $mime_map = [
            'application/zip'  => 'zip',
            'application/pdf'  => 'pdf',
            'image/jpeg'       => 'jpg',
            'image/png'        => 'png',
            'text/plain'       => 'txt',
            'text/html'        => 'html',
            'text/css'         => 'css',
            'application/json' => 'txt',
        ];
        foreach ($mime_map as $mime => $ext) {
            if (strpos($content_type, $mime) !== false) {
                $file_ext = $ext;
                break;
            }
        }
    }

    if ($file_ext === '') {
        $file_ext = 'bin';
    }
    if (!in_array($file_ext, $ctx['allowed_types'], true)) {
        return result(['Tipo de arquivo não permitido: ' . $file_ext]);
    }

    $filename    = preg_replace('/[^A-Za-z0-9._-]/', '_', $original_name) . '_' . time() . '.' . $file_ext;
    $target_path = $dest . $filename;

    if (@file_put_contents($target_path, $content) === false) {
        return result(['Erro ao salvar o arquivo.']);
    }
    @chmod($target_path, 0644);

    return result([], 'Arquivo baixado: <strong>' . e($filename) . '</strong><br>'
        . 'Tamanho: ' . formatSize(strlen($content))
        . ' &middot; Destino: /' . e(relativeTo($dest, $ctx['base_dir'])));
}

function actionLocalUpload(array $file, array $post, array $ctx) {
    $dest        = uploadTarget($post['upload_dir'] ?? '', $ctx);
    $file_name   = basename($file['name'] ?? '');
    $target_file = $dest . $file_name;
    $file_type   = fileExt($target_file);

    if (!is_uploaded_file($file['tmp_name'] ?? '')) {
        return result(['Arquivo inválido ou upload interrompido.']);
    }
    if ($file['size'] > $ctx['max_size']) {
        return result(['Arquivo muito grande. Máximo: ' . formatSize($ctx['max_size'])]);
    }
    if (!in_array($file_type, $ctx['allowed_types'], true)) {
        return result(['Tipo de arquivo não permitido: ' . ($file_type ?: 'desconhecido')]);
    }
    if (file_exists($target_file)) {
        return result(['Já existe um arquivo com este nome no destino.']);
    }
    if (!move_uploaded_file($file['tmp_name'], $target_file)) {
        return result(['Erro ao mover o arquivo enviado.']);
    }
    @chmod($target_file, 0644);

    return result([], 'Upload concluído: <strong>' . e($file_name) . '</strong><br>'
        . 'Tamanho: ' . formatSize($file['size'])
        . ' &middot; Destino: /' . e(relativeTo($dest, $ctx['base_dir'])));
}

$errors  = [];
$success = '';

$rel_dir     = isset($_GET['dir']) ? trim($_GET['dir'], '/\\') : '';
$current_dir = $CONFIG['base_dir'];

if ($rel_dir !== '') {
    $test_dir = securePath($rel_dir, $CONFIG['base_dir']);
    if ($test_dir !== false && is_dir($test_dir)) {
        $current_dir = $test_dir;
    } else {
        $errors[] = 'Diretório inválido ou acesso negado.';
        $rel_dir  = '';
    }
}

$display_rel = relativeTo($current_dir, $CONFIG['base_dir']);
$ctx = $CONFIG + ['current_dir' => $current_dir, 'rel_dir' => $rel_dir];

$outcome = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrfValid()) {
    $errors[] = 'Sessão expirada ou requisição inválida. Recarregue a página e tente novamente.';
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $action = $_POST['action'] ?? '';

    if (isset($_POST['delete_item'])) {
        $outcome = actionDelete($_POST['delete_item'], $ctx);
    } else {
        switch ($action) {
            case 'compress':
                $outcome = actionCompress(postedItems(), $ctx);
                break;
            case 'download_selected':
                $outcome = actionDownloadSelected(postedItems(), $ctx);
                break;
            case 'copy':
                $outcome = actionCopy($_POST, $ctx);
                break;
            case 'mkdir':
                $outcome = actionMkdir($_POST['folder_name'] ?? '', $ctx);
                break;
            case 'delete':
                $outcome = actionDelete($_POST['item'] ?? '', $ctx);
                break;
            case 'upload_url':
                $outcome = actionUrlUpload($_POST, $ctx);
                break;
            default:
                if (!empty($_FILES['fileToUpload']['name'])) {
                    $outcome = actionLocalUpload($_FILES['fileToUpload'], $_POST, $ctx);
                }
        }
    }
} elseif (!empty($_GET['download'])) {
    $outcome = actionDownload($_GET['download'], $ctx);
} elseif (!empty($_GET['view'])) {
    $outcome = actionView($_GET['view'], $ctx);
}

if (is_array($outcome)) {
    $errors  = array_merge($errors, $outcome['errors']);
    $success = $outcome['success'];
}

function listDirectory($current_dir, $temp_dir) {
    $entries = @scandir($current_dir);
    if ($entries === false) {
        return false;
    }

    $dirs  = [];
    $files = [];

    foreach ($entries as $entry) {
        if ($entry === '.' || $entry === '..') continue;
        if ($entry === 'temp' && realpath($current_dir . $entry) === realpath($temp_dir)) continue;

        $full = $current_dir . $entry;
        $row  = [
            'name'     => $entry,
            'is_dir'   => is_dir($full),
            'size'     => is_dir($full) ? 0 : (int) @filesize($full),
            'modified' => (int) @filemtime($full),
        ];
        if ($row['is_dir']) {
            $dirs[] = $row;
        } else {
            $files[] = $row;
        }
    }

    $by_name = function ($a, $b) {
        return strnatcasecmp($a['name'], $b['name']);
    };
    usort($dirs, $by_name);
    usort($files, $by_name);

    return ['dirs' => $dirs, 'files' => $files, 'all' => array_merge($dirs, $files)];
}

function breadcrumbSegments($display_rel) {
    $segments = [];
    $acc      = '';

    foreach (explode('/', trim($display_rel, '/')) as $part) {
        if ($part === '') continue;
        $acc .= ($acc === '' ? '' : '/') . $part;
        $segments[] = ['label' => $part, 'path' => $acc];
    }

    return $segments;
}

$listing     = listDirectory($current_dir, $CONFIG['temp_dir']);
$crumbs      = breadcrumbSegments($display_rel);
$parent_rel  = count($crumbs) > 1 ? $crumbs[count($crumbs) - 2]['path'] : '';
$total_dirs  = $listing ? count($listing['dirs']) : 0;
$total_files = $listing ? count($listing['files']) : 0;
$total_size  = 0;
if ($listing) {
    foreach ($listing['files'] as $file) {
        $total_size += $file['size'];
    }
}
?>
<!DOCTYPE html>
<html lang="pt-BR" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gerenciador de Arquivos</title>

<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-regular-rounded/css/uicons-regular-rounded.css">
<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-solid-rounded/css/uicons-solid-rounded.css">

<style>
:root {
    --bg:          #f4f6fb;
    --surface:     #ffffff;
    --surface-alt: #f8fafc;
    --border:      #e4e8f0;
    --text:        #16202c;
    --text-soft:   #64748b;
    --accent:      #4f46e5;
    --accent-soft: #eef2ff;
    --accent-text: #ffffff;
    --green:       #16a34a;
    --green-soft:  #e9f9ef;
    --red:         #dc2626;
    --red-soft:    #fdecec;
    --amber:       #d97706;
    --amber-soft:  #fff5e6;
    --blue:        #0284c7;
    --blue-soft:   #e6f4fd;
    --violet:      #7c3aed;
    --shadow-sm:   0 1px 2px rgba(15, 23, 42, .06);
    --shadow-md:   0 6px 20px rgba(15, 23, 42, .08);
    --shadow-lg:   0 18px 45px rgba(15, 23, 42, .16);
    --radius:      14px;
    --radius-sm:   9px;
}
[data-theme="dark"] {
    --bg:          #0e131b;
    --surface:     #161d28;
    --surface-alt: #1c2431;
    --border:      #27313f;
    --text:        #e8edf5;
    --text-soft:   #94a3b8;
    --accent:      #7c83ff;
    --accent-soft: #212a45;
    --accent-text: #0e131b;
    --green:       #4ade80;
    --green-soft:  #16281e;
    --red:         #f87171;
    --red-soft:    #2c1a1c;
    --amber:       #fbbf24;
    --amber-soft:  #2c2313;
    --blue:        #38bdf8;
    --blue-soft:   #12283a;
    --violet:      #a78bfa;
    --shadow-sm:   0 1px 2px rgba(0, 0, 0, .4);
    --shadow-md:   0 6px 20px rgba(0, 0, 0, .45);
    --shadow-lg:   0 18px 45px rgba(0, 0, 0, .55);
}

* { box-sizing: border-box; }

body {
    margin: 0;
    padding: 28px 20px 120px;
    background: var(--bg);
    color: var(--text);
    font-family: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1180px; margin: 0 auto; }

[class^="fi-"], [class*=" fi-"] { display: inline-flex; line-height: 0; vertical-align: -.14em; }

.topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 22px;
}
.brand { display: flex; align-items: center; gap: 13px; }
.brand-icon {
    width: 44px; height: 44px;
    display: grid; place-items: center;
    border-radius: 13px;
    background: linear-gradient(135deg, var(--accent), var(--violet));
    color: #fff;
    font-size: 20px;
    box-shadow: var(--shadow-md);
}
.brand h1 { margin: 0; font-size: 19px; font-weight: 650; letter-spacing: -.2px; }
.brand p  { margin: 2px 0 0; font-size: 12.5px; color: var(--text-soft); }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.pill {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 13px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 12.5px;
    color: var(--text-soft);
    box-shadow: var(--shadow-sm);
    max-width: 340px;
}
.pill code {
    font-family: ui-monospace, 'Cascadia Code', Consolas, monospace;
    color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pill-user {
    color: var(--text);
    font-weight: 550;
    background: var(--accent-soft);
    border-color: transparent;
}
.pill-user .fi { color: var(--accent); }
.btn-logout:hover { border-color: var(--red); color: var(--red); background: var(--red-soft); }

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    margin-bottom: 20px;
    overflow: hidden;
}
.card-head {
    display: flex; align-items: center; gap: 10px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-alt);
}
.card-head h2 { margin: 0; font-size: 14.5px; font-weight: 620; }
.card-head .fi-rr-folder-open,
.card-head [class^="fi-"] { color: var(--accent); font-size: 15px; }
.card-body { padding: 20px; }

.alert {
    display: flex; gap: 12px;
    padding: 14px 16px;
    border-radius: var(--radius-sm);
    margin-bottom: 16px;
    border: 1px solid transparent;
    font-size: 13.5px;
}
.alert .fi { font-size: 17px; margin-top: 1px; }
.alert ul { margin: 6px 0 0; padding-left: 18px; }
.alert-error   { background: var(--red-soft);   border-color: var(--red);   color: var(--red); }
.alert-success { background: var(--green-soft); border-color: var(--green); color: var(--green); }
.alert strong  { color: inherit; }
.alert-body    { color: var(--text); }
.alert-error .alert-title, .alert-success .alert-title { font-weight: 640; display: block; margin-bottom: 2px; }

.tabs {
    display: inline-flex;
    gap: 4px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    margin-bottom: 20px;
    box-shadow: var(--shadow-sm);
    flex-wrap: wrap;
}
.tab {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 9px 16px;
    border: none;
    background: transparent;
    color: var(--text-soft);
    border-radius: 9px;
    font-size: 13.5px;
    font-weight: 550;
    font-family: inherit;
    cursor: pointer;
    transition: background .18s, color .18s;
}
.tab:hover  { background: var(--surface-alt); color: var(--text); }
.tab.active { background: var(--accent); color: var(--accent-text); box-shadow: var(--shadow-sm); }
.tab-panel  { display: none; }
.tab-panel.active { display: block; animation: fade .22s ease; }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 9px 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font-size: 13.5px;
    font-weight: 550;
    font-family: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: transform .12s, box-shadow .18s, background .18s, border-color .18s;
}
.btn:hover  { border-color: var(--accent); color: var(--accent); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.btn-primary:hover { background: var(--accent); color: var(--accent-text); box-shadow: var(--shadow-md); }
.btn-ghost { background: transparent; }
.btn-sm { padding: 7px 12px; font-size: 12.5px; }
.btn-icon {
    width: 34px; height: 34px;
    padding: 0;
    justify-content: center;
    border-radius: 9px;
}

.row-actions { display: flex; gap: 5px; justify-content: flex-end; }
.act {
    width: 31px; height: 31px;
    display: grid; place-items: center;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    color: var(--text-soft);
    font-size: 13px;
    cursor: pointer;
    text-decoration: none;
    transition: all .16s;
}
.act:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.act-download:hover { color: var(--green); border-color: var(--green); background: var(--green-soft); }
.act-view:hover     { color: var(--blue);  border-color: var(--blue);  background: var(--blue-soft); }
.act-copy:hover     { color: var(--amber); border-color: var(--amber); background: var(--amber-soft); }
.act-delete:hover   { color: var(--red);   border-color: var(--red);   background: var(--red-soft); }

.field { margin-bottom: 16px; }
.field label {
    display: block;
    margin-bottom: 6px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-soft);
    text-transform: uppercase;
    letter-spacing: .4px;
}
input[type="text"], input[type="url"], input[type="file"], select {
    width: 100%;
    padding: 10px 13px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text);
    font-size: 13.5px;
    font-family: inherit;
    transition: border-color .18s, box-shadow .18s;
}
input[type="text"]:focus, input[type="url"]:focus, select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    background: var(--surface);
}
.hint { display: block; margin-top: 6px; font-size: 12px; color: var(--text-soft); }

.toolbar {
    display: flex; align-items: center; gap: 10px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--border);
}
.crumbs {
    display: flex; align-items: center; gap: 4px;
    flex-wrap: wrap;
    flex: 1;
    min-width: 220px;
    font-size: 13px;
}
.crumbs a {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 10px;
    border-radius: 8px;
    color: var(--text-soft);
    text-decoration: none;
    transition: background .16s, color .16s;
}
.crumbs a:hover { background: var(--accent-soft); color: var(--accent); }
.crumbs a.current { color: var(--text); font-weight: 600; background: var(--surface-alt); }
.crumbs .sep { color: var(--text-soft); opacity: .5; font-size: 11px; }

.search-box { position: relative; }
.search-box .fi {
    position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
    color: var(--text-soft); font-size: 13px; pointer-events: none;
}
.search-box input { width: 210px; padding-left: 32px; }

.table-scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
thead th {
    padding: 11px 16px;
    background: var(--surface-alt);
    border-bottom: 1px solid var(--border);
    text-align: left;
    font-size: 11.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text-soft);
    white-space: nowrap;
    user-select: none;
}
thead th.sortable { cursor: pointer; }
thead th.sortable:hover { color: var(--accent); }
thead th .fi { font-size: 10px; opacity: .45; margin-left: 4px; }
thead th.sorted .fi { opacity: 1; color: var(--accent); }
tbody td {
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}
tbody tr { transition: background .14s; }
tbody tr:hover { background: var(--surface-alt); }
tbody tr.selected { background: var(--accent-soft); }
tbody tr:last-child td { border-bottom: none; }
.col-check { width: 42px; text-align: center; }
.col-size, .col-date { white-space: nowrap; color: var(--text-soft); font-size: 13px; }
.col-actions { width: 1%; }

.item {
    display: flex; align-items: center; gap: 11px;
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
    min-width: 0;
}
.item:hover .item-name { color: var(--accent); }
.item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46vw; }
.item-icon {
    width: 33px; height: 33px;
    display: grid; place-items: center;
    border-radius: 9px;
    font-size: 14px;
    flex-shrink: 0;
}
.ico-folder  { background: var(--amber-soft); color: var(--amber); }
.ico-archive { background: var(--accent-soft); color: var(--violet); }
.ico-image   { background: var(--blue-soft); color: var(--blue); }
.ico-pdf     { background: var(--red-soft); color: var(--red); }
.ico-doc     { background: var(--blue-soft); color: var(--blue); }
.ico-sheet   { background: var(--green-soft); color: var(--green); }
.ico-code    { background: var(--accent-soft); color: var(--accent); }
.ico-media   { background: var(--red-soft); color: var(--red); }
.ico-text    { background: var(--surface-alt); color: var(--text-soft); }
.ico-file    { background: var(--surface-alt); color: var(--text-soft); }

input[type="checkbox"] {
    width: 16px; height: 16px;
    accent-color: var(--accent);
    cursor: pointer;
    margin: 0;
}

.empty { padding: 52px 20px; text-align: center; color: var(--text-soft); }
.empty .fi { font-size: 34px; opacity: .35; display: block; margin: 0 auto 10px; }

.table-foot {
    display: flex; align-items: center; gap: 14px;
    flex-wrap: wrap;
    padding: 12px 20px;
    border-top: 1px solid var(--border);
    background: var(--surface-alt);
    font-size: 12.5px;
    color: var(--text-soft);
}
.table-foot span { display: inline-flex; align-items: center; gap: 6px; }
.table-foot span[hidden] { display: none; }

.selection-bar {
    position: fixed;
    left: 50%; bottom: 24px;
    transform: translate(-50%, 130%);
    display: flex; align-items: center; gap: 14px;
    flex-wrap: wrap;
    padding: 12px 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 15px;
    box-shadow: var(--shadow-lg);
    transition: transform .28s cubic-bezier(.2, .8, .3, 1);
    z-index: 60;
    max-width: calc(100vw - 32px);
}
.selection-bar.active { transform: translate(-50%, 0); }
.selection-count {
    display: inline-flex; align-items: center; gap: 8px;
    font-weight: 600;
    color: var(--text);
    padding-right: 14px;
    border-right: 1px solid var(--border);
}
.selection-count .badge {
    min-width: 24px; height: 24px;
    display: grid; place-items: center;
    padding: 0 7px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-text);
    font-size: 12px;
}

.dropzone {
    position: relative;
    display: grid; place-items: center;
    gap: 6px;
    padding: 34px 20px;
    border: 2px dashed var(--border);
    border-radius: var(--radius);
    background: var(--surface-alt);
    text-align: center;
    cursor: pointer;
    transition: border-color .2s, background .2s;
}
.dropzone:hover, .dropzone.dragging { border-color: var(--accent); background: var(--accent-soft); }
.dropzone .fi { font-size: 30px; color: var(--accent); }
.dropzone strong { font-size: 14px; }
.dropzone input[type="file"] {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    opacity: 0;
    cursor: pointer;
}
.dropzone-name { font-size: 12.5px; color: var(--text-soft); }

.modal {
    position: fixed; inset: 0;
    display: none;
    place-items: center;
    background: rgba(9, 14, 22, .55);
    backdrop-filter: blur(3px);
    z-index: 90;
    padding: 20px;
}
.modal.open { display: grid; }
.modal-card {
    width: 100%;
    max-width: 420px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    animation: pop .2s ease;
}
@keyframes pop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }
.modal-head {
    display: flex; align-items: center; gap: 10px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border);
}
.modal-head h3 { margin: 0; font-size: 15px; font-weight: 620; }
.modal-head .btn-icon { margin-left: auto; }
.modal-body { padding: 20px; }
.modal-foot {
    display: flex; justify-content: flex-end; gap: 10px;
    padding: 14px 20px;
    border-top: 1px solid var(--border);
    background: var(--surface-alt);
}

.info-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    align-items: start;
    gap: 12px;
}
.info-item {
    display: flex; align-items: flex-start; gap: 11px;
    padding: 13px 15px;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.info-item .fi { color: var(--accent); font-size: 15px; margin-top: 2px; }
.info-item .label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .4px; color: var(--text-soft); }
.info-item .value { font-size: 13px; font-weight: 550; word-break: break-word; }
.info-item.wide { grid-column: 1 / -1; }
.info-item.wide .value {
    font-family: ui-monospace, 'Cascadia Code', Consolas, monospace;
    font-weight: 500;
    font-size: 12.5px;
}
.ok  { color: var(--green); }
.off { color: var(--red); }

.note {
    display: flex; gap: 10px;
    margin-top: 16px;
    padding: 12px 15px;
    border-radius: var(--radius-sm);
    background: var(--amber-soft);
    color: var(--amber);
    font-size: 12.5px;
}
.note .fi { font-size: 15px; }

@media (max-width: 720px) {
    body { padding: 18px 12px 120px; }
    .col-date { display: none; }
    .search-box input { width: 100%; }
    .item-name { max-width: 42vw; }
}
</style>
</head>
<body>
<div class="wrap">

    <header class="topbar">
        <div class="brand">
            <div class="brand-icon"><i class="fi fi-sr-folder"></i></div>
            <div>
                <h1>Gerenciador de Arquivos</h1>
                <p>Navegue, envie, compacte e baixe seus arquivos</p>
            </div>
        </div>
        <div class="topbar-actions">
            <span class="pill" title="Diretório base permitido">
                <i class="fi fi-rr-hdd"></i> <code><?= e($CONFIG['base_dir']) ?></code>
            </span>
            <span class="pill pill-user" title="Sessão encerra após <?= (int) ($AUTH['timeout'] / 60) ?> min sem uso">
                <i class="fi fi-rr-circle-user"></i> <?= e($_SESSION['fm_user'] ?? 'sessão ativa') ?>
            </span>
            <button type="button" class="btn btn-icon" id="theme-toggle" title="Alternar tema">
                <i class="fi fi-rr-moon"></i>
            </button>
            <a class="btn btn-icon btn-logout" href="?logout=<?= e(csrfToken()) ?>" title="Sair">
                <i class="fi fi-rr-sign-out-alt"></i>
            </a>
        </div>
    </header>

    <?php if (!empty($errors)): ?>
    <div class="alert alert-error">
        <i class="fi fi-sr-cross-circle"></i>
        <div>
            <span class="alert-title">Não foi possível concluir</span>
            <ul class="alert-body">
                <?php foreach ($errors as $error): ?>
                <li><?= e($error) ?></li>
                <?php endforeach; ?>
            </ul>
        </div>
    </div>
    <?php endif; ?>

    <?php if ($success !== ''): ?>
    <div class="alert alert-success">
        <i class="fi fi-sr-check-circle"></i>
        <div>
            <span class="alert-title">Tudo certo</span>
            <div class="alert-body"><?= $success ?></div>
        </div>
    </div>
    <?php endif; ?>

    <nav class="tabs">
        <button type="button" class="tab active" data-tab="panel-manager">
            <i class="fi fi-rr-folder-open"></i> Gerenciador
        </button>
        <button type="button" class="tab" data-tab="panel-local">
            <i class="fi fi-rr-cloud-upload"></i> Upload Local
        </button>
        <button type="button" class="tab" data-tab="panel-url">
            <i class="fi fi-rr-link-alt"></i> Upload via URL
        </button>
    </nav>

    <section id="panel-manager" class="tab-panel active">
        <div class="card">

            <div class="toolbar">
                <nav class="crumbs">
                    <a href="?dir=" <?= $display_rel === '' ? 'class="current"' : '' ?>>
                        <i class="fi fi-rr-home"></i> Raiz
                    </a>
                    <?php foreach ($crumbs as $i => $crumb): ?>
                        <span class="sep"><i class="fi fi-rr-angle-right"></i></span>
                        <a href="?dir=<?= rawurlencode($crumb['path']) ?>"
                           <?= $i === count($crumbs) - 1 ? 'class="current"' : '' ?>>
                            <i class="fi fi-rr-folder"></i> <?= e($crumb['label']) ?>
                        </a>
                    <?php endforeach; ?>
                </nav>

                <?php if (!empty($crumbs)): ?>
                <a class="btn btn-sm" href="?dir=<?= rawurlencode($parent_rel) ?>" title="Voltar um nível">
                    <i class="fi fi-rr-arrow-small-left"></i> Voltar
                </a>
                <?php endif; ?>

                <div class="search-box">
                    <i class="fi fi-rr-search"></i>
                    <input type="text" id="filter" placeholder="Filtrar nesta pasta..." autocomplete="off">
                </div>

                <button type="button" class="btn btn-primary btn-sm" id="open-mkdir">
                    <i class="fi fi-rr-add-folder"></i> Nova pasta
                </button>
            </div>

            <form method="post" id="fm">
                <?= csrfField() ?>
                <div class="table-scroll">
                    <table id="file-table">
                        <thead>
                            <tr>
                                <th class="col-check">
                                    <input type="checkbox" id="check-all" title="Selecionar todos">
                                </th>
                                <th class="sortable sorted" data-sort="name">Nome <i class="fi fi-rr-sort-alt"></i></th>
                                <th class="sortable col-size" data-sort="size">Tamanho <i class="fi fi-rr-sort-alt"></i></th>
                                <th class="sortable col-date" data-sort="time">Modificado <i class="fi fi-rr-sort-alt"></i></th>
                                <th class="col-actions">Ações</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php if ($listing === false): ?>
                            <tr>
                                <td colspan="5">
                                    <div class="empty">
                                        <i class="fi fi-rr-shield-check"></i>
                                        Sem permissão para acessar este diretório.
                                    </div>
                                </td>
                            </tr>
                        <?php elseif (empty($listing['all'])): ?>
                            <tr>
                                <td colspan="5">
                                    <div class="empty">
                                        <i class="fi fi-rr-folder-open"></i>
                                        Esta pasta está vazia.
                                    </div>
                                </td>
                            </tr>
                        <?php else: ?>
                            <?php foreach ($listing['all'] as $row):
                                $name   = $row['name'];
                                $is_dir = $row['is_dir'];
                                [$icon, $icon_class] = itemIcon($name, $is_dir);
                                $child_rel  = ($display_rel !== '' ? $display_rel . '/' : '') . $name;
                                $is_text    = !$is_dir && in_array(fileExt($name), $CONFIG['text_extensions'], true);
                            ?>
                            <tr data-name="<?= e(strtolower($name)) ?>"
                                data-size="<?= $row['size'] ?>"
                                data-time="<?= $row['modified'] ?>"
                                data-dir="<?= $is_dir ? 1 : 0 ?>">

                                <td class="col-check">
                                    <input type="checkbox" class="pick" name="items[]" value="<?= e($name) ?>">
                                </td>

                                <td>
                                    <?php if ($is_dir): ?>
                                    <a class="item" href="?dir=<?= rawurlencode($child_rel) ?>">
                                        <span class="item-icon <?= $icon_class ?>"><i class="fi <?= $icon ?>"></i></span>
                                        <span class="item-name"><?= e($name) ?></span>
                                    </a>
                                    <?php else: ?>
                                    <span class="item">
                                        <span class="item-icon <?= $icon_class ?>"><i class="fi <?= $icon ?>"></i></span>
                                        <span class="item-name"><?= e($name) ?></span>
                                    </span>
                                    <?php endif; ?>
                                </td>

                                <td class="col-size"><?= $is_dir ? '&mdash;' : formatSize($row['size']) ?></td>
                                <td class="col-date"><?= $row['modified'] ? date('d/m/Y H:i', $row['modified']) : '&mdash;' ?></td>

                                <td class="col-actions">
                                    <div class="row-actions">
                                        <?php if (!$is_dir): ?>
                                            <a class="act act-download" title="Baixar"
                                               href="?dir=<?= rawurlencode($display_rel) ?>&amp;download=<?= rawurlencode($name) ?>">
                                                <i class="fi fi-rr-download"></i>
                                            </a>
                                            <?php if ($is_text): ?>
                                            <a class="act act-view" title="Visualizar" target="_blank"
                                               href="?dir=<?= rawurlencode($display_rel) ?>&amp;view=<?= rawurlencode($name) ?>">
                                                <i class="fi fi-rr-eye"></i>
                                            </a>
                                            <?php endif; ?>
                                            <button type="button" class="act act-copy" title="Copiar"
                                                    data-copy="<?= e($name) ?>">
                                                <i class="fi fi-rr-copy"></i>
                                            </button>
                                        <?php endif; ?>
                                        <button type="submit" class="act act-delete" title="Excluir"
                                                name="delete_item" value="<?= e($name) ?>"
                                                data-confirm="<?= $is_dir ? 'a pasta' : 'o arquivo' ?> &quot;<?= e($name) ?>&quot;">
                                            <i class="fi fi-rr-trash"></i>
                                        </button>
                                    </div>
                                </td>
                            </tr>
                            <?php endforeach; ?>
                        <?php endif; ?>
                        </tbody>
                    </table>
                </div>
            </form>

            <div class="table-foot">
                <span><i class="fi fi-rr-folder"></i> <?= $total_dirs ?> pasta<?= $total_dirs === 1 ? '' : 's' ?></span>
                <span><i class="fi fi-rr-file"></i> <?= $total_files ?> arquivo<?= $total_files === 1 ? '' : 's' ?></span>
                <span><i class="fi fi-rr-disk"></i> <?= formatSize($total_size) ?></span>
                <span id="filter-info" hidden><i class="fi fi-rr-search"></i> <b>0</b> resultado(s)</span>
            </div>
        </div>
    </section>

    <section id="panel-local" class="tab-panel">
        <div class="card">
            <div class="card-head">
                <i class="fi fi-rr-cloud-upload"></i>
                <h2>Enviar arquivo do computador</h2>
            </div>
            <div class="card-body">
                <form method="post" enctype="multipart/form-data">
                    <?= csrfField() ?>
                    <div class="field">
                        <label>Arquivo</label>
                        <div class="dropzone" id="dropzone">
                            <i class="fi fi-rr-cloud-upload"></i>
                            <strong>Arraste o arquivo aqui</strong>
                            <span class="dropzone-name" id="dropzone-name">ou clique para selecionar</span>
                            <input type="file" name="fileToUpload" id="file-input" required>
                        </div>
                        <span class="hint">
                            Máximo <?= formatSize($CONFIG['max_size']) ?> &middot;
                            Tipos: <?= e(implode(', ', $CONFIG['allowed_types'])) ?>
                        </span>
                    </div>
                    <div class="field">
                        <label>Destino (relativo à base)</label>
                        <input type="text" name="upload_dir" value="<?= e($display_rel) ?>" placeholder="subpasta/">
                        <span class="hint">Deixe em branco para salvar na raiz.</span>
                    </div>
                    <button type="submit" class="btn btn-primary">
                        <i class="fi fi-rr-upload"></i> Enviar arquivo
                    </button>
                </form>
            </div>
        </div>
    </section>

    <section id="panel-url" class="tab-panel">
        <div class="card">
            <div class="card-head">
                <i class="fi fi-rr-link-alt"></i>
                <h2>Baixar de uma URL (cURL)</h2>
            </div>
            <div class="card-body">
                <form method="post">
                    <?= csrfField() ?>
                    <input type="hidden" name="action" value="upload_url">
                    <div class="field">
                        <label>URL do arquivo</label>
                        <input type="url" name="url" placeholder="https://exemplo.com/arquivo.zip" required>
                        <span class="hint">O tipo é detectado pela extensão ou pelo Content-Type da resposta.</span>
                    </div>
                    <div class="field">
                        <label>Destino (relativo à base)</label>
                        <input type="text" name="upload_dir" value="<?= e($display_rel) ?>" placeholder="subpasta/">
                    </div>
                    <button type="submit" class="btn btn-primary">
                        <i class="fi fi-rr-cloud-download-alt"></i> Baixar arquivo
                    </button>
                </form>
            </div>
        </div>
    </section>

    <div class="card">
        <div class="card-head">
            <i class="fi fi-rr-info"></i>
            <h2>Informações do ambiente</h2>
        </div>
        <div class="card-body">
            <div class="info-grid">
                <div class="info-item">
                    <i class="fi fi-rr-disk"></i>
                    <div>
                        <div class="label">Upload máximo</div>
                        <div class="value"><?= formatSize($CONFIG['max_size']) ?></div>
                    </div>
                </div>
                <div class="info-item">
                    <i class="fi fi-rr-file"></i>
                    <div>
                        <div class="label">Tipos permitidos</div>
                        <div class="value"><?= e(implode(', ', $CONFIG['allowed_types'])) ?></div>
                    </div>
                </div>
                <div class="info-item">
                    <i class="fi fi-rr-cloud-download-alt"></i>
                    <div>
                        <div class="label">cURL</div>
                        <div class="value <?= function_exists('curl_version') ? 'ok' : 'off' ?>">
                            <?= function_exists('curl_version') ? 'Disponível' : 'Indisponível' ?>
                        </div>
                    </div>
                </div>
                <div class="info-item">
                    <i class="fi fi-rr-file-zipper"></i>
                    <div>
                        <div class="label">ZipArchive</div>
                        <div class="value <?= class_exists('ZipArchive') ? 'ok' : 'off' ?>">
                            <?= class_exists('ZipArchive') ? 'Disponível' : 'Indisponível' ?>
                        </div>
                    </div>
                </div>
                <div class="info-item wide">
                    <i class="fi fi-rr-hdd"></i>
                    <div>
                        <div class="label">Diretório base</div>
                        <div class="value"><?= e($CONFIG['base_dir']) ?></div>
                    </div>
                </div>
                <div class="info-item wide">
                    <i class="fi fi-rr-browser"></i>
                    <div>
                        <div class="label">Local do script</div>
                        <div class="value"><?= e($CONFIG['script_dir']) ?></div>
                    </div>
                </div>
            </div>
            <div class="note">
                <i class="fi fi-rr-triangle-warning"></i>
                <div>Este script dá acesso total ao sistema de arquivos. Mantenha-o protegido por autenticação e nunca o exponha publicamente.</div>
            </div>
        </div>
    </div>
</div>

<div class="selection-bar" id="selection-bar">
    <span class="selection-count">
        <span class="badge" id="selection-count">0</span> selecionado(s)
    </span>
    <button type="submit" form="fm" name="action" value="download_selected" class="btn btn-primary btn-sm">
        <i class="fi fi-rr-download"></i> Baixar
    </button>
    <button type="submit" form="fm" name="action" value="compress" class="btn btn-sm" id="btn-compress">
        <i class="fi fi-rr-file-zipper"></i> Compactar
    </button>
    <button type="button" class="btn btn-sm btn-ghost" id="clear-selection">
        <i class="fi fi-rr-cross-small"></i> Limpar
    </button>
</div>

<div class="modal" id="modal-mkdir">
    <div class="modal-card">
        <form method="post">
            <div class="modal-head">
                <i class="fi fi-rr-add-folder"></i>
                <h3>Nova pasta</h3>
                <button type="button" class="btn btn-icon btn-ghost" data-close><i class="fi fi-rr-cross-small"></i></button>
            </div>
            <div class="modal-body">
                <?= csrfField() ?>
                <input type="hidden" name="action" value="mkdir">
                <div class="field" style="margin:0">
                    <label>Nome da pasta</label>
                    <input type="text" name="folder_name" id="mkdir-name" placeholder="minha-pasta" required>
                    <span class="hint">Será criada em /<?= e($display_rel) ?></span>
                </div>
            </div>
            <div class="modal-foot">
                <button type="button" class="btn btn-sm" data-close>Cancelar</button>
                <button type="submit" class="btn btn-primary btn-sm"><i class="fi fi-rr-check"></i> Criar</button>
            </div>
        </form>
    </div>
</div>

<div class="modal" id="modal-copy">
    <div class="modal-card">
        <form method="post" action="?dir=<?= rawurlencode($display_rel) ?>">
            <div class="modal-head">
                <i class="fi fi-rr-copy"></i>
                <h3>Copiar arquivo</h3>
                <button type="button" class="btn btn-icon btn-ghost" data-close><i class="fi fi-rr-cross-small"></i></button>
            </div>
            <div class="modal-body">
                <?= csrfField() ?>
                <input type="hidden" name="action" value="copy">
                <input type="hidden" name="source" id="copy-source">
                <div class="field">
                    <label>Origem</label>
                    <input type="text" id="copy-source-label" disabled>
                </div>
                <div class="field">
                    <label>Novo nome</label>
                    <input type="text" name="target" id="copy-target" placeholder="novo_nome.ext" required>
                </div>
                <div class="field" style="margin:0">
                    <label>Pasta de destino (relativa à base)</label>
                    <input type="text" name="target_dir" value="<?= e($display_rel) ?>" placeholder="subpasta/">
                </div>
            </div>
            <div class="modal-foot">
                <button type="button" class="btn btn-sm" data-close>Cancelar</button>
                <button type="submit" class="btn btn-primary btn-sm"><i class="fi fi-rr-check"></i> Copiar</button>
            </div>
        </form>
    </div>
</div>

<script>
(function () {
    'use strict';

    var $  = function (sel, ctx) { return (ctx || document).querySelector(sel); };
    var $$ = function (sel, ctx) { return Array.prototype.slice.call((ctx || document).querySelectorAll(sel)); };

    var themeToggle = $('#theme-toggle');
    function applyTheme(theme) {
        document.documentElement.setAttribute('data-theme', theme);
        themeToggle.innerHTML = theme === 'dark'
            ? '<i class="fi fi-rr-sun"></i>'
            : '<i class="fi fi-rr-moon"></i>';
        try { localStorage.setItem('fm-theme', theme); } catch (err) {}
    }
    var savedTheme = null;
    try { savedTheme = localStorage.getItem('fm-theme'); } catch (err) {}
    applyTheme(savedTheme || (window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
    themeToggle.addEventListener('click', function () {
        applyTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
    });

    $$('.tab').forEach(function (tab) {
        tab.addEventListener('click', function () {
            $$('.tab').forEach(function (t) { t.classList.remove('active'); });
            $$('.tab-panel').forEach(function (p) { p.classList.remove('active'); });
            tab.classList.add('active');
            var panel = document.getElementById(tab.dataset.tab);
            if (panel) panel.classList.add('active');
        });
    });

    var picks     = $$('.pick');
    var checkAll  = $('#check-all');
    var bar       = $('#selection-bar');
    var counter   = $('#selection-count');

    function visiblePicks() {
        return picks.filter(function (c) { return c.closest('tr').style.display !== 'none'; });
    }

    function refreshSelection() {
        var chosen = picks.filter(function (c) { return c.checked; });
        counter.textContent = chosen.length;
        bar.classList.toggle('active', chosen.length > 0);

        picks.forEach(function (c) { c.closest('tr').classList.toggle('selected', c.checked); });

        if (checkAll) {
            var shown = visiblePicks();
            var all   = shown.length > 0 && shown.every(function (c) { return c.checked; });
            checkAll.checked = all;
            checkAll.indeterminate = !all && chosen.length > 0;
        }
    }

    picks.forEach(function (c) { c.addEventListener('change', refreshSelection); });

    if (checkAll) {
        checkAll.addEventListener('change', function () {
            visiblePicks().forEach(function (c) { c.checked = checkAll.checked; });
            refreshSelection();
        });
    }

    var clearBtn = $('#clear-selection');
    if (clearBtn) {
        clearBtn.addEventListener('click', function () {
            picks.forEach(function (c) { c.checked = false; });
            refreshSelection();
        });
    }

    var compressBtn = $('#btn-compress');
    if (compressBtn) {
        compressBtn.addEventListener('click', function (ev) {
            var total = picks.filter(function (c) { return c.checked; }).length;
            if (!confirm('Compactar ' + total + ' item(ns) em um arquivo ZIP?')) ev.preventDefault();
        });
    }

    $$('.act-delete').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            if (!confirm('Deseja realmente excluir ' + btn.dataset.confirm + '?')) ev.preventDefault();
        });
    });

    function openModal(id)  { var m = document.getElementById(id); if (m) m.classList.add('open'); }
    function closeModals()  { $$('.modal').forEach(function (m) { m.classList.remove('open'); }); }

    $$('[data-close]').forEach(function (b) { b.addEventListener('click', closeModals); });
    $$('.modal').forEach(function (m) {
        m.addEventListener('click', function (ev) { if (ev.target === m) closeModals(); });
    });
    document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape') closeModals(); });

    var mkdirBtn = $('#open-mkdir');
    if (mkdirBtn) {
        mkdirBtn.addEventListener('click', function () {
            openModal('modal-mkdir');
            setTimeout(function () { $('#mkdir-name').focus(); }, 60);
        });
    }

    $$('[data-copy]').forEach(function (btn) {
        btn.addEventListener('click', function () {
            var name = btn.dataset.copy;
            $('#copy-source').value = name;
            $('#copy-source-label').value = name;
            $('#copy-target').value = name.replace(/(\.[^.]+)?$/, function (ext) { return '-copia' + (ext || ''); });
            openModal('modal-copy');
            setTimeout(function () { $('#copy-target').select(); }, 60);
        });
    });

    var filter     = $('#filter');
    var filterInfo = $('#filter-info');
    var rows       = $$('#file-table tbody tr[data-name]');

    if (filter) {
        filter.addEventListener('input', function () {
            var term = filter.value.trim().toLowerCase();
            var hits = 0;

            rows.forEach(function (row) {
                var match = term === '' || row.dataset.name.indexOf(term) !== -1;
                row.style.display = match ? '' : 'none';
                if (match) hits++;
                if (!match) {
                    var box = row.querySelector('.pick');
                    if (box) box.checked = false;
                }
            });

            filterInfo.hidden = term === '';
            filterInfo.querySelector('b').textContent = hits;
            refreshSelection();
        });
    }

    var tbody = $('#file-table tbody');
    var sortState = { key: 'name', asc: true };

    $$('#file-table th.sortable').forEach(function (th) {
        th.addEventListener('click', function () {
            var key = th.dataset.sort;
            sortState.asc = sortState.key === key ? !sortState.asc : true;
            sortState.key = key;

            $$('#file-table th.sortable').forEach(function (h) { h.classList.remove('sorted'); });
            th.classList.add('sorted');

            rows.slice().sort(function (a, b) {
                if (a.dataset.dir !== b.dataset.dir) return b.dataset.dir - a.dataset.dir;

                var diff = key === 'name'
                    ? a.dataset.name.localeCompare(b.dataset.name, 'pt-BR', { numeric: true })
                    : Number(a.dataset[key]) - Number(b.dataset[key]);

                return sortState.asc ? diff : -diff;
            }).forEach(function (row) { tbody.appendChild(row); });
        });
    });

    var dropzone  = $('#dropzone');
    var fileInput = $('#file-input');

    if (dropzone && fileInput) {
        ['dragenter', 'dragover'].forEach(function (evt) {
            dropzone.addEventListener(evt, function (ev) {
                ev.preventDefault();
                dropzone.classList.add('dragging');
            });
        });
        ['dragleave', 'drop'].forEach(function (evt) {
            dropzone.addEventListener(evt, function (ev) {
                ev.preventDefault();
                dropzone.classList.remove('dragging');
            });
        });
        dropzone.addEventListener('drop', function (ev) {
            if (ev.dataTransfer && ev.dataTransfer.files.length) {
                fileInput.files = ev.dataTransfer.files;
                fileInput.dispatchEvent(new Event('change'));
            }
        });
        fileInput.addEventListener('change', function () {
            $('#dropzone-name').textContent = fileInput.files.length
                ? fileInput.files[0].name
                : 'ou clique para selecionar';
        });
    }

    refreshSelection();
})();
</script>
</body>
</html>
