<?php
declare(strict_types=1);

/**
 * /users.php
 * Каталог користувачів:
 * - список усіх зареєстрованих
 * - фільтри: країна (country_code + fallback з users.country) / стать / вік(від-до)
 * - сортування: дата реєстрації / вік / username
 * - перехід у профіль
 * - кнопка "Додати в друзі" без переходу (через friend_requests)
 *
 * ✅ ДОДАНО:
 * - режим запрошення в групу: /users.php?invite_group=ID
 *   показує кнопку "Запросити в групу" біля кожного профілю (для менеджера групи)
 *   POST action=group_invite -> створює pending-запрошення + notification + email
 *
 * ✅ ДОДАНО:
 * - email-сповіщення про нову заявку в друзі
 * - email-сповіщення про нове запрошення в групу
 *
 * ✅ UX FIX:
 * - перед submit зберігаємо scrollY у hidden input scroll_y
 * - дублюємо scrollY у sessionStorage як fallback
 * - після POST редіректимо назад з ?sy=...
 * - при наявності sy сторінка тимчасово ховається, щоб не було видимого стрибка вгору
 * - після відновлення скролу сторінка показується
 *
 * ✅ FIX:
 * - використано актуальні функції ntf_dispatch_*
 * - виправлено порядок аргументів для email-сповіщень
 * - для friend_request передається requestId після INSERT/UPDATE
 * - враховано uniq_from_to: старий запис перевикористовується замість дублюючого INSERT
 */

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

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

$pdo = db();
$me  = current_user();
$meId = (int)($me['id'] ?? 0);

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): void { header('Location: '.$url); exit; }
}
if (!function_exists('is_post')) {
  function is_post(): bool { return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'; }
}
if (!function_exists('get_str')) {
  function get_str(string $k): string { return isset($_GET[$k]) ? trim((string)$_GET[$k]) : ''; }
}
if (!function_exists('get_int')) {
  function get_int(string $k, int $def = 0): int {
    $v = $_GET[$k] ?? null;
    if ($v === null || $v === '') return $def;
    return (int)$v;
  }
}
if (!function_exists('post_str')) {
  function post_str(string $k): string { return isset($_POST[$k]) ? trim((string)$_POST[$k]) : ''; }
}
if (!function_exists('post_int')) {
  function post_int(string $k, int $def = 0): int {
    $v = $_POST[$k] ?? null;
    if ($v === null || $v === '') return $def;
    return (int)$v;
  }
}

/* CSRF fallback */
if (!function_exists('csrf_token')) {
  function csrf_token(): string {
    if (!isset($_SESSION)) return '';
    if (empty($_SESSION['_csrf'])) $_SESSION['_csrf'] = bin2hex(random_bytes(16));
    return (string)$_SESSION['_csrf'];
  }
}
if (!function_exists('csrf_check')) {
  function csrf_check(string $t): bool {
    if (!isset($_SESSION)) return false;
    return isset($_SESSION['_csrf']) && hash_equals((string)$_SESSION['_csrf'], (string)$t);
  }
}

/* safe local back (fallback) */
if (!function_exists('safe_local_path')) {
  function safe_local_path(string $raw, string $default = '/'): string {
    $raw = trim($raw);
    if ($raw === '') return $default;

    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] !== '/') return $default;
    if (str_starts_with($raw, '//')) return $default;
    return $raw;
  }
}

/* db_has_column fallback */
if (!function_exists('db_has_column')) {
  function db_has_column(PDO $pdo, string $table, string $col): bool {
    static $cache = [];
    $k = $table.'.'.$col;
    if (array_key_exists($k, $cache)) return (bool)$cache[$k];

    try {
      $st = $pdo->prepare("
        SELECT COUNT(*)
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
      ");
      $st->execute([$table, $col]);
      $cache[$k] = ((int)$st->fetchColumn() > 0);
      return (bool)$cache[$k];
    } catch (Throwable $e) {
      $cache[$k] = false;
      return false;
    }
  }
}

/* db_has_table fallback */
if (!function_exists('db_has_table')) {
  function db_has_table(PDO $pdo, string $table): bool {
    static $cache = [];
    $k = 't:' . $table;
    if (array_key_exists($k, $cache)) return (bool)$cache[$k];

    try {
      $st = $pdo->prepare("
        SELECT COUNT(*)
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
      ");
      $st->execute([$table]);
      $cache[$k] = ((int)$st->fetchColumn() > 0);
      return (bool)$cache[$k];
    } catch (Throwable $e) {
      $cache[$k] = false;
      return false;
    }
  }
}

/* ===== notifications helpers ===== */
if (!function_exists('users_notifications_ensure_schema')) {
  function users_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('users.php notifications schema error: ' . $e->getMessage());
      return false;
    }
  }
}

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

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

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

    if (!users_notifications_ensure_schema($pdo)) return;

    $msg = users_group_invite_message($lang, $groupName);

    try {
      $st = $pdo->prepare("
        SELECT id
        FROM notifications
        WHERE user_id = ?
          AND actor_user_id = ?
          AND type = 'group_invite'
          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_invite', 'group', ?, NULL, ?, ?, 0, NOW(), NULL)
        ");
        $ins->execute([$toUserId, $actorUserId, $groupId, $groupId, $msg]);
      }
    } catch (Throwable $e) {
      error_log('users.php group invite notification error: ' . $e->getMessage());
    }
  }
}

if (!function_exists('users_try_send_friend_request_email')) {
  function users_try_send_friend_request_email(PDO $pdo, int $toUserId, int $fromUserId, int $requestId = 0): void
  {
    if ($toUserId <= 0 || $fromUserId <= 0 || $toUserId === $fromUserId) return;
    if (!function_exists('ntf_dispatch_friend_request')) return;

    try {
      ntf_dispatch_friend_request($pdo, $fromUserId, $toUserId, $requestId);
    } catch (Throwable $e) {
      error_log('users.php friend request email error: ' . $e->getMessage());
    }
  }
}

if (!function_exists('users_try_send_group_invite_email')) {
  function users_try_send_group_invite_email(PDO $pdo, int $toUserId, int $fromUserId, int $groupId, bool $isPendingInvite): void
  {
    if (!$isPendingInvite) return;
    if ($toUserId <= 0 || $fromUserId <= 0 || $groupId <= 0) return;
    if ($toUserId === $fromUserId) return;
    if (!function_exists('ntf_dispatch_group_invite')) return;

    try {
      ntf_dispatch_group_invite($pdo, $fromUserId, $toUserId, $groupId);
    } catch (Throwable $e) {
      error_log('users.php group invite email error: ' . $e->getMessage());
    }
  }
}

if (!function_exists('users_is_duplicate_key_exception')) {
  function users_is_duplicate_key_exception(Throwable $e): bool
  {
    if (!$e instanceof PDOException) return false;
    $sqlState = (string)($e->errorInfo[0] ?? '');
    $driverCode = (int)($e->errorInfo[1] ?? 0);
    return ($sqlState === '23000' && $driverCode === 1062);
  }
}

/* i18n */
$lang = (string)($_SESSION['lang'] ?? ($me['lang'] ?? 'uk'));
if (!in_array($lang, ['uk','ru','en'], true)) $lang = 'uk';
$T = function(string $uk, string $ru, string $en) use ($lang): string {
  if ($lang === 'ru') return $ru;
  if ($lang === 'en') return $en;
  return $uk;
};

/* ===== invite mode (GET) ===== */
$inviteGroupId = (int)get_int('invite_group', 0);
$back = safe_local_path(get_str('back'), '');

/**
 * helper: redirect back to same users list with query string
 * + optional sy (scrollY) to avoid "jump"
 * + optional #anchor fallback
 */
function redirect_to_same_users_list(): void {
  $qs = $_GET ?? [];
  if (!is_array($qs)) $qs = [];
  unset($qs['m']);

  $sy = post_int('scroll_y', -1);
  if ($sy >= 0 && $sy <= 10000000) {
    $qs['sy'] = $sy;
  } else {
    unset($qs['sy']);
  }

  $url = '/users.php';
  $q = http_build_query($qs);
  if ($q !== '') $url .= '?' . $q;

  if (!isset($qs['sy'])) {
    $anchor = post_str('anchor');
    if ($anchor !== '' && preg_match('~^u\d+$~', $anchor)) {
      $url .= '#' . $anchor;
    }
  }

  redirect($url);
}

/* ===== columns detection ===== */
$hasCountry     = db_has_column($pdo, 'users', 'country');
$hasCountryCode = db_has_column($pdo, 'users', 'country_code');
$hasCity        = db_has_column($pdo, 'users', 'city');

$hasGender    = db_has_column($pdo, 'users', 'gender');
$hasSex       = db_has_column($pdo, 'users', 'sex');

$hasBirthdate = db_has_column($pdo, 'users', 'birthdate');
$hasBirthDate = db_has_column($pdo, 'users', 'birth_date');

$hasCreatedAt = db_has_column($pdo, 'users', 'created_at');
$hasAvatar    = db_has_column($pdo, 'users', 'avatar_path');
$hasFullName  = db_has_column($pdo, 'users', 'full_name');
$hasFirst     = db_has_column($pdo, 'users', 'first_name');
$hasLast      = db_has_column($pdo, 'users', 'last_name');
$hasActive    = db_has_column($pdo, 'users', 'is_active');
$hasLastSeen  = db_has_column($pdo, 'users', 'last_seen_at');

/* ===== full ISO list ===== */
$COUNTRIES = [
  'AF'=>'Afghanistan','AL'=>'Albania','DZ'=>'Algeria','AS'=>'American Samoa','AD'=>'Andorra','AO'=>'Angola','AI'=>'Anguilla','AQ'=>'Antarctica','AG'=>'Antigua and Barbuda','AR'=>'Argentina','AM'=>'Armenia','AW'=>'Aruba','AU'=>'Australia','AT'=>'Austria','AZ'=>'Azerbaijan',
  'BS'=>'Bahamas','BH'=>'Bahrain','BD'=>'Bangladesh','BB'=>'Barbados','BY'=>'Belarus','BE'=>'Belgium','BZ'=>'Belize','BJ'=>'Benin','BM'=>'Bermuda','BT'=>'Bhutan','BO'=>'Bolivia','BA'=>'Bosnia and Herzegovina','BW'=>'Botswana','BV'=>'Bouvet Island','BR'=>'Brazil','IO'=>'British Indian Ocean Territory','BN'=>'Brunei','BG'=>'Bulgaria','BF'=>'Burkina Faso','BI'=>'Burundi',
  'KH'=>'Cambodia','CM'=>'Cameroon','CA'=>'Canada','CV'=>'Cape Verde','KY'=>'Cayman Islands','CF'=>'Central African Republic','TD'=>'Chad','CL'=>'Chile','CN'=>'China','CX'=>'Christmas Island','CC'=>'Cocos (Keeling) Islands','CO'=>'Colombia','KM'=>'Comoros','CG'=>'Congo','CD'=>'Congo (DRC)','CK'=>'Cook Islands','CR'=>'Costa Rica','CI'=>"Côte d’Ivoire",'HR'=>'Croatia','CU'=>'Cuba','CY'=>'Cyprus','CZ'=>'Czechia',
  'DK'=>'Denmark','DJ'=>'Djibouti','DM'=>'Dominica','DO'=>'Dominican Republic',
  'EC'=>'Ecuador','EG'=>'Egypt','SV'=>'El Salvador','GQ'=>'Equatorial Guinea','ER'=>'Eritrea','EE'=>'Estonia','SZ'=>'Eswatini','ET'=>'Ethiopia',
  'FK'=>'Falkland Islands','FO'=>'Faroe Islands','FJ'=>'Fiji','FI'=>'Finland','FR'=>'France','GF'=>'French Guiana','PF'=>'French Polynesia','TF'=>'French Southern Territories',
  'GA'=>'Gabon','GM'=>'Gambia','GE'=>'Georgia','DE'=>'Germany','GH'=>'Ghana','GI'=>'Gibraltar','GR'=>'Greece','GL'=>'Greenland','GD'=>'Grenada','GP'=>'Guadeloupe','GU'=>'Guam','GT'=>'Guatemala','GG'=>'Guernsey','GN'=>'Guinea','GW'=>'Guinea-Bissau','GY'=>'Guyana',
  'HT'=>'Haiti','HM'=>'Heard Island and McDonald Islands','VA'=>'Vatican City','HN'=>'Honduras','HK'=>'Hong Kong','HU'=>'Hungary',
  'IS'=>'Iceland','IN'=>'India','ID'=>'Indonesia','IR'=>'Iran','IQ'=>'Iraq','IE'=>'Ireland','IM'=>'Isle of Man','IL'=>'Israel','IT'=>'Italy',
  'JM'=>'Jamaica','JP'=>'Japan','JE'=>'Jersey','JO'=>'Jordan',
  'KZ'=>'Kazakhstan','KE'=>'Kenya','KI'=>'Kiribati','KP'=>'North Korea','KR'=>'South Korea','KW'=>'Kuwait','KG'=>'Kyrgyzstan',
  'LA'=>'Laos','LV'=>'Latvia','LB'=>'Lebanon','LS'=>'Lesotho','LR'=>'Liberia','LY'=>'Libya','LI'=>'Liechtenstein','LT'=>'Lithuania','LU'=>'Luxembourg',
  'MO'=>'Macao','MG'=>'Madagascar','MW'=>'Malawi','MY'=>'Malaysia','MV'=>'Maldives','ML'=>'Mali','MT'=>'Malta','MH'=>'Marshall Islands','MQ'=>'Martinique','MR'=>'Mauritania','MU'=>'Mauritius','YT'=>'Mayotte','MX'=>'Mexico','FM'=>'Micronesia','MD'=>'Moldova','MC'=>'Monaco','MN'=>'Mongolia','ME'=>'Montenegro','MS'=>'Montserrat','MA'=>'Morocco','MZ'=>'Mozambique','MM'=>'Myanmar',
  'NA'=>'Namibia','NR'=>'Nauru','NP'=>'Nepal','NL'=>'Netherlands','NC'=>'New Caledonia','NZ'=>'New Zealand','NI'=>'Nicaragua','NE'=>'Niger','NG'=>'Nigeria','NU'=>'Niue','NF'=>'Norfolk Island','MK'=>'North Macedonia','MP'=>'Northern Mariana Islands','NO'=>'Norway',
  'OM'=>'Oman',
  'PK'=>'Pakistan','PW'=>'Palau','PS'=>'Palestine','PA'=>'Panama','PG'=>'Papua New Guinea','PY'=>'Paraguay','PE'=>'Peru','PH'=>'Philippines','PN'=>'Pitcairn Islands','PL'=>'Poland','PT'=>'Portugal','PR'=>'Puerto Rico',
  'QA'=>'Qatar',
  'RE'=>'Réunion','RO'=>'Romania','RU'=>'Russia','RW'=>'Rwanda',
  'BL'=>'Saint Barthélemy','SH'=>'Saint Helena','KN'=>'Saint Kitts and Nevis','LC'=>'Saint Lucia','MF'=>'Saint Martin','PM'=>'Saint Pierre and Miquelon','VC'=>'Saint Vincent and the Grenadines','WS'=>'Samoa','SM'=>'San Marino','ST'=>'São Tomé and Príncipe','SA'=>'Saudi Arabia','SN'=>'Senegal','RS'=>'Serbia','SC'=>'Seychelles','SL'=>'Sierra Leone','SG'=>'Singapore','SX'=>'Sint Maarten','SK'=>'Slovakia','SI'=>'Slovenia','SB'=>'Solomon Islands','SO'=>'Somalia','ZA'=>'South Africa','GS'=>'South Georgia and the South Sandwich Islands','SS'=>'South Sudan','ES'=>'Spain','LK'=>'Sri Lanka','SD'=>'Sudan','SR'=>'Suriname','SJ'=>'Svalbard and Jan Mayen','SE'=>'Sweden','CH'=>'Switzerland','SY'=>'Syria',
  'TW'=>'Taiwan','TJ'=>'Tajikistan','TZ'=>'Tanzania','TH'=>'Thailand','TL'=>'Timor-Leste','TG'=>'Togo','TK'=>'Tokelau','TO'=>'Tonga','TT'=>'Trinidad and Tobago','TN'=>'Tunisia','TR'=>'Turkey','TM'=>'Turkmenistan','TC'=>'Turks and Caicos Islands','TV'=>'Tuvalu',
  'UG'=>'Uganda','UA'=>'Ukraine','AE'=>'United Arab Emirates','GB'=>'United Kingdom','US'=>'United States','UM'=>'U.S. Minor Outlying Islands','UY'=>'Uruguay','UZ'=>'Uzbekistan',
  'VU'=>'Vanuatu','VE'=>'Venezuela','VN'=>'Vietnam','VG'=>'British Virgin Islands','VI'=>'U.S. Virgin Islands',
  'WF'=>'Wallis and Futuna',
  'EH'=>'Western Sahara',
  'YE'=>'Yemen',
  'ZM'=>'Zambia','ZW'=>'Zimbabwe',
];

$COUNTRY_ALIASES = [
  'UA' => ['ukraine','україна','украина','ua'],
  'US' => ['usa','u.s.a.','united states','unites states','сша','соединенные штаты','united states of america'],
  'AE' => ['uae','u.a.e.','united arab emirates','оаэ','объединенные арабские эмираты','обʼєднані арабські емірати','обєднані арабські емірати'],
  'IN' => ['india','індія','индия'],
];

function norm_country_text(string $s): string {
  $s = trim(mb_strtolower($s));
  $s = str_replace(["\t","\r","\n"], ' ', $s);
  $s = preg_replace('~\s+~u', ' ', $s) ?: $s;
  $s = str_replace(['.',',',';','"',"'",'’','`'], '', $s);
  return trim($s);
}

function infer_country_code_from_text(string $countryText, array $COUNTRY_ALIASES): string {
  $n = norm_country_text($countryText);
  if ($n === '') return '';
  if (preg_match('~^[a-z]{2}$~', $n)) return strtoupper($n);

  foreach ($COUNTRY_ALIASES as $code => $list) {
    foreach ($list as $v) {
      if ($n === norm_country_text((string)$v)) return (string)$code;
    }
  }
  return '';
}

function country_label(string $code, array $COUNTRIES): string {
  $code = strtoupper(trim($code));
  return (string)($COUNTRIES[$code] ?? $code);
}

/* ===== group invite state (for UI) ===== */
$groupInviteMode = ($inviteGroupId > 0);
$canInviteGroup  = false;
$inviteGroupName = '';
$inviteGroupSlug = '';

$groupsOk = $groupInviteMode && db_has_table($pdo, 'groups') && db_has_table($pdo, 'group_members');
$groupsHasOwner = $groupsOk ? db_has_column($pdo, 'groups', 'owner_user_id') : false;
$groupsHasName  = $groupsOk ? db_has_column($pdo, 'groups', 'name') : false;
$groupsHasSlug  = $groupsOk ? db_has_column($pdo, 'groups', 'slug') : false;

$gmHasRole   = $groupsOk ? db_has_column($pdo, 'group_members', 'role') : false;
$gmHasStatus = $groupsOk ? db_has_column($pdo, 'group_members', 'status') : false;

if ($groupsOk) {
  try {
    $sel = "id";
    if ($groupsHasName) $sel .= ", name";
    if ($groupsHasSlug) $sel .= ", slug";
    if ($groupsHasOwner) $sel .= ", owner_user_id";
    $stG = $pdo->prepare("SELECT {$sel} FROM `groups` WHERE id=? LIMIT 1");
    $stG->execute([$inviteGroupId]);
    $gRow = $stG->fetch(PDO::FETCH_ASSOC);

    if ($gRow) {
      $inviteGroupName = (string)($gRow['name'] ?? '');
      $inviteGroupSlug = (string)($gRow['slug'] ?? '');

      if ($meId > 0) {
        $ownerId = (int)($gRow['owner_user_id'] ?? 0);
        if ($groupsHasOwner && $ownerId > 0 && $ownerId === $meId) {
          $canInviteGroup = true;
        } else {
          if ($gmHasRole) {
            $statusCond = $gmHasStatus ? " AND status='active' " : "";
            $stM = $pdo->prepare("
              SELECT role
              FROM group_members
              WHERE group_id=? AND user_id=? {$statusCond}
              LIMIT 1
            ");
            $stM->execute([$inviteGroupId, $meId]);
            $role = (string)($stM->fetchColumn() ?: '');
            if (in_array($role, ['owner','admin'], true)) $canInviteGroup = true;
          } else {
            $statusCond = $gmHasStatus ? " AND status='active' " : "";
            $stM = $pdo->prepare("SELECT 1 FROM group_members WHERE group_id=? AND user_id=? {$statusCond} LIMIT 1");
            $stM->execute([$inviteGroupId, $meId]);
            if ((int)($stM->fetchColumn() ?: 0) === 1) $canInviteGroup = true;
          }
        }
      }
    } else {
      $groupsOk = false;
      $groupInviteMode = false;
      $inviteGroupId = 0;
    }
  } catch (Throwable $e) {
    $groupsOk = false;
    $groupInviteMode = false;
    $inviteGroupId = 0;
  }
}

/* ===== friend request handling (POST) + group invite (POST) ===== */
if (is_post()) {
  $action = post_str('action');

  if ($action === 'friend_request') {
    if ($meId <= 0) {
      $to = urlencode((string)current_uri());
      redirect('/login.php?to='.$to);
    }

    if (!csrf_check(post_str('csrf'))) {
      flash_set('err', $T('Помилка безпеки (CSRF).', 'Ошибка безопасности (CSRF).', 'Security error (CSRF).'));
      redirect_to_same_users_list();
    }

    $toUserId = post_int('to_user_id', 0);
    if ($toUserId <= 0 || $toUserId === $meId) {
      flash_set('err', $T('Некоректний користувач.', 'Некорректный пользователь.', 'Invalid user.'));
      redirect_to_same_users_list();
    }

    $stU = $pdo->prepare("SELECT id FROM users WHERE id = ? LIMIT 1");
    $stU->execute([$toUserId]);
    $exists = (int)$stU->fetchColumn();
    if ($exists <= 0) {
      flash_set('err', $T('Користувача не знайдено.', 'Пользователь не найден.', 'User not found.'));
      redirect_to_same_users_list();
    }

    $now = date('Y-m-d H:i:s');

    /* exact outgoing row: me -> target */
    $stOut = $pdo->prepare("
      SELECT id, status
      FROM friend_requests
      WHERE from_user_id = ? AND to_user_id = ?
      LIMIT 1
    ");
    $stOut->execute([$meId, $toUserId]);
    $outRow = $stOut->fetch(PDO::FETCH_ASSOC) ?: null;

    if ($outRow) {
      $outId = (int)($outRow['id'] ?? 0);
      $outStatus = (string)($outRow['status'] ?? '');

      if ($outStatus === 'accepted') {
        flash_set('ok', $T('Ви вже друзі ✅', 'Вы уже друзья ✅', 'You are already friends ✅'));
        redirect_to_same_users_list();
      }

      if ($outStatus === 'pending') {
        flash_set('ok', $T('Заявка вже надіслана ✅', 'Заявка уже отправлена ✅', 'Request already sent ✅'));
        redirect_to_same_users_list();
      }

      if ($outId > 0) {
        $up = $pdo->prepare("
          UPDATE friend_requests
          SET status = 'pending',
              created_at = ?,
              responded_at = NULL,
              accepted_at = NULL
          WHERE id = ?
          LIMIT 1
        ");
        $up->execute([$now, $outId]);

        users_try_send_friend_request_email($pdo, $toUserId, $meId, $outId);

        flash_set('ok', $T('Заявку в друзі надіслано ✅', 'Заявка в друзья отправлена ✅', 'Friend request sent ✅'));
        redirect_to_same_users_list();
      }
    }

    /* exact incoming row: target -> me */
    $stIn = $pdo->prepare("
      SELECT id, status
      FROM friend_requests
      WHERE from_user_id = ? AND to_user_id = ?
      LIMIT 1
    ");
    $stIn->execute([$toUserId, $meId]);
    $inRow = $stIn->fetch(PDO::FETCH_ASSOC) ?: null;

    if ($inRow) {
      $inId = (int)($inRow['id'] ?? 0);
      $inStatus = (string)($inRow['status'] ?? '');

      if ($inStatus === 'accepted') {
        flash_set('ok', $T('Ви вже друзі ✅', 'Вы уже друзья ✅', 'You are already friends ✅'));
        redirect_to_same_users_list();
      }

      if ($inStatus === 'pending' && $inId > 0) {
        $up = $pdo->prepare("
          UPDATE friend_requests
          SET status = 'accepted', responded_at = ?, accepted_at = ?
          WHERE id = ?
          LIMIT 1
        ");
        $up->execute([$now, $now, $inId]);

        flash_set('ok', $T('Заявку прийнято ✅', 'Заявка принята ✅', 'Request accepted ✅'));
        redirect_to_same_users_list();
      }
    }

    try {
      $ins = $pdo->prepare("
        INSERT INTO friend_requests (from_user_id, to_user_id, status, created_at, responded_at, accepted_at)
        VALUES (?, ?, 'pending', ?, NULL, NULL)
      ");
      $ins->execute([$meId, $toUserId, $now]);

      $requestId = (int)$pdo->lastInsertId();
      users_try_send_friend_request_email($pdo, $toUserId, $meId, $requestId);

      flash_set('ok', $T('Заявку в друзі надіслано ✅', 'Заявка в друзья отправлена ✅', 'Friend request sent ✅'));
      redirect_to_same_users_list();
    } catch (Throwable $e) {
      if (users_is_duplicate_key_exception($e)) {
        try {
          $stRetry = $pdo->prepare("
            SELECT id, status
            FROM friend_requests
            WHERE from_user_id = ? AND to_user_id = ?
            LIMIT 1
          ");
          $stRetry->execute([$meId, $toUserId]);
          $retryRow = $stRetry->fetch(PDO::FETCH_ASSOC) ?: null;

          if ($retryRow) {
            $retryId = (int)($retryRow['id'] ?? 0);
            $retryStatus = (string)($retryRow['status'] ?? '');

            if ($retryStatus === 'accepted') {
              flash_set('ok', $T('Ви вже друзі ✅', 'Вы уже друзья ✅', 'You are already friends ✅'));
              redirect_to_same_users_list();
            }

            if ($retryStatus === 'pending') {
              flash_set('ok', $T('Заявка вже надіслана ✅', 'Заявка уже отправлена ✅', 'Request already sent ✅'));
              redirect_to_same_users_list();
            }

            if ($retryId > 0) {
              $up = $pdo->prepare("
                UPDATE friend_requests
                SET status = 'pending',
                    created_at = ?,
                    responded_at = NULL,
                    accepted_at = NULL
                WHERE id = ?
                LIMIT 1
              ");
              $up->execute([$now, $retryId]);

              users_try_send_friend_request_email($pdo, $toUserId, $meId, $retryId);

              flash_set('ok', $T('Заявку в друзі надіслано ✅', 'Заявка в друзья отправлена ✅', 'Friend request sent ✅'));
              redirect_to_same_users_list();
            }
          }
        } catch (Throwable $e2) {
          error_log('users.php friend_request duplicate recovery error: ' . $e2->getMessage());
        }
      }

      error_log('users.php friend_request error: ' . $e->getMessage());
      flash_set('err', $T('Помилка сервера.', 'Ошибка сервера.', 'Server error.'));
      redirect_to_same_users_list();
    }
  }

  if ($action === 'group_invite') {
    if ($meId <= 0) {
      $to = urlencode((string)current_uri());
      redirect('/login.php?to='.$to);
    }

    if (!csrf_check(post_str('csrf'))) {
      flash_set('err', $T('Помилка безпеки (CSRF).', 'Ошибка безопасности (CSRF).', 'Security error (CSRF).'));
      redirect_to_same_users_list();
    }

    $groupId = post_int('group_id', 0);
    $toUserId = post_int('to_user_id', 0);

    if ($groupId <= 0 || $toUserId <= 0 || $toUserId === $meId) {
      flash_set('err', $T('Некоректні дані.', 'Некорректные данные.', 'Invalid data.'));
      redirect_to_same_users_list();
    }

    if (!db_has_table($pdo, 'groups') || !db_has_table($pdo, 'group_members')) {
      flash_set('err', $T('Модуль груп не підключено.', 'Модуль групп не подключен.', 'Groups module is not enabled.'));
      redirect_to_same_users_list();
    }

    $gHasOwner = db_has_column($pdo, 'groups', 'owner_user_id');
    $gHasName  = db_has_column($pdo, 'groups', 'name');
    $gHasSlug  = db_has_column($pdo, 'groups', 'slug');

    $mHasRole  = db_has_column($pdo, 'group_members', 'role');
    $mHasStat  = db_has_column($pdo, 'group_members', 'status');

    $sel = "id";
    if ($gHasOwner) $sel .= ", owner_user_id";
    if ($gHasName)  $sel .= ", name";
    if ($gHasSlug)  $sel .= ", slug";

    $stG = $pdo->prepare("SELECT {$sel} FROM `groups` WHERE id=? LIMIT 1");
    $stG->execute([$groupId]);
    $gRow = $stG->fetch(PDO::FETCH_ASSOC);

    if (!$gRow) {
      flash_set('err', $T('Групу не знайдено.', 'Группа не найдена.', 'Group not found.'));
      redirect_to_same_users_list();
    }

    $isManager = false;
    $ownerId = (int)($gRow['owner_user_id'] ?? 0);
    if ($gHasOwner && $ownerId > 0 && $ownerId === $meId) {
      $isManager = true;
    } else {
      $statusCond = $mHasStat ? " AND status='active' " : "";
      if ($mHasRole) {
        $stM = $pdo->prepare("SELECT role FROM group_members WHERE group_id=? AND user_id=? {$statusCond} LIMIT 1");
        $stM->execute([$groupId, $meId]);
        $role = (string)($stM->fetchColumn() ?: '');
        if (in_array($role, ['owner','admin'], true)) $isManager = true;
      } else {
        $stM = $pdo->prepare("SELECT 1 FROM group_members WHERE group_id=? AND user_id=? {$statusCond} LIMIT 1");
        $stM->execute([$groupId, $meId]);
        if ((int)($stM->fetchColumn() ?: 0) === 1) $isManager = true;
      }
    }

    if (!$isManager) {
      flash_set('err', $T('У вас немає прав запрошувати в цю групу.', 'У вас нет прав приглашать в эту группу.', 'You are not allowed to invite to this group.'));
      redirect_to_same_users_list();
    }

    $stU = $pdo->prepare("SELECT id FROM users WHERE id=? LIMIT 1");
    $stU->execute([$toUserId]);
    if (!(int)$stU->fetchColumn()) {
      flash_set('err', $T('Користувача не знайдено.', 'Пользователь не найден.', 'User not found.'));
      redirect_to_same_users_list();
    }

    try {
      if ($mHasStat) {
        $st = $pdo->prepare("SELECT status FROM group_members WHERE group_id=? AND user_id=? LIMIT 1");
        $st->execute([$groupId, $toUserId]);
        $stt = $st->fetchColumn();
        $stt = $stt !== false ? (string)$stt : '';

        if ($stt === 'banned') {
          flash_set('err', $T('Користувач заблокований у групі.', 'Пользователь заблокирован в группе.', 'User is banned in the group.'));
          redirect_to_same_users_list();
        }
        if ($stt === 'active') {
          flash_set('ok', $T('Користувач вже у групі ✅', 'Пользователь уже в группе ✅', 'User is already in the group ✅'));
          redirect_to_same_users_list();
        }
        if ($stt === 'pending') {
          users_notification_upsert_group_invite(
            $pdo,
            $toUserId,
            $meId,
            $groupId,
            (string)($gRow['name'] ?? ''),
            $lang
          );

          users_try_send_group_invite_email($pdo, $toUserId, $meId, $groupId, true);

          flash_set('ok', $T('Запрошення вже очікує підтвердження ✅', 'Приглашение уже ожидает подтверждения ✅', 'Invite is already pending ✅'));
          redirect_to_same_users_list();
        }
      } else {
        $st = $pdo->prepare("SELECT 1 FROM group_members WHERE group_id=? AND user_id=? LIMIT 1");
        $st->execute([$groupId, $toUserId]);
        if ((int)($st->fetchColumn() ?: 0) === 1) {
          flash_set('ok', $T('Користувач вже у групі ✅', 'Пользователь уже в группе ✅', 'User is already in the group ✅'));
          redirect_to_same_users_list();
        }
      }

      if ($mHasRole && $mHasStat && db_has_column($pdo, 'group_members', 'created_at')) {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id, role, status, created_at) VALUES (?, ?, 'member', 'pending', NOW())");
        $ins->execute([$groupId, $toUserId]);
      } elseif ($mHasRole && $mHasStat) {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id, role, status) VALUES (?, ?, 'member', 'pending')");
        $ins->execute([$groupId, $toUserId]);
      } elseif ($mHasStat && db_has_column($pdo, 'group_members', 'created_at')) {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id, status, created_at) VALUES (?, ?, 'pending', NOW())");
        $ins->execute([$groupId, $toUserId]);
      } elseif ($mHasStat) {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id, status) VALUES (?, ?, 'pending')");
        $ins->execute([$groupId, $toUserId]);
      } elseif (db_has_column($pdo, 'group_members', 'created_at')) {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id, created_at) VALUES (?, ?, NOW())");
        $ins->execute([$groupId, $toUserId]);
      } else {
        $ins = $pdo->prepare("INSERT INTO group_members (group_id, user_id) VALUES (?, ?)");
        $ins->execute([$groupId, $toUserId]);
      }

      users_notification_upsert_group_invite(
        $pdo,
        $toUserId,
        $meId,
        $groupId,
        (string)($gRow['name'] ?? ''),
        $lang
      );

      users_try_send_group_invite_email($pdo, $toUserId, $meId, $groupId, $mHasStat);

      flash_set('ok', $T('Запрошення надіслано ✅', 'Приглашение отправлено ✅', 'Invite sent ✅'));
      redirect_to_same_users_list();
    } catch (Throwable $e) {
      error_log('users.php group_invite error: ' . $e->getMessage());
      flash_set('err', $T('Помилка сервера.', 'Ошибка сервера.', 'Server error.'));
      redirect_to_same_users_list();
    }
  }
}

/* ===== filters (GET) ===== */
$country  = get_str('country');
$gender   = get_str('gender');
$presence = get_str('presence');
$ageMin   = get_int('age_min', 0);
$ageMax   = get_int('age_max', 0);
$sort     = get_str('sort');
$page     = max(1, get_int('page', 1));
$perPage = 24;

$sort = $sort !== '' ? $sort : 'reg_desc';
if (!in_array($presence, ['online', 'offline'], true)) $presence = '';

/* ===== build SQL ===== */
$where = [];
$args  = [];

if ($hasActive) {
  $where[] = "is_active = 1";
}

$genderCol = null;
if ($hasSex) $genderCol = 'sex';
elseif ($hasGender) $genderCol = 'gender';

if ($genderCol && in_array($gender, ['m','f','o'], true)) {
  $where[] = "{$genderCol} = ?";
  $args[] = $gender;
}

if ($hasLastSeen && $presence === 'online') {
  $where[] = "last_seen_at IS NOT NULL AND last_seen_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)";
  $args[] = 300;
} elseif ($hasLastSeen && $presence === 'offline') {
  $where[] = "(last_seen_at IS NULL OR last_seen_at < DATE_SUB(NOW(), INTERVAL ? SECOND))";
  $args[] = 300;
}

$birthCol = null;
if ($hasBirthDate) $birthCol = 'birth_date';
elseif ($hasBirthdate) $birthCol = 'birthdate';

if ($birthCol) {
  if ($ageMin > 0) {
    $where[] = "{$birthCol} IS NOT NULL AND {$birthCol} <= DATE_SUB(CURDATE(), INTERVAL ? YEAR)";
    $args[] = $ageMin;
  }
  if ($ageMax > 0) {
    $where[] = "{$birthCol} IS NOT NULL AND {$birthCol} >= DATE_SUB(CURDATE(), INTERVAL ? YEAR)";
    $args[] = $ageMax;
  }
}

$country = strtoupper(trim($country));
if ($country !== '' && preg_match('~^[A-Z]{2}$~', $country)) {
  $legacyVals = $COUNTRY_ALIASES[$country] ?? [];
  $legacyVals = array_values(array_unique(array_map('strval', $legacyVals)));

  if ($hasCountryCode && $hasCountry) {
    $cond = [];
    $cond[] = "(country_code = ?)";
    $args[] = $country;

    if ($legacyVals) {
      $in = implode(',', array_fill(0, count($legacyVals), '?'));
      $cond[] = "((country_code IS NULL OR TRIM(country_code) = '') AND country IS NOT NULL AND TRIM(country) <> '' AND LOWER(TRIM(country)) IN ($in))";
      foreach ($legacyVals as $v) $args[] = mb_strtolower(trim((string)$v));
    }

    $where[] = '(' . implode(' OR ', $cond) . ')';
  } elseif ($hasCountryCode) {
    $where[] = "country_code = ?";
    $args[] = $country;
  } elseif ($hasCountry) {
    if ($legacyVals) {
      $in = implode(',', array_fill(0, count($legacyVals), '?'));
      $where[] = "LOWER(TRIM(country)) IN ($in)";
      foreach ($legacyVals as $v) $args[] = mb_strtolower(trim((string)$v));
    }
  }
}

$wSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

$orderSql = "ORDER BY id DESC";
if ($sort === 'reg_desc') {
  $orderSql = $hasCreatedAt ? "ORDER BY created_at DESC, id DESC" : "ORDER BY id DESC";
} elseif ($sort === 'reg_asc') {
  $orderSql = $hasCreatedAt ? "ORDER BY created_at ASC, id ASC" : "ORDER BY id ASC";
} elseif ($sort === 'age_asc' && $birthCol) {
  $orderSql = "ORDER BY {$birthCol} DESC, id DESC";
} elseif ($sort === 'age_desc' && $birthCol) {
  $orderSql = "ORDER BY {$birthCol} ASC, id DESC";
} elseif ($sort === 'name_asc') {
  $orderSql = "ORDER BY username ASC, id DESC";
}

$stCnt = $pdo->prepare("SELECT COUNT(*) FROM users {$wSql}");
$stCnt->execute($args);
$total = (int)$stCnt->fetchColumn();
$pages = max(1, (int)ceil($total / $perPage));
if ($page > $pages) $page = $pages;
$offset = ($page - 1) * $perPage;

$fields = [
  "id",
  "username",
];
if ($hasFullName) $fields[] = "full_name";
if ($hasFirst) $fields[] = "first_name";
if ($hasLast) $fields[] = "last_name";

if ($hasCountryCode) $fields[] = "country_code";
if ($hasCountry)     $fields[] = "country";
if ($hasCity)        $fields[] = "city";

if ($genderCol) $fields[] = $genderCol;
if ($birthCol)  $fields[] = $birthCol;
if ($hasCreatedAt) $fields[] = "created_at";
if ($hasAvatar) $fields[] = "avatar_path";
if ($hasLastSeen) $fields[] = "last_seen_at";

$sql = "SELECT " . implode(", ", $fields) . " FROM users {$wSql} {$orderSql} LIMIT {$perPage} OFFSET {$offset}";
$stL = $pdo->prepare($sql);
$stL->execute($args);
$users = $stL->fetchAll(PDO::FETCH_ASSOC);

/* friend status map */
$friendMap = [];
if ($meId > 0 && !empty($users)) {
  $ids = array_map(fn($r) => (int)($r['id'] ?? 0), $users);
  $ids = array_values(array_filter($ids, fn($x) => $x > 0 && $x !== $meId));
  if ($ids) {
    $in = implode(',', array_fill(0, count($ids), '?'));
    $stF = $pdo->prepare("
      SELECT from_user_id, to_user_id, status
      FROM friend_requests
      WHERE (from_user_id = ? AND to_user_id IN ($in))
         OR (to_user_id = ? AND from_user_id IN ($in))
    ");
    $params = array_merge([$meId], $ids, [$meId], $ids);
    $stF->execute($params);
    while ($r = $stF->fetch(PDO::FETCH_ASSOC)) {
      $from = (int)$r['from_user_id'];
      $to   = (int)$r['to_user_id'];
      $stt  = (string)$r['status'];
      $other = ($from === $meId) ? $to : $from;
      $friendMap[$other] = ($stt === 'accepted')
        ? 'accepted'
        : (($stt === 'pending') ? (($from === $meId) ? 'pending_out' : 'pending_in') : $stt);
    }
  }
}

/* ===== group membership map for invite buttons ===== */
$groupMemberMap = [];
if ($groupInviteMode && $groupsOk && $canInviteGroup && $users) {
  $ids = array_map(fn($r) => (int)($r['id'] ?? 0), $users);
  $ids = array_values(array_filter($ids, fn($x) => $x > 0));
  if ($ids) {
    $in = implode(',', array_fill(0, count($ids), '?'));
    $sel = "user_id";
    if ($gmHasStatus) $sel .= ", status";
    $stGM = $pdo->prepare("SELECT {$sel} FROM group_members WHERE group_id=? AND user_id IN ($in)");
    $stGM->execute(array_merge([$inviteGroupId], $ids));
    while ($r = $stGM->fetch(PDO::FETCH_ASSOC)) {
      $uid = (int)($r['user_id'] ?? 0);
      if ($uid <= 0) continue;
      if ($gmHasStatus) {
        $groupMemberMap[$uid] = (string)($r['status'] ?? 'active');
      } else {
        $groupMemberMap[$uid] = 'active';
      }
    }
  }
}

/* ===== build countries list for filter ===== */
$filterCountryCodes = [];

if ($hasCountryCode) {
  $st = $pdo->query("
    SELECT DISTINCT country_code
    FROM users
    WHERE country_code IS NOT NULL AND TRIM(country_code) <> ''
    ORDER BY country_code ASC
  ");
  $codes = $st ? $st->fetchAll(PDO::FETCH_COLUMN) : [];
  foreach ($codes as $cc) {
    $cc = strtoupper(trim((string)$cc));
    if ($cc !== '' && preg_match('~^[A-Z]{2}$~', $cc)) $filterCountryCodes[$cc] = true;
  }
}

if ($hasCountry) {
  $st = $pdo->query("
    SELECT DISTINCT country
    FROM users
    WHERE country IS NOT NULL AND TRIM(country) <> ''
    ORDER BY country ASC
  ");
  $vals = $st ? $st->fetchAll(PDO::FETCH_COLUMN) : [];
  foreach ($vals as $ct) {
    $ct = (string)$ct;
    $code = infer_country_code_from_text($ct, $COUNTRY_ALIASES);
    if ($code !== '') $filterCountryCodes[$code] = true;
  }
}

$filterCountryCodes = array_keys($filterCountryCodes);
sort($filterCountryCodes);

/* SEO */
$title = $T('Користувачі', 'Пользователи', 'Users') . ' — ' . (string)t('site');
$meta_desc = $T(
  'Каталог користувачів: фільтри за країною, статтю, віком та датою реєстрації.',
  'Каталог пользователей: фильтры по стране, полу, возрасту и дате регистрации.',
  'User directory with filters by country, gender, age and registration date.'
);
$canonical = function_exists('abs_url') ? (string)abs_url('/users.php') : '/users.php';

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

$syGet = get_int('sy', -1);
if ($syGet >= 0 && $syGet <= 10000000): ?>
<style id="users-scroll-hide-style">
  html, body { scroll-behavior: auto !important; }
  body { visibility: hidden; }
</style>
<script>
(function(){
  try {
    if ('scrollRestoration' in history) {
      history.scrollRestoration = 'manual';
    }

    var sy = <?= (int)$syGet ?>;
    var storageKey = 'vm_users_scroll_restore';
    var finished = false;

    try {
      sessionStorage.setItem(storageKey, String(sy));
    } catch (e) {}

    function restoreNow() {
      try { window.scrollTo(0, sy); } catch (e) {}
    }

    function showPage() {
      var style = document.getElementById('users-scroll-hide-style');
      if (style) style.remove();
    }

    function cleanUrl() {
      try {
        var url = new URL(window.location.href);
        url.searchParams.delete('sy');

        var clean = url.pathname;
        var q = url.searchParams.toString();
        if (q) clean += '?' + q;
        if (url.hash) clean += url.hash;

        history.replaceState(null, '', clean);
      } catch (e) {}
    }

    function finish() {
      if (finished) return;
      finished = true;

      restoreNow();

      requestAnimationFrame(function(){
        restoreNow();
        showPage();

        setTimeout(function(){
          restoreNow();
          cleanUrl();
          try { sessionStorage.removeItem(storageKey); } catch (e) {}
        }, 60);
      });
    }

    restoreNow();

    document.addEventListener('DOMContentLoaded', restoreNow, { once:true });
    window.addEventListener('load', finish, { once:true });

    setTimeout(restoreNow, 30);
    setTimeout(restoreNow, 90);
    setTimeout(finish, 220);
    setTimeout(finish, 500);
  } catch(e) {
    var style = document.getElementById('users-scroll-hide-style');
    if (style) style.remove();
  }
})();
</script>
<?php else: ?>
<script>
(function(){
  try {
    var storageKey = 'vm_users_scroll_restore';
    var raw = sessionStorage.getItem(storageKey);
    if (!raw) return;
    if (location.pathname !== '/users.php') return;

    var sy = parseInt(raw, 10);
    if (!Number.isFinite(sy) || sy < 0) {
      sessionStorage.removeItem(storageKey);
      return;
    }

    function restoreNow() {
      try { window.scrollTo(0, sy); } catch(e) {}
    }

    restoreNow();
    requestAnimationFrame(function(){ restoreNow(); });
    window.addEventListener('load', function(){
      restoreNow();
      setTimeout(function(){
        restoreNow();
        try { sessionStorage.removeItem(storageKey); } catch(e) {}
      }, 120);
    }, { once:true });
  } catch(e) {}
})();
</script>
<?php endif; ?>

<?php
function user_display_name(array $u): string {
  $full = trim((string)($u['full_name'] ?? ''));
  if ($full !== '') return $full;

  $fn = trim((string)($u['first_name'] ?? ''));
  $ln = trim((string)($u['last_name'] ?? ''));
  $nm = trim($fn . ' ' . $ln);
  if ($nm !== '') return $nm;

  return (string)($u['username'] ?? '');
}

function gender_label(?string $g, callable $T): string {
  $g = (string)$g;
  return match ($g) {
    'm' => $T('Чоловік', 'Мужчина', 'Male'),
    'f' => $T('Жінка', 'Женщина', 'Female'),
    'o' => $T('Інше', 'Другое', 'Other'),
    default => '',
  };
}

function calc_age(?string $birth): ?int {
  $birth = trim((string)$birth);
  if ($birth === '') return null;
  try {
    $b = new DateTime($birth);
    $now = new DateTime('now');
    $diff = $now->diff($b);
    return (int)$diff->y;
  } catch (Throwable $e) {
    return null;
  }
}

function users_presence_meta(?string $lastSeenAt, callable $T, int $onlineWindowSec = 300): array {
  $raw = trim((string)$lastSeenAt);
  if ($raw === '') {
    return [false, $T('Не в мережі', 'Не в сети', 'Offline')];
  }

  $ts = strtotime($raw);
  if ($ts === false || $ts <= 0) {
    return [false, $T('Не в мережі', 'Не в сети', 'Offline')];
  }

  $diff = time() - $ts;
  if ($diff < 0) $diff = 0;

  if ($diff <= $onlineWindowSec) {
    return [true, $T('Онлайн', 'Онлайн', 'Online')];
  }

  if ($diff < 3600) {
    $mins = max(1, (int)floor($diff / 60));
    return [false, sprintf($T('Був(ла) %d хв тому', 'Был(а) %d мин назад', 'Last seen %d min ago'), $mins)];
  }

  if ($diff < 86400) {
    return [false, sprintf($T('Був(ла) сьогодні о %s', 'Был(а) сегодня в %s', 'Last seen today at %s'), date('H:i', $ts))];
  }

  if ($diff < 172800) {
    return [false, sprintf($T('Був(ла) вчора о %s', 'Был(а) вчера в %s', 'Last seen yesterday at %s'), date('H:i', $ts))];
  }

  return [false, sprintf($T('Був(ла) %s', 'Был(а) %s', 'Last seen %s'), date('d.m.Y H:i', $ts))];
}

function qs_keep(array $extra = []): string {
  $qs = $_GET ?? [];
  if (!is_array($qs)) $qs = [];
  foreach ($extra as $k => $v) {
    if ($v === null) unset($qs[$k]);
    else $qs[$k] = $v;
  }
  return http_build_query($qs);
}

$csrf = csrf_token();
?>

<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-3">
  <div>
    <h1 class="h4 mb-1"><?= h($T('Користувачі', 'Пользователи', 'Users')) ?></h1>
    <div class="text-muted small">
      <?= h($T('Всього:', 'Всего:', 'Total:')) ?> <?= (int)$total ?>
      <?php if ($groupInviteMode && $groupsOk): ?>
        <span class="mx-2">•</span>
        <span>
          <?= h($T('Запрошення в групу:', 'Приглашение в группу:', 'Inviting to group:')) ?>
          <strong><?= h($inviteGroupName !== '' ? $inviteGroupName : ('#' . (int)$inviteGroupId)) ?></strong>
          <?php if ($inviteGroupSlug !== ''): ?>
            <span class="text-muted"> @<?= h($inviteGroupSlug) ?></span>
          <?php endif; ?>
        </span>
      <?php endif; ?>
    </div>
  </div>

  <div class="d-flex gap-2">
    <?php if ($back !== ''): ?>
      <a class="btn btn-outline-secondary btn-sm" href="<?= h($back) ?>">
        <?= h($T('Назад', 'Назад', 'Back')) ?>
      </a>
    <?php endif; ?>

    <?php if ($groupInviteMode): ?>
      <a class="btn btn-outline-secondary btn-sm" href="/users.php?<?= h(qs_keep(['invite_group' => null, 'back' => null])) ?>">
        <?= h($T('Вийти з режиму запрошень', 'Выйти из режима приглашений', 'Exit invite mode')) ?>
      </a>
    <?php endif; ?>
  </div>
</div>

<?php if ($groupInviteMode && $groupsOk): ?>
  <?php if ($meId <= 0): ?>
    <div class="alert alert-warning">
      <?= h($T(
        'Щоб запрошувати людей у групу — увійдіть.',
        'Чтобы приглашать людей в группу — войдите.',
        'Log in to invite people to the group.'
      )) ?>
    </div>
  <?php elseif (!$canInviteGroup): ?>
    <div class="alert alert-danger">
      <?= h($T(
        'У вас немає прав запрошувати людей у цю групу.',
        'У вас нет прав приглашать людей в эту группу.',
        'You are not allowed to invite people to this group.'
      )) ?>
    </div>
  <?php endif; ?>
<?php endif; ?>

<div class="card mb-3">
  <div class="card-body">
    <form class="row g-2 align-items-end" method="get" action="/users.php">

      <?php if ($groupInviteMode): ?>
        <input type="hidden" name="invite_group" value="<?= (int)$inviteGroupId ?>">
        <?php if ($back !== ''): ?>
          <input type="hidden" name="back" value="<?= h($back) ?>">
        <?php endif; ?>
      <?php endif; ?>

      <div class="col-12 col-md-3">
        <label class="form-label small mb-1"><?= h($T('Країна', 'Страна', 'Country')) ?></label>
        <select class="form-select form-select-sm" name="country">
          <option value=""><?= h($T('Усі', 'Все', 'All')) ?></option>
          <?php foreach ($filterCountryCodes as $cc): ?>
            <?php
              $cc = strtoupper((string)$cc);
              $lbl = country_label($cc, $COUNTRIES);
            ?>
            <option value="<?= h($cc) ?>" <?= ($cc === $country ? 'selected' : '') ?>>
              <?= h($lbl) ?> (<?= h($cc) ?>)
            </option>
          <?php endforeach; ?>
        </select>
      </div>

      <?php if ($hasLastSeen): ?>
        <div class="col-6 col-md-2">
          <label class="form-label small mb-1"><?= h($T('Статус', 'Статус', 'Status')) ?></label>
          <select class="form-select form-select-sm" name="presence">
            <option value=""><?= h($T('Усі', 'Все', 'All')) ?></option>
            <option value="online" <?= ($presence === 'online' ? 'selected' : '') ?>><?= h($T('Онлайн', 'Онлайн', 'Online')) ?></option>
            <option value="offline" <?= ($presence === 'offline' ? 'selected' : '') ?>><?= h($T('Офлайн', 'Офлайн', 'Offline')) ?></option>
          </select>
        </div>
      <?php endif; ?>

      <?php if ($genderCol): ?>
        <div class="col-6 col-md-2">
          <label class="form-label small mb-1"><?= h($T('Стать', 'Пол', 'Gender')) ?></label>
          <select class="form-select form-select-sm" name="gender">
            <option value=""><?= h($T('Будь-яка', 'Любой', 'Any')) ?></option>
            <option value="m" <?= ($gender === 'm' ? 'selected' : '') ?>><?= h($T('Чоловік', 'Мужчина', 'Male')) ?></option>
            <option value="f" <?= ($gender === 'f' ? 'selected' : '') ?>><?= h($T('Жінка', 'Женщина', 'Female')) ?></option>
            <option value="o" <?= ($gender === 'o' ? 'selected' : '') ?>><?= h($T('Інше', 'Другое', 'Other')) ?></option>
          </select>
        </div>
      <?php endif; ?>

      <div class="col-6 col-md-2">
        <label class="form-label small mb-1"><?= h($T('Вік від', 'Возраст от', 'Age from')) ?></label>
        <input type="number" min="0" max="120" class="form-control form-control-sm" name="age_min" value="<?= (int)$ageMin ?>">
      </div>

      <div class="col-6 col-md-2">
        <label class="form-label small mb-1"><?= h($T('Вік до', 'Возраст до', 'Age to')) ?></label>
        <input type="number" min="0" max="120" class="form-control form-control-sm" name="age_max" value="<?= (int)$ageMax ?>">
      </div>

      <div class="col-12 col-md-3">
        <label class="form-label small mb-1"><?= h($T('Сортування', 'Сортировка', 'Sort')) ?></label>
        <select class="form-select form-select-sm" name="sort">
          <option value="reg_desc" <?= ($sort === 'reg_desc' ? 'selected' : '') ?>><?= h($T('Новіші (реєстрація)', 'Новые (регистрация)', 'Newest (registration)')) ?></option>
          <option value="reg_asc" <?= ($sort === 'reg_asc' ? 'selected' : '') ?>><?= h($T('Старіші (реєстрація)', 'Старые (регистрация)', 'Oldest (registration)')) ?></option>
          <?php if ($birthCol): ?>
            <option value="age_desc" <?= ($sort === 'age_desc' ? 'selected' : '') ?>><?= h($T('Старші (вік)', 'Старше (возраст)', 'Older (age)')) ?></option>
            <option value="age_asc" <?= ($sort === 'age_asc' ? 'selected' : '') ?>><?= h($T('Молодші (вік)', 'Младше (возраст)', 'Younger (age)')) ?></option>
          <?php endif; ?>
          <option value="name_asc" <?= ($sort === 'name_asc' ? 'selected' : '') ?>><?= h($T('Імʼя користувача A→Z', 'Имя пользователя A→Z', 'Username A→Z')) ?></option>
        </select>
      </div>

      <div class="col-12 d-flex gap-2">
        <button class="btn btn-primary btn-sm" type="submit"><?= h($T('Застосувати', 'Применить', 'Apply')) ?></button>
        <?php if ($groupInviteMode): ?>
          <a class="btn btn-outline-secondary btn-sm" href="/users.php?<?= h(http_build_query(['invite_group' => $inviteGroupId, 'back' => $back])) ?>">
            <?= h($T('Скинути фільтри', 'Сбросить фильтры', 'Reset filters')) ?>
          </a>
        <?php else: ?>
          <a class="btn btn-outline-secondary btn-sm" href="/users.php"><?= h($T('Скинути', 'Сбросить', 'Reset')) ?></a>
        <?php endif; ?>
      </div>
    </form>
  </div>
</div>

<?php if (!$users): ?>
  <div class="alert alert-info">
    <?= h($T('Нічого не знайдено за вибраними фільтрами.', 'Ничего не найдено по выбранным фильтрам.', 'No users found for selected filters.')) ?>
  </div>
<?php else: ?>

  <div class="row g-3">
    <?php foreach ($users as $uu): ?>
      <?php
        $uid = (int)($uu['id'] ?? 0);
        $username = (string)($uu['username'] ?? '');
        $name = user_display_name($uu);

        $cityV = (string)($uu['city'] ?? '');

        $countryCode = (string)($uu['country_code'] ?? '');
        $countryText = (string)($uu['country'] ?? '');

        $code = '';
        if ($countryCode !== '' && preg_match('~^[A-Za-z]{2}$~', $countryCode)) {
          $code = strtoupper($countryCode);
        } else {
          $code = infer_country_code_from_text($countryText, $COUNTRY_ALIASES);
        }

        $countryView = ($code !== '' ? country_label($code, $COUNTRIES) : trim($countryText));

        $g = $genderCol ? (string)($uu[$genderCol] ?? '') : '';
        $birth = $birthCol ? (string)($uu[$birthCol] ?? '') : '';
        $age = $birthCol ? calc_age($birth) : null;

        $avatar = '';
        if ($hasAvatar) $avatar = (string)($uu['avatar_path'] ?? '');

        $lastSeenAt = $hasLastSeen ? trim((string)($uu['last_seen_at'] ?? '')) : '';
        [$isOnline, $presenceText] = users_presence_meta($lastSeenAt, $T);

        $profileUrl = '/profile/?u=' . urlencode($username);

        $fState = ($uid > 0 && isset($friendMap[$uid])) ? (string)$friendMap[$uid] : '';
        $btnText = $T('Додати в друзі', 'Добавить в друзья', 'Add friend');
        $btnDisabled = false;

        if ($meId > 0) {
          if ($uid === $meId) {
            $btnText = $T('Це ви', 'Это вы', 'It’s you');
            $btnDisabled = true;
          } elseif ($fState === 'accepted') {
            $btnText = $T('Ви друзі ✅', 'Вы друзья ✅', 'Friends ✅');
            $btnDisabled = true;
          } elseif ($fState === 'pending_out') {
            $btnText = $T('Заявка надіслана', 'Заявка отправлена', 'Request sent');
            $btnDisabled = true;
          } elseif ($fState === 'pending_in') {
            $btnText = $T('Прийняти заявку', 'Принять заявку', 'Accept request');
            $btnDisabled = false;
          }
        }

        $invText = $T('Запросити в групу', 'Пригласить в группу', 'Invite to group');
        $invDisabled = false;
        $invState = (string)($groupMemberMap[$uid] ?? '');
        if ($uid <= 0) {
          $invDisabled = true;
        } elseif ($meId <= 0) {
          $invText = $T('Увійти щоб запросити', 'Войти чтобы пригласить', 'Login to invite');
          $invDisabled = true;
        } elseif ($uid === $meId) {
          $invText = $T('Це ви', 'Это вы', 'It’s you');
          $invDisabled = true;
        } elseif ($invState === 'banned') {
          $invText = $T('Заблокований', 'Заблокирован', 'Banned');
          $invDisabled = true;
        } elseif ($invState === 'pending') {
          $invText = $T('Очікує', 'Ожидает', 'Pending');
          $invDisabled = true;
        } elseif ($invState === 'active' || $invState === 'member') {
          $invText = $T('Вже у групі ✅', 'Уже в группе ✅', 'Already in group ✅');
          $invDisabled = true;
        }

        $cardAnchor = 'u' . (int)$uid;
      ?>

      <div class="col-12 col-md-6 col-lg-4" id="<?= h($cardAnchor) ?>">
        <div class="card h-100">
          <div class="card-body d-flex gap-3">
            <div class="flex-shrink-0">
              <?php if ($avatar !== ''): ?>
                <img src="<?= h($avatar) ?>" alt="<?= h($name) ?>" class="rounded-circle" style="width:56px;height:56px;object-fit:cover;">
              <?php else: ?>
                <div class="rounded-circle bg-secondary-subtle d-flex align-items-center justify-content-center"
                     style="width:56px;height:56px;">
                  <span class="text-secondary fw-bold">
                    <?= h(mb_strtoupper(mb_substr($username ?: $name, 0, 1))) ?>
                  </span>
                </div>
              <?php endif; ?>
            </div>

            <div class="flex-grow-1">
              <div class="d-flex align-items-start justify-content-between gap-2">
                <div>
                  <div class="fw-semibold">
                    <a class="text-decoration-none" href="<?= h($profileUrl) ?>"><?= h($name) ?></a>
                  </div>
                  <div class="text-muted small">@<?= h($username) ?></div>
                </div>

                <a class="btn btn-outline-primary btn-sm" href="<?= h($profileUrl) ?>">
                  <?= h($T('Профіль', 'Профиль', 'Profile')) ?>
                </a>
              </div>

              <div class="mt-2 small">
                <div class="d-inline-flex align-items-center gap-2">
                  <span class="rounded-circle flex-shrink-0 <?= $isOnline ? 'bg-success' : 'bg-secondary' ?>" style="width:10px;height:10px;"></span>
                  <span class="<?= $isOnline ? 'text-success' : 'text-muted' ?>"><?= h($presenceText) ?></span>
                </div>
              </div>

              <div class="mt-2 small text-muted">
                <?php
                  $parts = [];
                  if ($countryView !== '') {
                    $parts[] = ($code !== '' ? ($countryView . ' (' . $code . ')') : $countryView);
                  }
                  if ($cityV !== '') $parts[] = $cityV;
                  if ($g !== '') $parts[] = gender_label($g, $T);
                  if ($age !== null) $parts[] = $T('Вік:', 'Возраст:', 'Age:') . ' ' . (int)$age;
                  echo h(implode(' • ', array_filter($parts, fn($x) => trim((string)$x) !== '')));
                ?>
              </div>

              <div class="mt-3 d-flex gap-2 flex-wrap">
                <?php if ($meId > 0): ?>
                  <form method="post" action="/users.php?<?= h(qs_keep(['page' => $page])) ?>" class="m-0 js-stay-scroll">
                    <input type="hidden" name="csrf" value="<?= h($csrf) ?>">
                    <input type="hidden" name="action" value="friend_request">
                    <input type="hidden" name="to_user_id" value="<?= (int)$uid ?>">
                    <input type="hidden" name="anchor" value="<?= h($cardAnchor) ?>">
                    <input type="hidden" name="scroll_y" value="">
                    <button class="btn btn-success btn-sm" type="submit" <?= $btnDisabled ? 'disabled' : '' ?>>
                      <?= h($btnText) ?>
                    </button>
                  </form>
                <?php else: ?>
                  <a class="btn btn-success btn-sm" data-no-pjax="1"
                     href="/login.php?to=<?= urlencode('/users.php?' . qs_keep(['page' => $page])) ?>">
                    <?= h($T('Увійти щоб додати', 'Войти чтобы добавить', 'Login to add')) ?>
                  </a>
                <?php endif; ?>

                <?php if ($groupInviteMode && $groupsOk && $canInviteGroup): ?>
                  <form method="post" action="/users.php?<?= h(qs_keep(['page' => $page])) ?>" class="m-0 js-stay-scroll">
                    <input type="hidden" name="csrf" value="<?= h($csrf) ?>">
                    <input type="hidden" name="action" value="group_invite">
                    <input type="hidden" name="group_id" value="<?= (int)$inviteGroupId ?>">
                    <input type="hidden" name="to_user_id" value="<?= (int)$uid ?>">
                    <input type="hidden" name="anchor" value="<?= h($cardAnchor) ?>">
                    <input type="hidden" name="scroll_y" value="">
                    <button class="btn btn-outline-success btn-sm" type="submit" <?= $invDisabled ? 'disabled' : '' ?>>
                      <?= h($invText) ?>
                    </button>
                  </form>
                <?php endif; ?>
              </div>

            </div>
          </div>
        </div>
      </div>

    <?php endforeach; ?>
  </div>

  <?php if ($pages > 1): ?>
    <nav class="mt-4" aria-label="Users pagination">
      <ul class="pagination pagination-sm flex-wrap">
        <?php
          $mk = function(int $p) {
            $qs = $_GET ?? [];
            if (!is_array($qs)) $qs = [];
            $qs['page'] = $p;
            foreach ($qs as $k => $v) {
              if ($v === null) unset($qs[$k]);
            }
            return '/users.php?' . http_build_query($qs);
          };
        ?>
        <li class="page-item <?= ($page <= 1 ? 'disabled' : '') ?>">
          <a class="page-link" href="<?= h($mk(max(1, $page-1))) ?>"><?= h($T('Назад', 'Назад', 'Prev')) ?></a>
        </li>

        <?php
          $start = max(1, $page - 3);
          $end   = min($pages, $page + 3);

          if ($start > 1) {
            echo '<li class="page-item"><a class="page-link" href="'.h($mk(1)).'">1</a></li>';
            if ($start > 2) echo '<li class="page-item disabled"><span class="page-link">…</span></li>';
          }

          for ($p = $start; $p <= $end; $p++) {
            $active = ($p === $page) ? 'active' : '';
            echo '<li class="page-item '.$active.'"><a class="page-link" href="'.h($mk($p)).'">'.(int)$p.'</a></li>';
          }

          if ($end < $pages) {
            if ($end < $pages - 1) echo '<li class="page-item disabled"><span class="page-link">…</span></li>';
            echo '<li class="page-item"><a class="page-link" href="'.h($mk($pages)).'">'.(int)$pages.'</a></li>';
          }
        ?>

        <li class="page-item <?= ($page >= $pages ? 'disabled' : '') ?>">
          <a class="page-link" href="<?= h($mk(min($pages, $page+1))) ?>"><?= h($T('Далі', 'Далее', 'Next')) ?></a>
        </li>
      </ul>
    </nav>
  <?php endif; ?>

  <script>
  (function(){
    try {
      var storageKey = 'vm_users_scroll_restore';
      var forms = document.querySelectorAll('form.js-stay-scroll');

      forms.forEach(function(f){
        f.addEventListener('submit', function(){
          var y = Math.max(0, Math.round(window.scrollY || window.pageYOffset || 0));

          var inp = f.querySelector('input[name="scroll_y"]');
          if (inp) inp.value = String(y);

          try {
            sessionStorage.setItem(storageKey, String(y));
          } catch (e) {}
        });
      });
    } catch(e) {}
  })();
  </script>

<?php endif; ?>

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