<?php
        declare(strict_types=1);

        require_once __DIR__ . '/db/config.php';

        const APP_MAX_UPLOAD_MB = 512;
        const APP_RETENTION_HOURS = 24;
        const APP_UPLOAD_DIR = __DIR__ . '/var/uploads';
        const APP_OUTPUT_DIR = __DIR__ . '/var/converted';
        const APP_MIGRATION_FILE = __DIR__ . '/db/migrations/20260419_create_video_conversion_jobs.sql';

        function project_name(): string
        {
            return trim((string)($_SERVER['PROJECT_NAME'] ?? 'FormatShift')) ?: 'FormatShift';
        }

        function project_description(): string
        {
            return trim((string)($_SERVER['PROJECT_DESCRIPTION'] ?? 'Server-side video, social export, and subtitle conversion tools.'))
                ?: 'Server-side video, social export, and subtitle conversion tools.';
        }

        function page_meta(string $fallbackTitle, string $fallbackDescription): array
        {
            return [
                'title' => trim((string)($_SERVER['PROJECT_NAME'] ?? '')) ?: $fallbackTitle,
                'description' => trim((string)($_SERVER['PROJECT_DESCRIPTION'] ?? '')) ?: $fallbackDescription,
                'image' => trim((string)($_SERVER['PROJECT_IMAGE_URL'] ?? '')),
            ];
        }

        function base_url(): string
        {
            $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
            $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
            return $scheme . '://' . $host;
        }

        function ffmpeg_binary(): ?string
        {
            static $path = null;
            if ($path !== null) {
                return $path;
            }

            $output = [];
            $code = 0;
            @exec('command -v ffmpeg 2>/dev/null', $output, $code);
            $resolved = trim((string)($output[0] ?? ''));
            $path = ($code === 0 && $resolved !== '') ? $resolved : null;
            return $path;
        }

        function ffmpeg_is_available(): bool
        {
            return ffmpeg_binary() !== null;
        }

        function ensure_runtime_dirs(): void
        {
            foreach ([APP_UPLOAD_DIR, APP_OUTPUT_DIR] as $dir) {
                if (!is_dir($dir)) {
                    mkdir($dir, 0775, true);
                }
            }
        }

        function ensure_video_jobs_table(): void
        {
            static $ready = false;
            if ($ready) {
                return;
            }

            $sql = file_get_contents(APP_MIGRATION_FILE);
            if ($sql === false) {
                throw new RuntimeException('Failed to load migration SQL.');
            }

            db()->exec($sql);
            ensure_video_job_columns();
            $ready = true;
        }

        function ensure_video_job_columns(): void
        {
            $columns = [
                'tool_key' => "VARCHAR(40) NOT NULL DEFAULT 'webm_mp4'",
                'source_format' => 'VARCHAR(32) DEFAULT NULL',
                'target_format' => 'VARCHAR(32) DEFAULT NULL',
                'preset_key' => 'VARCHAR(40) DEFAULT NULL',
                'output_mime' => 'VARCHAR(100) DEFAULT NULL',
                'download_name' => 'VARCHAR(255) DEFAULT NULL',
            ];

            foreach ($columns as $column => $definition) {
                ensure_column_exists('video_jobs', $column, $definition);
            }
        }

        function ensure_column_exists(string $table, string $column, string $definition): void
        {
            $stmt = db()->prepare('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = :schema_name AND TABLE_NAME = :table_name AND COLUMN_NAME = :column_name');
            $stmt->bindValue(':schema_name', DB_NAME, PDO::PARAM_STR);
            $stmt->bindValue(':table_name', $table, PDO::PARAM_STR);
            $stmt->bindValue(':column_name', $column, PDO::PARAM_STR);
            $stmt->execute();

            if ((int) $stmt->fetchColumn() > 0) {
                return;
            }

            db()->exec(sprintf('ALTER TABLE `%s` ADD COLUMN `%s` %s', $table, $column, $definition));
        }

        function cleanup_expired_jobs(): void
        {
            static $done = false;
            if ($done) {
                return;
            }
            $done = true;

            ensure_runtime_dirs();
            ensure_video_jobs_table();

            $stmt = db()->prepare('SELECT id, input_path, output_path FROM video_jobs WHERE created_at < (UTC_TIMESTAMP() - INTERVAL :hours HOUR)');
            $stmt->bindValue(':hours', APP_RETENTION_HOURS, PDO::PARAM_INT);
            $stmt->execute();
            $jobs = $stmt->fetchAll();

            foreach ($jobs as $job) {
                foreach (['input_path', 'output_path'] as $field) {
                    $path = (string)($job[$field] ?? '');
                    if ($path !== '' && is_file($path)) {
                        @unlink($path);
                    }
                }
            }

            $delete = db()->prepare('DELETE FROM video_jobs WHERE created_at < (UTC_TIMESTAMP() - INTERVAL :hours HOUR)');
            $delete->bindValue(':hours', APP_RETENTION_HOURS, PDO::PARAM_INT);
            $delete->execute();
        }

        function app_boot(): void
        {
            ensure_runtime_dirs();
            ensure_video_jobs_table();
            cleanup_expired_jobs();
        }

        function format_bytes(?int $bytes): string
        {
            if (!$bytes || $bytes < 1) {
                return '—';
            }
            $units = ['B', 'KB', 'MB', 'GB'];
            $power = min((int) floor(log($bytes, 1024)), count($units) - 1);
            $value = $bytes / (1024 ** $power);
            return number_format($value, $power === 0 ? 0 : 1) . ' ' . $units[$power];
        }

        function format_datetime(?string $value): string
        {
            if (!$value) {
                return '—';
            }
            try {
                return (new DateTimeImmutable($value, new DateTimeZone('UTC')))->format('M j, Y · H:i') . ' UTC';
            } catch (Throwable $e) {
                return $value;
            }
        }

        function parse_ini_size_to_bytes(string $value): int
        {
            $value = trim($value);
            if ($value === '') {
                return 0;
            }

            $number = (float) $value;
            $unit = strtolower(substr($value, -1));

            return match ($unit) {
                'g' => (int) round($number * 1024 * 1024 * 1024),
                'm' => (int) round($number * 1024 * 1024),
                'k' => (int) round($number * 1024),
                default => (int) round($number),
            };
        }

        function php_upload_limit_bytes(): int
        {
            $uploadMax = parse_ini_size_to_bytes((string) ini_get('upload_max_filesize'));
            $postMax = parse_ini_size_to_bytes((string) ini_get('post_max_size'));

            if ($uploadMax > 0 && $postMax > 0) {
                return min($uploadMax, $postMax);
            }

            return max($uploadMax, $postMax);
        }

        function effective_upload_limit_bytes(): int
        {
            $appLimit = APP_MAX_UPLOAD_MB * 1024 * 1024;
            $phpLimit = php_upload_limit_bytes();

            if ($phpLimit > 0) {
                return min($appLimit, $phpLimit);
            }

            return $appLimit;
        }

        function effective_upload_limit_mb(): int
        {
            return (int) ceil(effective_upload_limit_bytes() / (1024 * 1024));
        }

        function request_exceeds_upload_limit(): bool
        {
            $contentLength = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
            $limit = effective_upload_limit_bytes();

            return $limit > 0 && $contentLength > $limit && empty($_FILES);
        }

        function h(?string $value): string
        {
            return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        }

        function format_label(?string $value): string
        {
            $value = strtoupper((string) $value);
            return $value !== '' ? $value : '—';
        }

        function file_extension(string $filename): string
        {
            return strtolower((string) pathinfo($filename, PATHINFO_EXTENSION));
        }

        function status_badge_class(string $status): string
        {
            return match ($status) {
                'completed' => 'success',
                'failed' => 'danger',
                default => 'warning',
            };
        }

        function tool_catalog(): array
        {
            static $catalog = null;
            if ($catalog !== null) {
                return $catalog;
            }

            $catalog = [
                'webm_mp4' => [
                    'label' => 'WEBM → MP4',
                    'short_label' => 'WEBM to MP4',
                    'description' => 'Convert a WEBM upload into a playback-friendly MP4 for decks, CMS uploads, and cross-device sharing.',
                    'input_extensions' => ['webm'],
                    'output_extension' => 'mp4',
                    'output_mime' => 'video/mp4',
                    'requires_ffmpeg' => true,
                    'submit_label' => 'Convert to MP4',
                    'accept_summary' => 'Accepts WEBM video files.',
                    'presets' => [],
                ],
                'social_mp4' => [
                    'label' => 'Social export presets',
                    'short_label' => 'Social MP4',
                    'description' => 'Resize WEBM, MP4, or MOV clips into publish-ready MP4 exports for landscape, square, and vertical placements.',
                    'input_extensions' => ['webm', 'mp4', 'mov'],
                    'output_extension' => 'mp4',
                    'output_mime' => 'video/mp4',
                    'requires_ffmpeg' => true,
                    'submit_label' => 'Create social export',
                    'accept_summary' => 'Accepts WEBM, MP4, and MOV video files.',
                    'presets' => [
                        'landscape_16_9' => [
                            'label' => 'Landscape 16:9',
                            'description' => '1920 × 1080 for YouTube, decks, and standard ads.',
                            'suffix' => 'landscape-16x9',
                            'filter' => 'scale=w=1920:h=1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1',
                        ],
                        'square_1_1' => [
                            'label' => 'Square 1:1',
                            'description' => '1080 × 1080 for feeds and mixed placements.',
                            'suffix' => 'square-1x1',
                            'filter' => 'scale=w=1080:h=1080:force_original_aspect_ratio=decrease,pad=1080:1080:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1',
                        ],
                        'vertical_9_16' => [
                            'label' => 'Vertical 9:16',
                            'description' => '1080 × 1920 for Reels, Shorts, and story placements.',
                            'suffix' => 'vertical-9x16',
                            'filter' => 'scale=w=1080:h=1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1',
                        ],
                    ],
                ],
                'subtitle_convert' => [
                    'label' => 'Subtitle converter',
                    'short_label' => 'SRT ↔ VTT',
                    'description' => 'Convert subtitle assets between SRT and WebVTT without leaving the browser workflow.',
                    'input_extensions' => ['srt', 'vtt'],
                    'output_extension' => '',
                    'output_mime' => '',
                    'requires_ffmpeg' => false,
                    'submit_label' => 'Convert subtitles',
                    'accept_summary' => 'Accepts SRT and VTT subtitle files.',
                    'presets' => [],
                ],
            ];

            return $catalog;
        }

        function tool_option(string $toolKey): array
        {
            $catalog = tool_catalog();
            return $catalog[$toolKey] ?? $catalog['webm_mp4'];
        }

        function preset_option(string $toolKey, ?string $presetKey): ?array
        {
            if ($presetKey === null || $presetKey === '') {
                return null;
            }

            $tool = tool_option($toolKey);
            return $tool['presets'][$presetKey] ?? null;
        }

        function tool_accept_attribute(string $toolKey): string
        {
            $tool = tool_option($toolKey);
            $extensions = array_map(static fn(string $extension): string => '.' . $extension, $tool['input_extensions']);
            return implode(',', $extensions);
        }

        function tool_requires_ffmpeg(string $toolKey): bool
        {
            return !empty(tool_option($toolKey)['requires_ffmpeg']);
        }

        function subtitle_target_options(): array
        {
            return ['srt' => 'SRT', 'vtt' => 'VTT'];
        }

        function build_download_name(string $originalName, string $toolKey, string $targetFormat, ?string $presetKey = null): string
        {
            $base = preg_replace('/[^A-Za-z0-9._-]/', '-', (string) pathinfo($originalName, PATHINFO_FILENAME)) ?: 'converted-file';
            $suffix = '';

            if ($toolKey === 'social_mp4') {
                $preset = preset_option($toolKey, $presetKey);
                $suffix = '-' . ($preset['suffix'] ?? 'social-export');
            } elseif ($toolKey === 'subtitle_convert') {
                $suffix = '-converted';
            }

            return $base . $suffix . '.' . strtolower($targetFormat);
        }

        function fetch_recent_jobs(int $limit = 8): array
        {
            app_boot();
            $stmt = db()->prepare('SELECT id, public_id, original_name, status, input_size, output_size, created_at, completed_at, tool_key, source_format, target_format, preset_key, output_mime, download_name FROM video_jobs ORDER BY id DESC LIMIT :limit');
            $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
            $stmt->execute();
            return $stmt->fetchAll();
        }

        function find_job(string $publicId): ?array
        {
            app_boot();
            $stmt = db()->prepare('SELECT * FROM video_jobs WHERE public_id = :public_id LIMIT 1');
            $stmt->bindValue(':public_id', $publicId, PDO::PARAM_STR);
            $stmt->execute();
            $job = $stmt->fetch();
            return $job ?: null;
        }

        function notify_redirect(string $location, string $type, string $message): void
        {
            $qs = http_build_query(['notice' => $message, 'type' => $type]);
            $separator = str_contains($location, '?') ? '&' : '?';
            header('Location: ' . $location . $separator . $qs);
            exit;
        }

        function create_job_record(
            string $publicId,
            string $originalName,
            string $inputPath,
            int $inputSize,
            string $toolKey,
            string $sourceFormat,
            string $targetFormat,
            ?string $presetKey,
            ?string $outputMime,
            string $downloadName
        ): int {
            $stmt = db()->prepare('INSERT INTO video_jobs (public_id, original_name, input_path, input_size, status, tool_key, source_format, target_format, preset_key, output_mime, download_name) VALUES (:public_id, :original_name, :input_path, :input_size, :status, :tool_key, :source_format, :target_format, :preset_key, :output_mime, :download_name)');
            $stmt->bindValue(':public_id', $publicId, PDO::PARAM_STR);
            $stmt->bindValue(':original_name', $originalName, PDO::PARAM_STR);
            $stmt->bindValue(':input_path', $inputPath, PDO::PARAM_STR);
            $stmt->bindValue(':input_size', $inputSize, PDO::PARAM_INT);
            $stmt->bindValue(':status', 'processing', PDO::PARAM_STR);
            $stmt->bindValue(':tool_key', $toolKey, PDO::PARAM_STR);
            $stmt->bindValue(':source_format', $sourceFormat, PDO::PARAM_STR);
            $stmt->bindValue(':target_format', $targetFormat, PDO::PARAM_STR);
            $stmt->bindValue(':preset_key', $presetKey, $presetKey !== null ? PDO::PARAM_STR : PDO::PARAM_NULL);
            $stmt->bindValue(':output_mime', $outputMime, $outputMime !== null ? PDO::PARAM_STR : PDO::PARAM_NULL);
            $stmt->bindValue(':download_name', $downloadName, PDO::PARAM_STR);
            $stmt->execute();
            return (int) db()->lastInsertId();
        }

        function mark_job_completed(int $id, string $outputPath, int $outputSize): void
        {
            $stmt = db()->prepare('UPDATE video_jobs SET status = :status, output_path = :output_path, output_size = :output_size, completed_at = UTC_TIMESTAMP(), error_message = NULL WHERE id = :id');
            $stmt->bindValue(':status', 'completed', PDO::PARAM_STR);
            $stmt->bindValue(':output_path', $outputPath, PDO::PARAM_STR);
            $stmt->bindValue(':output_size', $outputSize, PDO::PARAM_INT);
            $stmt->bindValue(':id', $id, PDO::PARAM_INT);
            $stmt->execute();
        }

        function mark_job_failed(int $id, string $error): void
        {
            $stmt = db()->prepare('UPDATE video_jobs SET status = :status, error_message = :error_message WHERE id = :id');
            $stmt->bindValue(':status', 'failed', PDO::PARAM_STR);
            $stmt->bindValue(':error_message', mb_substr($error, 0, 1500), PDO::PARAM_STR);
            $stmt->bindValue(':id', $id, PDO::PARAM_INT);
            $stmt->execute();
        }

        function convert_media_to_mp4(string $inputPath, string $outputPath, ?string $videoFilter = null): array
        {
            $binary = ffmpeg_binary();
            if (!$binary) {
                return ['success' => false, 'message' => 'FFmpeg is not installed on this server yet.'];
            }

            $filterArg = $videoFilter ? ' -vf ' . escapeshellarg($videoFilter) : '';
            $cmd = sprintf(
                '%s -y -i %s%s -c:v libx264 -preset veryfast -crf 22 -movflags +faststart -pix_fmt yuv420p -c:a aac -b:a 128k %s 2>&1',
                escapeshellarg($binary),
                escapeshellarg($inputPath),
                $filterArg,
                escapeshellarg($outputPath)
            );

            $output = [];
            $code = 0;
            exec($cmd, $output, $code);
            $log = trim(implode(PHP_EOL, $output));

            if ($code !== 0 || !is_file($outputPath)) {
                return ['success' => false, 'message' => $log !== '' ? $log : 'Conversion failed.'];
            }

            return ['success' => true, 'message' => 'Conversion completed.'];
        }

        function normalize_newlines(string $content): string
        {
            $content = preg_replace('/^ï»¿/', '', $content) ?? $content;
            return str_replace(["
", "
"], "
", $content);
        }

        function convert_srt_content_to_vtt(string $content): string
        {
            $content = normalize_newlines($content);
            $content = preg_replace('/(\d{2}:\d{2}:\d{2}),(\d{3})/', '$1.$2', $content) ?? $content;
            return "WEBVTT

" . trim($content) . "
";
        }

        function convert_vtt_content_to_srt(string $content): string
        {
            $content = normalize_newlines($content);
            $content = preg_replace('/^WEBVTT\s*/', '', $content) ?? $content;
            $blocks = preg_split('/
{2,}/', trim($content)) ?: [];
            $cues = [];
            $counter = 1;

            foreach ($blocks as $block) {
                $lines = array_values(array_filter(explode("
", trim($block)), static fn(string $line): bool => trim($line) !== ''));
                if ($lines === []) {
                    continue;
                }

                $headline = strtoupper(trim($lines[0]));
                if (str_starts_with($headline, 'NOTE') || str_starts_with($headline, 'STYLE') || str_starts_with($headline, 'REGION')) {
                    continue;
                }

                if (count($lines) > 1 && !str_contains($lines[0], '-->') && str_contains($lines[1], '-->')) {
                    array_shift($lines);
                }

                $timecode = array_shift($lines);
                if ($timecode === null || !str_contains($timecode, '-->')) {
                    continue;
                }

                $timecode = preg_replace('/(\d{2}:\d{2}:\d{2})\.(\d{3})/', '$1,$2', $timecode) ?? $timecode;
                $cues[] = $counter . "
" . $timecode . "
" . implode("
", $lines);
                $counter++;
            }

            return $cues !== [] ? implode("

", $cues) . "
" : '';
        }

        function convert_subtitle_file(string $inputPath, string $outputPath, string $sourceFormat, string $targetFormat): array
        {
            $content = file_get_contents($inputPath);
            if ($content === false) {
                return ['success' => false, 'message' => 'Could not read the uploaded subtitle file.'];
            }

            $converted = match (strtolower($sourceFormat) . ':' . strtolower($targetFormat)) {
                'srt:vtt' => convert_srt_content_to_vtt($content),
                'vtt:srt' => convert_vtt_content_to_srt($content),
                default => null,
            };

            if ($converted === null || $converted === '') {
                return ['success' => false, 'message' => 'Unsupported subtitle conversion request.'];
            }

            if (file_put_contents($outputPath, $converted) === false) {
                return ['success' => false, 'message' => 'Could not write the converted subtitle file.'];
            }

            return ['success' => true, 'message' => 'Subtitle conversion completed.'];
        }

        function job_tool_key(array $job): string
        {
            $toolKey = (string)($job['tool_key'] ?? '');
            return array_key_exists($toolKey, tool_catalog()) ? $toolKey : 'webm_mp4';
        }

        function job_source_format(array $job): string
        {
            $source = strtolower((string)($job['source_format'] ?? ''));
            if ($source !== '') {
                return $source;
            }
            return file_extension((string)($job['original_name'] ?? 'video.webm')) ?: 'webm';
        }

        function job_target_format(array $job): string
        {
            $target = strtolower((string)($job['target_format'] ?? ''));
            if ($target !== '') {
                return $target;
            }
            return 'mp4';
        }

        function job_tool_label(array $job): string
        {
            return tool_option(job_tool_key($job))['label'];
        }

        function job_preset_label(array $job): string
        {
            $preset = preset_option(job_tool_key($job), (string)($job['preset_key'] ?? ''));
            return $preset['label'] ?? '';
        }

        function job_conversion_summary(array $job): string
        {
            $summary = format_label(job_source_format($job)) . ' → ' . format_label(job_target_format($job));
            $presetLabel = job_preset_label($job);
            if ($presetLabel !== '') {
                $summary .= ' · ' . $presetLabel;
            }
            return $summary;
        }

        function job_download_label(array $job): string
        {
            return 'Download ' . format_label(job_target_format($job));
        }

        function job_output_exists(array $job): bool
        {
            return !empty($job['output_path']) && is_file((string) $job['output_path']);
        }

        function job_download_name(array $job): string
        {
            $downloadName = trim((string)($job['download_name'] ?? ''));
            if ($downloadName !== '') {
                return $downloadName;
            }

            return build_download_name(
                (string)($job['original_name'] ?? 'converted-file'),
                job_tool_key($job),
                job_target_format($job),
                (string)($job['preset_key'] ?? '')
            );
        }

        function job_output_mime(array $job): string
        {
            $mime = trim((string)($job['output_mime'] ?? ''));
            if ($mime !== '') {
                return $mime;
            }

            return match (job_target_format($job)) {
                'vtt' => 'text/vtt',
                'srt' => 'application/x-subrip',
                default => 'video/mp4',
            };
        }
