<?php
declare(strict_types=1);

/**
 * /group.php
 * 1) /group.php                — каталог груп (пошук/фільтри/ТОП-10)
 * 2) /group.php?id=123         — редірект на /functional/group/view.php?g=SLUG
 *
 * Під схему:
 * groups: id, owner_user_id, name, slug, topic, about, avatar_path, cover_path, created_at, updated_at, topic_id, is_closed
 * group_members: group_id, user_id, role, status, pending_kind, created_at
 * group_topics: id, code, name_uk, name_en, name_ru, sort
 *
 * ОНОВЛЕНО:
 * - підтримка group_members.pending_kind:
 *   - invite  => запрошення в групу
 *   - request => заявка на вступ у закриту групу
 * - для закритих груп кнопка в каталозі тепер подає заявку
 * - для pending invite кнопка в каталозі приймає запрошення
 * - для pending request показується "Заявка надіслана"
 * - менеджерам групи створюється внутрішнє повідомлення type=group_join_request
 * - менеджерам групи надсилається email через ntf_dispatch_group_join_request()
 * - admin сайту більше не обходить правила закритої групи
 */

require_once __DIR__ . '/includes/start.php';

$notificationsEvents = __DIR__ . '/functional/notifications/events.php';
if (is_file($notificationsEvents)) {
  require_once $notificationsEvents;
}
unset($notificationsEvents);

$pdo = db();
$me  = current_user();

/* ===== fallbacks ===== */
if (!function_exists('h')) {
  function h(?string $s): string { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
}
if (!function_exists('redirect')) {
  function redirect(string $url, int $code = 302): void { header('Location: ' . $url, true, $code); exit; }
}
if (!function_exists('current_uri')) {
  function current_uri(): string {
    $uri = (string)($_SERVER['REQUEST_URI'] ?? '/');
    return $uri !== '' ? $uri : '/';
  }
}
function tr(string $uk, string $en, string $ru): string {
  $l = function_exists('lang') ? (string)lang() : 'uk';
  return match ($l) {
    'en' => $en,
    'ru' => $ru,
    default => $uk,
  };
}
if (!function_exists('flash_set')) {
  function flash_set(string $key, string $val): void {
    if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
    if (!isset($_SESSION['_flash']) || !is_array($_SESSION['_flash'])) $_SESSION['_flash'] = [];
    $_SESSION['_flash'][$key] = $val;
  }
}
if (!function_exists('flash_get')) {
  function flash_get(string $key): ?string {
    if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
    $v = $_SESSION['_flash'][$key] ?? null;
    if (isset($_SESSION['_flash'][$key])) unset($_SESSION['_flash'][$key]);
    return is_string($v) ? $v : null;
  }
}
if (!function_exists('csrf_token')) {
  function csrf_token(): string {
    if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
    if (empty($_SESSION['_csrf']) || !is_string($_SESSION['_csrf'])) {
      $_SESSION['_csrf'] = bin2hex(random_bytes(16));
    }
    return (string)$_SESSION['_csrf'];
  }
}
if (!function_exists('csrf_check')) {
  function csrf_check(?string $t): bool {
    if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
    if (!is_string($t) || $t === '') return false;
    $s = $_SESSION['_csrf'] ?? '';
    return is_string($s) && $s !== '' && hash_equals($s, $t);
  }
}

/* ===== notifications helpers ===== */
if (!function_exists('grp_notifications_ensure_schema')) {
  function grp_notifications_ensure_schema(PDO $pdo): bool {
    try {
      $pdo->exec("
        CREATE TABLE IF NOT EXISTS `notifications` (
          `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
          `user_id` INT(10) UNSIGNED NOT NULL,
          `actor_user_id` INT(10) UNSIGNED DEFAULT NULL,
          `type` VARCHAR(50) NOT NULL,
          `entity_type` VARCHAR(50) DEFAULT NULL,
          `entity_id` BIGINT(20) UNSIGNED DEFAULT NULL,
          `post_id` BIGINT(20) UNSIGNED DEFAULT NULL,
          `group_id` INT(10) UNSIGNED DEFAULT NULL,
          `message` VARCHAR(500) DEFAULT NULL,
          `is_read` TINYINT(1) NOT NULL DEFAULT 0,
          `created_at` DATETIME NOT NULL,
          `read_at` DATETIME DEFAULT NULL,
          PRIMARY KEY (`id`),
          KEY `idx_user_read_created` (`user_id`,`is_read`,`created_at`),
          KEY `idx_type_entity` (`type`,`entity_type`,`entity_id`),
          KEY `idx_actor_user` (`actor_user_id`),
          KEY `idx_post_id` (`post_id`),
          KEY `idx_group_id` (`group_id`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
      ");
      return true;
    } catch (Throwable $e) {
      error_log('group.php notifications schema error: ' . $e->getMessage());
      return false;
    }
  }
}

if (!function_exists('grp_user_lang')) {
  function grp_user_lang(PDO $pdo, int $userId): string {
    if ($userId <= 0) return 'uk';

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

if (!function_exists('grp_join_request_message')) {
  function grp_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 . '"',
    };
  }
}

if (!function_exists('grp_notification_upsert_join_request')) {
  function grp_notification_upsert_join_request(
    PDO $pdo,
    int $toUserId,
    int $actorUserId,
    int $groupId,
    string $groupName
  ): void {
    if ($toUserId <= 0 || $actorUserId <= 0 || $groupId <= 0) return;
    if ($toUserId === $actorUserId) return;
    if (!grp_notifications_ensure_schema($pdo)) return;

    $lang = grp_user_lang($pdo, $toUserId);
    $msg  = grp_join_request_message($lang, $groupName);

    try {
      $st = $pdo->prepare("
        SELECT id
        FROM notifications
        WHERE user_id = ?
          AND actor_user_id = ?
          AND type = 'group_join_request'
          AND group_id = ?
        ORDER BY id DESC
        LIMIT 1
      ");
      $st->execute([$toUserId, $actorUserId, $groupId]);
      $nid = (int)($st->fetchColumn() ?: 0);

      if ($nid > 0) {
        $up = $pdo->prepare("
          UPDATE notifications
          SET entity_type = 'group',
              entity_id = ?,
              group_id = ?,
              message = ?,
              is_read = 0,
              read_at = NULL,
              created_at = NOW()
          WHERE id = ?
          LIMIT 1
        ");
        $up->execute([$groupId, $groupId, $msg, $nid]);
      } else {
        $ins = $pdo->prepare("
          INSERT INTO notifications
            (user_id, actor_user_id, type, entity_type, entity_id, post_id, group_id, message, is_read, created_at, read_at)
          VALUES
            (?, ?, 'group_join_request', 'group', ?, NULL, ?, ?, 0, NOW(), NULL)
        ");
        $ins->execute([$toUserId, $actorUserId, $groupId, $groupId, $msg]);
      }
    } catch (Throwable $e) {
      error_log('group.php join request notification error: ' . $e->getMessage());
    }
  }
}

if (!function_exists('grp_group_manager_ids')) {
  function grp_group_manager_ids(PDO $pdo, int $groupId, int $excludeUserId = 0): array {
    $out = [];

    if ($groupId <= 0) return [];

    try {
      $st = $pdo->prepare("SELECT owner_user_id FROM `groups` WHERE id=? LIMIT 1");
      $st->execute([$groupId]);
      $ownerId = (int)($st->fetchColumn() ?: 0);
      if ($ownerId > 0 && $ownerId !== $excludeUserId) {
        $out[$ownerId] = $ownerId;
      }
    } catch (Throwable $e) {
    }

    try {
      $hasStatus = false;
      $hasRole = false;

      try {
        $stCol = $pdo->prepare("
          SELECT column_name
          FROM information_schema.columns
          WHERE table_schema = DATABASE()
            AND table_name = 'group_members'
            AND column_name IN ('status','role')
        ");
        $stCol->execute();
        $cols = $stCol->fetchAll(PDO::FETCH_COLUMN) ?: [];
        $hasStatus = in_array('status', $cols, true);
        $hasRole   = in_array('role', $cols, true);
      } catch (Throwable $e) {
      }

      if ($hasRole) {
        $sql = "
          SELECT user_id
          FROM group_members
          WHERE group_id = ?
            AND role IN ('owner','admin')
        ";
        $args = [$groupId];

        if ($hasStatus) {
          $sql .= " AND status = 'active'";
        }
        if ($excludeUserId > 0) {
          $sql .= " AND user_id <> ?";
          $args[] = $excludeUserId;
        }

        $st = $pdo->prepare($sql);
        $st->execute($args);

        while ($uid = $st->fetchColumn()) {
          $uid = (int)$uid;
          if ($uid > 0 && $uid !== $excludeUserId) {
            $out[$uid] = $uid;
          }
        }
      }
    } catch (Throwable $e) {
      error_log('group.php manager_ids error: ' . $e->getMessage());
    }

    return array_values($out);
  }
}

if (!function_exists('grp_send_join_request_emails')) {
  function grp_send_join_request_emails(PDO $pdo, int $fromUserId, int $groupId, array $managerIds): void {
    if ($fromUserId <= 0 || $groupId <= 0 || !$managerIds) return;
    if (!function_exists('ntf_dispatch_group_join_request')) return;

    foreach ($managerIds as $toUserId) {
      $toUserId = (int)$toUserId;
      if ($toUserId <= 0 || $toUserId === $fromUserId) continue;

      try {
        $dedupeKey = 'gjr:g' . $groupId . ':from' . $fromUserId . ':to' . $toUserId;
        ntf_dispatch_group_join_request($pdo, $fromUserId, $toUserId, $groupId, $dedupeKey);
      } catch (Throwable $e) {
        error_log('group.php join request email error: ' . $e->getMessage());
      }
    }
  }
}

/* ===== safe identifiers ===== */
function grp_safe_name(string $s): bool { return (bool)preg_match('~^[a-z0-9_]+$~i', $s); }
function grp_tbl(string $t): string { return grp_safe_name($t) ? ('`' . $t . '`') : '``'; }
function grp_col(string $c): string { return grp_safe_name($c) ? ('`' . $c . '`') : '``'; }

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

/* ===== DB helpers ===== */
function grp_try_table(PDO $pdo, string $table): bool {
  if (!grp_safe_name($table)) return false;
  try {
    $pdo->query("SELECT 1 FROM " . grp_tbl($table) . " LIMIT 1");
    return true;
  } catch (Throwable $e) {
    return false;
  }
}
function grp_get_columns(PDO $pdo, string $table): array {
  if (!grp_safe_name($table)) return [];
  try {
    $st = $pdo->query("SHOW COLUMNS FROM " . grp_tbl($table));
    $set = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
      $f = (string)($r['Field'] ?? '');
      if ($f !== '') $set[$f] = true;
    }
    return $set;
  } catch (Throwable $e) {
    return [];
  }
}
function grp_pick_col(array $set, array $candidates): ?string {
  foreach ($candidates as $c) if (isset($set[$c])) return $c;
  return null;
}

/* ===== tables (fixed) ===== */
$groupsTable  = 'groups';
$membersTable = 'group_members';
$topicsTable  = 'group_topics';

$tablesOk = grp_try_table($pdo, $groupsTable) && grp_try_table($pdo, $membersTable) && grp_try_table($pdo, $topicsTable);

/* ===== detect columns (if tables exist) ===== */
$gCols  = $tablesOk ? grp_get_columns($pdo, $groupsTable) : [];
$gmCols = $tablesOk ? grp_get_columns($pdo, $membersTable) : [];
$tCols  = $tablesOk ? grp_get_columns($pdo, $topicsTable) : [];

$gIdCol       = grp_pick_col($gCols,  ['id']);
$gNameCol     = grp_pick_col($gCols,  ['name','title']);
$gAboutCol    = grp_pick_col($gCols,  ['about','description','bio']);
$gSlugCol     = grp_pick_col($gCols,  ['slug']);
$gAvatarCol   = grp_pick_col($gCols,  ['avatar_path']);
$gCoverCol    = grp_pick_col($gCols,  ['cover_path']);
$gTopicIdCol  = grp_pick_col($gCols,  ['topic_id']);
$gTopicStrCol = grp_pick_col($gCols,  ['topic']);
$gOwnerCol    = grp_pick_col($gCols,  ['owner_user_id']);
$gIsClosedCol = grp_pick_col($gCols,  ['is_closed']);

$gmGroupCol      = grp_pick_col($gmCols, ['group_id','gid']);
$gmUserCol       = grp_pick_col($gmCols, ['user_id','uid']);
$gmStatusCol     = grp_pick_col($gmCols, ['status','state']);
$gmRoleCol       = grp_pick_col($gmCols, ['role','type']);
$gmCreatedCol    = grp_pick_col($gmCols, ['created_at','joined_at']);
$gmPendingKindCol= grp_pick_col($gmCols, ['pending_kind']);

$tIdCol   = grp_pick_col($tCols, ['id','topic_id']);
$tCodeCol = grp_pick_col($tCols, ['code']);
$tUkCol   = grp_pick_col($tCols, ['name_uk','title_uk','name']);
$tEnCol   = grp_pick_col($tCols, ['name_en']);
$tRuCol   = grp_pick_col($tCols, ['name_ru']);
$tSortCol = grp_pick_col($tCols, ['sort','pos','position','order']);

/* ===== identifiers for queries ===== */
$gT  = grp_tbl($groupsTable);
$gmT = grp_tbl($membersTable);
$tT  = grp_tbl($topicsTable);

/* =========================================================
   REDIRECT VIEW: /group.php?id=... -> /functional/group/view.php?g=slug (301)
========================================================= */
$viewId = (int)($_GET['id'] ?? 0);
if ($viewId > 0 && $tablesOk && $gIdCol && $gNameCol) {
  $gId   = grp_col($gIdCol);
  $gName = grp_col($gNameCol);
  $gSlugExpr = ($gSlugCol ? ("COALESCE(g." . grp_col($gSlugCol) . ",'')") : "''");

  try {
    $st = $pdo->prepare("SELECT g.$gName AS title, $gSlugExpr AS slug FROM $gT g WHERE g.$gId=? LIMIT 1");
    $st->execute([$viewId]);
    $r = $st->fetch(PDO::FETCH_ASSOC) ?: null;

    if ($r) {
      $slug = trim((string)($r['slug'] ?? ''));
      if ($slug !== '') {
        $langQ = trim((string)($_GET['lang'] ?? ''));
        $url = group_view_url($slug);
        if ($langQ !== '') $url .= '&lang=' . rawurlencode($langQ);
        redirect($url, 301);
      }
    }
  } catch (Throwable $e) {
  }
}

/* =========================================================
   SEO
========================================================= */
$q   = trim((string)($_GET['q'] ?? ''));
$view = (string)($_GET['view'] ?? 'all');
$topicKey = trim((string)($_GET['topic'] ?? ''));

$title = tr('Групи', 'Groups', 'Группы');
$meta_desc = tr(
  'Каталог груп VMEREGI: пошук за назвою та тематикою, ТОП груп і вступ до спільнот.',
  'VMEREGI groups catalog: search by name/topic, TOP groups and joining communities.',
  'Каталог групп VMEREGI: поиск по названию/тематике, ТОП групп и вступление в сообщества.'
);
$canonical = abs_url('/group.php');

$seo_noindex = ($q !== '' || $topicKey !== '' || ($view !== '' && $view !== 'all'));

/* ===== header ONCE ===== */
require __DIR__ . '/includes/header.php';

/* ===== DB errors ===== */
if (!$tablesOk) {
  ?>
  <div class="container my-4">
    <div class="alert alert-danger">
      <?= h(tr('Проблема з БД: не знайдені таблиці груп/учасників/тематик.', 'DB problem: groups/members/topics tables not found.', 'Проблема БД: таблицы групп/участников/тематик не найдены.')) ?>
    </div>
  </div>
  <?php
  require __DIR__ . '/includes/footer.php';
  exit;
}

if (!$gIdCol || !$gNameCol || !$gmGroupCol || !$gmUserCol || !$tIdCol || !$tUkCol) {
  ?>
  <div class="container my-4">
    <div class="alert alert-danger">
      <?= h(tr('Помилка структури таблиць (не знайдені потрібні колонки).', 'DB schema error (required columns missing).', 'Ошибка структуры таблиц (нет нужных колонок).')) ?>
    </div>
  </div>
  <?php
  require __DIR__ . '/includes/footer.php';
  exit;
}

/* ===== identifiers ===== */
$gId   = grp_col($gIdCol);
$gName = grp_col($gNameCol);

$gSlugExpr      = ($gSlugCol ? ("COALESCE(g." . grp_col($gSlugCol) . ",'')") : "''");
$gAboutExpr     = ($gAboutCol ? ("COALESCE(g." . grp_col($gAboutCol) . ",'')") : "''");
$gAvatarExpr    = ($gAvatarCol ? ("COALESCE(g." . grp_col($gAvatarCol) . ",'')") : "''");
$gCoverExpr     = ($gCoverCol ? ("COALESCE(g." . grp_col($gCoverCol) . ",'')") : "''");
$gTopicStrExpr  = ($gTopicStrCol ? ("TRIM(COALESCE(g." . grp_col($gTopicStrCol) . ",''))") : "''");
$gClosedExpr    = ($gIsClosedCol ? ("COALESCE(g." . grp_col($gIsClosedCol) . ",0)") : "0");
$gOwnerExpr     = ($gOwnerCol ? ("COALESCE(g." . grp_col($gOwnerCol) . ",0)") : "0");

$topicsMode = ($gTopicIdCol ? 'id' : ($gTopicStrCol ? 'str' : 'none'));

/* ===== member count join ===== */
$gmG = grp_col($gmGroupCol);
$gmU = grp_col($gmUserCol);

$activeWhere = ($gmStatusCol ? ("WHERE " . grp_col($gmStatusCol) . "='active'") : "");

$membersCountExpr = "COALESCE(mc.cnt,0)";
$membersJoinSql = "
  LEFT JOIN (
    SELECT $gmG AS group_id, COUNT(*) AS cnt
    FROM $gmT
    $activeWhere
    GROUP BY $gmG
  ) mc ON mc.group_id = g.$gId
";

$meJoinSql = "
  0 AS has_membership,
  0 AS is_member,
  '' AS my_status,
  '' AS my_role,
  '' AS my_pending_kind
";
$meJoinJoin = "";
$paramsMeJoin = [];

if ($me) {
  $statusExpr = $gmStatusCol ? ("COALESCE(gm." . grp_col($gmStatusCol) . ",'')") : "'active'";
  $roleExpr   = $gmRoleCol ? ("COALESCE(gm." . grp_col($gmRoleCol) . ",'')") : "''";
  $pkExpr     = $gmPendingKindCol ? ("COALESCE(gm." . grp_col($gmPendingKindCol) . ",'')") : "''";

  $meJoinSql = "
    CASE WHEN gm.$gmU IS NULL THEN 0 ELSE 1 END AS has_membership,
    CASE
      WHEN gm.$gmU IS NULL THEN 0
      " . ($gmStatusCol ? "WHEN gm." . grp_col($gmStatusCol) . "='active' THEN 1" : "ELSE 1") . "
      " . ($gmStatusCol ? "ELSE 0" : "") . "
    END AS is_member,
    $statusExpr AS my_status,
    $roleExpr AS my_role,
    $pkExpr AS my_pending_kind
  ";

  $meJoinJoin = "LEFT JOIN $gmT gm ON gm.$gmG = g.$gId AND gm.$gmU = ?";
  $paramsMeJoin[] = (int)$me['id'];
}

/* ===== topics select/join ===== */
$topicSelect = "'' AS topic_uk, '' AS topic_en, '' AS topic_ru, '' AS topic_label, '' AS topic_str, NULL AS topic_id";
$topicJoin   = "";

if ($topicsMode === 'id') {
  $tId = grp_col($tIdCol);

  $uk = "COALESCE(t." . grp_col($tUkCol) . ",'')";
  $en = $tEnCol ? "COALESCE(t." . grp_col($tEnCol) . ",'')" : $uk;
  $ru = $tRuCol ? "COALESCE(t." . grp_col($tRuCol) . ",'')" : $uk;

  $topicSelect = "
    $uk AS topic_uk,
    $en AS topic_en,
    $ru AS topic_ru,
    COALESCE(NULLIF($uk,''), $gTopicStrExpr) AS topic_label,
    $gTopicStrExpr AS topic_str,
    t.$tId AS topic_id
  ";
  $topicJoin   = "LEFT JOIN $tT t ON t.$tId = g." . grp_col($gTopicIdCol);
} elseif ($topicsMode === 'str') {
  $topicSelect = "
    '' AS topic_uk,
    '' AS topic_en,
    '' AS topic_ru,
    $gTopicStrExpr AS topic_label,
    $gTopicStrExpr AS topic_str,
    NULL AS topic_id
  ";
}

/* ===== JOIN/LEAVE ===== */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $action  = (string)($_POST['action'] ?? '');
  $groupId = (int)($_POST['group_id'] ?? 0);
  $csrf    = (string)($_POST['csrf'] ?? '');

  if (!$me) redirect('/login.php?to=' . urlencode(current_uri()));
  if (!csrf_check($csrf)) {
    flash_set('err', tr('Помилка безпеки (CSRF).', 'Security error (CSRF).', 'Ошибка безопасности (CSRF).'));
    redirect('/group.php');
  }
  if ($groupId <= 0) {
    flash_set('err', tr('Невірна група.', 'Invalid group.', 'Неверная группа.'));
    redirect('/group.php');
  }

  try {
    $groupSql = "
      SELECT
        g.$gId AS id,
        g.$gName AS title,
        $gSlugExpr AS slug,
        $gClosedExpr AS is_closed,
        $gOwnerExpr AS owner_user_id
      FROM $gT g
      WHERE g.$gId=?
      LIMIT 1
    ";
    $st = $pdo->prepare($groupSql);
    $st->execute([$groupId]);
    $groupRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;

    if (!$groupRow) {
      flash_set('err', tr('Групу не знайдено.', 'Group not found.', 'Группа не найдена.'));
      redirect('/group.php');
    }

    $groupName = (string)($groupRow['title'] ?? '');
    $groupClosed = ((int)($groupRow['is_closed'] ?? 0) === 1);
    $ownerUserId = (int)($groupRow['owner_user_id'] ?? 0);

    if ($action === 'join') {
      if ($ownerUserId > 0 && $ownerUserId === (int)$me['id']) {
        flash_set('ok', tr('Ви вже власник цієї групи ✅', 'You already own this group ✅', 'Вы уже владелец этой группы ✅'));
        redirect('/group.php');
      }

      $selectMembership = "SELECT 1";
      if ($gmStatusCol)      $selectMembership .= ", " . grp_col($gmStatusCol) . " AS status";
      if ($gmRoleCol)        $selectMembership .= ", " . grp_col($gmRoleCol) . " AS role";
      if ($gmPendingKindCol) $selectMembership .= ", " . grp_col($gmPendingKindCol) . " AS pending_kind";

      $stM = $pdo->prepare("
        SELECT $selectMembership
        FROM $gmT
        WHERE $gmG=? AND $gmU=?
        LIMIT 1
      ");
      $stM->execute([$groupId, (int)$me['id']]);
      $mem = $stM->fetch(PDO::FETCH_ASSOC) ?: null;

      $curStatus = $gmStatusCol ? trim((string)($mem['status'] ?? '')) : ($mem ? 'active' : '');
      $curRole   = $gmRoleCol ? trim((string)($mem['role'] ?? '')) : '';
      $pendingKind = $gmPendingKindCol ? trim((string)($mem['pending_kind'] ?? '')) : '';

      if ($mem) {
        if ($gmStatusCol && $curStatus === 'banned') {
          flash_set('err', tr('Вас заблоковано в цій групі.', 'You are banned in this group.', 'Вы заблокированы в этой группе.'));
          redirect('/group.php');
        }

        if (!$gmStatusCol || $curStatus === 'active') {
          flash_set('ok', tr('Ви вже в групі ✅', 'You are already in the group ✅', 'Вы уже в группе ✅'));
          redirect('/group.php');
        }

        if ($gmStatusCol && $curStatus === 'pending') {
          if ($gmPendingKindCol && $pendingKind === 'request') {
            flash_set('ok', tr('Заявку вже надіслано ✅', 'Join request already sent ✅', 'Заявка уже отправлена ✅'));
            redirect('/group.php');
          }

          $updateSql = "UPDATE $gmT SET " . grp_col($gmStatusCol) . "='active'";
          if ($gmPendingKindCol) {
            $updateSql .= ", " . grp_col($gmPendingKindCol) . "=NULL";
          }
          if ($gmRoleCol && $curRole === '') {
            $updateSql .= ", " . grp_col($gmRoleCol) . "='member'";
          }
          $updateSql .= " WHERE $gmG=? AND $gmU=? LIMIT 1";

          $up = $pdo->prepare($updateSql);
          $up->execute([$groupId, (int)$me['id']]);

          try {
            if (grp_notifications_ensure_schema($pdo)) {
              $markSql = "UPDATE notifications SET is_read=1, read_at=COALESCE(read_at, NOW())
                          WHERE user_id=? AND group_id=? AND type='group_invite' AND is_read=0";
              $mark = $pdo->prepare($markSql);
              $mark->execute([(int)$me['id'], $groupId]);
            }
          } catch (Throwable $e) {
          }

          flash_set('ok', tr('Ви вступили до групи ✅', 'You joined the group ✅', 'Вы вступили в группу ✅'));
          redirect('/group.php');
        }
      }

      if ($groupClosed && $gmStatusCol) {
        $fields = [$gmG, $gmU];
        $values = ['?', '?'];
        $binds  = [$groupId, (int)$me['id']];

        if ($gmRoleCol) {
          $fields[] = grp_col($gmRoleCol);
          $values[] = "'member'";
        }
        if ($gmStatusCol) {
          $fields[] = grp_col($gmStatusCol);
          $values[] = "'pending'";
        }
        if ($gmPendingKindCol) {
          $fields[] = grp_col($gmPendingKindCol);
          $values[] = "'request'";
        }
        if ($gmCreatedCol) {
          $fields[] = grp_col($gmCreatedCol);
          $values[] = "NOW()";
        }

        $ins = $pdo->prepare("INSERT INTO $gmT (" . implode(',', $fields) . ") VALUES (" . implode(',', $values) . ")");
        $ins->execute($binds);

        $managerIds = grp_group_manager_ids($pdo, $groupId, (int)$me['id']);
        foreach ($managerIds as $managerId) {
          grp_notification_upsert_join_request($pdo, (int)$managerId, (int)$me['id'], $groupId, $groupName);
        }
        grp_send_join_request_emails($pdo, (int)$me['id'], $groupId, $managerIds);

        flash_set('ok', tr('Заявку на вступ надіслано ✅', 'Join request sent ✅', 'Заявка на вступление отправлена ✅'));
        redirect('/group.php');
      }

      $fields = [$gmG, $gmU];
      $values = ['?', '?'];
      $binds  = [$groupId, (int)$me['id']];

      if ($gmRoleCol) {
        $fields[] = grp_col($gmRoleCol);
        $values[] = "'member'";
      }
      if ($gmStatusCol) {
        $fields[] = grp_col($gmStatusCol);
        $values[] = "'active'";
      }
      if ($gmPendingKindCol) {
        $fields[] = grp_col($gmPendingKindCol);
        $values[] = "NULL";
      }
      if ($gmCreatedCol) {
        $fields[] = grp_col($gmCreatedCol);
        $values[] = "NOW()";
      }

      $ins = $pdo->prepare("INSERT INTO $gmT (" . implode(',', $fields) . ") VALUES (" . implode(',', $values) . ")");
      $ins->execute($binds);

      flash_set('ok', tr('Ви вступили до групи ✅', 'You joined the group ✅', 'Вы вступили в группу ✅'));
    }

    if ($action === 'leave') {
      if ($gmRoleCol) {
        $gmR = grp_col($gmRoleCol);
        $st = $pdo->prepare("DELETE FROM $gmT WHERE $gmG=? AND $gmU=? AND $gmR <> 'owner' LIMIT 1");
        $st->execute([$groupId, (int)$me['id']]);
      } else {
        $st = $pdo->prepare("DELETE FROM $gmT WHERE $gmG=? AND $gmU=? LIMIT 1");
        $st->execute([$groupId, (int)$me['id']]);
      }
      flash_set('ok', tr('Ви вийшли з групи.', 'You left the group.', 'Вы вышли из группы.'));
    }

  } catch (Throwable $e) {
    error_log('group.php join/leave error: ' . $e->getMessage());
    flash_set('err', tr('Помилка. Спробуйте ще раз.', 'Error. Try again.', 'Ошибка. Попробуйте ещё раз.'));
  }

  $qs = [];
  if (isset($_GET['q']) && (string)$_GET['q'] !== '') $qs['q'] = (string)$_GET['q'];
  if (isset($_GET['topic']) && (string)$_GET['topic'] !== '') $qs['topic'] = (string)$_GET['topic'];
  if (isset($_GET['view']) && (string)($_GET['view'] ?? '') !== '') $qs['view'] = (string)$_GET['view'];
  redirect('/group.php' . ($qs ? ('?' . http_build_query($qs)) : ''));
}

/* ===== page state ===== */
$err = flash_get('err');
$ok  = flash_get('ok');

/* ===== topic filter parse ===== */
$topicFilterId = 0;
$topicFilterStr = '';
$topicFilterNoTopic = false;

if ($topicKey === '') {
} elseif ($topicKey === '-1') {
  $topicFilterNoTopic = true;
} elseif (preg_match('~^s:(.+)$~u', $topicKey, $m)) {
  $topicFilterStr = trim((string)$m[1]);
} elseif (ctype_digit($topicKey)) {
  $topicFilterId = (int)$topicKey;
} else {
  $topicFilterStr = $topicKey;
  $topicKey = 's:' . $topicFilterStr;
}

/* ===== topics list ===== */
$topics = [];
$topicKeysSet = [];

if ($topicsMode === 'id') {
  $tId = grp_col($tIdCol);
  $tSort = $tSortCol ? grp_col($tSortCol) : '';
  $topicIdCol = grp_col($gTopicIdCol);

  $sqlTopics = "
    SELECT
      t.$tId AS id,
      t." . grp_col($tUkCol) . " AS name_uk,
      " . ($tEnCol ? "t." . grp_col($tEnCol) : "''") . " AS name_en,
      " . ($tRuCol ? "t." . grp_col($tRuCol) : "''") . " AS name_ru,
      " . ($tCodeCol ? "t." . grp_col($tCodeCol) : "''") . " AS code,
      COALESCE(gc.cnt,0) AS groups_cnt
    FROM $tT t
    LEFT JOIN (
      SELECT $topicIdCol AS topic_id, COUNT(*) AS cnt
      FROM $gT
      GROUP BY $topicIdCol
    ) gc ON gc.topic_id = t.$tId
    ORDER BY " . ($tSort ? "COALESCE(t.$tSort,100) ASC, " : "") . " t.$tId ASC
  ";
  $st = $pdo->query($sqlTopics);
  $rows = $st->fetchAll(PDO::FETCH_ASSOC);

  $cntNoTopic = 0;
  try {
    if ($gTopicStrCol) {
      $st2 = $pdo->query("
        SELECT COUNT(*)
        FROM $gT g
        WHERE (g.$topicIdCol IS NULL OR g.$topicIdCol=0)
          AND TRIM(COALESCE(g." . grp_col($gTopicStrCol) . " ,''))=''
      ");

    } else {
      $st2 = $pdo->query("SELECT COUNT(*) FROM $gT g WHERE g.$topicIdCol IS NULL OR g.$topicIdCol=0");
    }
    $cntNoTopic = (int)$st2->fetchColumn();
  } catch (Throwable $e) {
    $cntNoTopic = 0;
  }

  $topics[] = ['key' => '-1', 'label' => tr('Без теми','No topic','Без темы'), 'cnt' => $cntNoTopic];
  $topicKeysSet['-1'] = true;

  foreach ($rows as $r) {
    $l = function_exists('lang') ? (string)lang() : 'uk';
    $label = (string)($r['name_uk'] ?? '');

    if ($l === 'en' && (string)($r['name_en'] ?? '') !== '') $label = (string)$r['name_en'];
    elseif ($l === 'ru' && (string)($r['name_ru'] ?? '') !== '') $label = (string)$r['name_ru'];

    if (trim($label) === '') {
      $label = (string)($r['code'] ?? '');
      if (trim($label) === '') $label = '#' . (int)$r['id'];
    }

    $k = (string)((int)($r['id'] ?? 0));
    if ($k === '0') continue;

    $topics[] = ['key' => $k, 'label' => $label, 'cnt' => (int)($r['groups_cnt'] ?? 0)];
    $topicKeysSet[$k] = true;
  }
}

if ($gTopicStrCol) {
  try {
    $st = $pdo->query("
      SELECT TRIM(COALESCE(g." . grp_col($gTopicStrCol) . ",'')) AS t, COUNT(*) AS cnt
      FROM $gT g
      WHERE TRIM(COALESCE(g." . grp_col($gTopicStrCol) . ",'')) <> ''
      GROUP BY t
      ORDER BY cnt DESC, t ASC
      LIMIT 300
    ");
    $rows = $st->fetchAll(PDO::FETCH_ASSOC);
    foreach ($rows as $r) {
      $t = trim((string)($r['t'] ?? ''));
      if ($t === '') continue;
      $k = 's:' . $t;
      if (isset($topicKeysSet[$k])) continue;
      $topics[] = ['key' => $k, 'label' => $t, 'cnt' => (int)($r['cnt'] ?? 0)];
      $topicKeysSet[$k] = true;
    }
  } catch (Throwable $e) {
  }
}

/* ===== build WHERE ===== */
$where = [];
$params = [];

$baseWhere = [];
$baseParams = [];

if ($q !== '') {
  $baseWhere[] = "g.$gName LIKE ?";
  $baseParams[] = '%' . $q . '%';

  $where[] = "g.$gName LIKE ?";
  $params[] = '%' . $q . '%';
}

if ($topicsMode === 'id') {
  $topicIdCol = grp_col($gTopicIdCol);

  if ($topicFilterNoTopic) {
    if ($gTopicStrCol) $where[] = "((g.$topicIdCol IS NULL OR g.$topicIdCol=0) AND $gTopicStrExpr='')";
    else $where[] = "(g.$topicIdCol IS NULL OR g.$topicIdCol=0)";
  } elseif ($topicFilterId > 0) {
    $where[] = "g.$topicIdCol = ?";
    $params[] = $topicFilterId;
  } elseif ($topicFilterStr !== '' && $gTopicStrCol) {
    $where[] = "$gTopicStrExpr = ?";
    $params[] = $topicFilterStr;
  }
} elseif ($topicsMode === 'str') {
  if ($topicFilterNoTopic) {
    $where[] = "($gTopicStrExpr='')";
  } elseif ($topicFilterStr !== '') {
    $where[] = "$gTopicStrExpr = ?";
    $params[] = $topicFilterStr;
  }
}

$whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
$baseWhereSql = $baseWhere ? ('WHERE ' . implode(' AND ', $baseWhere)) : '';

/* ===== total groups for sidebar ===== */
$totalGroupsForSidebar = 0;
try {
  $st = $pdo->prepare("SELECT COUNT(*) FROM $gT g $baseWhereSql");
  $st->execute($baseParams);
  $totalGroupsForSidebar = (int)$st->fetchColumn();
} catch (Throwable $e) {
  $totalGroupsForSidebar = 0;
}

/* ===== TOP10 ===== */
$topSql = "
  SELECT
    g.$gId AS id,
    g.$gName AS title,
    $gSlugExpr AS slug,
    $gAboutExpr AS description,
    $topicSelect,
    $gClosedExpr AS is_closed,
    $membersCountExpr AS members_cnt,
    $meJoinSql
  FROM $gT g
  $topicJoin
  $membersJoinSql
  $meJoinJoin
  ORDER BY members_cnt DESC, g.$gId DESC
  LIMIT 10
";
$st = $pdo->prepare($topSql);
$st->execute($paramsMeJoin);
$top10 = $st->fetchAll(PDO::FETCH_ASSOC);

/* ===== list ===== */
$listSql = "
  SELECT
    g.$gId AS id,
    g.$gName AS title,
    $gSlugExpr AS slug,
    $gAboutExpr AS description,
    $topicSelect,
    $gClosedExpr AS is_closed,
    $membersCountExpr AS members_cnt,
    $meJoinSql
  FROM $gT g
  $topicJoin
  $membersJoinSql
  $meJoinJoin
  $whereSql
  ORDER BY members_cnt DESC, g.$gId DESC
  LIMIT 500
";
$st = $pdo->prepare($listSql);
$st->execute(array_merge($paramsMeJoin, $params));
$groups = $st->fetchAll(PDO::FETCH_ASSOC);

/* ===== by-topic view ===== */
$byTopic = [];
if ($view === 'topics') {
  foreach ($groups as $g) {
    $label = '';
    if (function_exists('lang')) {
      $l = (string)lang();
      if ($l === 'en') $label = trim((string)($g['topic_en'] ?? ''));
      elseif ($l === 'ru') $label = trim((string)($g['topic_ru'] ?? ''));
      else $label = trim((string)($g['topic_uk'] ?? ''));
    }
    if ($label === '') $label = trim((string)($g['topic_label'] ?? ''));
    if ($label === '') $label = tr('Без теми','No topic','Без темы');

    if (!isset($byTopic[$label])) $byTopic[$label] = [];
    $byTopic[$label][] = $g;
  }
  uasort($byTopic, fn($a,$b) => count($b) <=> count($a));
}
?>

<?php if ($err): ?>
  <div class="alert alert-danger"><?= h($err) ?></div>
<?php endif; ?>
<?php if ($ok): ?>
  <div class="alert alert-success"><?= h($ok) ?></div>
<?php endif; ?>

<div class="d-flex align-items-center justify-content-between mb-3">
  <div>
    <h1 class="h4 mb-1"><?= h($title) ?></h1>
    <div class="text-muted small"><?= h(tr('Обери групу та вступай.', 'Choose a group and join.', 'Выбери группу и вступай.')) ?></div>
  </div>
</div>

<div class="row g-3">
  <div class="col-12 col-lg-8">

    <div class="card shadow-sm mb-3">
      <div class="card-body">
        <form class="row g-2 align-items-center" method="get" action="/group.php">
          <div class="col-12 col-md-6">
            <input type="text" name="q" value="<?= h($q) ?>" class="form-control" placeholder="<?= h(tr('Пошук груп…','Search groups…','Поиск групп…')) ?>">
          </div>

          <div class="col-12 col-md-4">
            <select class="form-select" name="topic">
              <option value=""><?= h(tr('Усі тематики','All topics','Все тематики')) ?></option>
              <?php foreach ($topics as $t): ?>
                <?php $key = (string)$t['key']; $sel = ($key === $topicKey); ?>
                <option value="<?= h($key) ?>" <?= $sel ? 'selected' : '' ?>>
                  <?= h((string)$t['label']) ?> (<?= (int)$t['cnt'] ?>)
                </option>
              <?php endforeach; ?>
            </select>
          </div>

          <div class="col-12 col-md-2 d-grid">
            <button class="btn btn-primary"><?= h(tr('Знайти','Search','Найти')) ?></button>
          </div>

          <input type="hidden" name="view" value="<?= h($view) ?>">
        </form>
      </div>
    </div>

    <ul class="nav nav-pills mb-3">
      <li class="nav-item">
        <a class="nav-link <?= $view !== 'topics' ? 'active' : '' ?>"
           href="/group.php?<?= h(http_build_query(array_filter(['q'=>$q,'topic'=>($_GET['topic'] ?? null),'view'=>'all']))) ?>">
          <?= h(tr('Усі групи','All groups','Все группы')) ?>
        </a>
      </li>
      <li class="nav-item">
        <a class="nav-link <?= $view === 'topics' ? 'active' : '' ?>"
           href="/group.php?<?= h(http_build_query(array_filter(['q'=>$q,'topic'=>($_GET['topic'] ?? null),'view'=>'topics']))) ?>">
          <?= h(tr('За тематикою','By topics','По тематике')) ?>
        </a>
      </li>
    </ul>

    <?php if ($view === 'topics'): ?>

      <?php if (!$byTopic): ?>
        <div class="alert alert-light border"><?= h(tr('Груп не знайдено.','No groups found.','Группы не найдены.')) ?></div>
      <?php else: ?>
        <div class="accordion" id="accTopics">
          <?php $i=0; foreach ($byTopic as $topicLabel => $list): $i++; ?>
            <div class="accordion-item">
              <h2 class="accordion-header" id="th<?= $i ?>">
                <button class="accordion-button <?= $i===1 ? '' : 'collapsed' ?>" type="button"
                        data-bs-toggle="collapse" data-bs-target="#tc<?= $i ?>">
                  <?= h($topicLabel) ?> <span class="ms-2 badge bg-secondary"><?= count($list) ?></span>
                </button>
              </h2>
              <div id="tc<?= $i ?>" class="accordion-collapse collapse <?= $i===1 ? 'show' : '' ?>" data-bs-parent="#accTopics">
                <div class="accordion-body">
                  <div class="list-group">
                    <?php foreach ($list as $g): ?>
                      <?php
                        $gUrl = group_url_from_row($g);
                        $loginTo = $gUrl;

                        $isClosed = ((int)($g['is_closed'] ?? 0) === 1);
                        $hasMembership = ((int)($g['has_membership'] ?? 0) === 1);
                        $isMember = ((int)($g['is_member'] ?? 0) === 1);
                        $myStatus = trim((string)($g['my_status'] ?? ''));
                        $myPendingKind = trim((string)($g['my_pending_kind'] ?? ''));
                      ?>
                      <div class="list-group-item d-flex align-items-start justify-content-between gap-3">
                        <div class="flex-grow-1">
                          <div class="fw-semibold">
                            <a class="text-decoration-none" href="<?= h($gUrl) ?>"><?= h((string)$g['title']) ?></a>
                          </div>
                          <div class="text-muted small">
                            <?= h(tr('Учасників:','Members:','Участников:')) ?> <?= (int)$g['members_cnt'] ?>
                            <span class="mx-1">•</span>
                            <?= h($isClosed ? tr('Закрита','Closed','Закрытая') : tr('Відкрита','Open','Открытая')) ?>
                          </div>
                          <?php if (!empty($g['description'])): ?>
                            <div class="text-muted small mt-1"><?= h(mb_strimwidth((string)$g['description'], 0, 220, '…', 'UTF-8')) ?></div>
                          <?php endif; ?>
                        </div>

                        <div class="d-flex align-items-center gap-2">
                          <?php if (!$me): ?>
                            <a class="btn btn-sm btn-outline-primary" href="/login.php?to=<?= urlencode($loginTo) ?>"><?= h(tr('Увійти','Login','Войти')) ?></a>
                          <?php else: ?>
                            <form method="post" class="m-0">
                              <input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
                              <input type="hidden" name="group_id" value="<?= (int)$g['id'] ?>">

                              <?php if ($isMember): ?>
                                <input type="hidden" name="action" value="leave">
                                <button class="btn btn-sm btn-outline-secondary"><?= h(tr('Вийти','Leave','Выйти')) ?></button>

                              <?php elseif ($myStatus === 'pending' && $myPendingKind === 'request'): ?>
                                <button class="btn btn-sm btn-outline-warning" type="button" disabled><?= h(tr('Заявка надіслана','Request sent','Заявка отправлена')) ?></button>

                              <?php elseif ($myStatus === 'pending'): ?>
                                <input type="hidden" name="action" value="join">
                                <button class="btn btn-sm btn-success"><?= h(tr('Прийняти запрошення','Accept invite','Принять приглашение')) ?></button>

                              <?php elseif ($myStatus === 'banned'): ?>
                                <button class="btn btn-sm btn-danger" type="button" disabled><?= h(tr('Вас заблоковано','You are banned','Вы заблокированы')) ?></button>

                              <?php else: ?>
                                <input type="hidden" name="action" value="join">
                                <button class="btn btn-sm btn-primary">
                                  <?= h($isClosed ? tr('Подати заявку','Request to join','Подать заявку') : tr('Вступити','Join','Вступить')) ?>
                                </button>
                              <?php endif; ?>
                            </form>
                          <?php endif; ?>
                        </div>
                      </div>
                    <?php endforeach; ?>
                  </div>
                </div>
              </div>
            </div>
          <?php endforeach; ?>
        </div>
      <?php endif; ?>

    <?php else: ?>

      <?php if (!$groups): ?>
        <div class="alert alert-light border"><?= h(tr('Груп не знайдено.','No groups found.','Группы не найдены.')) ?></div>
      <?php else: ?>
        <div class="list-group shadow-sm">
          <?php foreach ($groups as $g): ?>
            <?php
              $tLabel = '';
              if (function_exists('lang')) {
                $l = (string)lang();
                if ($l === 'en') $tLabel = trim((string)($g['topic_en'] ?? ''));
                elseif ($l === 'ru') $tLabel = trim((string)($g['topic_ru'] ?? ''));
                else $tLabel = trim((string)($g['topic_uk'] ?? ''));
              }
              if ($tLabel === '') $tLabel = trim((string)($g['topic_label'] ?? ''));
              $tId = isset($g['topic_id']) ? (int)($g['topic_id'] ?? 0) : 0;
              $tStr = trim((string)($g['topic_str'] ?? ''));

              $topicLink = '/group.php';
              $noTopicLabel = tr('Без теми','No topic','Без темы');

              if ($topicsMode !== 'none') {
                if ($tId > 0) {
                  $topicLink = '/group.php?topic=' . $tId;
                } elseif ($tStr !== '') {
                  $topicLink = '/group.php?topic=' . urlencode('s:' . $tStr);
                } else {
                  $tLabel = $noTopicLabel;
                  $topicLink = '/group.php?topic=-1';
                }
              }

              $gUrl = group_url_from_row($g);
              $loginTo = $gUrl;

              $isClosed = ((int)($g['is_closed'] ?? 0) === 1);
              $isMember = ((int)($g['is_member'] ?? 0) === 1);
              $myStatus = trim((string)($g['my_status'] ?? ''));
              $myPendingKind = trim((string)($g['my_pending_kind'] ?? ''));
            ?>
            <div class="list-group-item">
              <div class="d-flex align-items-start justify-content-between gap-3">
                <div class="flex-grow-1">
                  <div class="d-flex align-items-center gap-2 flex-wrap">
                    <div class="fw-bold">
                      <a class="text-decoration-none" href="<?= h($gUrl) ?>"><?= h((string)$g['title']) ?></a>
                    </div>

                    <?php if ($tLabel !== ''): ?>
                      <a class="badge bg-secondary text-decoration-none" href="<?= h($topicLink) ?>"><?= h($tLabel) ?></a>
                    <?php endif; ?>

                    <span class="badge bg-light text-dark border">
                      <?= h(tr('Учасників:','Members:','Участников:')) ?> <?= (int)$g['members_cnt'] ?>
                    </span>

                    <span class="badge <?= $isClosed ? 'bg-warning text-dark' : 'bg-success' ?>">
                      <?= h($isClosed ? tr('Закрита','Closed','Закрытая') : tr('Відкрита','Open','Открытая')) ?>
                    </span>
                  </div>

                  <?php if (!empty($g['description'])): ?>
                    <div class="text-muted small mt-1"><?= h(mb_strimwidth((string)$g['description'], 0, 220, '…', 'UTF-8')) ?></div>
                  <?php endif; ?>
                </div>

                <div class="d-flex align-items-center gap-2">
                  <?php if (!$me): ?>
                    <a class="btn btn-sm btn-outline-primary" href="/login.php?to=<?= urlencode($loginTo) ?>"><?= h(tr('Увійти','Login','Войти')) ?></a>
                  <?php else: ?>
                    <form method="post" class="m-0">
                      <input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
                      <input type="hidden" name="group_id" value="<?= (int)$g['id'] ?>">

                      <?php if ($isMember): ?>
                        <input type="hidden" name="action" value="leave">
                        <button class="btn btn-sm btn-outline-secondary"><?= h(tr('Вийти','Leave','Выйти')) ?></button>

                      <?php elseif ($myStatus === 'pending' && $myPendingKind === 'request'): ?>
                        <button class="btn btn-sm btn-outline-warning" type="button" disabled><?= h(tr('Заявка надіслана','Request sent','Заявка отправлена')) ?></button>

                      <?php elseif ($myStatus === 'pending'): ?>
                        <input type="hidden" name="action" value="join">
                        <button class="btn btn-sm btn-success"><?= h(tr('Прийняти запрошення','Accept invite','Принять приглашение')) ?></button>

                      <?php elseif ($myStatus === 'banned'): ?>
                        <button class="btn btn-sm btn-danger" type="button" disabled><?= h(tr('Вас заблоковано','You are banned','Вы заблокированы')) ?></button>

                      <?php else: ?>
                        <input type="hidden" name="action" value="join">
                        <button class="btn btn-sm btn-primary">
                          <?= h($isClosed ? tr('Подати заявку','Request to join','Подать заявку') : tr('Вступити','Join','Вступить')) ?>
                        </button>
                      <?php endif; ?>
                    </form>
                  <?php endif; ?>
                </div>

              </div>
            </div>
          <?php endforeach; ?>
        </div>
      <?php endif; ?>

    <?php endif; ?>

  </div>

  <div class="col-12 col-lg-4">

    <div class="card shadow-sm">
      <div class="card-header fw-semibold"><?= h(tr('ТОП 10 груп','TOP 10 groups','ТОП 10 групп')) ?></div>
      <div class="list-group list-group-flush">
        <?php if (!$top10): ?>
          <div class="list-group-item text-muted small"><?= h(tr('Поки немає груп.','No groups yet.','Пока нет групп.')) ?></div>
        <?php else: ?>
          <?php foreach ($top10 as $idx => $g): ?>
            <?php
              $gUrl = group_url_from_row($g);
              $loginTo = $gUrl;

              $isClosed = ((int)($g['is_closed'] ?? 0) === 1);
              $isMember = ((int)($g['is_member'] ?? 0) === 1);
              $myStatus = trim((string)($g['my_status'] ?? ''));
              $myPendingKind = trim((string)($g['my_pending_kind'] ?? ''));
            ?>
            <div class="list-group-item d-flex align-items-center justify-content-between gap-2">
              <div class="me-2">
                <div class="fw-semibold">
                  <span class="badge bg-dark me-1">#<?= $idx+1 ?></span>
                  <a class="text-decoration-none" href="<?= h($gUrl) ?>"><?= h((string)$g['title']) ?></a>
                </div>
                <div class="text-muted small">
                  <?= h(tr('Учасників:','Members:','Участников:')) ?> <?= (int)$g['members_cnt'] ?>
                  <span class="mx-1">•</span>
                  <?= h($isClosed ? tr('Закрита','Closed','Закрытая') : tr('Відкрита','Open','Открытая')) ?>
                </div>
              </div>

              <div>
                <?php if (!$me): ?>
                  <a class="btn btn-sm btn-outline-primary" href="/login.php?to=<?= urlencode($loginTo) ?>"><?= h(tr('Увійти','Login','Войти')) ?></a>
                <?php else: ?>
                  <form method="post" class="m-0">
                    <input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
                    <input type="hidden" name="group_id" value="<?= (int)$g['id'] ?>">

                    <?php if ($isMember): ?>
                      <input type="hidden" name="action" value="leave">
                      <button class="btn btn-sm btn-outline-secondary"><?= h(tr('Вийти','Leave','Выйти')) ?></button>

                    <?php elseif ($myStatus === 'pending' && $myPendingKind === 'request'): ?>
                      <button class="btn btn-sm btn-outline-warning" type="button" disabled><?= h(tr('Заявка надіслана','Request sent','Заявка отправлена')) ?></button>

                    <?php elseif ($myStatus === 'pending'): ?>
                      <input type="hidden" name="action" value="join">
                      <button class="btn btn-sm btn-success"><?= h(tr('Прийняти запрошення','Accept invite','Принять приглашение')) ?></button>

                    <?php elseif ($myStatus === 'banned'): ?>
                      <button class="btn btn-sm btn-danger" type="button" disabled><?= h(tr('Вас заблоковано','You are banned','Вы заблокированы')) ?></button>

                    <?php else: ?>
                      <input type="hidden" name="action" value="join">
                      <button class="btn btn-sm btn-primary">
                        <?= h($isClosed ? tr('Подати заявку','Request to join','Подать заявку') : tr('Вступити','Join','Вступить')) ?>
                      </button>
                    <?php endif; ?>
                  </form>
                <?php endif; ?>
              </div>
            </div>
          <?php endforeach; ?>
        <?php endif; ?>
      </div>
    </div>

    <?php if ($topicsMode !== 'none'): ?>
      <div class="card shadow-sm mt-3">
        <div class="card-header fw-semibold"><?= h(tr('Тематики','Topics','Тематики')) ?></div>

        <div class="list-group list-group-flush" style="max-height:360px; overflow:auto;">
          <a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
             href="/group.php<?= $q !== '' ? ('?'.h(http_build_query(['q'=>$q,'view'=>'all']))) : '' ?>">
            <span><?= h(tr('Усі тематики','All topics','Все тематики')) ?></span>
            <span class="badge bg-secondary"><?= (int)$totalGroupsForSidebar ?></span>
          </a>

          <?php foreach ($topics as $t): ?>
            <?php
              $qs = ['view'=>'all'];
              if ($q !== '') $qs['q'] = $q;
              $qs['topic'] = (string)$t['key'];
            ?>
            <a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
               href="/group.php?<?= h(http_build_query($qs)) ?>">
              <span><?= h((string)$t['label']) ?></span>
              <span class="badge bg-secondary"><?= (int)$t['cnt'] ?></span>
            </a>
          <?php endforeach; ?>
        </div>

      </div>
    <?php endif; ?>

  </div>
</div>

<?php require __DIR__ . '/includes/footer.php'; ?>