<?php
declare(strict_types=1);

/**
 * /functional/group/_lib.php
 * Спільні функції для модуля груп:
 * - i18n (UA/RU/EN) простий helper
 * - CSRF fallback (якщо в проекті немає csrf_token/csrf_check)
 * - slug генерація (з транслітерацією UA/RU -> EN)
 * - права доступу до групи
 * - отримання групи/участі/лічильників
 * - helper-и для upload директорій (avatar/cover)
 * - g_table_exists()/g_column_exists() з кешем
 *
 * ОНОВЛЕНО:
 * - admin сайту більше НЕ рахується менеджером групи автоматично
 * - g_can_manage_group(): враховує тільки owner_user_id та role=owner/admin у group_members
 * - додано g_group_role(), g_can_view_members(), g_can_manage_members()
 * - додано підтримку group_members.pending_kind:
 *   invite | request
 * - додано helper для внутрішнього notification про заявку в закриту групу
 */

if (defined('VM_GROUP_LIB_LOADED')) {
    return;
}
define('VM_GROUP_LIB_LOADED', true);

/* ===== internal helpers ===== */
function g_ensure_session(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) return;

    if (function_exists('ensure_session_started')) {
        try {
            ensure_session_started();
            return;
        } catch (Throwable $e) {
        }
    }

    @session_start();
}

function g_mb_lower(string $s): string
{
    if (function_exists('mb_strtolower')) {
        return (string)mb_strtolower($s, 'UTF-8');
    }
    return strtolower($s);
}

function g_norm_path(string $p): string
{
    $p = str_replace('\\', '/', $p);
    $p = preg_replace('~/+~', '/', $p) ?? $p;
    return rtrim($p, '/');
}

/* ===== db/me/html ===== */
function g_db(): PDO
{
    if (function_exists('db')) return db();

    if (isset($GLOBALS['pdo']) && $GLOBALS['pdo'] instanceof PDO) {
        return $GLOBALS['pdo'];
    }

    throw new RuntimeException('db() not found. Ensure includes/start.php defines db().');
}

function g_me(): ?array
{
    if (function_exists('current_user')) return current_user();

    if (isset($GLOBALS['me']) && is_array($GLOBALS['me'])) {
        return $GLOBALS['me'];
    }

    return null;
}

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

/* ===== Language helper ===== */
function g_lang(): string
{
    if (function_exists('lang')) {
        $l = strtolower(trim((string)lang()));
        if (in_array($l, ['uk', 'ru', 'en'], true)) return $l;
    }

    g_ensure_session();
    $me = g_me();

    $lang = (string)($_SESSION['lang'] ?? ($me['lang'] ?? 'uk'));
    $lang = strtolower(trim($lang));
    if (!in_array($lang, ['uk', 'ru', 'en'], true)) $lang = 'uk';

    return $lang;
}

function g_t(string $uk, string $ru, string $en): string
{
    $lang = g_lang();
    return $lang === 'ru' ? $ru : ($lang === 'en' ? $en : $uk);
}

/* ===== CSRF fallback ===== */
function g_csrf_token(): string
{
    if (function_exists('csrf_token')) return (string)csrf_token();

    g_ensure_session();

    $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;
    }

    return $a;
}

function g_csrf_check(?string $token): bool
{
    if (function_exists('csrf_check')) return (bool)csrf_check((string)$token);

    g_ensure_session();

    $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;
}

/* ===== Basic helpers ===== */
function g_now(): string
{
    return date('Y-m-d H:i:s');
}

/**
 * Стабільна транслітерація UA/RU -> латиниця + slug
 */
function g_slugify(string $s): string
{
    $s = trim($s);
    if ($s === '') return '';

    $s = g_mb_lower($s);

    $map = [
        'а'=>'a','б'=>'b','в'=>'v','г'=>'h','ґ'=>'g','д'=>'d','е'=>'e','є'=>'ye','ж'=>'zh','з'=>'z','и'=>'y','і'=>'i','ї'=>'yi','й'=>'y',
        'к'=>'k','л'=>'l','м'=>'m','н'=>'n','о'=>'o','п'=>'p','р'=>'r','с'=>'s','т'=>'t','у'=>'u','ф'=>'f','х'=>'kh','ц'=>'ts','ч'=>'ch','ш'=>'sh','щ'=>'shch',
        'ь'=>'','ю'=>'yu','я'=>'ya',
        'ё'=>'yo','э'=>'e','ъ'=>'',
    ];
    $s = strtr($s, $map);

    if (function_exists('iconv')) {
        $trans = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
        if (is_string($trans) && $trans !== '') {
            $s = $trans;
        }
    }

    $s = strtolower($s);
    $s = preg_replace('~[^a-z0-9]+~', '-', $s) ?? '';
    $s = trim($s, '-');
    $s = preg_replace('~-+~', '-', $s) ?? '';
    $s = substr($s, 0, 160);

    if ($s === '') {
        $s = 'group-' . substr(sha1((string)microtime(true) . '-' . random_int(0, PHP_INT_MAX)), 0, 10);
    }

    return $s;
}

function g_table_exists(PDO $pdo, string $table): bool
{
    static $cache = [];

    $k = strtolower($table);
    if (array_key_exists($k, $cache)) return (bool)$cache[$k];

    try {
        $st = $pdo->prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1");
        $st->execute([$table]);
        $cache[$k] = (bool)$st->fetchColumn();
        return (bool)$cache[$k];
    } catch (Throwable $e) {
        $cache[$k] = false;
        return false;
    }
}

function g_column_exists(PDO $pdo, string $table, string $col): bool
{
    static $cache = [];

    $k = strtolower($table . '.' . $col);
    if (array_key_exists($k, $cache)) return (bool)$cache[$k];

    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]);
        $cache[$k] = (bool)$st->fetchColumn();
        return (bool)$cache[$k];
    } catch (Throwable $e) {
        $cache[$k] = false;
        return false;
    }
}

/* ===== Group getters ===== */
function g_group_get(PDO $pdo, int $id): ?array
{
    try {
        $hasStatus = g_column_exists($pdo, 'group_members', 'status');
        $statusCond = $hasStatus ? " AND gm.status='active'" : '';

        $st = $pdo->prepare("
            SELECT g.*,
                   (SELECT COUNT(*) FROM group_members gm WHERE gm.group_id = g.id {$statusCond}) AS members_count
            FROM `groups` g
            WHERE g.id = ?
            LIMIT 1
        ");
        $st->execute([$id]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        try {
            $st = $pdo->prepare("SELECT g.* FROM `groups` g WHERE g.id = ? LIMIT 1");
            $st->execute([$id]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            if (!$row) return null;
            $row['members_count'] = 0;
            return $row;
        } catch (Throwable $e2) {
            return null;
        }
    }
}

function g_group_get_by_slug(PDO $pdo, string $slug): ?array
{
    try {
        $hasStatus = g_column_exists($pdo, 'group_members', 'status');
        $statusCond = $hasStatus ? " AND gm.status='active'" : '';

        $st = $pdo->prepare("
            SELECT g.*,
                   (SELECT COUNT(*) FROM group_members gm WHERE gm.group_id = g.id {$statusCond}) AS members_count
            FROM `groups` g
            WHERE g.slug = ?
            LIMIT 1
        ");
        $st->execute([$slug]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        try {
            $st = $pdo->prepare("SELECT g.* FROM `groups` g WHERE g.slug = ? LIMIT 1");
            $st->execute([$slug]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            if (!$row) return null;
            $row['members_count'] = 0;
            return $row;
        } catch (Throwable $e2) {
            return null;
        }
    }
}

function g_group_url(array $g): string
{
    $slug = trim((string)($g['slug'] ?? ''));
    if ($slug !== '') {
        return '/functional/group/view.php?g=' . rawurlencode($slug);
    }
    return '/functional/group/view.php?id=' . (int)($g['id'] ?? 0);
}

/* ===== Permissions ===== */
function g_is_admin(?array $me): bool
{
    return (bool)$me && (($me['role'] ?? '') === 'admin');
}

function g_membership(PDO $pdo, int $groupId, int $userId): ?array
{
    try {
        $st = $pdo->prepare("SELECT * FROM group_members WHERE group_id=? AND user_id=? LIMIT 1");
        $st->execute([$groupId, $userId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        return null;
    }
}

function g_group_role(PDO $pdo, int $groupId, int $userId): string
{
    if ($groupId <= 0 || $userId <= 0) return '';
    $mem = g_membership($pdo, $groupId, $userId);
    if (!$mem) return '';

    $hasStatus = g_column_exists($pdo, 'group_members', 'status');
    if ($hasStatus) {
        $status = (string)($mem['status'] ?? '');
        if ($status !== 'active') return '';
    }

    $role = trim((string)($mem['role'] ?? ''));
    return $role;
}

function g_can_manage_group(?array $me, array $g): bool
{
    if (!$me) return false;

    $meId = (int)($me['id'] ?? 0);
    if ($meId <= 0) return false;

    if ((int)($g['owner_user_id'] ?? 0) === $meId) return true;

    try {
        $pdo = g_db();
        $role = g_group_role($pdo, (int)($g['id'] ?? 0), $meId);
        return in_array($role, ['owner', 'admin'], true);
    } catch (Throwable $e) {
        return false;
    }
}

function g_can_view_members(?array $me, array $g): bool
{
    if (!$me) return false;

    $meId = (int)($me['id'] ?? 0);
    if ($meId <= 0) return false;

    if ((int)($g['owner_user_id'] ?? 0) === $meId) return true;

    try {
        $pdo = g_db();
        $role = g_group_role($pdo, (int)($g['id'] ?? 0), $meId);
        if (in_array($role, ['owner', 'admin', 'moderator'], true)) return true;

        $mem = g_membership($pdo, (int)($g['id'] ?? 0), $meId);
        if (!$mem) return false;

        $hasStatus = g_column_exists($pdo, 'group_members', 'status');
        if ($hasStatus) return ((string)($mem['status'] ?? '') === 'active');

        return true;
    } catch (Throwable $e) {
        return false;
    }
}

function g_can_manage_members(?array $me, array $g): bool
{
    if (!$me) return false;

    $meId = (int)($me['id'] ?? 0);
    if ($meId <= 0) return false;

    if ((int)($g['owner_user_id'] ?? 0) === $meId) return true;

    try {
        $pdo = g_db();
        $role = g_group_role($pdo, (int)($g['id'] ?? 0), $meId);
        return in_array($role, ['owner', 'admin'], true);
    } catch (Throwable $e) {
        return false;
    }
}

function g_ensure_owner_membership(PDO $pdo, int $groupId, int $ownerId): void
{
    $st = $pdo->prepare("SELECT 1 FROM group_members WHERE group_id=? AND user_id=? LIMIT 1");
    $st->execute([$groupId, $ownerId]);
    if ($st->fetchColumn()) return;

    $hasPendingKind = g_column_exists($pdo, 'group_members', 'pending_kind');

    if ($hasPendingKind) {
        $ins = $pdo->prepare("INSERT INTO group_members(group_id,user_id,role,status,pending_kind,created_at) VALUES(?,?,?,?,?,?)");
        $ins->execute([$groupId, $ownerId, 'owner', 'active', null, g_now()]);
        return;
    }

    $ins = $pdo->prepare("INSERT INTO group_members(group_id,user_id,role,status,created_at) VALUES(?,?,?,?,?)");
    $ins->execute([$groupId, $ownerId, 'owner', 'active', g_now()]);
}

/* ===== pending_kind helpers ===== */
function g_membership_pending_kind(?array $membership): string
{
    if (!$membership) return '';
    return trim((string)($membership['pending_kind'] ?? ''));
}

function g_is_pending_invite_membership(?array $membership): bool
{
    if (!$membership) return false;

    $status = trim((string)($membership['status'] ?? ''));
    if ($status !== 'pending') return false;

    $kind = g_membership_pending_kind($membership);
    return ($kind === '' || $kind === 'invite');
}

function g_is_pending_request_membership(?array $membership): bool
{
    if (!$membership) return false;

    $status = trim((string)($membership['status'] ?? ''));
    if ($status !== 'pending') return false;

    return (g_membership_pending_kind($membership) === 'request');
}

/* ===== notifications helpers ===== */
function g_notifications_table_ready(PDO $pdo): bool
{
    if (!g_table_exists($pdo, 'notifications')) return false;

    $need = ['user_id', 'type', 'is_read', 'created_at'];
    foreach ($need as $col) {
        if (!g_column_exists($pdo, 'notifications', $col)) return false;
    }

    return true;
}

function g_notification_user_lang(PDO $pdo, int $userId, string $fallback = 'uk'): string
{
    if ($userId <= 0 || !g_table_exists($pdo, 'users') || !g_column_exists($pdo, 'users', 'lang')) {
        return $fallback;
    }

    try {
        $st = $pdo->prepare("SELECT lang FROM users WHERE id=? LIMIT 1");
        $st->execute([$userId]);
        $lang = strtolower(trim((string)($st->fetchColumn() ?: '')));
        if (!in_array($lang, ['uk', 'ru', 'en'], true)) return $fallback;
        return $lang;
    } catch (Throwable $e) {
        return $fallback;
    }
}

function g_group_join_request_message(string $lang, string $groupName): string
{
    $groupName = trim($groupName);
    if ($groupName === '') $groupName = '#group';

    return match ($lang) {
        'ru' => 'подал(а) заявку в группу "' . $groupName . '"',
        'en' => 'requested to join the group "' . $groupName . '"',
        default => 'подав(ла) заявку до групи "' . $groupName . '"',
    };
}

function g_notification_upsert_group_join_request(PDO $pdo, int $toUserId, int $actorUserId, array $g): void
{
    if ($toUserId <= 0 || $actorUserId <= 0 || $toUserId === $actorUserId) return;
    if (!g_notifications_table_ready($pdo)) return;

    $groupId = (int)($g['id'] ?? 0);
    if ($groupId <= 0) return;

    $groupName = trim((string)($g['name'] ?? ''));
    $lang = g_notification_user_lang($pdo, $toUserId, 'uk');
    $msg = g_group_join_request_message($lang, $groupName);

    $hasActor   = g_column_exists($pdo, 'notifications', 'actor_user_id');
    $hasEntityT = g_column_exists($pdo, 'notifications', 'entity_type');
    $hasEntityI = g_column_exists($pdo, 'notifications', 'entity_id');
    $hasPostId  = g_column_exists($pdo, 'notifications', 'post_id');
    $hasGroupId = g_column_exists($pdo, 'notifications', 'group_id');
    $hasMessage = g_column_exists($pdo, 'notifications', 'message');
    $hasReadAt  = g_column_exists($pdo, 'notifications', 'read_at');

    try {
        $where = ["user_id = ?", "type = 'group_join_request'"];
        $args  = [$toUserId];

        if ($hasActor) {
            $where[] = "actor_user_id = ?";
            $args[] = $actorUserId;
        }
        if ($hasGroupId) {
            $where[] = "group_id = ?";
            $args[] = $groupId;
        } elseif ($hasEntityT && $hasEntityI) {
            $where[] = "entity_type = 'group' AND entity_id = ?";
            $args[] = $groupId;
        }

        $st = $pdo->prepare("
            SELECT id
            FROM notifications
            WHERE " . implode(' AND ', $where) . "
            ORDER BY id DESC
            LIMIT 1
        ");
        $st->execute($args);
        $nid = (int)($st->fetchColumn() ?: 0);

        $set = ["is_read = 0", "created_at = NOW()"];
        $updArgs = [];

        if ($hasActor) {
            $set[] = "actor_user_id = ?";
            $updArgs[] = $actorUserId;
        }
        if ($hasEntityT) {
            $set[] = "entity_type = 'group'";
        }
        if ($hasEntityI) {
            $set[] = "entity_id = ?";
            $updArgs[] = $groupId;
        }
        if ($hasPostId) {
            $set[] = "post_id = NULL";
        }
        if ($hasGroupId) {
            $set[] = "group_id = ?";
            $updArgs[] = $groupId;
        }
        if ($hasMessage) {
            $set[] = "message = ?";
            $updArgs[] = $msg;
        }
        if ($hasReadAt) {
            $set[] = "read_at = NULL";
        }

        if ($nid > 0) {
            $updArgs[] = $nid;
            $up = $pdo->prepare("
                UPDATE notifications
                SET " . implode(', ', $set) . "
                WHERE id = ?
                LIMIT 1
            ");
            $up->execute($updArgs);
            return;
        }

        $cols = ['user_id', 'type', 'is_read', 'created_at'];
        $vals = ['?', "'group_join_request'", '0', 'NOW()'];
        $insArgs = [$toUserId];

        if ($hasActor) {
            $cols[] = 'actor_user_id';
            $vals[] = '?';
            $insArgs[] = $actorUserId;
        }
        if ($hasEntityT) {
            $cols[] = 'entity_type';
            $vals[] = "'group'";
        }
        if ($hasEntityI) {
            $cols[] = 'entity_id';
            $vals[] = '?';
            $insArgs[] = $groupId;
        }
        if ($hasPostId) {
            $cols[] = 'post_id';
            $vals[] = 'NULL';
        }
        if ($hasGroupId) {
            $cols[] = 'group_id';
            $vals[] = '?';
            $insArgs[] = $groupId;
        }
        if ($hasMessage) {
            $cols[] = 'message';
            $vals[] = '?';
            $insArgs[] = $msg;
        }
        if ($hasReadAt) {
            $cols[] = 'read_at';
            $vals[] = 'NULL';
        }

        $ins = $pdo->prepare("
            INSERT INTO notifications (" . implode(', ', $cols) . ")
            VALUES (" . implode(', ', $vals) . ")
        ");
        $ins->execute($insArgs);
    } catch (Throwable $e) {
        error_log('[group join request notification] ' . $e->getMessage());
    }
}

/* ===== Upload helpers ===== */
function g_upload_root_abs(): string
{
    $p = realpath(__DIR__ . '/../../upload');
    return $p ?: (__DIR__ . '/../../upload');
}

function g_group_upload_dir_abs(string $groupSlug, string $kind): string
{
    $kind = $kind === 'cover' ? 'cover' : 'avatar';
    $safe = preg_replace('~[^a-zA-Z0-9_-]+~', '_', $groupSlug) ?: 'group';
    return rtrim(g_upload_root_abs(), '/') . '/group/' . $safe . '/' . $kind;
}

function g_move_uploaded_image(array $file, string $destDirAbs, int $maxBytes = 8000000): array
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Upload error: ' . (int)($file['error'] ?? -1));
    }

    $tmp = (string)($file['tmp_name'] ?? '');
    if (!is_uploaded_file($tmp)) {
        throw new RuntimeException('Not an uploaded file.');
    }

    $orig = (string)($file['name'] ?? 'file');
    $size = (int)($file['size'] ?? 0);

    if ($size <= 0) {
        throw new RuntimeException('Empty file.');
    }
    if ($maxBytes > 0 && $size > $maxBytes) {
        throw new RuntimeException('File too large.');
    }

    if (function_exists('getimagesize')) {
        $gi = @getimagesize($tmp);
        if (!$gi || !is_array($gi)) {
            throw new RuntimeException('Invalid image.');
        }
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = (string)$finfo->file($tmp);

    $allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
    if (!isset($allowed[$mime])) {
        throw new RuntimeException('Only JPG/PNG/WEBP allowed.');
    }

    if (!is_dir($destDirAbs) && !mkdir($destDirAbs, 0775, true) && !is_dir($destDirAbs)) {
        throw new RuntimeException('Failed to create directory: ' . $destDirAbs);
    }

    $ext = $allowed[$mime];
    $name = date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;

    $destAbs = rtrim($destDirAbs, '/') . '/' . $name;
    if (!move_uploaded_file($tmp, $destAbs)) {
        throw new RuntimeException('Failed to move uploaded file.');
    }

    @chmod($destAbs, 0644);

    $uploadAbs = realpath(g_upload_root_abs()) ?: g_upload_root_abs();
    $uploadAbs = g_norm_path($uploadAbs);

    $destReal = realpath($destAbs) ?: $destAbs;
    $destReal = g_norm_path($destReal);

    if ($uploadAbs !== '' && str_starts_with($destReal, $uploadAbs)) {
        $rel = substr($destReal, strlen($uploadAbs));
        $rel = ltrim((string)$rel, '/');
        $urlPath = '/upload/' . $rel;
    } else {
        $urlPath = str_replace($uploadAbs, '/upload', $destReal);
        $urlPath = str_replace('\\', '/', $urlPath);
    }

    $urlPath = preg_replace('~/+~', '/', $urlPath) ?? $urlPath;

    return [$urlPath, $mime, $size, $orig];
}