<?php
declare(strict_types=1);

/**
 * /includes/helpers.php
 * Загальні хелпери:
 * - esc HTML (h)
 * - redirect (PRG: 303 після POST)
 * - flash (set/get)
 * - CSRF (token/field/check) — сумісність зі старим _csrf і новим csrf_token
 * - URL helpers: current_uri/current_path/safe_local_path/base_url/abs_url
 * - user UI helpers (ПІБ/аватар/ініціали)
 * - DB schema helpers (db_has_column/db_has_table)
 * - safe unlink для /upload
 * - link preview / linkify helpers
 */

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

/* =========================
   Request helpers
========================= */
if (!function_exists('is_post')) {
    function is_post(): bool
    {
        return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
    }
}

if (!function_exists('redirect')) {
    function redirect(string $to): void
    {
        // PRG: після POST краще 303, щоб браузер не повторював POST
        $code = is_post() ? 303 : 302;

        if (!headers_sent()) {
            header('Location: ' . $to, true, $code);
            exit;
        }

        // fallback якщо headers вже відправлені
        echo '<meta http-equiv="refresh" content="0;url=' . h($to) . '">';
        echo '<script>location.href=' . json_encode($to) . ';</script>';
        exit;
    }
}

if (!function_exists('get_str')) {
    function get_str(string $k, string $default = ''): string
    {
        $v = $_GET[$k] ?? $default;
        return is_string($v) ? trim($v) : $default;
    }
}

if (!function_exists('post_str')) {
    function post_str(string $k, string $default = ''): string
    {
        $v = $_POST[$k] ?? $default;
        return is_string($v) ? trim($v) : $default;
    }
}

/* =========================
   Session helpers (safe)
========================= */
if (!function_exists('ensure_session_started')) {
    function ensure_session_started(): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) return;
        if (function_exists('start_app_session')) {
            try { start_app_session(); return; } catch (Throwable $e) {}
        }
        @session_start();
    }
}

/* =========================
   Flash
========================= */
if (!function_exists('flash_set')) {
    function flash_set(string $key, string $msg): void
    {
        ensure_session_started();
        if (!isset($_SESSION['_flash']) || !is_array($_SESSION['_flash'])) $_SESSION['_flash'] = [];
        $_SESSION['_flash'][$key] = $msg;

        // compat: деякі сторінки читають з $_SESSION['flash']
        if (!isset($_SESSION['flash']) || !is_array($_SESSION['flash'])) $_SESSION['flash'] = [];
        $_SESSION['flash'][$key] = $msg;

        // compat alias
        if ($key === 'err') $_SESSION['flash']['error'] = $msg;
        if ($key === 'ok')  $_SESSION['flash']['success'] = $msg;
    }
}

if (!function_exists('flash_get')) {
    function flash_get(string $key): ?string
    {
        ensure_session_started();

        $v = null;

        if (isset($_SESSION['_flash']) && is_array($_SESSION['_flash']) && array_key_exists($key, $_SESSION['_flash'])) {
            $v = $_SESSION['_flash'][$key];
            unset($_SESSION['_flash'][$key]);
        }

        if ($v === null && isset($_SESSION['flash']) && is_array($_SESSION['flash']) && array_key_exists($key, $_SESSION['flash'])) {
            $v = $_SESSION['flash'][$key];
            unset($_SESSION['flash'][$key]);
        }

        if (!is_string($v)) return null;
        $v = trim($v);
        return $v === '' ? null : $v;
    }
}

/* =========================
   CSRF
========================= */
if (!function_exists('csrf_token')) {
    function csrf_token(): string
    {
        ensure_session_started();

        // сумісність: підтримуємо і старий ключ _csrf, і новий csrf_token
        $a = (string)($_SESSION['_csrf'] ?? '');
        $b = (string)($_SESSION['csrf_token'] ?? '');

        if ($a === '' && $b === '') {
            $tok = bin2hex(random_bytes(32));
            $_SESSION['_csrf'] = $tok;
            $_SESSION['csrf_token'] = $tok;
            return $tok;
        }

        if ($a === '' && $b !== '') {
            $_SESSION['_csrf'] = $b;
            return $b;
        }

        if ($a !== '' && $b === '') {
            $_SESSION['csrf_token'] = $a;
            return $a;
        }

        // обидва є — пріоритет _csrf
        return $a;
    }
}

if (!function_exists('csrf_field')) {
    function csrf_field(): string
    {
        return '<input type="hidden" name="csrf" value="' . h(csrf_token()) . '">';
    }
}

if (!function_exists('csrf_check')) {
    function csrf_check(?string $token): bool
    {
        ensure_session_started();

        $token = is_string($token) ? trim($token) : '';
        if ($token === '') return false;

        $a = (string)($_SESSION['_csrf'] ?? '');
        $b = (string)($_SESSION['csrf_token'] ?? '');

        if ($a !== '' && hash_equals($a, $token)) return true;
        if ($b !== '' && hash_equals($b, $token)) return true;

        return false;
    }
}

/* =========================
   URL helpers
========================= */
if (!function_exists('current_uri')) {
    function current_uri(): string
    {
        $uri = (string)($_SERVER['REQUEST_URI'] ?? '/');
        return $uri !== '' ? $uri : '/';
    }
}

/**
 * Тільки path (без query/fragment) — зручно для canonical/noindex правил.
 */
if (!function_exists('current_path')) {
    function current_path(): string
    {
        $uri = (string)($_SERVER['REQUEST_URI'] ?? '/');
        if ($uri === '') $uri = '/';
        $path = (string)parse_url($uri, PHP_URL_PATH);
        return $path !== '' ? $path : '/';
    }
}

if (!function_exists('safe_local_path')) {
    function safe_local_path(string $path, string $fallback = '/'): string
    {
        $path = trim($path);
        if ($path === '') return $fallback;

        // якщо абсолютний URL — беремо тільки path+query+fragment
        if (preg_match('~^https?://~i', $path)) {
            $p = parse_url($path);
            $pp = (string)($p['path'] ?? '/');
            $qq = isset($p['query']) && (string)$p['query'] !== '' ? ('?' . (string)$p['query']) : '';
            $ff = isset($p['fragment']) && (string)$p['fragment'] !== '' ? ('#' . (string)$p['fragment']) : '';
            $path = $pp . $qq . $ff;
        }

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

        // control chars / null byte
        if (preg_match('~[\x00-\x1F\x7F]~', $path)) return $fallback;

        // обмеження довжини
        if (strlen($path) > 2048) return $fallback;

        // перевіряємо тільки path-частину на ../ сегменти
        $p = (string)parse_url($path, PHP_URL_PATH);
        if ($p === '') $p = '/';

        // backslash не допускаємо
        if (str_contains($p, '\\')) return $fallback;

        // заборона сегментів ".."
        if (preg_match('~(^|/)\.\.(/|$)~', $p)) return $fallback;

        return $path;
    }
}

if (!function_exists('base_url')) {
    function base_url(): string
    {
        // 1) Cloudflare
        $proto = '';
        $cfv = (string)($_SERVER['HTTP_CF_VISITOR'] ?? '');
        if ($cfv !== '') {
            $j = json_decode($cfv, true);
            if (is_array($j) && isset($j['scheme']) && is_string($j['scheme'])) {
                $proto = strtolower(trim($j['scheme']));
            }
        }

        // 2) Proxy / LB headers
        if ($proto === '') {
            $xfp = (string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '');
            if ($xfp !== '') {
                $first = trim(explode(',', $xfp)[0]);
                $proto = strtolower($first);
            }
        }

        // 2.1) X-Forwarded-Ssl
        if ($proto === '') {
            $xfs = strtolower(trim((string)($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '')));
            if ($xfs === 'on' || $xfs === '1' || $xfs === 'true') {
                $proto = 'https';
            }
        }

        // 3) Fallback HTTPS
        if ($proto === '') {
            $https = strtolower(trim((string)($_SERVER['HTTPS'] ?? '')));
            $proto = ($https !== '' && $https !== 'off' && $https !== '0') ? 'https' : 'http';
        }

        if ($proto !== 'https') $proto = 'http';

        // Host: prefer forwarded host
        $host = (string)($_SERVER['HTTP_X_FORWARDED_HOST'] ?? '');
        if ($host !== '') {
            $host = trim(explode(',', $host)[0]);
        } else {
            $host = (string)($_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] ?? 'localhost'));
            $host = trim(explode(',', $host)[0]);
        }

        $host = trim($host);
        if ($host === '') $host = 'localhost';

        // базова санітизація host
        $host = preg_replace('~[^a-zA-Z0-9\.\-\:\[\]]~', '', $host) ?? 'localhost';
        if ($host === '') $host = 'localhost';

        return $proto . '://' . $host;
    }
}

if (!function_exists('abs_url')) {
    function abs_url(string $path): string
    {
        $path = trim($path);
        if ($path === '') $path = '/';

        if (preg_match('~^https?://~i', $path)) return $path;

        if ($path[0] !== '/') $path = '/' . $path;
        return base_url() . $path;
    }
}

/* =========================
   Users UI helpers (ПІБ/аватар)
========================= */
if (!function_exists('user_full_name')) {
    function user_full_name(array $row, string $prefix = ''): string
    {
        $fn = trim((string)($row[$prefix . 'first_name'] ?? ''));
        $ln = trim((string)($row[$prefix . 'last_name'] ?? ''));
        $name = trim($fn . ' ' . $ln);
        if ($name !== '') return $name;

        $full = trim((string)($row[$prefix . 'full_name'] ?? ''));
        if ($full !== '') return $full;

        $username = trim((string)($row[$prefix . 'username'] ?? ''));
        return $username !== '' ? $username : 'User';
    }
}

if (!function_exists('user_avatar_path')) {
    function user_avatar_path(array $row, string $prefix = ''): string
    {
        return trim((string)($row[$prefix . 'avatar_path'] ?? ''));
    }
}

if (!function_exists('user_initials')) {
    function user_initials(string $fullName, string $fallback = 'U'): string
    {
        $s = trim($fullName);
        if ($s === '') return mb_strtoupper(mb_substr($fallback, 0, 1, 'UTF-8'), 'UTF-8');

        $parts = preg_split('~\s+~u', $s, -1, PREG_SPLIT_NO_EMPTY) ?: [];
        $a = $parts[0] ?? '';
        $b = $parts[1] ?? '';
        $i1 = $a !== '' ? mb_substr($a, 0, 1, 'UTF-8') : '';
        $i2 = $b !== '' ? mb_substr($b, 0, 1, 'UTF-8') : '';

        $out = trim($i1 . $i2);
        if ($out === '') $out = mb_substr($s, 0, 1, 'UTF-8');
        return mb_strtoupper($out, 'UTF-8');
    }
}

/* =========================
   Permissions
========================= */
if (!function_exists('is_admin')) {
    function is_admin(?array $me): bool
    {
        return (bool)$me && ((string)($me['role'] ?? '')) === 'admin';
    }
}

if (!function_exists('can_edit_owner')) {
    function can_edit_owner(?array $me, int $ownerId): bool
    {
        if (!$me) return false;
        $meId = (int)($me['id'] ?? 0);
        if ($meId <= 0) return false;
        return is_admin($me) || $meId === $ownerId;
    }
}

/* =========================
   DB schema helper (for safe rollout)
========================= */
if (!function_exists('db_has_column')) {
    function db_has_column(PDO $pdo, string $table, string $col): bool
    {
        try {
            $st = $pdo->prepare("
                SELECT 1
                FROM information_schema.columns
                WHERE table_schema = DATABASE()
                  AND table_name = ?
                  AND column_name = ?
                LIMIT 1
            ");
            $st->execute([$table, $col]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('db_has_table')) {
    function db_has_table(PDO $pdo, string $table): bool
    {
        try {
            $st = $pdo->prepare("
                SELECT 1
                FROM information_schema.tables
                WHERE table_schema = DATABASE()
                  AND table_name = ?
                LIMIT 1
            ");
            $st->execute([$table]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

/* =========================
   Safe unlink for /upload/* paths
========================= */
if (!function_exists('unlink_upload_webpath')) {
    function unlink_upload_webpath(string $webPath, string $projectRootDir): void
    {
        $webPath = trim($webPath);
        if ($webPath === '') return;

        // беремо тільки path якщо раптом абсолютний URL
        if (preg_match('~^https?://~i', $webPath)) {
            $p = parse_url($webPath);
            $webPath = (string)($p['path'] ?? '');
        }

        if ($webPath === '' || !str_starts_with($webPath, '/upload/')) return;
        if (preg_match('~[\x00-\x1F\x7F]~', $webPath)) return;

        $root = rtrim($projectRootDir, '/');
        $uploadRoot = realpath($root . '/upload');
        if ($uploadRoot === false) return;

        $abs = realpath($root . $webPath);
        if ($abs === false) return;

        $uploadRoot = rtrim($uploadRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $absNorm = rtrim($abs, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

        if (strpos($absNorm, $uploadRoot) !== 0) return;

        $absFile = rtrim($abs, DIRECTORY_SEPARATOR);
        if (is_file($absFile)) {
            @unlink($absFile);
        }
    }
}

/* =========================================================
   Link preview / Linkify helpers
   ========================================================= */

/**
 * Витягує перший URL з тексту (http/https). Повертає '' якщо не знайшли.
 */
if (!function_exists('extract_first_url')) {
    function extract_first_url(string $text): string
    {
        $text = trim($text);
        if ($text === '') return '';

        if (preg_match('~\bhttps?://[^\s<>"\']+~i', $text, $m)) {
            $url = (string)($m[0] ?? '');
            $url = rtrim($url, '.,);!?]}>');
            return $url;
        }
        return '';
    }
}

/**
 * Нормалізує URL (мінімально): прибирає пробіли, валідні схеми, обрізає довжину.
 */
if (!function_exists('normalize_url')) {
    function normalize_url(string $url): string
    {
        $url = trim($url);
        if ($url === '') return '';
        if (strlen($url) > 2000) $url = substr($url, 0, 2000);

        if (!preg_match('~^https?://~i', $url)) return '';
        return $url;
    }
}

/**
 * Безпечний linkify: екранує весь текст, потім підміняє URL на <a>.
 * Повертає HTML рядок.
 */
if (!function_exists('linkify_text')) {
    function linkify_text(string $text): string
    {
        $text = (string)$text;
        if ($text === '') return '';

        $safe = h($text);

        $re = '~\bhttps?://[^\s<]+~i';
        $out = preg_replace_callback($re, function (array $m) {
            $raw = (string)($m[0] ?? '');
            $raw = rtrim($raw, '.,);!?]}>');
            $url = normalize_url($raw);
            if ($url === '') return $m[0];

            $disp = $raw;
            if (mb_strlen($disp, 'UTF-8') > 80) {
                $disp = mb_substr($disp, 0, 60, 'UTF-8') . '…' . mb_substr($disp, -15, 15, 'UTF-8');
            }

            return '<a href="' . h($url) . '" target="_blank" rel="noopener nofollow ugc">' . h($disp) . '</a>';
        }, $safe);

        return nl2br((string)$out);
    }
}

/**
 * SHA1-хеш URL для ключа.
 */
if (!function_exists('url_hash')) {
    function url_hash(string $url): string
    {
        return sha1($url);
    }
}