<?php
setlocale(LC_CTYPE, "pl_PL.UTF-8", "en_US.UTF-8", "C.UTF-8");
require_once 'functions.php';

header('Content-Type: application/json; charset=utf-8');

$action = $_POST['action'] ?? '';

// WYJĄTEK: Zapis i natychmiastowy odczyt czasu działają na Ciasteczku (omijają blokadę F5)
// Zapis i odczyt postępu wymagają teraz aktywnej, uwierzytelnionej sesji.
checkAuth();

if (!validateCsrfToken()) {
    die(json_encode(['status'=>'error', 'msg'=>'Nieprawidłowy token bezpieczeństwa.']));
}

$cfg = cfg();
$folderKey = $_POST['folder'] ?? '';
$subPath = sanitizePath($_POST['path'] ?? '');

$actionsWithFolder = ['delete', 'multi_delete', 'rename', 'upload', 'mkdir', 'move', 'copy', 'unpack', 'convert', 'ytdlp', 'share', 'save_progress', 'get_progress'];
if (in_array($action, $actionsWithFolder, true) && !isset($cfg['allowedFolders'][$folderKey])) {
    die(json_encode(['status'=>'error', 'msg'=>'Błędny folder.']));
}

$basePath = isset($cfg['allowedFolders'][$folderKey]) ? '/mnt/mega' . $cfg['allowedFolders'][$folderKey] : '';
$targetDir = rtrim($basePath . '/' . $subPath, '/');

if (($_SESSION['role'] ?? '') !== 'admin' && !in_array($action, ['share', 'save_progress', 'get_progress'], true)) {
    die(json_encode(['status'=>'error', 'msg'=>'Odmowa dostępu!']));
}

// Uruchamianie workera konwersji jest sprawdzane zamiast cichego wysyłania polecenia do /dev/null.
function diskStatusDirectorySize($path) {
    if (!is_dir($path)) return 0;
    $size = 0;
    try {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );
        foreach ($iterator as $item) {
            if ($item->isLink() || !$item->isFile()) continue;
            $fileSize = $item->getSize();
            if ($fileSize > 0) $size += $fileSize;
        }
    } catch (Throwable $e) {
        return 0;
    }
    return $size;
}

function diskStatusFilesystem($path) {
    $total = @disk_total_space($path);
    $free = @disk_free_space($path);
    if ($total === false || $free === false || $total <= 0 || $total >= (PHP_INT_MAX * 0.99)) {
        return ['available'=>false, 'total'=>0, 'free'=>0, 'used'=>0, 'percent'=>0];
    }
    $total = (float)$total;
    $free = max(0, (float)$free);
    $used = max(0, $total - $free);
    $percent = $total > 0 ? round(($used / $total) * 100, 1) : 0;
    return ['available'=>true, 'total'=>$total, 'free'=>$free, 'used'=>$used, 'percent'=>$percent];
}

function convertWorkerIsRunning($jobsDir) {
    $lockFile = $jobsDir . '/worker.lock';
    $lockHandle = @fopen($lockFile, 'c+');
    if ($lockHandle === false) return false;
    if (!@flock($lockHandle, LOCK_EX | LOCK_NB)) {
        fclose($lockHandle);
        return true;
    }
    @flock($lockHandle, LOCK_UN);
    fclose($lockHandle);
    return false;
}

function convertFindPhpCli() {
    $candidates = [];

    // Najpierw używamy PHP dostępnego w PATH użytkownika serwera WWW.
    // Jest to ważne na serwerach z wieloma wersjami PHP, gdzie /usr/bin/php
    // może nie istnieć albo PHP_BINARY może wskazywać proces php-fpm.
    $pathOutput = [];
    $pathCode = 1;
    @exec('/bin/sh -c ' . escapeshellarg('command -v php 2>/dev/null'), $pathOutput, $pathCode);
    if ($pathCode === 0 && !empty($pathOutput)) $candidates[] = trim((string)$pathOutput[0]);

    if (defined('PHP_BINDIR')) $candidates[] = rtrim((string)PHP_BINDIR, '/') . '/php';
    $candidates[] = '/usr/bin/php';
    $candidates[] = '/usr/local/bin/php';
    foreach (['8.4', '8.3', '8.2', '8.1', '8.0', '7.4'] as $version) {
        $candidates[] = '/usr/bin/php' . $version;
        $candidates[] = '/usr/local/bin/php' . $version;
    }
    if (defined('PHP_BINARY')) $candidates[] = PHP_BINARY;

    $worker = __DIR__ . '/convert_worker.php';
    $checked = [];
    foreach ($candidates as $candidate) {
        $candidate = trim((string)$candidate);
        if ($candidate === '' || isset($checked[$candidate])) continue;
        $checked[$candidate] = true;

        // Sprawdzamy nie tylko PHP_SAPI, ale również funkcje wymagane przez
        // konwersję i zgodność składni workera z wybraną wersją PHP CLI.
        $testCode = 'exit(PHP_SAPI === "cli" && function_exists("proc_open") && function_exists("exec") && function_exists("shell_exec") ? 0 : 1);';
        $testOutput = [];
        $testReturnCode = 1;
        @exec(escapeshellarg($candidate) . ' -r ' . escapeshellarg($testCode) . ' >/dev/null 2>&1', $testOutput, $testReturnCode);
        if ($testReturnCode !== 0) continue;

        $lintOutput = [];
        $lintReturnCode = 1;
        @exec(escapeshellarg($candidate) . ' -l ' . escapeshellarg($worker) . ' >/dev/null 2>&1', $lintOutput, $lintReturnCode);
        if ($lintReturnCode === 0) return $candidate;
    }
    return '';
}

function convertStartWorker($jobsDir) {
    @mkdir($jobsDir, 0777, true);
    if (convertWorkerIsRunning($jobsDir)) return ['ok'=>true, 'msg'=>'Worker konwersji już działa.'];

    $queuedBeforeStart = [];
    foreach (glob($jobsDir . '/job_*.json') ?: [] as $queuedJobFile) {
        $queuedJob = readJsonFile($queuedJobFile, []);
        if (($queuedJob['status'] ?? '') === 'queued') $queuedBeforeStart[] = $queuedJobFile;
    }
    if (empty($queuedBeforeStart)) return ['ok'=>true, 'msg'=>'Brak zadań oczekujących na uruchomienie.'];

    if (!function_exists('exec')) return ['ok'=>false, 'msg'=>'PHP ma wyłączoną funkcję exec(), więc nie może uruchomić konwersji w tle.'];
    $disabled = array_filter(array_map('trim', explode(',', (string)ini_get('disable_functions'))));
    if (in_array('exec', $disabled, true)) return ['ok'=>false, 'msg'=>'Funkcja exec() jest wyłączona w konfiguracji PHP.'];

    $worker = __DIR__ . '/convert_worker.php';
    if (!is_file($worker) || !is_readable($worker)) return ['ok'=>false, 'msg'=>'Brak pliku convert_worker.php albo użytkownik PHP nie może go odczytać.'];

    $phpCli = convertFindPhpCli();
    if ($phpCli === '') return ['ok'=>false, 'msg'=>'Nie znaleziono zgodnego PHP CLI z włączonymi funkcjami proc_open(), exec() i shell_exec().'];

    $nohupOutput = [];
    $nohupCode = 1;
    @exec('/bin/sh -c ' . escapeshellarg('command -v nohup 2>/dev/null'), $nohupOutput, $nohupCode);
    $nohup = ($nohupCode === 0 && !empty($nohupOutput)) ? trim((string)$nohupOutput[0]) : '';
    if ($nohup === '') return ['ok'=>false, 'msg'=>'Nie znaleziono programu nohup potrzebnego do uruchomienia zadania w tle.'];

    $startLog = $jobsDir . '/worker_start.log';
    @file_put_contents(
        $startLog,
        '[' . date('Y-m-d H:i:s') . '] Próba uruchomienia przez ' . $phpCli . PHP_EOL,
        FILE_APPEND | LOCK_EX
    );

    $backgroundCommand = escapeshellarg($nohup) . ' ' . escapeshellarg($phpCli) . ' ' . escapeshellarg($worker)
        . ' >> ' . escapeshellarg($startLog) . ' 2>&1 < /dev/null & echo $!';
    $output = [];
    $returnCode = 1;
    @exec('/bin/sh -c ' . escapeshellarg($backgroundCommand), $output, $returnCode);
    $pid = !empty($output) ? (int)trim((string)end($output)) : 0;

    if ($returnCode !== 0 || $pid <= 1) {
        return ['ok'=>false, 'msg'=>'Powłoka nie uruchomiła workera konwersji. Kod uruchomienia: ' . $returnCode . '.', 'pid'=>$pid];
    }

    // Worker przed analizą pliku zmienia stan zadania z „queued” na „probing”.
    // Dzięki temu nie uznajemy za sukces procesu, który wystartował i od razu
    // zakończył się błędem po utworzeniu samego pliku heartbeat.
    for ($i = 0; $i < 40; $i++) {
        usleep(100000);
        foreach ($queuedBeforeStart as $jobFile) {
            $job = readJsonFile($jobFile, []);
            $status = (string)($job['status'] ?? '');
            if ($status !== '' && $status !== 'queued') {
                return ['ok'=>true, 'msg'=>'Worker konwersji został uruchomiony.', 'pid'=>$pid];
            }
        }

        $pidFile = $jobsDir . '/worker.pid';
        $workerPid = is_file($pidFile) ? (int)trim((string)@file_get_contents($pidFile)) : 0;
        if ($workerPid > 1) {
            $checkOutput = [];
            $checkCode = 1;
            @exec('/bin/kill -0 ' . $workerPid . ' >/dev/null 2>&1', $checkOutput, $checkCode);
            if ($checkCode !== 0) break;
        }
    }

    $logTail = '';
    if (is_file($startLog)) {
        $lines = @file($startLog, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (is_array($lines) && !empty($lines)) $logTail = trim(implode(' | ', array_slice($lines, -8)));
    }
    $message = 'Worker PHP uruchomił się, ale nie przejął zadania z kolejki.';
    if ($logTail !== '') $message .= ' Log: ' . $logTail;
    return ['ok'=>false, 'msg'=>$message, 'pid'=>$pid];
}

if ($action === 'delete') {
    $file = basename($_POST['file'] ?? '');
    $fullPath = $targetDir . '/' . $file;
    if (file_exists($fullPath)) {
        is_dir($fullPath) ? exec("rm -rf " . escapeshellarg($fullPath)) : unlink($fullPath);
        invalidateSearchIndex();
        echo json_encode(['status'=>'ok']);
    } else echo json_encode(['status'=>'error']);
}
elseif ($action === 'multi_delete') {
    $files = $_POST['files'] ?? [];
    foreach($files as $file) {
        $safeName = basename($file);
        $fullPath = $targetDir . '/' . $safeName;
        if(file_exists($fullPath)) {
            is_dir($fullPath) ? exec("rm -rf " . escapeshellarg($fullPath)) : unlink($fullPath);
        }
    }
    invalidateSearchIndex();
    echo json_encode(['status'=>'ok']);
}
elseif ($action === 'rename') {
    $old = basename($_POST['old_name'] ?? ''); $new = basename($_POST['new_name'] ?? '');
    if(rename($targetDir . '/' . $old, $targetDir . '/' . $new)) {
        invalidateSearchIndex();
        echo json_encode(['status'=>'ok']);
    } else echo json_encode(['status'=>'error']);
}
elseif ($action === 'upload') {
    if (!empty($_FILES['files']['name'][0])) {
        $errors = [];
        foreach($_FILES['files']['name'] as $k => $name) {
            $filename = basename($name);
            $tmpName = $_FILES['files']['tmp_name'][$k];
            if(!move_uploaded_file($tmpName, $targetDir . '/' . $filename)) $errors[] = $filename;
        }
        exec("nohup bash -c 'sleep 600 && sudo -n /usr/local/bin/clean_vfs.sh' >/dev/null 2>&1 < /dev/null &");
        invalidateSearchIndex();
        if (empty($errors)) echo json_encode(['status'=>'ok']); else echo json_encode(['status'=>'error', 'msg'=>'Błąd wgrywania: ' . implode(', ', $errors)]);
    } else echo json_encode(['status'=>'error', 'msg'=>'Brak plików.']);
}
elseif ($action === 'upload_cookies') {
    if (!empty($_FILES['cookie_file']['tmp_name'])) {
        $target = __DIR__ . '/data/cookies.txt';
        $content = file_get_contents($_FILES['cookie_file']['tmp_name']);
        if ($content === false) die(json_encode(['status'=>'error', 'msg'=>'Serwer nie mógł odczytać wgranego pliku!']));
        if(file_put_contents($target, $content, LOCK_EX) !== false) {
            chmod($target, 0666);
            $rozmiar = round(strlen($content) / 1024, 2);
            echo json_encode(['status'=>'ok', 'msg'=>'Sukces! Ciasteczka wgrane! Waga: ' . $rozmiar . ' KB. Wyczyść log i pobieraj.']);
        } else echo json_encode(['status'=>'error', 'msg'=>'Błąd zapisu! Sprawdź uprawnienia do folderu data/']);
    } else echo json_encode(['status'=>'error', 'msg'=>'Nie przesłano żadnego pliku.']);
}
elseif ($action === 'mkdir') {
    $name = basename($_POST['name'] ?? '');
    if(mkdir($targetDir . '/' . $name, 0777, true)) {
        invalidateSearchIndex();
        echo json_encode(['status'=>'ok']);
    } else echo json_encode(['status'=>'error', 'msg'=>'Błąd tworzenia folderu.']);
}
elseif ($action === 'move' || $action === 'copy') {
    $files = $_POST['files'] ?? [];
    $destFolderKey = $_POST['dest_folder'] ?? ''; $destSubPath = sanitizePath($_POST['dest_path'] ?? '');
    if (!isset($cfg['allowedFolders'][$destFolderKey])) die(json_encode(['status'=>'error', 'msg'=>'Błędny folder docelowy']));
    $destBase = rtrim('/mnt/mega' . $cfg['allowedFolders'][$destFolderKey] . '/' . $destSubPath, '/');
    if (!is_dir($destBase)) mkdir($destBase, 0777, true);

    foreach($files as $file) {
        $safeName = basename($file);
        $src = $targetDir . '/' . $safeName; $dst = $destBase . '/' . $safeName;
        if(file_exists($src)) {
            if ($action === 'move') rename($src, $dst);
            if ($action === 'copy') exec("cp -r " . escapeshellarg($src) . " " . escapeshellarg($dst));
        }
    }
    invalidateSearchIndex();
    echo json_encode(['status'=>'ok']);
}
elseif ($action === 'unpack') {
    $files = $_POST['files'] ?? [];
    if (empty($files)) die(json_encode(['status'=>'error', 'msg'=>'Brak plików.']));
    
    $localTempDir = __DIR__ . '/data/yt_temp';
    @mkdir($localTempDir, 0777, true);
    $uniq = uniqid();
    $jobDir = $localTempDir . '/unpack_' . $uniq;
    @mkdir($jobDir, 0777, true);
    @touch($jobDir . '/.active');
    
    $shPath = $jobDir . '/unpack_' . $uniq . '.sh';
    
    $sh = "#!/bin/bash\n";
    $unpackTrap = 'rm -f ' . escapeshellarg($jobDir . '/.active');
    $sh .= 'trap ' . escapeshellarg($unpackTrap) . " EXIT\n";
    foreach($files as $file) {
        $fullPath = $targetDir . '/' . basename($file);
        $extractDir = $jobDir . '/dir';
        $archiveName = pathinfo(basename($file), PATHINFO_FILENAME);
        $finalDir = $targetDir . '/' . preg_replace('/[^A-Za-z0-9_\-\.]/', '_', $archiveName);
        
        $sh .= "mkdir -p ".escapeshellarg($extractDir)."\n";
        if (strtolower(pathinfo($fullPath, PATHINFO_EXTENSION)) == 'zip') 
            $sh .= "unzip -o ".escapeshellarg($fullPath)." -d ".escapeshellarg($extractDir)." >/dev/null 2>&1\n";
        else 
            $sh .= "unrar x -y -o+ ".escapeshellarg($fullPath)." ".escapeshellarg($extractDir)."/ >/dev/null 2>&1\n";
            
        $sh .= "mkdir -p ".escapeshellarg($finalDir)."\n";
        $sh .= "\\cp -rf ".escapeshellarg($extractDir)."/* ".escapeshellarg($finalDir)."/ >/dev/null 2>&1\n";
        $sh .= "rm -rf ".escapeshellarg($extractDir)."\n";
    }
    $sh .= "( sleep 600 && sudo -n /usr/local/bin/clean_vfs.sh ) &\n";
    $sh .= "rm -rf ".escapeshellarg($jobDir)."\n";
    
    file_put_contents($shPath, $sh, LOCK_EX);
    chmod($shPath, 0777);
    exec("nohup bash ".escapeshellarg($shPath)." >/dev/null 2>&1 < /dev/null &");
    invalidateSearchIndex();
    
    echo json_encode(['status'=>'ok', 'msg'=>'Rozpoczęto rozpakowywanie w tle. Pliki pojawią się w folderze po odświeżeniu za kilka chwil.']);
}
elseif ($action === 'convert') {
    $files = $_POST['files'] ?? [];
    if (!is_array($files) || empty($files)) die(json_encode(['status'=>'error', 'msg'=>'Nie wybrano żadnego filmu.']));

    $supportedConvertExts = ['mp4', 'webm', 'mkv', 'avi', 'ts', 'mts'];
    $jobsDir = __DIR__ . '/data/convert_jobs';
    @mkdir($jobsDir, 0777, true);

    $activeSignatures = [];
    foreach (glob($jobsDir . '/job_*.json') ?: [] as $existingJobFile) {
        $existingJob = readJsonFile($existingJobFile, []);
        if (in_array($existingJob['status'] ?? '', ['queued', 'probing', 'converting', 'remuxing', 'uploading'], true)) {
            $signature = (string)($existingJob['source_signature'] ?? '');
            if ($signature !== '') $activeSignatures[$signature] = true;
        }
    }

    $queued = 0;
    $skipped = [];
    $errors = [];
    foreach (array_slice($files, 0, 100) as $file) {
        $fileName = basename((string)$file);
        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
        if (!in_array($ext, $supportedConvertExts, true)) {
            $skipped[] = $fileName;
            continue;
        }

        $resolved = resolveAllowedFile($folderKey, $subPath, $fileName);
        if ($resolved === false) {
            $errors[] = $fileName;
            continue;
        }

        $sourceSize = @filesize($resolved['full']);
        $sourceMtime = @filemtime($resolved['full']);
        if ($sourceSize === false || $sourceMtime === false) {
            $errors[] = $fileName;
            continue;
        }

        $sourceSignature = hash('sha256', $resolved['full'] . '|' . $sourceSize . '|' . $sourceMtime);
        if (isset($activeSignatures[$sourceSignature])) {
            $skipped[] = $fileName;
            continue;
        }

        $jobId = 'job_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5));
        $jobFile = $jobsDir . '/' . $jobId . '.json';
        $jobData = [
            'id' => $jobId,
            'folder' => $folderKey,
            'path' => $subPath,
            'file' => $resolved['file'],
            'source_size' => (int)$sourceSize,
            'source_mtime' => (int)$sourceMtime,
            'source_signature' => $sourceSignature,
            'status' => 'queued',
            'progress' => 0,
            'message' => 'Oczekuje w kolejce...',
            'output' => '',
            'pid' => 0,
            'created_at' => time(),
            'updated_at' => time()
        ];

        if (writeJsonFile($jobFile, $jobData)) {
            $activeSignatures[$sourceSignature] = true;
            $queued++;
        } else {
            $errors[] = $fileName;
        }
    }

    $workerStart = ['ok'=>true, 'msg'=>''];
    $hasQueuedJobs = $queued > 0;
    if (!$hasQueuedJobs) {
        foreach (glob($jobsDir . '/job_*.json') ?: [] as $queuedJobFile) {
            $queuedJob = readJsonFile($queuedJobFile, []);
            if (($queuedJob['status'] ?? '') === 'queued') { $hasQueuedJobs = true; break; }
        }
    }
    if ($hasQueuedJobs) $workerStart = convertStartWorker($jobsDir);

    if (!$workerStart['ok']) {
        foreach (glob($jobsDir . '/job_*.json') ?: [] as $queuedJobFile) {
            $queuedJob = readJsonFile($queuedJobFile, []);
            if (($queuedJob['status'] ?? '') !== 'queued') continue;
            updateJsonFile($queuedJobFile, function (&$job) use ($workerStart) {
                $job['message'] = $workerStart['msg'];
                $job['updated_at'] = time();
            }, []);
        }
    }

    $parts = [];
    if ($queued > 0) $parts[] = 'Dodano do kolejki: ' . $queued;
    if (!empty($skipped)) $parts[] = 'Pominięto: ' . count($skipped) . ' (nieobsługiwany typ lub zadanie już działa)';
    if (!empty($errors)) $parts[] = 'Błędy: ' . count($errors);
    if (!$workerStart['ok']) {
        $parts[] = $workerStart['msg'];
        die(json_encode(['status'=>'error', 'msg'=>implode('. ', $parts)]));
    }
    if ($queued === 0 && $hasQueuedJobs) {
        echo json_encode(['status'=>'ok', 'msg'=>'Istniejące zadanie oczekujące zostało ponownie uruchomione. Konwersje działają pojedynczo w tle.']);
    }
    elseif ($queued === 0) {
        die(json_encode(['status'=>'error', 'msg'=>implode('. ', $parts) ?: 'Nie znaleziono filmu możliwego do konwersji.']));
    }
    else {
        echo json_encode(['status'=>'ok', 'msg'=>implode('. ', $parts) . '. Konwersje działają pojedynczo w tle.']);
    }
}
elseif ($action === 'convert_status') {
    $jobsDir = __DIR__ . '/data/convert_jobs';
    $queuedWaiting = false;
    foreach (glob($jobsDir . '/job_*.json') ?: [] as $queuedJobFile) {
        $queuedJob = readJsonFile($queuedJobFile, []);
        if (($queuedJob['status'] ?? '') === 'queued') { $queuedWaiting = true; break; }
    }
    if ($queuedWaiting && !convertWorkerIsRunning($jobsDir)) {
        $workerStart = convertStartWorker($jobsDir);
        if (!$workerStart['ok']) {
            foreach (glob($jobsDir . '/job_*.json') ?: [] as $queuedJobFile) {
                $queuedJob = readJsonFile($queuedJobFile, []);
                if (($queuedJob['status'] ?? '') !== 'queued') continue;
                updateJsonFile($queuedJobFile, function (&$job) use ($workerStart) {
                    $job['message'] = $workerStart['msg'];
                    $job['updated_at'] = time();
                }, []);
            }
        }
    }
    $jobs = [];
    foreach (glob($jobsDir . '/job_*.json') ?: [] as $jobFile) {
        $job = readJsonFile($jobFile, []);
        if (empty($job)) continue;
        $jobs[] = [
            'id' => (string)($job['id'] ?? basename($jobFile, '.json')),
            'file' => (string)($job['file'] ?? ''),
            'status' => (string)($job['status'] ?? 'unknown'),
            'progress' => max(0, min(100, (int)($job['progress'] ?? 0))),
            'message' => (string)($job['message'] ?? ''),
            'output' => (string)($job['output'] ?? ''),
            'created_at' => (int)($job['created_at'] ?? 0),
            'updated_at' => (int)($job['updated_at'] ?? 0)
        ];
    }
    usort($jobs, function($a, $b) { return $b['created_at'] <=> $a['created_at']; });
    $jobs = array_slice($jobs, 0, 30);
    echo json_encode(['status'=>'ok', 'jobs'=>$jobs]);
}
elseif ($action === 'disk_status') {
    set_time_limit(30);

    $local = diskStatusFilesystem(__DIR__);
    $cloud = diskStatusFilesystem('/mnt/mega');

    $cachePaths = [
        'thumbnails' => __DIR__ . '/data/thumbnails',
        'video_thumbs' => __DIR__ . '/data/video_thumbs',
        'image_thumbs' => __DIR__ . '/data/thumbs',
        'convert_temp' => __DIR__ . '/data/convert_temp',
        'yt_temp' => __DIR__ . '/data/yt_temp'
    ];
    $cache = [];
    foreach ($cachePaths as $key => $path) {
        $bytes = diskStatusDirectorySize($path);
        $cache[$key] = ['bytes'=>$bytes, 'formatted'=>formatSize($bytes)];
    }

    foreach (['local', 'cloud'] as $name) {
        $value = ${$name};
        $value['total_formatted'] = formatSize((int)round($value['total']));
        $value['free_formatted'] = formatSize((int)round($value['free']));
        $value['used_formatted'] = formatSize((int)round($value['used']));
        ${$name} = $value;
    }

    echo json_encode([
        'status'=>'ok',
        'local'=>$local,
        'cloud'=>$cloud,
        'cache'=>$cache,
        'checked_at'=>time()
    ]);
}
elseif ($action === 'ytdlp') {
    $urlsRaw = $_POST['url'] ?? '';
    $urlsArray = preg_split('/[\r\n\s,]+/', trim($urlsRaw), -1, PREG_SPLIT_NO_EMPTY);
    
    if(empty($urlsArray)) die(json_encode(['status'=>'error', 'msg'=>'Brak poprawnych linków.']));
    
    $localTempDir = __DIR__ . '/data/yt_temp';
    if (!is_dir($localTempDir)) { mkdir($localTempDir, 0777, true); chmod($localTempDir, 0777); }
    $uniqId = uniqid();
    $jobTempDir = $localTempDir . '/job_' . $uniqId;
    @mkdir($jobTempDir, 0777, true);
    @touch($jobTempDir . '/.active');
    
    $safeTargetDir = escapeshellarg($targetDir);
    $safeTempDir = escapeshellarg($jobTempDir);
    
    $logFileBase = __DIR__ . '/data/yt.log';
    if (!file_exists($logFileBase)) { touch($logFileBase); chmod($logFileBase, 0666); }
    
    $cookiesFile = __DIR__ . '/data/cookies.txt';
    $cookiesArg = file_exists($cookiesFile) ? "--cookies " . escapeshellarg($cookiesFile) : "";
    
    $cacheDir = __DIR__ . '/data/yt_cache';
    if (!is_dir($cacheDir)) { mkdir($cacheDir, 0777, true); }
    $cacheArg = "--cache-dir " . escapeshellarg($cacheDir);
    
    $scriptContent  = "#!/bin/bash\n";
    $scriptContent .= "exec >> " . escapeshellarg($logFileBase) . " 2>&1\n";
    $ytdlpTrap = 'rm -f ' . escapeshellarg($jobTempDir . '/.active');
    $scriptContent .= 'trap ' . escapeshellarg($ytdlpTrap) . " EXIT\n";
    $scriptContent .= "echo '=== START MASOWEGO POBIERANIA Z LINKOW ==='\n";

    foreach ($urlsArray as $u) {
        $safeUrl = escapeshellarg($u);
        
        $pathInfo = pathinfo(parse_url($u, PHP_URL_PATH) ?? '');
        $ext = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : '';
        $isDirectFile = in_array($ext, ['zip', 'rar', 'txt', 'py', 'jpg', 'png', 'pdf', 'mp4', 'avi', 'mkv', 'iso', 'exe', 'bin', 'tar', 'gz']);
        $isYouTubeOrSimilar = (strpos($u, 'youtube.com') !== false || strpos($u, 'youtu.be') !== false || strpos($u, 'vimeo') !== false);
        
        $scriptContent .= "echo '===================================='\n";
        $scriptContent .= "printf '%s\\n' " . escapeshellarg('Przetwarzanie: ' . $u) . "\n";
        
        if ($isDirectFile && !$isYouTubeOrSimilar) {
            $filenameExtracted = escapeshellarg(basename(parse_url($u, PHP_URL_PATH) ?? 'plik'));
            $scriptContent .= "echo '[METODA] Serwer zewnetrzny. Uzywam bezposredniego pobierania (wget).'\n";
            $scriptContent .= "wget -q --show-progress -O $safeTempDir/$filenameExtracted $safeUrl\n";
            $scriptContent .= "echo '[Przenoszenie do chmury...]'\n";
            $scriptContent .= "mv -f $safeTempDir/$filenameExtracted $safeTargetDir/ 2>/dev/null\n";
        } else {
            $playlistArg = (strpos($u, 'watch?v=') === false && (strpos($u, 'playlist') !== false || strpos($u, '&list=') !== false)) ? "--yes-playlist" : "--no-playlist";
            $outputTemplate = escapeshellarg('%(title)s.%(ext)s');
            
            $scriptContent .= "echo '[METODA] Strumien wideo. Uzywam yt-dlp.'\n";
            $ytCmd = "sudo -n /usr/local/bin/yt-dlp --js-runtimes deno:/usr/local/bin/deno --remote-components ejs:github $cacheArg $cookiesArg --ffmpeg-location /usr/bin/ffmpeg -P temp:$safeTempDir -P home:$safeTargetDir -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' -o $outputTemplate $playlistArg --no-playlist-reverse --ignore-errors $safeUrl";
            $scriptContent .= $ytCmd . "\n";
        }
        $scriptContent .= "printf '%s\\n' " . escapeshellarg('[ZAKONCZONO]: ' . $u) . "\n";
    }

    $scriptContent .= "echo '===================================='\n";
    $scriptContent .= "echo '[SYSTEM] Sprzatanie lokalnych smieci (yt_temp)...'\n";
    $scriptContent .= "rm -rf $safeTempDir/* 2>/dev/null\n";
    $scriptContent .= "echo '[SYSTEM] Odliczam 10 minut do wyczyszczenia chmury (VFS Cache)...'\n";
    $scriptContent .= "sleep 600\n";
    $scriptContent .= "sudo -n /usr/local/bin/clean_vfs.sh\n";
    $scriptContent .= "echo '[SYSTEM] Czysto!'\n";
    $scriptContent .= "rm -rf " . escapeshellarg($jobTempDir) . "\n";
    $scriptContent .= "rm -f \"\$0\"\n";

    $shFile = __DIR__ . "/data/mass_download_{$uniqId}.sh";
    file_put_contents($shFile, $scriptContent, LOCK_EX);
    chmod($shFile, 0777);
    
    $cmd = "nohup bash " . escapeshellarg($shFile) . " >/dev/null 2>&1 < /dev/null &";
    exec($cmd);
    invalidateSearchIndex();
    
    echo json_encode(['status'=>'ok', 'msg'=>"Pobieranie uruchomione pomyślnie."]);
}
elseif ($action === 'yt_log') {
    $logFile = __DIR__ . '/data/yt.log';
    if(!file_exists($logFile)) die(json_encode(['status'=>'ok', 'log'=>'Brak pliku logów. Pobieranie jeszcze nie ruszyło.']));
    $log = shell_exec("tail -n 50 " . escapeshellarg($logFile));
    echo json_encode(['status'=>'ok', 'log'=>$log]);
}
elseif ($action === 'yt_log_clear') {
    $logFile = __DIR__ . '/data/yt.log';
    if(file_exists($logFile)) unlink($logFile);
    echo json_encode(['status'=>'ok']);
}
elseif ($action === 'share') {
    $fileName = basename($_POST['file'] ?? '');
    $hash = createShare($folderKey, $subPath, $fileName);
    if ($hash === false) die(json_encode(['status'=>'error', 'msg'=>'Plik nie istnieje lub znajduje się poza dozwolonym folderem.']));
    
    $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
    $hostPath = $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . "/share.php?h=" . $hash;
    $fullUrl = $scheme . "://" . $hostPath;
    
    echo json_encode(['status'=>'ok', 'link'=>$fullUrl]);
}

// --- AKCJA ZAPISU HISTORII OGLĄDANIA ---
elseif ($action === 'save_progress') {
    $file = basename($_POST['file'] ?? '');
    $time = max(0, (float)($_POST['time'] ?? 0));
    $resolved = resolveAllowedFile($folderKey, $subPath, $file);
    if ($resolved === false) die(json_encode(['status'=>'error', 'msg'=>'Nieprawidłowy plik.']));
    saveProgressTime($folderKey, $subPath, $file, $time);
    echo json_encode(['status'=>'ok']);
    exit;
}

// --- AKCJA POBRANIA CZASU (OMINIĘCIE CACHE PRZEGLĄDARKI) ---
elseif ($action === 'get_progress') {
    $file = basename($_POST['file'] ?? '');
    $resolved = resolveAllowedFile($folderKey, $subPath, $file);
    if ($resolved === false) die(json_encode(['status'=>'error', 'msg'=>'Nieprawidłowy plik.']));
    $time = getProgressTime($folderKey, $subPath, $file);
    echo json_encode(['status'=>'ok', 'time'=>$time]);
    exit;
}
else {
    echo json_encode(['status'=>'error', 'msg'=>'Nieznana akcja.']);
}
