<?php
require __DIR__ . '/auth.php';
mvlog_require_login();
$config = require __DIR__ . "/config.php";
foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[$d])) mkdir($config[$d], 0775, true);
if (!is_dir(__DIR__ . "/cache")) mkdir(__DIR__ . "/cache", 0775, true);
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); }
function ajax_json($data){ header('Content-Type: application/json; charset=UTF-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; }
function slugify($s){ $s = strtolower(trim($s)); $s = preg_replace('/[^a-z0-9]+/', '-', $s); return trim($s, '-') ?: 'movie'; }
function rrmdir($dir){ if(!is_dir($dir)) return; foreach(scandir($dir) as $f){ if($f==='.'||$f==='..') continue; $p="$dir/$f"; is_dir($p)?rrmdir($p):unlink($p);} rmdir($dir); }
function input_dirs($base){ return glob($base.'/*', GLOB_ONLYDIR) ?: []; }
function safe_input_dir($base, $name){ $name = basename((string)$name); $path = realpath($base . '/' . $name); $root = realpath($base); if (!$path || !$root || !str_starts_with($path, $root . DIRECTORY_SEPARATOR) || !is_dir($path)) throw new RuntimeException('Invalid input directory.'); return $path; }
function read_data($dir){
    $data = ['title'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $file = $dir . '/data.txt';
    if (!is_file($file)) return $data;
    $lines = file($file, FILE_IGNORE_NEW_LINES);
    for ($i = 0; $i < count($lines); $i++) {
        $line = trim($lines[$i]);
        if ($line === '' || str_starts_with($line, '#') || !str_contains($line, ':')) continue;
        [$key, $value] = array_map('trim', explode(':', $line, 2));
        $low = strtolower($key);
        if ($value === '|' && in_array($low, ['description','desc','synopsis'], true)) {
            $block = [];
            while ($i + 1 < count($lines)) {
                $next = $lines[$i + 1];
                if (trim($next) !== '' && $next[0] !== ' ' && $next[0] !== "\t") break;
                $i++;
                if (str_starts_with($next, '  ')) $next = substr($next, 2);
                elseif (str_starts_with($next, ' ') || str_starts_with($next, "\t")) $next = substr($next, 1);
                $block[] = rtrim($next);
            }
            $data['description'] = trim(implode("\n", $block));
        } elseif (in_array($low, ['title','name'], true)) $data['title'] = $value;
        elseif (in_array($low, ['date','dates','when'], true)) $data['date'] = $value;
        elseif (in_array($low, ['place','location','where'], true)) $data['place'] = $value;
        elseif (in_array($low, ['description','desc','synopsis'], true)) $data['description'] = $value;
        elseif (str_ends_with($low, ' audio')) { $fileKey = trim(substr($key, 0, -6)); if ($fileKey !== '' && in_array(strtolower($value), ['1','yes','true','on'], true)) $data['video_audio'][$fileKey] = true; }
        elseif ($key !== '') $data['captions'][$key] = $value;
    }
    return $data;
}
function write_data($dir, $post){
    $title = trim($post['title'] ?? '');
    $place = trim($post['place'] ?? '');
    $date = trim($post['date'] ?? '');
    $description = trim($post['description'] ?? '');
    if (function_exists('mb_substr')) $description = mb_substr($description, 0, 5000, 'UTF-8'); else $description = substr($description, 0, 5000);
    $lines = [];
    if ($title !== '') $lines[] = 'Title: ' . $title;
    if ($place !== '') $lines[] = 'Location: ' . $place;
    if ($date !== '') $lines[] = 'Date: ' . $date;
    if ($description !== '') {
        $lines[] = 'Description: |';
        $description = str_replace(["\r\n", "\r"], "\n", $description);
        foreach (explode("\n", $description) as $descLine) $lines[] = '  ' . rtrim($descLine);
    }
    $useAudioFiles = $post['use_audio_files'] ?? [];
    $useAudio = is_array($useAudioFiles) ? array_flip(array_map('basename', array_map('strval', $useAudioFiles))) : [];
    foreach (array_keys($useAudio) as $fileName) if ($fileName !== '') $lines[] = $fileName . ' audio: yes';
    $captionFiles = $post['caption_files'] ?? [];
    $captions = $post['captions'] ?? [];
    if (is_array($captionFiles) && is_array($captions)) {
        foreach ($captionFiles as $i => $fileName) {
            $fileName = basename((string)$fileName);
            $caption = trim((string)($captions[$i] ?? ''));
            if ($fileName !== '' && $caption !== '') $lines[] = $fileName . ': ' . $caption;
        }
    }
    file_put_contents($dir . '/data.txt', implode("\n", $lines) . "\n");
}
function editable_file($f){ $ext = strtolower(pathinfo((string)$f, PATHINFO_EXTENSION)); return $f !== '' && $f[0] !== '.' && $f !== 'data.txt' && in_array($ext, ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm','mp3','wav','m4a','aac','ogg','flac'], true); }
function natural_file_compare($a, $b){ return strnatcasecmp((string)$a, (string)$b); }
function list_files($dir){ $files = array_values(array_filter(scandir($dir), fn($f)=>editable_file($f) && is_file($dir.'/'.$f))); usort($files, 'natural_file_compare'); return $files; }
function media_sort_files($dir, $files){
    usort($files, function($a, $b) use ($dir) {
        $da = media_file_date($dir . '/' . $a) ?? date('Ymd', filemtime($dir . '/' . $a));
        $db = media_file_date($dir . '/' . $b) ?? date('Ymd', filemtime($dir . '/' . $b));
        return $da === $db ? natural_file_compare($a, $b) : strcmp($da, $db);
    });
    return $files;
}
function media_file_date($path){
    $ext = strtolower(pathinfo((string)$path, PATHINFO_EXTENSION));
    if (in_array($ext, ['jpg','jpeg','tif','tiff'], true) && function_exists('exif_read_data')) {
        $exif = @exif_read_data($path);
        foreach (['DateTimeOriginal','DateTimeDigitized','DateTime'] as $k) {
            if (!empty($exif[$k]) && preg_match('/^(\d{4}):(\d{2}):(\d{2})/', (string)$exif[$k], $m)) return "$m[1]$m[2]$m[3]";
        }
    }
    if (in_array($ext, ['mp4','mov','m4v','avi','mkv','webm'], true)) {
        $ffprobe = trim((string)shell_exec('command -v ffprobe 2>/dev/null'));
        if ($ffprobe !== '') {
            $json = shell_exec(escapeshellarg($ffprobe).' -v quiet -print_format json -show_entries format_tags=creation_time:stream_tags=creation_time '.escapeshellarg($path).' 2>/dev/null');
            $data = json_decode((string)$json, true);
            $candidates = [];
            if (is_array($data['format']['tags'] ?? null)) $candidates[] = $data['format']['tags']['creation_time'] ?? '';
            foreach (($data['streams'] ?? []) as $stream) if (is_array($stream['tags'] ?? null)) $candidates[] = $stream['tags']['creation_time'] ?? '';
            foreach ($candidates as $value) {
                if (preg_match('/^(\d{4})-(\d{2})-(\d{2})/', (string)$value, $m)) return "$m[1]$m[2]$m[3]";
            }
        }
    }
    return null;
}
function input_dir_date_from_media($dir){
    $dates = [];
    foreach (list_files($dir) as $file) {
        $date = media_file_date($dir . '/' . $file);
        if ($date !== null) $dates[] = $date;
    }
    if ($dates) {
        sort($dates, SORT_STRING);
        return $dates[0];
    }
    return date('Ymd');
}
function input_dir_date_from_text($text){
    $text = trim((string)$text);
    if ($text === '') return null;
    if (preg_match('/^(\d{4})[.-]?(\d{2})[.-]?(\d{2})$/', $text, $m) && checkdate((int)$m[2], (int)$m[3], (int)$m[1])) return "$m[1]$m[2]$m[3]";
    if (preg_match('/^(\d{1,2})[\/. -](\d{1,2})[\/. -](\d{4})$/', $text, $m) && checkdate((int)$m[2], (int)$m[1], (int)$m[3])) return sprintf('%04d%02d%02d', $m[3], $m[2], $m[1]);
    $ts = strtotime($text);
    if ($ts !== false) return date('Ymd', $ts);
    if (preg_match('/^(jan|january|feb|february|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)\s+(\d{4})$/i', $text, $m)) {
        $months = ['jan'=>1,'january'=>1,'feb'=>2,'february'=>2,'mar'=>3,'march'=>3,'apr'=>4,'april'=>4,'may'=>5,'jun'=>6,'june'=>6,'jul'=>7,'july'=>7,'aug'=>8,'august'=>8,'sep'=>9,'sept'=>9,'september'=>9,'oct'=>10,'october'=>10,'nov'=>11,'november'=>11,'dec'=>12,'december'=>12];
        return sprintf('%04d%02d01', (int)$m[2], $months[strtolower($m[1])]);
    }
    return null;
}
function unique_input_dir($base, $slug){
    $dir = $base . '/' . $slug;
    if (!file_exists($dir)) return $dir;
    for ($i = 2; ; $i++) {
        $candidate = $base . '/' . $slug . '-' . $i;
        if (!file_exists($candidate)) return $candidate;
    }
}
function input_dir_signature($dir){
    $entries = [];
    foreach (scandir($dir) ?: [] as $file) {
        if ($file === '.' || $file === '..' || in_array($file, ['.movmaker-state.json','.movmaker-enabled','.movmaker-preview','.mvlog-hidden'], true)) continue;
        if ($file === '.movmaker-lock') continue;
        $path = $dir . '/' . $file;
        if (is_file($path)) $entries[] = [$file, filesize($path), filemtime($path)];
    }
    sort($entries);
    return hash('sha256', json_encode($entries, JSON_UNESCAPED_UNICODE));
}
function input_dir_info($dir){
    $data = read_data($dir);
    $files = list_files($dir);
    return [
        'name' => basename($dir),
        'title' => $data['title'] ?: basename($dir),
        'sort_date' => input_dir_date_from_text($data['date'] ?? '') ?? input_dir_date_from_media($dir),
        'file_count' => count($files),
        'signature' => input_dir_signature($dir),
    ];
}
function load_input_dir_cache($dirs){
    $cacheFile = __DIR__ . '/cache/input-dirs.json';
    $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : [];
    if (!is_array($cache) || ($cache['version'] ?? 0) !== 1) $cache = ['version'=>1,'items'=>[]];
    $oldItems = is_array($cache['items'] ?? null) ? $cache['items'] : [];
    $items = []; $changed = false;
    foreach ($dirs as $dir) {
        $name = basename($dir);
        $sig = input_dir_signature($dir);
        $cached = $oldItems[$name] ?? null;
        if (is_array($cached) && ($cached['signature'] ?? '') === $sig) {
            $items[$name] = $cached;
        } else {
            $items[$name] = input_dir_info($dir);
            $changed = true;
        }
    }
    if (array_diff(array_keys($oldItems), array_keys($items))) $changed = true;
    if ($changed) file_put_contents($cacheFile, json_encode(['version'=>1,'generated_at'=>date('c'),'items'=>$items], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
    return $items;
}
function sort_input_dirs_by_metadata_date(&$dirs, $dirInfo){
    usort($dirs, function($a, $b) use ($dirInfo) {
        $ad = $dirInfo[basename($a)]['sort_date'] ?? '00000000';
        $bd = $dirInfo[basename($b)]['sort_date'] ?? '00000000';
        $cmp = strcmp($bd, $ad);
        return $cmp !== 0 ? $cmp : strnatcasecmp(basename($b), basename($a));
    });
}
function orphan_videos($config){
    $referenced = [];
    foreach (input_dirs($config['uploads_dir']) as $dir) {
        $stateFile = $dir . '/.movmaker-state.json';
        $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
        if (is_array($state) && !empty($state['output'])) $referenced[basename((string)$state['output'])] = true;
    }
    $videos = glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: [];
    return array_values(array_filter($videos, fn($v)=>empty($referenced[basename($v)])));
}
function input_dir_status($dir){
    $stateFile = $dir . '/.movmaker-state.json';
    $lockDir = $dir . '/.movmaker-lock';
    $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
    $status = is_array($state) ? (string)($state['status'] ?? '') : '';
    if (is_dir($lockDir)) return $status !== '' ? $status : 'processing';
    return in_array($status, ['processing', 'stale', 'error'], true) ? $status : '';
}
function input_dir_running($dir){
    $stateFile = $dir . '/.movmaker-state.json';
    $lockDir = $dir . '/.movmaker-lock';
    $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
    $status = is_array($state) ? (string)($state['status'] ?? '') : '';
    return is_dir($lockDir) || $status === 'processing';
}
function input_dir_enabled($dir){ return is_file($dir . '/.movmaker-enabled'); }
function set_input_dir_enabled($dir, $enabled){
    $path = $dir . '/.movmaker-enabled';
    if ($enabled) { file_put_contents($path, "enabled\n"); chmod($path, 0664); }
    elseif (is_file($path)) unlink($path);
}
function input_dir_preview($dir){ return is_file($dir . '/.movmaker-preview'); }
function set_input_dir_preview($dir, $preview){
    $path = $dir . '/.movmaker-preview';
    if ($preview) { file_put_contents($path, "preview\n"); chmod($path, 0664); }
    elseif (is_file($path)) unlink($path);
}
function input_dir_visible($dir){ return !is_file($dir . '/.mvlog-hidden'); }
function set_input_dir_visible($dir, $visible){
    $path = $dir . '/.mvlog-hidden';
    if ($visible) { if (is_file($path)) unlink($path); }
    else { file_put_contents($path, "hidden\n"); chmod($path, 0664); }
}
function input_dir_state($dir){
    $stateFile = $dir . '/.movmaker-state.json';
    $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
    return is_array($state) ? $state : [];
}
function input_dir_output($dir){
    $state = input_dir_state($dir);
    return basename((string)($state['output'] ?? ''));
}
function cached_video_metadata($output, $fallback){
    $meta = ['title'=>$fallback['title'] ?? '', 'date'=>$fallback['date'] ?? '', 'location'=>$fallback['place'] ?? '', 'description'=>$fallback['description'] ?? ''];
    $cacheFile = __DIR__ . '/cache/videos.json';
    $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : [];
    $cached = $cache['items'][$output]['metadata'] ?? null;
    if (is_array($cached)) {
        foreach (['title','date','location','description'] as $k) if (!empty($cached[$k])) $meta[$k] = $cached[$k];
    }
    return $meta;
}
function paginate($items, $page, $perPage = 8){
    $total = count($items);
    $pages = max(1, (int)ceil($total / $perPage));
    $page = max(1, min((int)$page, $pages));
    return [$page, $pages, array_slice($items, ($page - 1) * $perPage, $perPage), $total];
}
function page_links($tab, $page, $pages){
    if ($pages <= 1) return '';
    $html = '<div class="pages">';
    for ($i = 1; $i <= $pages; $i++) {
        $class = $i === $page ? 'active' : '';
        $html .= '<a class="'.$class.'" href="new.php?tab='.rawurlencode($tab).'&page='.$i.'">'.$i.'</a>';
    }
    return $html . '</div>';
}
function save_uploads($field, $dest, $allowed){
    if (empty($_FILES[$field])) return [];
    $files = $_FILES[$field]; $saved = [];
    $count = is_array($files['name']) ? count($files['name']) : 0;
    for ($i=0; $i<$count; $i++) {
        if ($files['error'][$i] === UPLOAD_ERR_NO_FILE) continue;
        if ($files['error'][$i] !== UPLOAD_ERR_OK) throw new RuntimeException("Upload failed: ".$files['name'][$i]);
        $ext = strtolower(pathinfo($files['name'][$i], PATHINFO_EXTENSION));
        if (!in_array($ext, $allowed, true)) throw new RuntimeException("File type not allowed: ".$files['name'][$i]);
        $base = preg_replace('/[^A-Za-z0-9._-]+/', '_', basename($files['name'][$i]));
        $target = $dest . '/' . sprintf('%03d_', count(glob($dest.'/*') ?: [])+count($saved)+1) . $base;
        if (!move_uploaded_file($files['tmp_name'][$i], $target)) throw new RuntimeException("Cannot save upload");
        $saved[] = basename($target);
    }
    return $saved;
}
$msg = $_GET['msg'] ?? null; $err = null;
$allowedMedia = ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm'];
$allowedAudio = ['mp3','wav','m4a','aac','ogg','flac'];
try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = $_POST['action'] ?? 'create';
        if ($action === 'create') {
            $title = trim($_POST['title'] ?? '');
            if ($title === '') throw new RuntimeException('Title is required.');
            $tmpSlug = '.upload-' . date('YmdHis') . '-' . bin2hex(random_bytes(4));
            $tmpDir = $config['uploads_dir'] . '/' . $tmpSlug;
            if (!mkdir($tmpDir, 0775, true)) throw new RuntimeException('Cannot create temporary input directory.');
            chmod($tmpDir, 02775);
            $media = save_uploads('media', $tmpDir, $allowedMedia);
            if (!$media) { rrmdir($tmpDir); throw new RuntimeException('Upload at least one image or video.'); }
            save_uploads('audio', $tmpDir, $allowedAudio);
            $mediaDate = input_dir_date_from_text($_POST['date'] ?? '') ?? input_dir_date_from_media($tmpDir);
            $slug = $mediaDate . '_' . slugify($title);
            $dir = unique_input_dir($config['uploads_dir'], $slug);
            $slug = basename($dir);
            if (!rename($tmpDir, $dir)) { rrmdir($tmpDir); throw new RuntimeException('Cannot create input directory.'); }
            chmod($dir, 02775);
            write_data($dir, $_POST);
            set_input_dir_enabled($dir, false);
            set_input_dir_preview($dir, false);
            set_input_dir_visible($dir, false);
            $msg = "Created movmaker input directory: in-dir/$slug";
            $_GET['edit'] = $slug;
        } elseif ($action === 'set_enabled') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            $enabledNow = !empty($_POST['enabled']);
            set_input_dir_enabled($dir, $enabledNow);
            if ($enabledNow) set_input_dir_preview($dir, false);
            $state = input_dir_state($dir);
            $output = basename((string)($state['output'] ?? ''));
            $canShow = $output !== '' && is_file($config['videos_dir'].'/'.$output) && !input_dir_preview($dir);
            if (!$canShow) set_input_dir_visible($dir, false);
            if (!empty($_POST['ajax'])) ajax_json(['ok'=>true, 'enabled'=>$enabledNow, 'preview'=>input_dir_preview($dir), 'visible'=>input_dir_visible($dir) && $canShow, 'can_show'=>$canShow, 'dir'=>basename($dir)]);
            header('Location: new.php?tab=edit&msg=' . rawurlencode(($enabledNow ? 'Enabled rendering for: in-dir/' : 'Disabled rendering for: in-dir/') . basename($dir)));
            exit;
        } elseif ($action === 'set_preview') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            $previewNow = !empty($_POST['preview']);
            set_input_dir_preview($dir, $previewNow);
            if ($previewNow) { set_input_dir_enabled($dir, false); set_input_dir_visible($dir, false); }
            $state = input_dir_state($dir);
            $output = basename((string)($state['output'] ?? ''));
            $canShow = $output !== '' && is_file($config['videos_dir'].'/'.$output) && !input_dir_preview($dir);
            if (!empty($_POST['ajax'])) ajax_json(['ok'=>true, 'preview'=>$previewNow, 'enabled'=>input_dir_enabled($dir), 'visible'=>input_dir_visible($dir) && $canShow, 'can_show'=>$canShow, 'dir'=>basename($dir)]);
            header('Location: new.php?tab=edit&msg=' . rawurlencode(($previewNow ? 'Enabled preview for: in-dir/' : 'Disabled preview for: in-dir/') . basename($dir)));
            exit;
        } elseif ($action === 'set_visible') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            $visibleNow = !empty($_POST['visible']);
            $state = input_dir_state($dir);
            $output = basename((string)($state['output'] ?? ''));
            $canShow = $output !== '' && is_file($config['videos_dir'].'/'.$output) && !input_dir_preview($dir);
            if ($visibleNow && !$canShow) throw new RuntimeException('Only finished full-quality videos can be shown.');
            set_input_dir_visible($dir, $visibleNow && $canShow);
            if (!empty($_POST['ajax'])) ajax_json(['ok'=>true, 'visible'=>input_dir_visible($dir) && $canShow, 'can_show'=>$canShow, 'dir'=>basename($dir)]);
            header('Location: new.php?tab=edit&msg=' . rawurlencode(($visibleNow ? 'Shown: in-dir/' : 'Hidden: in-dir/') . basename($dir)));
            exit;
        } elseif ($action === 'update') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            if (input_dir_running($dir)) throw new RuntimeException('This input directory is being rendered. Editing is disabled until the job completes.');
            write_data($dir, $_POST);
            save_uploads('media', $dir, $allowedMedia);
            save_uploads('audio', $dir, $allowedAudio);
            header('Location: new.php?tab=edit&msg=' . rawurlencode('Updated input directory: in-dir/' . basename($dir)));
            exit;
        } elseif ($action === 'delete_file') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            if (input_dir_running($dir)) throw new RuntimeException('This input directory is being rendered. Editing is disabled until the job completes.');
            $file = basename((string)($_POST['file'] ?? ''));
            if (!editable_file($file) || !is_file($dir.'/'.$file)) throw new RuntimeException('Invalid file.');
            unlink($dir.'/'.$file);
            $msg = 'Removed file: ' . $file;
            $_GET['edit'] = basename($dir);
        } elseif ($action === 'delete_video') {
            $file = basename((string)($_POST['video'] ?? ''));
            $path = $config['videos_dir'] . '/' . $file;
            if ($file === '' || !is_file($path)) throw new RuntimeException('Invalid video.');
            unlink($path);
            $cache = __DIR__ . '/cache/videos.json';
            if (is_file($cache)) unlink($cache);
            $msg = 'Deleted video: ' . $file;
        } elseif ($action === 'delete_dir') {
            $dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
            if (input_dir_running($dir)) throw new RuntimeException('This input directory is being rendered. Editing is disabled until the job completes.');
            $name = basename($dir);
            $stateFile = $dir . '/.movmaker-state.json';
            $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
            $output = is_array($state) ? basename((string)($state['output'] ?? '')) : '';
            if (!empty($_POST['delete_output']) && $output !== '') {
                $outPath = $config['videos_dir'] . '/' . $output;
                if (is_file($outPath)) unlink($outPath);
                $cache = __DIR__ . '/cache/videos.json';
                if (is_file($cache)) unlink($cache);
            }
            rrmdir($dir);
            header('Location: new.php?msg=' . rawurlencode('Deleted input directory: in-dir/' . $name));
            exit;
        }
    }
} catch (Throwable $e) {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['ajax'])) {
        http_response_code(400);
        ajax_json(['ok'=>false, 'error'=>$e->getMessage()]);
    }
    $err = $e->getMessage();
}
$dirs = input_dirs($config['uploads_dir']);
$dirInfo = load_input_dir_cache($dirs);
sort_input_dirs_by_metadata_date($dirs, $dirInfo);
$orphanVideos = orphan_videos($config);
$tab = $_GET['tab'] ?? (isset($_GET['edit']) ? 'edit' : 'new');
$page = max(1, (int)($_GET['page'] ?? 1));
[$editPage, $editPages, $dirsPage, $dirsTotal] = paginate($dirs, $tab === 'edit' ? $page : 1);
[$videoPage, $videoPages, $orphanVideosPage, $orphanVideosTotal] = paginate($orphanVideos, $tab === 'videos' ? $page : 1);
$editName = $_GET['edit'] ?? '';
$editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $editFiles = [];
if ($editName !== '') { try { $editDir = safe_input_dir($config['uploads_dir'], $editName); $editStatus = input_dir_status($editDir); $editRunning = input_dir_running($editDir); if (!$editRunning) { $editData = read_data($editDir); $editFiles = media_sort_files($editDir, list_files($editDir)); } } catch (Throwable $e) { $err = $e->getMessage(); } }
?>
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Admin - <?=h($config['site_name'])?></title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css"></head><body>
<header class="site-header admin-header"><div class="admin-left"><nav class="tabs"><a class="<?= $tab==='new'?'active':'' ?>" href="new.php?tab=new">New</a><a class="<?= $tab==='edit'?'active':'' ?>" href="new.php?tab=edit">Edit</a><a class="<?= $tab==='videos'?'active':'' ?>" href="new.php?tab=videos">Videos</a></nav></div><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1>MVLog Admin</h1><p>Bubulescu.Org</p></a></div><div class="admin-right"><div id="job-status" class="job-status" hidden></div><a class="logout-link" href="logout.php">Log off</a></div></header><main>
<?php if($msg): ?><div class="ok"><?=h($msg)?></div><?php endif; ?><?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>

<?php if($tab==='videos'): ?><section><h2>Videos without input dir</h2><?php if(!$orphanVideos): ?><p>No orphan videos.</p><?php endif; ?><div class="admin-list"><?php foreach($orphanVideosPage as $v): $vn=basename($v); ?><form method="post" class="admin-item" onsubmit="return confirm('Delete this generated video?')"><div><strong><?=h($vn)?></strong><br><span><?=h(round(filesize($v)/1048576,1))?> MB</span></div><input type="hidden" name="action" value="delete_video"><input type="hidden" name="video" value="<?=h($vn)?>"><button type="submit">Delete video</button></form><?php endforeach; ?></div><?=page_links('videos',$videoPage,$videoPages)?></section><?php endif; ?>
<?php if($tab==='edit'): ?><section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $preview=input_dir_preview($d); $state=input_dir_state($d); $fullOutput=basename((string)($state['output'] ?? '')); $previewOutput=basename((string)($state['preview_output'] ?? '')); $hasFullVideo=$fullOutput !== '' && is_file($config['videos_dir'].'/'.$fullOutput); $hasPreviewVideo=$previewOutput !== '' && is_file($config['videos_dir'].'/'.$previewOutput); $displayOutput=($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : '')); $hasVideo=$displayOutput !== ''; $displayVideoPath=$hasVideo ? $config['videos_dir'].'/'.$displayOutput : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $meta=cached_video_metadata($displayOutput, $data); ?><article class="video-row admin-video-row"><div><?php if($hasVideo): ?><video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"></video><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches"><form method="post" class="inline-form"><input type="hidden" name="action" value="set_preview"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="preview" value="1" <?=$preview?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Preview</span></label></form><form method="post" class="inline-form"><input type="hidden" name="action" value="set_enabled"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="enabled" value="1" <?=$enabled?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Run</span></label></form><form method="post" class="inline-form"><input type="hidden" name="action" value="set_visible"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="visible" value="1" <?=$visible?'checked':''?> <?=!$canShow?'disabled':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Show</span></label></form><?php if($hasVideo): ?><time class="admin-time" title="Video created at"><?=h(date('d.m.Y H:i', filemtime($displayVideoPath)))?></time><?php endif; ?></div><div class="admin-actions"><?php if($running): ?><span class="button disabled" title="Rendering now" data-edit-action="1">Rendering</span><?php else: ?><a class="button" href="?edit=<?=rawurlencode(basename($d))?>" data-edit-action="1" data-edit-href="?edit=<?=rawurlencode(basename($d))?>">Edit</a><?php endif; ?></div></div><h2><?=h($meta['title'] ?: ($info['title'] ?? basename($d)))?></h2><p class="meta"><?php if($meta['date']): ?><span><?=h($meta['date'])?></span><?php endif; ?><?php if($meta['location']): ?><span><?=h($meta['location'])?></span><?php endif; ?><?php if(!$hasVideo): ?><span>No video</span><?php endif; ?><?php if($previewVideo): ?><span>Preview video</span><?php endif; ?></p><?php if($meta['description']): ?><p class="description"><?=nl2br(h($meta['description']), false)?></p><?php endif; ?><p class="details"><?=h(basename($d))?> · <?=h($info['file_count'] ?? count(list_files($d)))?> files · <?= $enabled ? 'Rendering enabled' : 'Rendering disabled' ?> · <?= $preview ? 'Preview enabled' : 'Preview disabled' ?> · <?= $visible ? 'Shown' : 'Hidden' ?><?= $running ? ' · '.h(ucfirst($runStatus)) : '' ?></p></div></article><?php endforeach; ?></div><?=page_links('edit',$editPage,$editPages)?></section><?php endif; ?>
<?php if($tab==='edit' && $editDir && $editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><div class="err">This input directory is <?=h($editStatus)?>. Editing is disabled until the job completes.</div></section><?php endif; ?>
<?php if($tab==='edit' && $editDir && !$editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-edit"><?=h($editData['description'])?></textarea><small id="description-counter-edit" class="counter"></small></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><h3>Files and captions</h3><div class="file-list"><?php foreach($editFiles as $f): $ext=strtolower(pathinfo($f, PATHINFO_EXTENSION)); $fileUrl='in-dir/'.rawurlencode(basename($editDir)).'/'.rawurlencode($f); ?><div class="caption-row"><div class="preview"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><img src="<?=h($fileUrl)?>" alt=""><?php elseif(in_array($ext,['mp4','mov','m4v','webm'])): ?><video src="<?=h($fileUrl)?>" muted preload="metadata"></video><?php else: ?><span><?=h(strtoupper($ext ?: 'FILE'))?></span><?php endif; ?></div><div class="caption-fields"><strong><?=h($f)?></strong><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><input type="hidden" name="caption_files[]" value="<?=h($f)?>"><textarea name="captions[]" placeholder="Optional caption for this file"><?=h($editData['captions'][$f] ?? '')?></textarea><?php if(in_array($ext,['mp4','mov','m4v','webm'])): ?><label class="checkbox-label"><input type="checkbox" name="use_audio_files[]" value="<?=h($f)?>" <?=!empty($editData['video_audio'][$f])?'checked':''?>> <span>Use video file audio</span></label><?php endif; ?><?php else: ?><small>No caption for audio files</small><?php endif; ?></div><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div><?php endforeach; ?></div><button type="submit">Save changes</button></form><form method="post" id="delete-file-form" style="display:none"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="file" id="delete-file-name" value=""></form><form method="post" onsubmit="return confirm('Delete this input directory?')"><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label class="checkbox-label"><input type="checkbox" name="delete_output" value="1"> <span>Also delete generated video, if any</span></label><button class="danger" type="submit">Delete input dir</button></form></section><?php endif; ?>
<?php if($tab==='new'): ?><section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-new"></textarea><small id="description-counter-new" class="counter"></small></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section><?php endif; ?>
<script>function deleteFile(name){if(confirm('Remove this file?')){document.getElementById('delete-file-name').value=name;document.getElementById('delete-file-form').submit();}}</script>
<script>
function showToast(message, ok){
  let wrap=document.getElementById('toast-wrap');
  if(!wrap){
    wrap=document.createElement('div');
    wrap.id='toast-wrap';
    wrap.className='toast-wrap';
    document.body.appendChild(wrap);
  }
  const toast=document.createElement('div');
  toast.className='toast '+(ok?'ok-toast':'err-toast');
  toast.textContent=message;
  wrap.appendChild(toast);
  requestAnimationFrame(function(){toast.classList.add('show');});
  setTimeout(function(){
    toast.classList.remove('show');
    setTimeout(function(){toast.remove();},250);
  },3200);
}
document.querySelectorAll('.admin-switches input[type="checkbox"]').forEach(function(input){
  input.addEventListener('change', async function(event){
    event.preventDefault();
    const form=input.form;
    const previous=!input.checked;
    const data=new FormData(form);
    data.set('ajax','1');
    input.disabled=true;
    try{
      const res=await fetch('new.php',{method:'POST',body:data,credentials:'same-origin',headers:{'Accept':'application/json'}});
      const json=await res.json().catch(function(){return {};});
      if(!res.ok || !json.ok) throw new Error(json.error || 'Switch update failed');
      const action=data.get('action');
      const titleEl=input.closest('.video-info')?.querySelector('h2');
      const title=(titleEl?.textContent || '').trim() || json.dir || data.get('dir') || 'input dir';
      const quotedTitle="'" + title + "'";if(action==='set_visible') showToast((input.checked?'Shown: ':'Hidden: ')+quotedTitle, true);
      else if(action==='set_enabled') showToast((input.checked?'Run enabled: ':'Run disabled: ')+quotedTitle, true);
      else if(action==='set_preview') showToast((input.checked?'Preview enabled: ':'Preview disabled: ')+quotedTitle, true);
      else showToast('Updated '+quotedTitle, true);
    }catch(e){
      input.checked=previous;
      showToast(e.message || 'Switch update failed', false);
    }finally{
      input.disabled=false;
    }
  });
});
</script>
<script>
(function(){
  const box=document.getElementById('job-status');
  if(!box) return;
  function fmt(job){
    const name=(job.name||'job').replace(/_/g,' ');
    return '<span class="job-dot"></span><span>'+name+'</span>';
  }
  function syncEditButtons(runningJobs){
    document.querySelectorAll('.admin-video-row[data-job]').forEach(function(row){
      const job=row.dataset.job || '';
      const edit=row.querySelector('[data-edit-action="1"]');
      if(!edit) return;
      const running=runningJobs.has(job);
      if(edit.tagName === 'A'){
        if(running){
          if(!edit.dataset.editHref) edit.dataset.editHref = edit.getAttribute('href') || '';
          edit.removeAttribute('href');
          edit.classList.add('disabled');
          edit.setAttribute('aria-disabled', 'true');
          edit.title='Rendering now';
        }else{
          const href=edit.dataset.editHref || ('?edit=' + encodeURIComponent(job));
          edit.setAttribute('href', href);
          edit.classList.remove('disabled');
          edit.removeAttribute('aria-disabled');
          edit.title='Edit';
        }
      }else{
        edit.classList.toggle('disabled', running);
        edit.title=running ? 'Rendering now' : '';
      }
    });
  }
  async function updateJobs(){
    try{
      const r=await fetch('job_status.php',{cache:'no-store'});
      if(!r.ok) throw new Error('status failed');
      const data=await r.json();
      const jobs=data.jobs||[];
      const runningJobs=new Set(jobs.map(function(job){ return job && job.name ? String(job.name) : ''; }).filter(Boolean));
      syncEditButtons(runningJobs);
      if(!jobs.length){box.hidden=true;box.innerHTML='';return;}
      box.hidden=false;
      box.innerHTML='<strong>Running</strong>'+jobs.map(fmt).join('');
    }catch(e){box.hidden=true;}
  }
  updateJobs();
  setInterval(updateJobs,30000);
})();
</script></main><footer>Input dirs are saved under <code>in-dir/</code>.</footer><script>document.querySelectorAll('textarea[data-counter]').forEach(function(t){var c=document.getElementById(t.dataset.counter);function u(){c.textContent=t.value.length+' / '+t.maxLength+' characters';}t.addEventListener('input',u);u();});</script></body></html>
