<?php
declare(strict_types=1);

/**
 * /functional/post/_lib.php
 *
 * Спільна логіка створення постів:
 * - особисті пости
 * - пости в профілі
 * - пости в групах
 *
 * Відповідає за:
 * - нормалізацію тексту
 * - перевірку "пост не порожній"
 * - перевірку/збереження вкладень
 * - перевірку/збереження опитування
 * - транзакцію створення поста
 * - cleanup файлів при rollback
 * - безпечне нарахування VM Coins після успішного створення поста
 * - Stage 3:
 *   - коректне тихе ігнорування штатних причин відмови VM Coins
 *     (денний ліміт, антиспам, дубльований контент, занадто часті дії)
 *
 * SEO:
 * - це службова бібліотека, не публічна сторінка
 * - рендеру HTML немає
 */

$__vmCoinsLib = __DIR__ . '/../VMCoins/_lib.php';
if (is_file($__vmCoinsLib)) {
    require_once $__vmCoinsLib;
}
unset($__vmCoinsLib);

if (!function_exists('vm_post_t')) {
    function vm_post_t(string $uk, string $ru, string $en): string
    {
        $lang = function_exists('lang') ? (string)lang() : (string)($_SESSION['lang'] ?? 'uk');
        if (!in_array($lang, ['uk', 'ru', 'en'], true)) $lang = 'uk';

        return match ($lang) {
            'ru' => $ru,
            'en' => $en,
            default => $uk,
        };
    }
}

if (!function_exists('vm_post_safe_back')) {
    function vm_post_safe_back(string $raw, string $fallback = '/'): string
    {
        if (function_exists('safe_local_path')) {
            return (string)safe_local_path($raw, $fallback);
        }

        $raw = trim($raw);
        if ($raw === '') return $fallback;

        if (preg_match('~^https?://~i', $raw)) {
            $p = parse_url($raw);
            $path = (string)($p['path'] ?? '/');
            $q = isset($p['query']) && $p['query'] !== '' ? ('?' . $p['query']) : '';
            $raw = $path . $q;
        }

        if ($raw === '' || $raw[0] !== '/' || str_starts_with($raw, '//')) return $fallback;
        return $raw;
    }
}

if (!function_exists('vm_post_normalize_text')) {
    function vm_post_normalize_text(string $text): string
    {
        $text = str_replace("\r\n", "\n", $text);
        $text = str_replace("\r", "\n", $text);
        $text = trim($text);

        return $text;
    }
}

if (!function_exists('vm_post_strlen')) {
    function vm_post_strlen(string $s): int
    {
        return function_exists('mb_strlen')
            ? (int)mb_strlen($s, 'UTF-8')
            : strlen($s);
    }
}

if (!function_exists('vm_post_substr')) {
    function vm_post_substr(string $s, int $start, int $length): string
    {
        return function_exists('mb_substr')
            ? (string)mb_substr($s, $start, $length, 'UTF-8')
            : substr($s, $start, $length);
    }
}

if (!function_exists('vm_post_normalize_original_name')) {
    function vm_post_normalize_original_name(string $name): string
    {
        $name = trim($name);
        if ($name === '') return 'file';

        $name = preg_replace('~[\x00-\x1F\x7F]+~u', '', $name) ?: 'file';
        $name = trim($name);
        if ($name === '') return 'file';

        if (vm_post_strlen($name) > 180) {
            $name = vm_post_substr($name, 0, 180);
        }

        return $name;
    }
}

if (!function_exists('vm_post_upload_has_files')) {
    function vm_post_upload_has_files(array $files): bool
    {
        if (empty($files) || !isset($files['name'])) return false;

        if (is_array($files['name'])) {
            foreach ($files['name'] as $i => $n) {
                $err = (int)($files['error'][$i] ?? UPLOAD_ERR_NO_FILE);
                if ($err !== UPLOAD_ERR_NO_FILE) return true;
            }
            return false;
        }

        $err = (int)($files['error'] ?? UPLOAD_ERR_NO_FILE);
        return $err !== UPLOAD_ERR_NO_FILE;
    }
}

if (!function_exists('vm_post_upload_mime_group')) {
    function vm_post_upload_mime_group(string $mime): string
    {
        $mime = strtolower(trim($mime));

        if (str_starts_with($mime, 'image/')) return 'image';
        if (str_starts_with($mime, 'audio/')) return 'audio';
        if (str_starts_with($mime, 'video/')) return 'video';

        return 'file';
    }
}

if (!function_exists('vm_post_safe_ext')) {
    function vm_post_safe_ext(string $name): string
    {
        $ext = strtolower(pathinfo((string)$name, PATHINFO_EXTENSION));
        $ext = preg_replace('~[^a-z0-9]+~', '', $ext) ?: '';

        $bad = [
            'php','phtml','php3','php4','php5','php7','php8',
            'phar','cgi','pl','py','rb','sh','bash',
            'asp','aspx','jsp','js','exe','com','dll'
        ];

        if ($ext !== '' && in_array($ext, $bad, true)) return '';
        return $ext;
    }
}

if (!function_exists('vm_post_allowed_file')) {
    function vm_post_allowed_file(string $group, string $mime, string $ext, int $size): array
    {
        $limits = [
            'image' => 20 * 1024 * 1024,
            'audio' => 50 * 1024 * 1024,
            'video' => 100 * 1024 * 1024,
            'file'  => 30 * 1024 * 1024,
        ];

        $max = $limits[$group] ?? (10 * 1024 * 1024);
        if ($size <= 0 || $size > $max) {
            return ['ok' => false, 'err' => function_exists('t') ? t('upload_too_big') : vm_post_t('Файл завеликий.', 'Файл слишком большой.', 'File is too large.')];
        }

        $mime = strtolower(trim($mime));
        $ext  = strtolower(trim($ext));

        if ($group === 'image' && ($mime === 'image/svg+xml' || $ext === 'svg')) {
            return ['ok' => false, 'err' => function_exists('t') ? t('upload_type_bad') : vm_post_t('Неприпустимий тип файлу.', 'Недопустимый тип файла.', 'File type not allowed.')];
        }

        if ($group === 'image') {
            $okExt = ['jpg','jpeg','png','webp','gif','bmp'];
            if ($ext !== '' && !in_array($ext, $okExt, true)) {
                return ['ok' => false, 'err' => function_exists('t') ? t('upload_type_bad') : vm_post_t('Неприпустимий тип файлу.', 'Недопустимый тип файла.', 'File type not allowed.')];
            }
            return ['ok' => true, 'err' => ''];
        }

        if ($group === 'audio') {
            $okExt = ['mp3','wav','ogg','m4a','aac','flac'];
            if ($ext !== '' && !in_array($ext, $okExt, true)) {
                return ['ok' => false, 'err' => function_exists('t') ? t('upload_type_bad') : vm_post_t('Неприпустимий тип файлу.', 'Недопустимый тип файла.', 'File type not allowed.')];
            }
            return ['ok' => true, 'err' => ''];
        }

        if ($group === 'video') {
            $okExt = ['mp4','mov','webm','mkv','avi','m4v'];
            if ($ext !== '' && !in_array($ext, $okExt, true)) {
                return ['ok' => false, 'err' => function_exists('t') ? t('upload_type_bad') : vm_post_t('Неприпустимий тип файлу.', 'Недопустимый тип файла.', 'File type not allowed.')];
            }
            return ['ok' => true, 'err' => ''];
        }

        $allowedExt = [
            'pdf','zip','rar','7z','txt','doc','docx','xls','xlsx',
            'png','jpg','jpeg','webp','gif',
            'mp3','wav','ogg','m4a',
            'mp4','mov','webm','mkv'
        ];

        if ($ext !== '' && in_array($ext, $allowedExt, true)) {
            return ['ok' => true, 'err' => ''];
        }

        return ['ok' => false, 'err' => function_exists('t') ? t('upload_type_bad') : vm_post_t('Неприпустимий тип файлу.', 'Недопустимый тип файла.', 'File type not allowed.')];
    }
}

if (!function_exists('vm_post_ensure_dir')) {
    function vm_post_ensure_dir(string $dir): void
    {
        if (is_dir($dir)) return;
        @mkdir($dir, 0775, true);
    }
}

if (!function_exists('vm_post_handle_uploads')) {
    /**
     * @return array{
     *   ok: bool,
     *   files: array<int, array{
     *     file_type:string,
     *     path:string,
     *     abs_path:string,
     *     original_name:string,
     *     mime:string,
     *     size_bytes:int
     *   }>,
     *   errors: array<int,string>
     * }
     */
    function vm_post_handle_uploads(array $files, int $postId, string $projectRoot): array
    {
        $result = [
            'ok' => true,
            'files' => [],
            'errors' => [],
        ];

        if (empty($files) || !isset($files['name']) || !is_array($files['name'])) {
            return $result;
        }

        $count = count($files['name']);
        if ($count <= 0) return $result;

        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $maxFiles = 10;

        $projectRoot = rtrim($projectRoot, '/');
        $Y = date('Y');
        $M = date('m');

        for ($i = 0; $i < $count; $i++) {
            if ($i >= $maxFiles) {
                $result['errors'][] = vm_post_t(
                    'Забагато файлів. Максимум 10.',
                    'Слишком много файлов. Максимум 10.',
                    'Too many files. Maximum is 10.'
                );
                break;
            }

            $err = (int)($files['error'][$i] ?? UPLOAD_ERR_NO_FILE);
            if ($err === UPLOAD_ERR_NO_FILE) continue;

            if ($err !== UPLOAD_ERR_OK) {
                $result['errors'][] = function_exists('t') ? t('upload_bad') : vm_post_t(
                    'Не вдалося завантажити файл.',
                    'Не удалось загрузить файл.',
                    'Failed to upload file.'
                );
                continue;
            }

            $tmp = (string)($files['tmp_name'][$i] ?? '');
            $name = vm_post_normalize_original_name((string)($files['name'][$i] ?? 'file'));
            $size = (int)($files['size'][$i] ?? 0);

            if ($tmp === '' || !is_uploaded_file($tmp)) {
                $result['errors'][] = function_exists('t') ? t('upload_bad') : vm_post_t(
                    'Не вдалося завантажити файл.',
                    'Не удалось загрузить файл.',
                    'Failed to upload file.'
                );
                continue;
            }

            $mime = '';
            try {
                $mime = (string)$finfo->file($tmp);
            } catch (Throwable $e) {
                $mime = '';
            }
            if ($mime === '') $mime = 'application/octet-stream';

            $group = vm_post_upload_mime_group($mime);
            $ext   = vm_post_safe_ext($name);

            if ($group === 'image') {
                if (strtolower($mime) === 'image/svg+xml' || $ext === 'svg' || @getimagesize($tmp) === false) {
                    $result['errors'][] = function_exists('t') ? t('upload_type_bad') : vm_post_t(
                        'Неприпустимий тип файлу.',
                        'Недопустимый тип файла.',
                        'File type not allowed.'
                    );
                    continue;
                }
            }

            $chk = vm_post_allowed_file($group, $mime, $ext, $size);
            if (!$chk['ok']) {
                $result['errors'][] = (string)($chk['err'] ?? '');
                continue;
            }

            $dirRel = "/upload/{$group}/posts/{$Y}/{$M}";
            $dirAbs = $projectRoot . $dirRel;
            vm_post_ensure_dir($dirAbs);

            $rand = bin2hex(random_bytes(12));
            $safeExt = $ext !== '' ? ('.' . $ext) : '';
            $fileName = $postId . '_' . $rand . $safeExt;

            $absPath = $dirAbs . '/' . $fileName;
            $relPath = $dirRel . '/' . $fileName;

            if (!@move_uploaded_file($tmp, $absPath)) {
                $result['errors'][] = function_exists('t') ? t('upload_bad') : vm_post_t(
                    'Не вдалося завантажити файл.',
                    'Не удалось загрузить файл.',
                    'Failed to upload file.'
                );
                continue;
            }

            @chmod($absPath, 0644);

            $result['files'][] = [
                'file_type' => $group,
                'path' => $relPath,
                'abs_path' => $absPath,
                'original_name' => $name,
                'mime' => $mime,
                'size_bytes' => $size,
            ];
        }

        return $result;
    }
}

if (!function_exists('vm_post_cleanup_uploaded_files')) {
    function vm_post_cleanup_uploaded_files(array $savedFiles): void
    {
        foreach ($savedFiles as $f) {
            $abs = trim((string)($f['abs_path'] ?? ''));
            if ($abs !== '' && is_file($abs)) {
                @unlink($abs);
            }
        }
    }
}

if (!function_exists('vm_post_parse_poll')) {
    /**
     * @return array{
     *   enabled: bool,
     *   question: string,
     *   options: array<int,string>,
     *   error: string
     * }
     */
    function vm_post_parse_poll(array $src): array
    {
        $question = vm_post_normalize_text((string)($src['poll_question'] ?? ''));
        $rawOptions = $src['poll_options'] ?? [];
        if (!is_array($rawOptions)) $rawOptions = [];

        $options = [];
        foreach ($rawOptions as $opt) {
            $opt = vm_post_normalize_text((string)$opt);
            if ($opt === '') continue;

            if (vm_post_strlen($opt) > 255) {
                $opt = vm_post_substr($opt, 0, 255);
            }

            $options[] = $opt;
        }

        $options = array_values(array_unique($options));

        /*
         * ВАЖЛИВО:
         * форма завжди може прислати порожній масив poll_options[] навіть коли
         * блок опитування не відкривали. Тому не можна визначати "опитування ввімкнене"
         * через !empty($rawOptions). Інакше звичайний пост падає з помилкою
         * "Вкажи питання опитування".
         */
        $pollTouched = ($question !== '') || !empty($options);

        if (!$pollTouched) {
            return [
                'enabled' => false,
                'question' => '',
                'options' => [],
                'error' => '',
            ];
        }

        if ($question === '') {
            return [
                'enabled' => false,
                'question' => '',
                'options' => [],
                'error' => vm_post_t(
                    'Вкажи питання опитування.',
                    'Укажи вопрос опроса.',
                    'Enter the poll question.'
                ),
            ];
        }

        if (count($options) < 2) {
            return [
                'enabled' => false,
                'question' => '',
                'options' => [],
                'error' => vm_post_t(
                    'Для опитування потрібно мінімум 2 варіанти.',
                    'Для опроса нужно минимум 2 варианта.',
                    'A poll needs at least 2 options.'
                ),
            ];
        }

        if (count($options) > 10) {
            $options = array_slice($options, 0, 10);
        }

        return [
            'enabled' => true,
            'question' => $question,
            'options' => $options,
            'error' => '',
        ];
    }
}

if (!function_exists('vm_post_insert_attachments')) {
    function vm_post_insert_attachments(PDO $pdo, int $postId, array $savedFiles): void
    {
        if (!$savedFiles) return;
        if (!function_exists('db_has_table') || !db_has_table($pdo, 'post_attachments')) return;

        $st = $pdo->prepare("
            INSERT INTO post_attachments
                (post_id, file_type, path, original_name, mime, size_bytes, created_at)
            VALUES
                (?, ?, ?, ?, ?, ?, NOW())
        ");

        foreach ($savedFiles as $f) {
            $st->execute([
                $postId,
                (string)$f['file_type'],
                (string)$f['path'],
                (string)$f['original_name'],
                (string)$f['mime'],
                (int)$f['size_bytes'],
            ]);
        }
    }
}

if (!function_exists('vm_post_insert_poll')) {
    function vm_post_insert_poll(PDO $pdo, int $postId, string $question, array $options): void
    {
        if (!function_exists('db_has_table')) return;
        if (!db_has_table($pdo, 'post_polls')) return;
        if (!db_has_table($pdo, 'post_poll_options')) return;

        $st = $pdo->prepare("
            INSERT INTO post_polls (post_id, question, created_at)
            VALUES (?, ?, NOW())
        ");
        $st->execute([$postId, $question]);
        $pollId = (int)$pdo->lastInsertId();

        if ($pollId <= 0) return;

        $stOpt = $pdo->prepare("
            INSERT INTO post_poll_options (poll_id, option_text, sort_order)
            VALUES (?, ?, ?)
        ");

        $sort = 1;
        foreach ($options as $opt) {
            $stOpt->execute([$pollId, (string)$opt, $sort]);
            $sort++;
        }
    }
}

if (!function_exists('vm_post_try_reward_coins_for_post')) {
    function vm_post_try_reward_coins_for_post(PDO $pdo, int $userId, int $postId, string $body): void
    {
        if ($userId <= 0 || $postId <= 0) {
            return;
        }

        if (!function_exists('vmc_reward_post_create')) {
            return;
        }

        try {
            $res = vmc_reward_post_create($pdo, $userId, $postId, $body);

            if (!is_array($res)) {
                return;
            }

            $ok = (bool)($res['ok'] ?? false);
            if ($ok) {
                return;
            }

            $reason = (string)($res['reason'] ?? '');
            $silentReasons = [
                'disabled',
                'schema_missing',
                'amount_zero',
                'post_not_meaningful',
                'bad_input',
                'zero_delta',
                'daily_limit_reached',
                'too_fast',
                'duplicate_content',
                'spam_like_text',
            ];

            if (!in_array($reason, $silentReasons, true)) {
                error_log('vm_post_try_reward_coins_for_post: ' . json_encode($res, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
            }
        } catch (Throwable $e) {
            error_log('vm_post_try_reward_coins_for_post exception: ' . $e->getMessage());
        }
    }
}

if (!function_exists('vm_post_create_core')) {
    /**
     * @param array $ctx [
     *   'pdo' => PDO,
     *   'me' => array,
     *   'project_root' => string,
     *   'body' => string,
     *   'group_id' => int,
     *   'files' => array,
     *   'poll_question' => string,
     *   'poll_options' => array,
     * ]
     *
     * @return array{
     *   ok: bool,
     *   post_id: int,
     *   error: string,
     *   upload_error: string
     * }
     */
    function vm_post_create_core(array $ctx): array
    {
        /** @var PDO|null $pdo */
        $pdo = $ctx['pdo'] ?? null;
        $me  = $ctx['me'] ?? null;

        if (!$pdo instanceof PDO || !is_array($me)) {
            return [
                'ok' => false,
                'post_id' => 0,
                'error' => vm_post_t('Помилка конфігурації.', 'Ошибка конфигурации.', 'Configuration error.'),
                'upload_error' => '',
            ];
        }

        $meId = (int)($me['id'] ?? 0);
        if ($meId <= 0) {
            return [
                'ok' => false,
                'post_id' => 0,
                'error' => function_exists('t') ? t('only_for_logged') : vm_post_t('Потрібен вхід.', 'Нужен вход.', 'Login required.'),
                'upload_error' => '',
            ];
        }

        $projectRoot = rtrim((string)($ctx['project_root'] ?? dirname(__DIR__, 2)), '/');
        $body = vm_post_normalize_text((string)($ctx['body'] ?? ''));
        $groupId = max(0, (int)($ctx['group_id'] ?? 0));
        $files = (array)($ctx['files'] ?? []);

        if (vm_post_strlen($body) > 20000) {
            return [
                'ok' => false,
                'post_id' => 0,
                'error' => vm_post_t('Пост занадто довгий.', 'Пост слишком длинный.', 'Post is too long.'),
                'upload_error' => '',
            ];
        }

        $poll = vm_post_parse_poll([
            'poll_question' => (string)($ctx['poll_question'] ?? ''),
            'poll_options' => (array)($ctx['poll_options'] ?? []),
        ]);

        if ($poll['error'] !== '') {
            return [
                'ok' => false,
                'post_id' => 0,
                'error' => $poll['error'],
                'upload_error' => '',
            ];
        }

        $hasFiles = vm_post_upload_has_files($files);
        $hasPoll  = (bool)$poll['enabled'];

        if ($body === '' && !$hasFiles && !$hasPoll) {
            return [
                'ok' => false,
                'post_id' => 0,
                'error' => function_exists('t') ? t('post_empty') : vm_post_t(
                    'Пост порожній.',
                    'Пост пустой.',
                    'Post is empty.'
                ),
                'upload_error' => '',
            ];
        }

        $savedFiles = [];
        $uploadError = '';

        try {
            $pdo->beginTransaction();

            $cols = ['user_id', 'body'];
            $vals = ['?', '?'];
            $params = [$meId, $body];

            $hasCreatedAt = function_exists('db_has_column') ? (bool)db_has_column($pdo, 'posts', 'created_at') : true;
            if ($hasCreatedAt) {
                $cols[] = 'created_at';
                $vals[] = 'NOW()';
            }

            $hasGroupId = function_exists('db_has_column') ? (bool)db_has_column($pdo, 'posts', 'group_id') : false;
            if ($hasGroupId) {
                $cols[] = 'group_id';
                $vals[] = '?';
                $params[] = ($groupId > 0 ? $groupId : null);
            }

            $sql = "INSERT INTO posts (" . implode(', ', $cols) . ") VALUES (" . implode(', ', $vals) . ")";
            $st = $pdo->prepare($sql);
            $st->execute($params);

            $postId = (int)$pdo->lastInsertId();
            if ($postId <= 0) {
                throw new RuntimeException('Post insert failed');
            }

            if ($hasFiles) {
                $up = vm_post_handle_uploads($files, $postId, $projectRoot);
                $savedFiles = $up['files'] ?? [];

                if (!empty($up['errors'])) {
                    $uploadError = (string)($up['errors'][0] ?? '');
                }

                vm_post_insert_attachments($pdo, $postId, $savedFiles);
            }

            if ($hasPoll) {
                vm_post_insert_poll($pdo, $postId, (string)$poll['question'], (array)$poll['options']);
            }

            $pdo->commit();

            vm_post_try_reward_coins_for_post($pdo, $meId, $postId, $body);

            return [
                'ok' => true,
                'post_id' => $postId,
                'error' => '',
                'upload_error' => $uploadError,
            ];
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            vm_post_cleanup_uploaded_files($savedFiles);

            error_log('vm_post_create_core error: ' . $e->getMessage());

            return [
                'ok' => false,
                'post_id' => 0,
                'error' => vm_post_t('Не вдалося створити пост.', 'Не удалось создать пост.', 'Failed to create post.'),
                'upload_error' => '',
            ];
        }
    }
}