<?php
// 功能：全局评论专用模板
// 作者：启肯科技
// Q Q：328493711
// 微信：13738373177（同手机）
// 官网：http://www.qiken.com
// 说明：QKCMS-轻量级AI智能内容管理建站系统
// 版权：© 2025 温州启肯网络有限公司 保留所有权利，盗版必究。

if (session_status() === PHP_SESSION_NONE) session_start();
if (!defined('COMMON_LOADED')) require_once dirname(__DIR__) . '/common.php';

// 确保 CSRF 令牌已生成
if (!isset($_SESSION['csrf_token'])) {
    generate_csrf_token();
}

if (!function_exists('json_response')) {
  function json_response($success, $message = '', $data = [])
  {
    if (ob_get_level() > 0) ob_clean();
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-cache, must-revalidate');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    $response = ['success' => (bool)$success, 'message' => (string)$message];
    if (is_array($data) && !empty($data)) $response = array_merge($response, $data);
    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    exit;
  }
}

// 过滤评论内容，防止 XSS 攻击
if (!function_exists('filter_comment_content')) {
  function filter_comment_content($content)
  {
    // 允许的标签
    $allowed_tags = '<p><br><img><video><strong><em><u><blockquote><code>';
    // 允许的 img 属性
    $allowed_img_attrs = ['src', 'alt', 'title', 'width', 'height'];
    $allowed_video_attrs = ['src', 'controls', 'playsinline', 'webkit-playsinline', 'preload', 'muted'];
    
    // 过滤内容
    $content = strip_tags($content, $allowed_tags);
    
    // 过滤 img 标签的属性
    $content = preg_replace_callback('/<img[^>]+>/i', function($matches) use ($allowed_img_attrs) {
      $img_tag = $matches[0];
      $filtered_attrs = [];
      
      // 提取 src 属性
      if (preg_match('/src=["\']([^"\']+)["\']/i', $img_tag, $src_match)) {
        $src = $src_match[1];
        // 只允许本地路径和安全的图片 URL
        if (strpos($src, '/uploads/') === 0 || strpos($src, '/assets/emoji/') === 0 || strpos($src, 'http://') === 0 || strpos($src, 'https://') === 0) {
          $filtered_attrs[] = 'src="' . htmlspecialchars($src) . '"';
        }
      }
      
      // 提取其他允许的属性
      foreach ($allowed_img_attrs as $attr) {
        if (preg_match('/' . $attr . '=["\']([^"\']+)["\']/i', $img_tag, $attr_match)) {
          $filtered_attrs[] = $attr . '="' . htmlspecialchars($attr_match[1]) . '"';
        }
      }
      
      return '<img ' . implode(' ', $filtered_attrs) . '>';
    }, $content);

    $content = preg_replace_callback('/<video\b[^>]*>(?:.*?)<\/video>|<video\b[^>]*>/is', function($matches) use ($allowed_video_attrs) {
      $video_tag = $matches[0];
      $filtered_attrs = [];
      $src = '';

      if (preg_match('/src=["\']([^"\']+)["\']/i', $video_tag, $src_match)) {
        $src = trim($src_match[1]);
      }

      if ($src === '') {
        return '';
      }

      if (!(strpos($src, '/uploads/') === 0 || strpos($src, 'http://') === 0 || strpos($src, 'https://') === 0)) {
        return '';
      }

      $filtered_attrs[] = 'src="' . htmlspecialchars($src) . '"';

      foreach ($allowed_video_attrs as $attr) {
        if ($attr === 'src') {
          continue;
        }
        if (preg_match('/\b' . preg_quote($attr, '/') . '(?:=(["\'])(.*?)\1)?/i', $video_tag)) {
          $filtered_attrs[] = $attr;
        }
      }

      if (!in_array('controls', $filtered_attrs, true)) {
        $filtered_attrs[] = 'controls';
      }
      if (!in_array('playsinline', $filtered_attrs, true)) {
        $filtered_attrs[] = 'playsinline';
      }
      if (!in_array('preload', $filtered_attrs, true)) {
        $filtered_attrs[] = 'preload="metadata"';
      }

      return '<video ' . implode(' ', $filtered_attrs) . '></video>';
    }, $content);
    
    return $content;
  }
}

// 初始化
$is_logged_in = isLoggedIn();
$is_user_banned = false;
$current_username = '游客';
$current_avatar = DEFAULT_AVATAR_PATH;
$user_id = 0;

if ($is_logged_in) {
  $user_id = intval($_SESSION['user_id'] ?? 0);
  if (isset($_SESSION['user_status'])) {
    $status = $_SESSION['user_status'];
    $is_user_banned = ($status === 0 || $status === '0' || $status === '被封禁');
  }

  // 提前获取用户信息，避免在 HTML 渲染中重复查询
  $user = get_single_row($GLOBALS['conn'], "SELECT username, avatar FROM users WHERE id = ?", [$user_id]);
  if ($user) {
    $current_username = $user['username'];
    $current_avatar = !empty($user['avatar']) ? $user['avatar'] : DEFAULT_AVATAR_PATH;
    if (strpos($current_avatar, 'http') !== 0 && strpos($current_avatar, '/') !== 0) {
      $current_avatar = '/' . $current_avatar;
    }
  }
}


// 通用评论系统 - 支持任何页面
// 获取post_id和content_type（由tplrdr智能获取并传递）
$post_id = 0;
$content_type = 'post';

// 从tplrdr传递的article_id获取
if (isset($GLOBALS['qkcms_article_id']) && $GLOBALS['qkcms_article_id'] > 0) {
  $post_id = intval($GLOBALS['qkcms_article_id']);
}

// AJAX请求时从POST参数获取
if ($post_id <= 0 && isset($_POST['post_id']) && $_POST['post_id'] > 0) {
  $post_id = intval($_POST['post_id']);
  if (isset($_POST['content_type'])) {
    $content_type = $_POST['content_type'];
  }
}

$settings = getSettings($GLOBALS['conn']);
$comment_order = isset($_GET['order']) ? intval($_GET['order']) : 1;
if (!in_array($comment_order, [1, 2, 3], true)) $comment_order = 1;

$base_path = strtok($_SERVER['REQUEST_URI'], '?');
$base_query = $_GET;
unset($base_query['order'], $base_query['page']);
$q1 = $base_query;
$q1['order'] = 1;
$q1['page'] = 1;
$q2 = $base_query;
$q2['order'] = 2;
$q2['page'] = 1;
$q3 = $base_query;
$q3['order'] = 3;
$q3['page'] = 1;
$order_url_1 = $base_path . (empty($q1) ? '' : ('?' . http_build_query($q1)));
$order_url_2 = $base_path . (empty($q2) ? '' : ('?' . http_build_query($q2)));
$order_url_3 = $base_path . (empty($q3) ? '' : ('?' . http_build_query($q3)));

// 插件配置开关检测
$comment_enabled = 1;
$like_animation_enabled = 1;
$plugin_comment_check = 0;
$security_filter_enabled = 1;
$ip_submit_limit = 0;
$ip_limit_unit = 'hour';
$image_upload_enabled = 1;
$image_size_limit = 600;
$image_upload_max_count = 5;
$video_insert_enabled = 0;
$plugin_found = false;
$plugin_file_path = dirname(__DIR__) . '/admin/Plugin/39_CommentMgr.php';
if (is_file($plugin_file_path) && is_readable($plugin_file_path)) {
  $content = @file_get_contents($plugin_file_path);
  if ($content !== false && preg_match('/\[config_params\](.*?)\[\/config_params\]/s', $content, $mm)) {
    $lines = explode("\n", trim($mm[1]));
    foreach ($lines as $line) {
      $line = trim($line);
      if ($line === '' || $line[0] === '#') continue;
      if (preg_match('/^\s*([^=]+)\s*=\s*(.*)\s*$/', $line, $kv)) {
        $k = trim($kv[1]);
        $v = trim($kv[2]);
        if ($k === 'comment_enabled') $comment_enabled = ($v === '1') ? 1 : 0;
        elseif ($k === 'like_animation_enabled') $like_animation_enabled = ($v === '1') ? 1 : 0;
        elseif ($k === 'comment_check') $plugin_comment_check = ($v === '1') ? 1 : 0;
        elseif ($k === 'security_filter_enabled') $security_filter_enabled = ($v === '1') ? 1 : 0;
        elseif ($k === 'ip_submit_limit') $ip_submit_limit = intval($v);
        elseif ($k === 'ip_limit_unit') $ip_limit_unit = in_array($v, array('hour', 'minute', 'second')) ? $v : 'hour';
        elseif ($k === 'image_upload_enabled') $image_upload_enabled = ($v === '1') ? 1 : 0;
        elseif ($k === 'image_size_limit') $image_size_limit = max(1, intval($v));
        elseif ($k === 'image_upload_max_count') $image_upload_max_count = max(1, min(20, intval($v)));
        elseif ($k === 'video_insert_enabled') $video_insert_enabled = ($v === '1') ? 1 : 0;
      }
    }
    $plugin_found = true;
  }
}

// 评论审核：插件存在则使用插件参数；插件不存在则默认不审核
if (isset($settings) && is_array($settings)) {
  $settings['comment_check'] = $plugin_found ? $plugin_comment_check : 0;
}

// 将必要参数传递给前端
echo '<script>window.pinlunjs=window.pinlunjs||{};window.pinlunjs.likeAnimationEnabled=' . ($like_animation_enabled ? 'true' : 'false') . ';window.pinlunjs.securityFilterEnabled=' . ($security_filter_enabled ? 'true' : 'false') . ';window.pinlunjs.ipLimitUnit=' . json_encode($ip_limit_unit) . ';window.pinlunjs.ipSubmitLimit=' . intval($ip_submit_limit) . ';window.pinlunjs.imageUploadEnabled=' . ($image_upload_enabled ? 'true' : 'false') . ';window.pinlunjs.imageSizeLimitKB=' . intval($image_size_limit) . ';window.pinlunjs.imageUploadMaxCount=' . intval($image_upload_max_count) . ';window.pinlunjs.videoInsertEnabled=' . ($video_insert_enabled ? 'true' : 'false') . ';</script>';
if ($comment_enabled === 0) {
  echo '<div class="comment-disabled-notice" style="display:flex;align-items:center;justify-content:center;gap:10px;padding:14px 16px;border:1px solid #fde68a;border-radius:12px;background:#fffbeb;color:#92400e;font-size:14px;line-height:1.6;box-sizing:border-box;"><svg viewBox="0 0 24 24" width="20" height="20" style="width:20px;height:20px;flex:0 0 20px;color:#f59e0b;" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg><span style="text-align:center;">很抱歉评论功能已关闭，可在后台评论助手管理插件再次开启</span></div>';
  return;
}

// AJAX 处理
if (isset($_POST['action']) && !empty($_POST['action'])) {
  // 确保数据库连接已初始化
  if (!isset($GLOBALS['conn']) || !is_object($GLOBALS['conn']) || isset($GLOBALS['conn']->connect_error)) {
    $GLOBALS['conn'] = get_db_connection();
  }
  global $conn, $settings;
  $conn = $GLOBALS['conn'];

  // 重新获取设置（确保在AJAX请求中可用）
  if (empty($settings)) {
    $settings = getSettings($conn);
  }
  // 再次同步审核开关（插件存在用插件参数，否则不审核）
  if (isset($settings) && is_array($settings)) {
    $settings['comment_check'] = $plugin_found ? $plugin_comment_check : 0;
  }

  $action = $_POST['action'];

  if ($action === 'get_comment_config') {
    json_response(true, '', [
      'ip_submit_limit' => intval($ip_submit_limit),
      'ip_limit_unit' => $ip_limit_unit,
      'image_upload_enabled' => intval($image_upload_enabled),
      'image_size_limit' => intval($image_size_limit),
      'image_upload_max_count' => intval($image_upload_max_count),
      'video_insert_enabled' => intval($video_insert_enabled)
    ]);
  }

  // 基础权限拦截
  $needs_auth = ['upload_comment_image', 'submit_comment', 'delete_comment', 'toggle_like', 'toggle_dislike'];
  if (in_array($action, $needs_auth)) {
    if (!$is_logged_in) json_response(false, '请先登录后再进行操作', ['error_code' => 'not_logged_in']);
    if ($is_user_banned) json_response(false, '您的账号已被封禁');
  }
  
  // CSRF 令牌验证
  if (in_array($action, $needs_auth)) {
    $csrf_token = '';
    if (isset($_POST['csrf_token'])) $csrf_token = (string)$_POST['csrf_token'];
    if ($csrf_token === '' && isset($_SERVER['HTTP_X_CSRF_TOKEN'])) $csrf_token = (string)$_SERVER['HTTP_X_CSRF_TOKEN'];
    if ($csrf_token === '' || !verify_csrf_token($csrf_token)) {
      json_response(false, 'CSRF校验失败');
    }
  }

  // 1. 图片上传
  if ($action === 'upload_comment_image' && isset($_FILES['image'])) {
    if (intval($image_upload_enabled) !== 1) json_response(false, '不允许上传图片');
    $file = $_FILES['image'];
    if ($file['error'] !== UPLOAD_ERR_OK) json_response(false, '文件上传失败');

    $file_info = getimagesize($file['tmp_name']);
    if (!$file_info) json_response(false, '只允许上传图片文件');

    $allowed = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
    if (!in_array($file_info['mime'], $allowed)) json_response(false, '只允许上传图片文件');
    $limit_kb = max(1, intval($image_size_limit));
    if ($file['size'] > $limit_kb * 1024) json_response(false, '图片大小不能超过' . $limit_kb . 'KB');

    $doc_root_upload = rtrim($_SERVER['DOCUMENT_ROOT'], '/\\');
    $target_dir = $doc_root_upload . '/uploads/Temp/';
    if (!is_dir($target_dir)) mkdir($target_dir, 0755, true);

    $ext = 'jpg';
    if ($file_info['mime'] === 'image/png') $ext = 'png';
    elseif ($file_info['mime'] === 'image/gif') $ext = 'gif';
    $unique_name = 'comments_' . uniqid() . time() . '.' . $ext;
    $file_path = $target_dir . $unique_name;

    if (move_uploaded_file($file['tmp_name'], $file_path)) {
      json_response(true, '', ['url' => '/uploads/Temp/' . $unique_name]);
    }
    json_response(false, '文件保存失败');
  }

  // 2. 提交评论/回复
  if ($action === 'submit_comment') {
    $post_id = intval($_POST['post_id'] ?? 0);
    $content = trim($_POST['content'] ?? '');
    $parent_id = intval($_POST['parent_id'] ?? 0);
    $reply_to = trim($_POST['reply_to'] ?? '');
    $content_type = $_POST['content_type'] ?? 'post';
    if (function_exists('mb_substr')) $reply_to = mb_substr($reply_to, 0, 50);
    else $reply_to = substr($reply_to, 0, 50);

    if ($post_id <= 0) {
      json_response(false, '请填写完整的评论信息: post_id=' . $post_id . ' (POST数据: ' . json_encode($_POST) . ')');
    }
    if (empty($content)) {
      json_response(false, '请填写完整的评论信息: content为空');
    }

    if ($parent_id > 0 && !get_single_row($GLOBALS['conn'], "SELECT id FROM comments WHERE id = ? AND post_id = ?", [$parent_id, $post_id])) {
      json_response(false, '回复的评论不存在');
    }

    if ($security_filter_enabled == 1 && function_exists('has_forbidden_words') && has_forbidden_words($content)) json_response(false, '评论内容包含违禁词');

    // 处理图片路径迁移
    $doc_root = rtrim($_SERVER['DOCUMENT_ROOT'], '/\\');
    if (preg_match_all('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches)) {
      foreach ($matches[1] as $src) {
        if (stripos($src, '/uploads/Temp/') !== false) {
          $fileName = basename(parse_url($src, PHP_URL_PATH));
          $tempPath = $doc_root . '/uploads/Temp/' . $fileName;
          $finalPath = $doc_root . '/uploads/comments/' . $fileName;
          if (!is_dir(dirname($finalPath))) mkdir(dirname($finalPath), 0755, true);
          if (file_exists($tempPath)) {
            rename($tempPath, $finalPath); // 优化：使用 rename 替代 copy+unlink
          }
          $content = str_replace($src, '/uploads/comments/' . $fileName, $content);
        }
      }
    }

    // 检查user_id
    if (!isset($_SESSION['user_id']) || $_SESSION['user_id'] <= 0) {
      json_response(false, '用户未登录或session无效');
    }

    // 会话级间隔限制（辅助防刷）
    if (intval($ip_submit_limit) > 0) {
      $seconds = 0;
      if ($ip_limit_unit === 'second') $seconds = intval($ip_submit_limit);
      elseif ($ip_limit_unit === 'minute') $seconds = intval($ip_submit_limit) * 60;
      elseif ($ip_limit_unit === 'hour') $seconds = intval($ip_submit_limit) * 3600;

      if ($seconds > 0) {
        $lastTs = intval($_SESSION['comment_last_submit_ts'] ?? 0);
        if ($lastTs && (time() - $lastTs) < $seconds) {
          json_response(false, '评论过于频繁，请稍后再试');
        }
        $_SESSION['comment_last_submit_ts'] = time();
      }
    }

    // 账号提交频率限制（按设置的间隔时间）
    if (intval($ip_submit_limit) > 0) {
      $seconds = 0;
      if ($ip_limit_unit === 'second') $seconds = intval($ip_submit_limit);
      elseif ($ip_limit_unit === 'minute') $seconds = intval($ip_submit_limit) * 60;
      elseif ($ip_limit_unit === 'hour') $seconds = intval($ip_submit_limit) * 3600;

      if ($seconds > 0) {
        $lastRow = get_single_row($GLOBALS['conn'], "SELECT created_at FROM comments WHERE user_id = ? ORDER BY id DESC LIMIT 1", [intval($_SESSION['user_id'])]);
        if ($lastRow && isset($lastRow['created_at'])) {
          $lastTime = strtotime($lastRow['created_at']);
          if (time() - $lastTime < $seconds) {
            json_response(false, '评论过于频繁，请稍后再试');
          }
        }
      }
    }

    // 如果是回复（parent_id > 0）且是商品评论（content_type = 'goods'），检查是否已购买（管理员除外）
    if ($parent_id > 0 && $content_type === 'goods' && !hasPermission('admin')) {
      $current_user_id = intval($_SESSION['user_id']);
      $has_purchased = false;
      if ($current_user_id > 0 && isset($GLOBALS['conn']) && $GLOBALS['conn']) {
        $order_check_sql = "SELECT COUNT(*) as count FROM `order` WHERE user_id = ? AND goods_id = ? AND pay_time IS NOT NULL";
        $order_stmt = mysqli_prepare($GLOBALS['conn'], $order_check_sql);
        if ($order_stmt) {
          mysqli_stmt_bind_param($order_stmt, 'ii', $current_user_id, $post_id);
          mysqli_stmt_execute($order_stmt);
          mysqli_stmt_bind_result($order_stmt, $order_count);
          mysqli_stmt_fetch($order_stmt);
          mysqli_stmt_close($order_stmt);
          $has_purchased = ($order_count > 0);
        }
      }
      if (!$has_purchased) {
        json_response(false, '购买商品后才能回复评论');
      }
    }

    $result = submitComment($post_id, $_SESSION['user_id'], $content, $parent_id, $reply_to, $content_type);
    if ($result['success']) {
      $msg = ($parent_id > 0 ? '回复' : '评论') . ($result['need_approval'] ? '提交成功，等待审核' : '提交成功！');
      if (function_exists('clearTempDirectory')) {
        clearTempDirectory();
      } else {
        $temp_dir = dirname(__DIR__) . '/../uploads/Temp/';
        if (is_dir($temp_dir)) {
          foreach (glob($temp_dir . '*') ?: [] as $f) {
            if (is_file($f)) @unlink($f);
          }
        }
      }
      $response_data = array_merge($result, [
        'ip_submit_limit' => intval($ip_submit_limit),
        'ip_limit_unit' => $ip_limit_unit
      ]);
      json_response(true, $msg, $response_data);
    }
    json_response(false, $result['message'] ?? '评论提交失败');
  }

  // 3. 删除评论
  if ($action === 'delete_comment') {
    $comment_id = intval($_POST['comment_id'] ?? 0);
    if ($comment_id <= 0) json_response(false, '无效的评论ID');

    $deleteResult = deleteComment($comment_id, $_SESSION['user_id']);
    if ($deleteResult['success']) {
      json_response(true, '评论删除成功！', ['deleted_count' => $deleteResult['deleted_count']]);
    }
    json_response(false, $deleteResult['message'] ?? '删除失败');
  }

  // 4. 点赞/踩
  if ($action === 'toggle_like' || $action === 'toggle_dislike') {
    $type = $action === 'toggle_like' ? 'like' : 'dislike';
    $result = toggleReaction('comment', intval($_POST['content_id'] ?? 0), $_SESSION['user_id'], $type);
    json_response($result['success'], $result['message'], [
      $type . 's_count' => $result['count'],
      'action' => $result['action']
    ]);
  }

  // 5. 清理过期临时文件
  if ($action === 'clear_temp') {
    clearTempFiles();
    json_response(true, '');
  }
  // 6. 获取最近点赞（用于前台浮动效果，非阻塞）
  if ($action === 'latest_likes') {
    $items = [];
    try {
      $sql = "SELECT content_id, created_at FROM likes WHERE content_type = 'comment' AND type = 'like' ORDER BY created_at DESC LIMIT 10";
      $res = @execute_query($GLOBALS['conn'], $sql, []);
      if ($res) {
        while ($row = $res->fetch_assoc()) {
          $items[] = [
            'content_id' => intval($row['content_id'] ?? 0),
            'time' => $row['created_at'] ?? ''
          ];
        }
      }
    } catch (Exception $e) {
    }
    json_response(true, '', ['items' => $items]);
  }
}

// --- 以下为业务逻辑函数 ---

// 提交评论逻辑优化
function submitComment($postId, $userId, $content, $parentId = 0, $replyTo = '', $contentType = 'post')
{
  global $settings, $conn;
  global $security_filter_enabled;
  try {
    // 调试信息
    if (!isset($conn) || !is_object($conn)) {
      return ['success' => false, 'message' => '数据库连接未初始化'];
    }
    if (isset($conn->connect_error)) {
      return ['success' => false, 'message' => '数据库连接错误: ' . $conn->connect_error];
    }

    $content = trim($content);
    // 过滤评论内容，防止 XSS 攻击
    $content = filter_comment_content($content);
    // 规范化图片在内容中的显示
    $content = preg_replace(['/([^\n])<img/i', '/(<img[^>]+>)/i', '/\n{3,}/'], ["$1\n<img", "\n$1\n", "\n\n"], $content);

    // 安全过滤（开启时启用以下检查）
    if (intval($security_filter_enabled) === 1) {
      // 纯数字
      if (preg_match('/^\d+$/', strip_tags($content))) {
        return ['success' => false, 'message' => '评论不能为纯数字'];
      }
      // 纯字母
      if (preg_match('/^[A-Za-z]+$/', strip_tags($content))) {
        return ['success' => false, 'message' => '评论不能为纯字母'];
      }
      // 重复字符（同一字符连续很多次）
      if (preg_match('/^(.)\\1{8,}$/u', strip_tags($content))) {
        return ['success' => false, 'message' => '评论内容过于重复'];
      }
      // 与该用户最近一次评论完全重复
      $dup = get_single_row($conn, "SELECT id FROM comments WHERE user_id = ? AND content = ? ORDER BY id DESC LIMIT 1", [$userId, $content]);
      if ($dup) {
        return ['success' => false, 'message' => '请不要重复提交相同评论'];
      }
    }

    $needApproval = $settings['comment_check'] ?? 0;
    $status = $needApproval ? 0 : 1;

    // 如果comments表有content_type字段，则使用；否则只使用post_id
    // 先检查表结构
    $checkColumn = $conn->query("SHOW COLUMNS FROM comments LIKE 'content_type'");
    if ($checkColumn && $checkColumn->num_rows > 0) {
      $sql = "INSERT INTO comments (post_id, user_id, content, created_at, status, parent_id, reply_to, content_type) VALUES (?, ?, ?, NOW(), ?, ?, ?, ?)";
      $params = [$postId, $userId, $content, $status, $parentId, $replyTo, $contentType];
    } else {
      $sql = "INSERT INTO comments (post_id, user_id, content, created_at, status, parent_id, reply_to) VALUES (?, ?, ?, NOW(), ?, ?, ?)";
      $params = [$postId, $userId, $content, $status, $parentId, $replyTo];
    }

    // 手动执行SQL以获取详细错误
    $stmt = $conn->prepare($sql);
    if (!$stmt) {
      return ['success' => false, 'message' => '服务器内部错误，请稍后再试'];
    }

    $types = '';
    $bind_params = [];
    foreach ($params as $key => &$value) {
      if (is_int($value)) $types .= 'i';
      elseif (is_float($value)) $types .= 'd';
      else $types .= 's';
      $bind_params[] = &$value;
    }

    // 使用引用传递参数
    array_unshift($bind_params, $types);
    $reflection = new ReflectionMethod('mysqli_stmt', 'bind_param');
    $reflection->invokeArgs($stmt, $bind_params);

    if (!$stmt->execute()) {
      return ['success' => false, 'message' => '服务器内部错误，请稍后再试'];
    }

    $affected_rows = $stmt->affected_rows;
    $insert_id = $stmt->insert_id;
    $stmt->close();
    $result = ['affected_rows' => $affected_rows, 'insert_id' => $insert_id];

    if ((!$result || $result['affected_rows'] <= 0) && isset($conn) && is_object($conn) && isset($conn->error) && (stripos($conn->error, 'Incorrect string value') !== false || intval($conn->errno ?? 0) === 1366)) {
      $content2 = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content);
      if (is_string($content2) && $content2 !== $content) {
        // 重新构建params数组
        $retryParams = [$postId, $userId, $content2, $status, $parentId, $replyTo];
        if ($checkColumn && $checkColumn->num_rows > 0) {
          $retryParams[] = $contentType;
        }
        $result = execute_non_query($conn, $sql, $retryParams);
        if ($result && $result['affected_rows'] > 0) {
          $content = $content2;
        }
      }
    }

    if ($result && $result['affected_rows'] > 0) {
      $commentId = $result['insert_id'];
      // 更新对应表的评论数
      if ($parentId == 0) {
        if ($contentType === 'goods') {
          execute_non_query($conn, "UPDATE goods SET comment_count = comment_count + 1 WHERE id = ?", [$postId]);
        } else {
          execute_non_query($conn, "UPDATE posts SET comment_count = comment_count + 1 WHERE id = ?", [$postId]);
        }
      }

      // 获取用户信息
      $user = get_single_row($conn, "SELECT username, avatar, levels_id FROM users WHERE id = ?", [$userId]);
      $levelName = '游客';
      $avatarPath = DEFAULT_AVATAR_PATH;
      $username = '用户';

      if ($user) {
        $username = !empty($user['username']) ? $user['username'] : '用户';
        $avatarPath = !empty($user['avatar']) ? $user['avatar'] : DEFAULT_AVATAR_PATH;
        if (strpos($avatarPath, 'http') !== 0 && strpos($avatarPath, '/') !== 0) {
          $avatarPath = '/' . $avatarPath;
        }

        // 获取等级名
        $levelId = intval($user['levels_id']);
        $levelRow = get_single_row($conn, "SELECT name FROM levels WHERE id = ?", [$levelId]);
        if ($levelRow && !empty($levelRow['name'])) {
          $levelName = $levelRow['name'];
        }
      }

      return [
        'success' => true,
        'need_approval' => (bool)$needApproval,
        'comment_id' => $commentId,
        'author' => $username,
        'avatar' => $avatarPath,
        'content' => $content,
        'date' => date('Y-n-j'),
        'user_level' => $levelName,
        'parent_id' => $parentId,
        'reply_to' => $replyTo
      ];
    }
    // 返回错误信息
    return ['success' => false, 'message' => '没有数据被插入 (affected_rows: ' . $result['affected_rows'] . ')'];
  } catch (Exception $e) {
    return ['success' => false, 'message' => '服务器内部错误，请稍后再试'];
  }
}

// 删除评论逻辑优化
function deleteComment($commentId, $userId)
{
  global $conn;
  $comment = get_single_row($conn, "SELECT id, post_id, user_id, parent_id FROM comments WHERE id = ?", [$commentId]);

  if (!$comment) return ['success' => false, 'message' => '评论不存在'];
  if ($comment['user_id'] != $userId && $userId != 1) return ['success' => false, 'message' => '无权限删除此评论'];

  mysqli_begin_transaction($conn);
  try {
    // 获取待删除的所有 ID（包括子评论）
    $ids = [$commentId];
    if (empty($comment['parent_id'])) {
      $replies = execute_query($conn, "SELECT id FROM comments WHERE parent_id = ?", [$commentId]);
      while ($r = $replies->fetch_assoc()) $ids[] = $r['id'];
    }

    $idPlaceholders = implode(',', array_fill(0, count($ids), '?'));

    // 先删除所有图片（与后台一致：使用全局工具方法）
    if (function_exists('extractAndDeleteCommentImages')) {
      foreach ($ids as $delId) extractAndDeleteCommentImages($delId, $conn);
    } else {
      $imgs = [];
      $res = execute_query($conn, "SELECT content FROM comments WHERE id IN ($idPlaceholders)", $ids);
      if ($res) {
        while ($row = $res->fetch_assoc()) {
          if (!empty($row['content'])) {
            if (preg_match_all('/<img[^>]+src=[\"\']([^\"\']+)[\"\']/i', $row['content'], $m)) {
              foreach ($m[1] as $src) {
                if (strpos($src, '/uploads/') !== false && strpos($src, '/assets/emoji/') === false) {
                  $imgs[] = $src;
                }
              }
            }
          }
        }
      }
      if (!empty($imgs)) {
        foreach ($imgs as $src) {
          if (strpos($src, '/assets/emoji/') !== false) continue;
          if (function_exists('deleteUploadFile')) deleteUploadFile($src);
          else {
            $p = parse_url($src, PHP_URL_PATH);
            if ($p && strpos($p, '/uploads/') === 0 && strpos($p, '/assets/emoji/') === false) {
              $full = rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') . $p;
              if (is_file($full)) @unlink($full);
            }
          }
        }
      }
    }

    // 批量删除评论和点赞记录
    execute_non_query($conn, "DELETE FROM comments WHERE id IN ($idPlaceholders)", $ids);
    $deletedCount = mysqli_affected_rows($conn);

    execute_non_query($conn, "DELETE FROM likes WHERE content_type = 'comment' AND content_id IN ($idPlaceholders)", $ids);

    if ($deletedCount > 0) {
      execute_non_query($conn, "UPDATE posts SET comment_count = GREATEST(0, comment_count - ?) WHERE id = ?", [$deletedCount, $comment['post_id']]);
    }

    mysqli_commit($conn);
    return ['success' => true, 'deleted_count' => $deletedCount];
  } catch (Exception $e) {
    mysqli_rollback($conn);
    return ['success' => false, 'message' => '服务器内部错误，请稍后再试'];
  }
}

// 点赞/踩统一处理优化
function toggleReaction($type, $contentId, $userId, $reactionType)
{
  global $conn;
  $field = $reactionType === 'like' ? 'likes' : 'dislike_count';

  $existing = get_single_row($conn, "SELECT id FROM likes WHERE content_type = ? AND content_id = ? AND user_id = ? AND type = ?", [$type, $contentId, $userId, $reactionType]);

  if ($existing) {
    execute_non_query($conn, "DELETE FROM likes WHERE id = ?", [$existing['id']]);
    $op = "- 1";
    $action = 'un' . $reactionType;
  } else {
    execute_non_query($conn, "INSERT INTO likes (content_type, content_id, user_id, type, created_at) VALUES (?, ?, ?, ?, NOW())", [$type, $contentId, $userId, $reactionType]);
    $op = "+ 1";
    $action = $reactionType;
  }

  // 更新并获取最新计数（原子操作后直接查询）
  execute_non_query($conn, "UPDATE comments SET $field = GREATEST(0, $field $op) WHERE id = ?", [$contentId]);
  $row = get_single_row($conn, "SELECT $field FROM comments WHERE id = ?", [$contentId]);
  $newCount = $row ? intval($row[$field]) : 0;

  $labels = [
    'like' => ['like' => '点赞成功！', 'unlike' => '已取消点赞！'],
    'dislike' => ['dislike' => '踩成功！', 'undislike' => '已取消踩！']
  ];
  $message = $labels[$reactionType][$action] ?? '操作成功';

  return ['success' => true, 'action' => $action, 'count' => $newCount, 'message' => $message];
}

// 5. 清理过期临时文件
function clearTempFiles()
{
  $temp_dir = dirname(__DIR__) . '/../uploads/Temp/';
  if (!is_dir($temp_dir)) return;

  $files = glob($temp_dir . '*');
  $now = time();
  foreach ($files as $f) {
    if (is_file($f) && ($now - filemtime($f) > 3600)) {
      @unlink($f);
    }
  }
}
?>

<link href="/assets/quill/quill.snow.css" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/ts_comment.css" />
<script src="/assets/quill/quill.min.js" defer></script>
<main class="pinlun-main-content">
  <div class="pinlun-container">
    <div class="pinlun-content-layout">
      <div class="pinlun-main-column">
        <section class="pinlun-topics-section">
          
          <div class="pinlun-comment-form-container" style="<?php echo (hasPermission('admin') || !isset($GLOBALS['has_purchased']) || (isset($GLOBALS['has_purchased']) && $GLOBALS['has_purchased']) || !$is_logged_in) ? '' : 'display: none;'; ?>">
            <div id="pinlun-comment-quill-shell">
              <div class="qk-quill-placeholder">
                <div class="qk-quill-placeholder-toolbar"></div>
                <div class="qk-quill-placeholder-text">写下你的评论...</div>
              </div>
              <div id="pinlun-comment-quill-editor"></div>
            </div>
            <div class="pinlun-comment-form-footer">
              <div class="pinlun-comment-user-info">
                <img src="<?php echo $current_avatar; ?>" alt="avatar" class="pinlun-comment-avatar-special" id="pinlun-comment-box-avatar">
                <span class="pinlun-comment-user-name" id="pinlun-comment-box-username"><?php echo $current_username; ?></span>
              </div>
              <button id="pinlun-comment-submit" class="pinlun-btn-primary " data-require-login="1" data-login-action="comment">提交评论</button>
            </div>
          </div>
          
          <div class="purchase-required-notice" style="text-align: center; padding: 40px 20px; margin-bottom: 20px; background: #f8f9fa; border-radius: 8px; color: #666; <?php echo ($is_logged_in && isset($GLOBALS['has_purchased']) && !$GLOBALS['has_purchased'] && !hasPermission('admin')) ? '' : 'display: none;'; ?>">
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5" style="margin-bottom: 15px;">
              <circle cx="12" cy="12" r="10" />
              <path d="M12 16v-4" />
              <path d="M12 8h.01" />
            </svg>
            <p style="font-size: 15px; margin-bottom: 8px;">购买商品后才能发表评论</p>
            <p style="font-size: 13px; color: #999;">您尚未购买此商品，购买后即可参与评价</p>
          </div>

          <div class="pinlun-sort-bar">
            <div class="pinlun-sort-actions" id="pinlun-comment-sort-actions" data-current-order="<?php echo $comment_order; ?>">
              <a href="<?php echo htmlspecialchars($order_url_1, ENT_QUOTES); ?>" class="pinlun-sort-btn <?php echo $comment_order === 1 ? 'active' : ''; ?>" data-order="1">最新评论</a>
              <a href="<?php echo htmlspecialchars($order_url_2, ENT_QUOTES); ?>" class="pinlun-sort-btn <?php echo $comment_order === 2 ? 'active' : ''; ?>" data-order="2">最早评论</a>
              <a href="<?php echo htmlspecialchars($order_url_3, ENT_QUOTES); ?>" class="pinlun-sort-btn <?php echo $comment_order === 3 ? 'active' : ''; ?>" data-order="3">最热评论</a><span id="pinlun-sort-loading" class="pinlun-sort-loading" aria-hidden="true"></span>
            </div>
            <div class="pinlun-avatars-row" id="pinlun-comment-avatars-row"></div>
          </div>

          
          <div class="pinlun-topics-content" style="position: relative;">
            <div id="pinlun-latest" class="pinlun-tab-content pinlun-active"> <?php echo '{qkcms:comments row=12 order=' . intval($comment_order) . ' date_format=2 article_id=' . intval($post_id) . ' page=1}'; ?>
              <div class="pinlun-topic-item" data-id="{id}">
                <div class="pinlun-avatar-container"> <a href="/Member_id{author_id}.html" target="_blank">
                  <img src="{avatar}" class="pinlun-topic-avatar"></a>
                  <div class="pinlun-user-level-tag"><span class="dot"></span>{user_level}</div>
                </div>
                <div class="pinlun-topic-content1">
                  <div class="pinlun-topic-header-row">
                    <div class="pinlun-comment-author-date">{author}&nbsp;{date}</div>
                    <div class="pinlun-topic-actions">
                      <button class="pinlun-comment-like" data-require-login="1" data-login-action="like" data-type="comment" data-id="{id}"> <span class="pinlun-icon-like1">
                          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="43 85 426 384" fill="currentColor">
                            <path d="M85 234Q94 234 100.50 240Q107 246 107 255L107 447Q107 456 100.50 462.50Q94 469 85 469L43 469L43 234ZM162 221L299 85L308 95Q320 107 327 121Q330 128 329 135L309 234L427 234Q444 234 456.50 247Q469 260 469 277L469 300Q469 309 466 317L409 448Q396 469 372 469L199 469Q178 469 163.50 454.50Q149 440 149 419L149 252Q149 234 162 221ZM268 226L280 164L192 251L192 419Q192 426 199 426L372 426L427 300L427 277L309 277Q290 277 276.50 261.50Q263 246 268 226Z" />
                          </svg>
                        </span> <span class="pinlun-action-count">{like_count}</span> </button>
                      <button class="pinlun-comment-dislike" data-require-login="1" data-login-action="dislike" data-type="comment" data-id="{id}"> <span class="pinlun-icon-dislike">
                          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="43 43 426 384" fill="currentColor">
                            <path d="M427 278Q418 278 411.50 272Q405 266 405 257L405 65Q405 56 411.50 49.50Q418 43 427 43L469 43L469 278ZM350 291L213 427L204 417Q192 405 185 391Q182 384 183 377L203 278L85 278Q68 278 55.50 265Q43 252 43 235L43 212Q43 202 46 195L103 64Q116 43 140 43L313 43Q334 43 348.50 57.50Q363 72 363 93L363 260Q363 278 350 291ZM244 286L232 348L320 261L320 93Q320 86 313 86L140 86L85 212L85 235L203 235Q222 235 235.50 250.50Q249 266 244 286Z" />
                          </svg>
                        </span> <span class="pinlun-action-count">{dislike_count}</span> </button>
                      <button class="pinlun-comment-reply" data-require-login="1" data-login-action="reply" data-type="comment" data-id="{id}" data-username="{author}">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -128 1792 1601.020751953125" fill="currentColor">
                          <path d="M243 494.02L640 891.02L640 961.02Q640 1003.02 601 1020.02Q588 1025.02 576 1025.02Q549 1025.02 531 1006.02L19 494.02Q0 475.02 0 449.02Q0 423.02 19 404.02L531-107.98Q560-138.98 601-121.98Q640-104.98 640-62.98L640 6.02L243 404.02Q224 423.02 224 449.02Q224 475.02 243 494.02ZM1792 929.02Q1792 987.02 1775 1062.52Q1758 1138.02 1736.50 1200.52Q1715 1263.02 1688.50 1325.52Q1662 1388.02 1648 1416.02Q1634 1444.02 1628 1456.02Q1620 1473.02 1600 1473.02Q1594 1473.02 1591 1472.02Q1566 1464.02 1568 1438.02Q1611 1038.02 1462 873.02Q1398 802.02 1291.50 762.52Q1185 723.02 1024 710.02L1024 961.02Q1024 1003.02 985 1020.02Q972 1025.02 960 1025.02Q933 1025.02 915 1006.02L403 494.02Q384 475.02 384 449.02Q384 423.02 403 404.02L915-107.98Q944-138.98 985-121.98Q1024-104.98 1024-62.98L1024 199.02Q1435 227.02 1623 420.02Q1792 593.02 1792 929.02Z" />
                        </svg>
                        回复</button>
                      <?php if (hasPermission('admin')): ?>
                        <button class="pinlun-comment-delete" data-require-login="1" data-login-action="comment" data-id="{id}" data-type="comment">删除</button>
                      <?php endif; ?>
                    </div>
                  </div>
                  <div class="pinlun-post01">{content}</div>
                  {reply_comment row=8 reply.date=1}
                  <div class="pinlun-comment-reply-item">
                    <div class="pinlun-reply-number"><a href="/Member_id{reply.user_id}.html" target="_blank"><img src="{reply.avatar}"></a></div>
                    <div class="pinlun-reply-content">
                      <div class="pinlun-reply-header">
                        <div class="pinlun-comment-author-date">{reply.author}&nbsp;{reply.date}&nbsp;回复 @{reply.reply_to}</div>
                        <div class="pinlun-topic-actions">
                          <button class="pinlun-reply-like" data-require-login="1" data-login-action="like" data-type="comment" data-id="{reply.id}">
                            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="43 85 426 384" fill="currentColor">
                              <path d="M85 234Q94 234 100.50 240Q107 246 107 255L107 447Q107 456 100.50 462.50Q94 469 85 469L43 469L43 234ZM162 221L299 85L308 95Q320 107 327 121Q330 128 329 135L309 234L427 234Q444 234 456.50 247Q469 260 469 277L469 300Q469 309 466 317L409 448Q396 469 372 469L199 469Q178 469 163.50 454.50Q149 440 149 419L149 252Q149 234 162 221ZM268 226L280 164L192 251L192 419Q192 426 199 426L372 426L427 300L427 277L309 277Q290 277 276.50 261.50Q263 246 268 226Z" />
                            </svg>
                            <span class="pinlun-action-count">{reply.like_count}</span> </button>
                          <button class="pinlun-reply-dislike" data-require-login="1" data-login-action="dislike" data-type="comment" data-id="{reply.id}">
                            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="43 43 426 384" fill="currentColor">
                              <path d="M427 278Q418 278 411.50 272Q405 266 405 257L405 65Q405 56 411.50 49.50Q418 43 427 43L469 43L469 278ZM350 291L213 427L204 417Q192 405 185 391Q182 384 183 377L203 278L85 278Q68 278 55.50 265Q43 252 43 235L43 212Q43 202 46 195L103 64Q116 43 140 43L313 43Q334 43 348.50 57.50Q363 72 363 93L363 260Q363 278 350 291ZM244 286L232 348L320 261L320 93Q320 86 313 86L140 86L85 212L85 235L203 235Q222 235 235.50 250.50Q249 266 244 286Z" />
                            </svg>
                            <span class="pinlun-action-count">{reply.dislike_count}</span> </button>
                          <?php if (hasPermission('admin')): ?>
                            <button class="pinlun-reply-delete" data-require-login="1" data-login-action="comment" data-id="{reply.id}" data-type="comment">删除</button>
                          <?php endif; ?>
                        </div>
                      </div>
                      <div class="pinlun-reply-body">{reply.content}</div>
                    </div>
                  </div>
                  {/reply_comment}
                </div>
              </div>
              {/qkcms:comments}
            </div>
            <?php if ($like_animation_enabled == 1): ?>
              <div id="pinlunjs-floating-likes" class="pinlunjs-floating-likes" aria-hidden="true"></div>
            <?php endif; ?>
          </div>


          
          <div class="pinlun-pagination pinlun-comment-pagination"> {qkcms:page type=2 page=1}{first_page}{prev_page}{page_numbers}{jump_to_page}{next_page}{last_page}{/qkcms:page} </div>
        </section>
      </div>
    </div>
  </div>
  <div id="pinlun-comment-modal" class="pinlun-modal">
    <div class="pinlun-modal-backdrop"></div>
    <div class="pinlun-modal-content">
      <div class="pinlun-modal-header">
        <h3 class="pinlun-modal-title">评论</h3>
        <button type="button" id="pinlun-close-comment-modal" class="pinlun-modal-close">×</button>
      </div>
      <div class="pinlun-modal-body">
        <div class="pinlun-form-group">
          <div class="pinlun-replied-comment-container pinlun-hidden">
            <div class="pinlun-replied-comment-label">回复的内容：</div>
            <div class="pinlun-replied-comment-text"></div>
          </div>
          <div id="pinlun-modal-quill-editor"></div>
        </div>
      </div>
      <div class="pinlun-modal-actions">
        <button type="button" id="pinlun-submit-modal-comment" class="pinlun-submit-button" data-require-login="1" data-login-action="comment">发表评论</button>
      </div>
    </div>
  </div>
</main>

<?php
$emoji_assets = array();
$emoji_dir = dirname(__DIR__) . '/assets/emoji';
if (is_dir($emoji_dir) && is_readable($emoji_dir)) {
  $files = array();
  foreach (array('gif', 'png', 'webp', 'jpg', 'jpeg', 'svg') as $ext) {
    $matched = glob($emoji_dir . '/*.' . $ext);
    if (!empty($matched)) $files = array_merge($files, $matched);
  }
  $files = array_values(array_unique($files));
  usort($files, function ($a, $b) {
    return strnatcasecmp(basename($a), basename($b));
  });
  $i = 1;
  foreach ($files as $file) {
    $base = basename($file);
    $emoji_assets[] = array(
      'id' => $i,
      'name' => pathinfo($base, PATHINFO_FILENAME),
      'url' => '/assets/emoji/' . $base
    );
    $i++;
  }
}
$emoji_assets_json = json_encode($emoji_assets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>


<script>
  // 全局变量
  window.pinlunjs = window.pinlunjs || {};
  window.pinlunjs.isUserBanned = <?php echo $is_user_banned ? 'true' : 'false'; ?>;
  window.pinlunjs.isLoggedIn = <?php echo $is_logged_in ? 'true' : 'false'; ?>;
  window.pinlunjs.isAdmin = <?php echo hasPermission('admin') ? 'true' : 'false'; ?>;
  window.pinlunjs.postId = <?php echo $post_id; ?>;
  window.pinlunjs.contentType = '<?php echo $content_type; ?>';
  window.pinlunjs.currentUserId = <?php echo isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0; ?>;
  window.pinlunjs.ajaxUrl = '<?php echo "/templates/" . basename(__FILE__); ?>';
  window.pinlunjs.csrfToken = '<?php echo generate_csrf_token(); ?>';
  window.pinlunjs.emojiAssets = <?php echo $emoji_assets_json ?: '[]'; ?>;

  (function() {
    const commentsPerPage = 12;
    const commentItemHeight = 130;

    function registerMp4Blot() {
      if (window.pinlunjs && window.pinlunjs.mp4BlotRegistered) return;
      if (typeof Quill === 'undefined') return;
      try {
        const BlockEmbed = Quill.import('blots/block/embed');
        class Mp4VideoBlot extends BlockEmbed {
          static create(value) {
            const node = super.create();
            node.setAttribute('src', value);
            node.setAttribute('controls', '');
            node.setAttribute('playsinline', '');
            node.setAttribute('preload', 'metadata');
            node.setAttribute('muted', '');
            node.muted = true;
            node.autoplay = false;
            return node;
          }
          static value(node) {
            return node.getAttribute('src') || '';
          }
        }
        Mp4VideoBlot.blotName = 'mp4';
        Mp4VideoBlot.tagName = 'video';
        Quill.register(Mp4VideoBlot, true);
        window.pinlunjs = window.pinlunjs || {};
        window.pinlunjs.mp4BlotRegistered = true;
      } catch (e) {}
    }

    const dom = {
      latest: document.getElementById('pinlun-latest'),
      modal: document.getElementById('pinlun-comment-modal'),
      submitBtn: document.getElementById('pinlun-comment-submit'),
      modalSubmitBtn: document.getElementById('pinlun-submit-modal-comment'),
      closeModalBtn: document.getElementById('pinlun-close-comment-modal'),
      pagination: document.querySelector('.pinlun-pagination')
    };
    const emojiIconSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M8.5 14.5c1.1 1.4 2.24 2 3.5 2s2.4-.6 3.5-2"></path><circle cx="9" cy="10" r="1"></circle><circle cx="15" cy="10" r="1"></circle></svg>';
    const emojiAssetList = (window.pinlunjs && Array.isArray(window.pinlunjs.emojiAssets) && window.pinlunjs.emojiAssets.length) ? window.pinlunjs.emojiAssets : Array.from({ length: 36 }, function(_, index) {
      return { id: index + 1, name: '表情' + (index + 1), url: '/assets/emoji/' + (index + 1) + '.gif' };
    });

    // 初始化回复窗口的Quill编辑器
    let modalQuill = null;
    let emojiPickerMain = null;

    function getMainToolbar() {
      return document.querySelector('#pinlun-comment-quill-shell .ql-toolbar');
    }

    function insertMainEmojiGif(quill, item) {
      if (!quill || !item || !item.url) return;
      const range = quill.getSelection(true) || { index: quill.getLength(), length: 0 };
      quill.insertEmbed(range.index, 'image', item.url, 'user');
      quill.setSelection(range.index + 1, 0, 'silent');
      quill.focus();
    }

    function createMainEmojiPicker(quill) {
      const picker = document.createElement('div');
      picker.className = 'pinlun-emoji-picker';
      picker.innerHTML = '<div class="pinlun-emoji-grid">' + emojiAssetList.map(function(item) {
        return '<button type="button" class="pinlun-emoji-item" data-id="' + item.id + '" data-url="' + item.url + '" title="' + item.name + '"><img src="' + item.url + '" alt="' + item.name + '"></button>';
      }).join('') + '</div>';
      picker.querySelectorAll('.pinlun-emoji-item').forEach(function(btn) {
        btn.addEventListener('click', function() {
          insertMainEmojiGif(quill, {
            id: btn.dataset.id,
            name: btn.getAttribute('title') || '',
            url: btn.dataset.url || ''
          });
          picker.classList.remove('show');
        });
      });
      return picker;
    }

    function ensureMainEmojiTrigger(quill) {
      const toolbar = getMainToolbar();
      if (!toolbar) return null;
      let trigger = toolbar.querySelector('.pinlun-emoji-trigger');
      if (trigger) return trigger;
      const targetGroup = toolbar.querySelector('.ql-formats:last-child');
      if (!targetGroup) return null;
      trigger = document.createElement('button');
      trigger.type = 'button';
      trigger.className = 'pinlun-emoji-trigger';
      trigger.setAttribute('aria-label', '表情');
      trigger.setAttribute('title', '表情');
      trigger.innerHTML = emojiIconSvg;
      trigger.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();
        toggleMainEmojiPicker(quill);
      });
      targetGroup.appendChild(trigger);
      return trigger;
    }

    function toggleMainEmojiPicker(quill) {
      const toolbar = getMainToolbar();
      if (!toolbar) return;
      const trigger = ensureMainEmojiTrigger(quill);
      if (!trigger) return;
      if (!emojiPickerMain) {
        toolbar.style.position = 'relative';
        emojiPickerMain = createMainEmojiPicker(quill);
        toolbar.appendChild(emojiPickerMain);
      }
      const triggerRect = trigger.getBoundingClientRect();
      const toolbarRect = toolbar.getBoundingClientRect();
      const triggerLeftX = triggerRect.left - toolbarRect.left;
      const offsetLeft = 30;
      emojiPickerMain.style.left = (triggerLeftX - offsetLeft) + 'px';
      const isShown = emojiPickerMain.classList.toggle('show');
      if (isShown) {
        setTimeout(() => {
          const pickerRect = emojiPickerMain.getBoundingClientRect();
          const windowWidth = window.innerWidth;
          if (pickerRect.right > windowWidth - 10) {
            const pickerWidth = pickerRect.width || emojiPickerMain.offsetWidth || 308;
            emojiPickerMain.style.left = (windowWidth - pickerWidth - 10 - toolbarRect.left) + 'px';
          }
        }, 10);
      }
    }

    function initModalQuill() {
      if (modalQuill) return modalQuill;
      if (typeof Quill === 'undefined') return null;
      registerMp4Blot();
      const enableImageUpload = !!window.pinlunjs.imageUploadEnabled;
      const enableVideoInsert = !!window.pinlunjs.videoInsertEnabled;
      modalQuill = new Quill('#pinlun-modal-quill-editor', {
        theme: 'snow',
        placeholder: '写下你的评论...',
        modules: {
          toolbar: {
            container: [
              ['bold', 'italic', 'underline'],
              [{
                'list': 'ordered'
              }, {
                'list': 'bullet'
              }],
              ['link'].concat(enableVideoInsert ? ['video'] : []).concat(enableImageUpload ? ['image'] : [])
            ],
            handlers: {
              video: () => {
                if (!window.pinlunjs.videoInsertEnabled) {
                  showToast('不允许插入视频', true);
                  return;
                }
                const url = (prompt('请输入MP4视频URL:') || '').trim();
                if (!url) return;
                const range = modalQuill.getSelection(true);
                modalQuill.insertEmbed(range.index, 'mp4', url, 'user');
                modalQuill.setSelection(range.index + 1);
              },
              image: () => {
                if (!window.pinlunjs.imageUploadEnabled) {
                  showToast('不允许上传图片', true);
                  return;
                }
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.png,.gif,.jpg,.jpeg';
                input.multiple = true;
                input.onchange = () => {
                  const maxCount = parseInt(window.pinlunjs.imageUploadMaxCount || 5, 10);
                  const files = Array.from(input.files);
                  const currentImageCount = (modalQuill.root.innerHTML.match(/<img/g) || []).length;
                  if (currentImageCount + files.length > maxCount) {
                    showToast('评论内容最多只能包含' + maxCount + '张图片，当前已有' + currentImageCount + '张', true);
                    return;
                  }
                  files.forEach(file => {
                    const maxKB = parseInt(window.pinlunjs.imageSizeLimitKB || 600, 10);
                    if (file.size > maxKB * 1024) {
                      showToast('图片大小不能超过' + maxKB + 'KB', true);
                      return;
                    }
                    const fd = new FormData();
                    fd.append('image', file);
                    fd.append('action', 'upload_comment_image');
                    fd.append('csrf_token', window.pinlunjs.csrfToken);
                    fetch(window.pinlunjs.ajaxUrl, {
                        method: 'POST',
                        body: fd,
                        credentials: 'include',
                        headers: {
                          'X-Requested-With': 'XMLHttpRequest',
                          'X-CSRF-Token': window.pinlunjs.csrfToken
                        }
                      })
                      .then(r => r.json())
                      .then(data => {
                        if (data.success) {
                          const range = modalQuill.getSelection(true);
                          modalQuill.insertEmbed(range.index, 'image', data.url);
                          modalQuill.setSelection(range.index + 1);
                          requestAnimationFrame(() => {
                            try {
                              const root = modalQuill.root;
                              const imgs = root ? root.querySelectorAll('img') : null;
                              const img = imgs && imgs.length ? imgs[imgs.length - 1] : null;
                              if (img && root) {
                                const top = img.offsetTop;
                                root.scrollTop = Math.max(0, top - 8);
                              }
                            } catch (e) {}
                          });
                          setTimeout(() => bindImagePreview(), 100);
                        } else {
                          showToast(data.message || '图片上传失败', true);
                        }
                      })
                      .catch(err => {
                        showToast('上传失败', true);
                      });
                  });
                };
                input.click();
              }
            }
          }
        }
      });
      try {
        const c = document.querySelector('#pinlun-modal-quill-editor .ql-container');
        const e = document.querySelector('#pinlun-modal-quill-editor .ql-editor');
        if (c) c.style.height = '100px';
        if (e) {
          e.style.height = '100px';
          e.style.minHeight = '100px';
          e.style.maxHeight = '100px';
          e.style.overflowY = 'auto';
        }
      } catch (e) {}

      return modalQuill;
    }

    let cooldownTimer = null;

    function getLimitMs() {
      const unit = (window.pinlunjs.ipLimitUnit || 'minute');
      const duration = parseInt(window.pinlunjs.ipSubmitLimit || 0, 10);
      if (!duration || duration <= 0) return 0;

      let unitSeconds = 0;
      if (unit === 'second') unitSeconds = 1;
      else if (unit === 'minute') unitSeconds = 60;
      else if (unit === 'hour') unitSeconds = 3600;

      return duration * unitSeconds * 1000;
    }

    function getConfigSig() {
      return String(window.pinlunjs.ipSubmitLimit || 0) + ':' + String(window.pinlunjs.ipLimitUnit || '');
    }

    function getCooldownKeyPrefix() {
      return 'pinlun:last_submit_ts:' + (window.pinlunjs.currentUserId || '0') + ':' + (window.pinlunjs.postId || '0') + ':';
    }

    function getCooldownKey() {
      return getCooldownKeyPrefix() + getConfigSig();
    }

    function clearCooldownForPost() {
      const prefix = getCooldownKeyPrefix();
      try {
        for (let i = localStorage.length - 1; i >= 0; i--) {
          const k = localStorage.key(i);
          if (k && k.indexOf(prefix) === 0) localStorage.removeItem(k);
        }
      } catch (e) {}
    }

    function resetCooldownUI() {
      if (cooldownTimer) {
        clearInterval(cooldownTimer);
        cooldownTimer = null;
      }
      if (!dom.submitBtn) return;
      const original = dom.submitBtn.dataset.originalText || dom.submitBtn.textContent;
      dom.submitBtn.textContent = original;
    }

    function applyRateLimitConfig(limit, unit) {
      const oldSig = getConfigSig();
      if (typeof limit !== 'undefined' && limit !== null) window.pinlunjs.ipSubmitLimit = limit;
      if (typeof unit !== 'undefined' && unit) window.pinlunjs.ipLimitUnit = unit;
      const newSig = getConfigSig();
      if (oldSig !== newSig) {
        clearCooldownForPost();
        resetCooldownUI();
        return true;
      }
      return false;
    }

    function syncImageUploadUI() {
      const tb = document.querySelector('.ql-toolbar');
      const btn = tb ? tb.querySelector('.ql-image') : null;
      if (btn) btn.style.display = window.pinlunjs.imageUploadEnabled ? '' : 'none';
    }

    function syncVideoInsertUI() {
      const tb = document.querySelector('.ql-toolbar');
      const btn = tb ? tb.querySelector('.ql-video') : null;
      if (btn) btn.style.display = window.pinlunjs.videoInsertEnabled ? '' : 'none';
    }

    document.addEventListener('click', function(e) {
      if (!emojiPickerMain || !emojiPickerMain.classList.contains('show')) return;
      const toolbar = getMainToolbar();
      const trigger = toolbar ? toolbar.querySelector('.pinlun-emoji-trigger') : null;
      if (!emojiPickerMain.contains(e.target) && !(trigger && trigger.contains(e.target))) {
        emojiPickerMain.classList.remove('show');
      }
    });

    async function refreshRateLimitConfig() {
      try {
        const res = await fetch(window.pinlunjs.ajaxUrl, {
          method: 'POST',
          credentials: 'include',
          headers: {
            'X-Requested-With': 'XMLHttpRequest'
          },
          body: new URLSearchParams({
            action: 'get_comment_config'
          })
        }).then(r => r.json());
        if (res && res.success) {
          applyRateLimitConfig(res.ip_submit_limit, res.ip_limit_unit);
          if (typeof res.image_upload_enabled !== 'undefined') window.pinlunjs.imageUploadEnabled = !!res.image_upload_enabled;
          if (typeof res.image_size_limit !== 'undefined') window.pinlunjs.imageSizeLimitKB = parseInt(res.image_size_limit, 10);
          if (typeof res.image_upload_max_count !== 'undefined') window.pinlunjs.imageUploadMaxCount = parseInt(res.image_upload_max_count, 10) || 5;
          if (typeof res.video_insert_enabled !== 'undefined') window.pinlunjs.videoInsertEnabled = !!res.video_insert_enabled;
          syncImageUploadUI();
          syncVideoInsertUI();
        }
      } catch (e) {}
    }

    function inCooldown() {
      const ms = getLimitMs();
      if (!ms) return false;
      const until = parseInt(localStorage.getItem(getCooldownKey()) || '0', 10);
      return until && Date.now() < until;
    }

    function startCooldown(untilTs) {
      const ms = getLimitMs();
      if (!ms || !dom.submitBtn) return;
      if (!dom.submitBtn.dataset.originalText) dom.submitBtn.dataset.originalText = dom.submitBtn.textContent;
      const original = dom.submitBtn.dataset.originalText;
      const key = getCooldownKey();
      const until = untilTs || parseInt(localStorage.getItem(key) || '0', 10) || 0;
      if (!until) return;
      if (cooldownTimer) clearInterval(cooldownTimer);
      cooldownTimer = setInterval(() => {
        const remain = Math.max(until - Date.now(), 0);
        if (remain <= 0) {
          clearInterval(cooldownTimer);
          cooldownTimer = null;
          dom.submitBtn.textContent = original;
          try {
            localStorage.removeItem(key);
          } catch (e) {}
        } else {
          const ticks = Math.ceil(remain / 1000);
          dom.submitBtn.textContent = '提交评论 (' + ticks + 's)';
        }
      }, 100);
    }
    async function initCooldownUI() {
      if (!dom.submitBtn) return;
      await refreshRateLimitConfig();
      const ms = getLimitMs();
      if (!ms) return;
      if (inCooldown()) startCooldown();
    }
    initCooldownUI();
    // 次数限制调试已移除
    const icons = {
      like: function(size) {
        return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size + '" viewBox="43 85 426 384" fill="currentColor"><path d="M85 234Q94 234 100.50 240Q107 246 107 255L107 447Q107 456 100.50 462.50Q94 469 85 469L43 469L43 234ZM162 221L299 85L308 95Q320 107 327 121Q330 128 329 135L309 234L427 234Q444 234 456.50 247Q469 260 469 277L469 300Q469 309 466 317L409 448Q396 469 372 469L199 469Q178 469 163.50 454.50Q149 440 149 419L149 252Q149 234 162 221ZM268 226L280 164L192 251L192 419Q192 426 199 426L372 426L427 300L427 277L309 277Q290 277 276.50 261.50Q263 246 268 226Z"/></svg>';
      },
      dislike: function(size) {
        return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size + '" viewBox="43 43 426 384" fill="currentColor"><path d="M427 278Q418 278 411.50 272Q405 266 405 257L405 65Q405 56 411.50 49.50Q418 43 427 43L469 43L469 278ZM350 291L213 427L204 417Q192 405 185 391Q182 384 183 377L203 278L85 278Q68 278 55.50 265Q43 252 43 235L43 212Q43 202 46 195L103 64Q116 43 140 43L313 43Q334 43 348.50 57.50Q363 72 363 93L363 260Q363 278 350 291ZM244 286L232 348L320 261L320 93Q320 86 313 86L140 86L85 212L85 235L203 235Q222 235 235.50 250.50Q249 266 244 286Z"/></svg>';
      },
      reply: '<svg class="icon-reply-svg" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -128 1792 1601.020751953125" fill="currentColor"><path d="M243 494.02L640 891.02L640 961.02Q640 1003.02 601 1020.02Q588 1025.02 576 1025.02Q549 1025.02 531 1006.02L19 494.02Q0 475.02 0 449.02Q0 423.02 19 404.02L531-107.98Q560-138.98 601-121.98Q640-104.98 640-62.98L640 6.02L243 404.02Q224 423.02 224 449.02Q224 475.02 243 494.02ZM1792 929.02Q1792 987.02 1775 1062.52Q1758 1138.02 1736.50 1200.52Q1715 1263.02 1688.50 1325.52Q1662 1388.02 1648 1416.02Q1634 1444.02 1628 1456.02Q1620 1473.02 1600 1473.02Q1594 1473.02 1591 1472.02Q1566 1464.02 1568 1438.02Q1611 1038.02 1462 873.02Q1398 802.02 1291.50 762.52Q1185 723.02 1024 710.02L1024 961.02Q1024 1003.02 985 1020.02Q972 1025.02 960 1025.02Q933 1025.02 915 1006.02L403 494.02Q384 475.02 384 449.02Q384 423.02 403 404.02L915-107.98Q944-138.98 985-121.98Q1024-104.98 1024-62.98L1024 199.02Q1435 227.02 1623 420.02Q1792 593.02 1792 929.02Z"/></svg>'
    };

    let currentReplyInfo = {
      parentId: 0,
      replyTo: ''
    };
    let toastTimer = null;
    let currentOrder = 1;

    function normalizeOrder(val) {
      const n = parseInt(val, 10);
      return n === 2 || n === 3 ? n : 1;
    }

    function syncSortButtons(order) {
      document.querySelectorAll('.pinlun-sort-btn').forEach(btn => {
        btn.classList.toggle('active', normalizeOrder(btn.dataset.order) === order);
      });
    }

    function applyOrderToUrl(rawUrl, order) {
      const u = new URL(rawUrl, location.origin);
      u.searchParams.set('order', String(order));
      return u.toString();
    }

    function shuffle(arr) {
      for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
      return arr;
    }

    function renderRandomAvatars() {
      const row = document.getElementById('pinlun-comment-avatars-row');
      if (!row || !dom.latest) return;
      const imgs = Array.from(dom.latest.querySelectorAll('.pinlun-avatar-container a[href*=\"/Member_id\" ] img, .pinlun-reply-number a[href*=\"/Member_id\" ] img, .pinlun-avatar-container img, .pinlun-reply-number img'));
      const seenIds = new Set();
      const candidates = [];
      for (const img of imgs) {
        const a = img.closest('a');
        let uid = null;
        if (a && a.getAttribute('href')) {
          const m = a.getAttribute('href').match(/Member_id(\d+)/i);
          if (m) uid = m[1];
        }
        const src = img.getAttribute('src') || '';
        const key = uid || ('src:' + src);
        if (!src || seenIds.has(key)) continue;
        seenIds.add(key);
        candidates.push({
          id: uid || '',
          src
        });
      }
      shuffle(candidates);
      const pick = candidates.slice(0, 10);
      row.innerHTML = pick.map(it => it.id ? ('<a href=\"/Member_id' + it.id + '.html\" target=\"_blank\"><img src=\"' + it.src + '\" alt=\"avatar\"></a>') : ('<a><img src=\"' + it.src + '\" alt=\"avatar\"></a>')).join('');
      row.style.display = pick.length ? 'flex' : 'none';
    }

    function buildOrderUrl(order) {
      const u = new URL(location.href);
      u.searchParams.set('order', String(order));
      u.searchParams.set('page', '1');
      return u.toString();
    }

    currentOrder = normalizeOrder(new URL(location.href).searchParams.get('order'));
    syncSortButtons(currentOrder);

    function adjustCommentListHeight() {
      if (!dom.latest) return;
      const maxH = commentsPerPage * commentItemHeight;
      document.documentElement.style.setProperty('--comment-list-max-height', maxH + 'px');
    }

    function syncTopicActionsVisibility(scope = document) {
      const isMobile = window.innerWidth <= 768;
      scope.querySelectorAll('.pinlun-topic-actions').forEach(el => {
        el.style.display = isMobile ? 'none' : '';
      });
    }

    adjustCommentListHeight();
    syncTopicActionsVisibility();

    function showToast(message, isError) {
      if (typeof window.showToast === 'function') {
        return window.showToast(message, isError ? 'error' : 'success');
      }
    }

    function checkPermission(action) {
      if (window.pinlunjs.isUserBanned) {
        showToast('您的账户已被封禁', true);
        return false;
      }
      return true;
    }

    async function apiRequest(action, data, callback) {
      try {
        const res = await fetch(window.pinlunjs.ajaxUrl, {
          method: 'POST',
          credentials: 'include',
          headers: {
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-Token': window.pinlunjs.csrfToken
          },
          body: new URLSearchParams({
            action,
            csrf_token: window.pinlunjs.csrfToken,
            ...data
          })
        }).then(r => r.json());

        if (res.success) {
          callback ? callback(res) : showToast(res.message);
        } else {
          showToast(res.message || '操作失败', true);
          if (res.error_code === 'not_logged_in') {
            setTimeout(() => location.href = '/admin/login.php?redirect=' + encodeURIComponent(location.href), 1500);
          }
        }
        return res;
      } catch (err) {
        showToast('网络错误，请稍后重试', true);
        return null;
      }
    }

    function closeModal() {
      dom.modal?.classList.remove('pinlun-modal-visible');
      // 清空回复窗口的Quill编辑器
      if (modalQuill) {
        modalQuill.root.innerHTML = '';
      }
    }

    function showReplyModal(commentId, replyTo) {
      if (!checkPermission('回复评论')) return;

      // 检查是否已购买商品（仅在商品详情页，管理员除外）
      if (!window.pinlunjs.isAdmin && (document.querySelector('.product-detail') || document.getElementById('tab-comments'))) {
        var purchaseNotice = document.querySelector('.purchase-required-notice');
        var commentForm = document.querySelector('.pinlun-comment-form-container');
        // 如果评论框隐藏且购买提示显示，说明未购买
        if (commentForm && commentForm.style.display === 'none' &&
          purchaseNotice && purchaseNotice.style.display !== 'none') {
          showToast('购买商品后才能回复评论', true);
          return;
        }
      }

      currentReplyInfo = {
        parentId: commentId,
        replyTo
      };

      // 初始化回复窗口的Quill编辑器
      const mQuill = initModalQuill();
      if (!mQuill) {
        showToast('编辑器加载中，请稍后重试', true);
        return;
      }
      mQuill.root.innerHTML = '';
      mQuill.root.setAttribute('data-placeholder', '回复 @' + replyTo + '...');

      dom.modal.querySelector('.pinlun-modal-title').textContent = '回复 @' + replyTo;

      const item = document.querySelector('.pinlun-topic-item[data-id="' + commentId + '"]');
      let content = item?.querySelector('.pinlun-post01')?.textContent.trim() || '[评论内容]';
      if (content.length > 160) content = content.substring(0, 160) + '...';

      const repliedContainer = dom.modal.querySelector('.pinlun-replied-comment-container');
      repliedContainer.querySelector('.pinlun-replied-comment-text').textContent = content;
      repliedContainer.classList.remove('pinlun-hidden');

      dom.modal.classList.add('pinlun-modal-visible');
      mQuill.focus();
      setTimeout(() => bindImagePreview(), 100);
    }

    async function submitModalComment() {
      if (!checkPermission(currentReplyInfo.parentId > 0 ? '回复评论' : '发表评论')) return;
      const content = modalQuill?.root.innerHTML;
      if (!content || content === '<p><br></p>') return showToast('请填写评论内容', true);

      // 频率限制：开启限制时，倒计时显示在提交按钮上；计时中点击直接提示过于频繁
      await refreshRateLimitConfig();
      const minMs = getLimitMs();
      if (minMs) {
        const key = getCooldownKey();
        const until = parseInt(localStorage.getItem(key) || '0', 10) || 0;
        if (until && Date.now() < until) {
          showToast('你的评论过于频繁，请稍后再试', true);
          return;
        }
      }

      if (typeof window.pinlunjs.validateCommentContent === 'function') {
        let plain = content.replace(/<[^>]*>/g, '').trim();
        if (!plain && (content.includes('<img') || content.includes('<video'))) {
          const imgCount = (content.match(/<img/g) || []).length;
          const videoCount = (content.match(/<video/g) || []).length;
          if (videoCount && !imgCount) plain = '[视频评论：' + videoCount + '个]';
          else if (imgCount && !videoCount) plain = '[图片评论：' + imgCount + '张]';
          else plain = '[媒体评论]';
        }
        if (!window.pinlunjs.validateCommentContent(plain)) return showToast(window.pinlunjs.validationError || '内容验证失败', true);
      }

      if (submitModalComment.submitting) return;
      submitModalComment.submitting = true;
      await apiRequest('submit_comment', {
        post_id: window.pinlunjs.postId,
        content_type: window.pinlunjs.contentType,
        content,
        parent_id: currentReplyInfo.parentId,
        reply_to: currentReplyInfo.replyTo
      }, res => {
        applyRateLimitConfig(res.ip_submit_limit, res.ip_limit_unit);
        const newCooldownMs = getLimitMs();
        if (newCooldownMs > 0) {
          const newUntil = Date.now() + newCooldownMs;
          try {
            localStorage.setItem(getCooldownKey(), String(newUntil));
          } catch (e) {}
          startCooldown(newUntil);
        }

        closeModal();
        currentReplyInfo = {
          parentId: 0,
          replyTo: ''
        };
        if (!res.need_approval) addCommentToList(res);
        showToast(res.message);
      });
      submitModalComment.submitting = false;
    }

    // 主评论提交函数
    async function submitMainComment(content) {
      if (!checkPermission('发表评论')) return;
      if (!content || content === '<p><br></p>') return showToast('请填写评论内容', true);

      // 频率限制
      await refreshRateLimitConfig();
      const minMs = getLimitMs();
      if (minMs) {
        const key = getCooldownKey();
        const until = parseInt(localStorage.getItem(key) || '0', 10) || 0;
        if (until && Date.now() < until) {
          showToast('你的评论过于频繁，请稍后再试', true);
          return;
        }
      }

      if (typeof window.pinlunjs.validateCommentContent === 'function') {
        let plain = content.replace(/<[^>]*>/g, '').trim();
        if (!plain && (content.includes('<img') || content.includes('<video'))) {
          const imgCount = (content.match(/<img/g) || []).length;
          const videoCount = (content.match(/<video/g) || []).length;
          if (videoCount && !imgCount) plain = '[视频评论：' + videoCount + '个]';
          else if (imgCount && !videoCount) plain = '[图片评论：' + imgCount + '张]';
          else plain = '[媒体评论]';
        }
        if (!window.pinlunjs.validateCommentContent(plain)) return showToast(window.pinlunjs.validationError || '内容验证失败', true);
      }

      if (submitMainComment.submitting) return;
      submitMainComment.submitting = true;
      await apiRequest('submit_comment', {
        post_id: window.pinlunjs.postId,
        content_type: window.pinlunjs.contentType,
        content,
        parent_id: 0,
        reply_to: ''
      }, (res) => {
        if (minMs) {
          const newUntil = Date.now() + minMs;
          localStorage.setItem(getCooldownKey(), String(newUntil));
          startCooldown(newUntil);
        }

        // 清空主评论框
        if (window.pinlunjs.quill) window.pinlunjs.quill.root.innerHTML = '';

        if (!res.need_approval) addCommentToList(res);
        showToast(res.message);
      });
      submitMainComment.submitting = false;
    }

    function addCommentToList(data) {
      const isAdmin = <?php echo hasPermission('admin') ? 'true' : 'false'; ?>;
      const avatar = data.avatar || '<?php echo DEFAULT_AVATAR_PATH; ?>';
      const author = data.author || '用户';
      const comment_id = data.comment_id || '';
      const date = data.date || '刚刚';
      const content = data.content || '';
      const user_level = data.user_level || '游客';
      const parent_id = data.parent_id || 0;
      const reply_to = data.reply_to || '用户';

      if (parent_id > 0) {
        const parent = document.querySelector('.pinlun-topic-item[data-id="' + parent_id + '"]');
        if (!parent) return;
        var html = '<div class="pinlun-comment-reply-item pinlun-fade-in">' +
          '<div class="pinlun-reply-number"><a href="/Member_id' + (window.pinlunjs.currentUserId || '') + '.html" target="_blank"><img src="' + avatar + '"></a></div>' +
          '<div class="pinlun-reply-content">' +
          '<div class="pinlun-reply-header">' +
          '<div class="pinlun-comment-author-date">' + author + '&nbsp;' + date + '&nbsp;回复 @' + reply_to + '</div>' +
          '<div class="pinlun-topic-actions">' +
          '<button class="pinlun-reply-like" data-id="' + comment_id + '">' + icons.like(14) + '<span class="pinlun-action-count">0</span></button>' +
          '<button class="pinlun-reply-dislike" data-id="' + comment_id + '">' + icons.dislike(14) + '<span class="pinlun-action-count">0</span></button>' +
          (isAdmin ? '<button class="pinlun-reply-delete" data-id="' + comment_id + '">删除</button>' : '') +
          '</div>' +
          '</div>' +
          '<div class="pinlun-reply-body">' + content + '</div>' +
          '</div>' +
          '</div>';
        const host = parent.querySelector('.pinlun-topic-content1');
        host.insertAdjacentHTML('beforeend', html);
        bindImagePreview();
        bindVideoFullscreen(host);
        applyLevelStyles(host);
        return;
      }

      var html = '<div class="pinlun-topic-item pinlun-fade-in" data-id="' + comment_id + '">' +
        '<div class="pinlun-avatar-container">' +
        '<a href="/Member_id' + (window.pinlunjs.currentUserId || '') + '.html" target="_blank"><img src="' + avatar + '" class="pinlun-topic-avatar"></a>' +
        '<div class="pinlun-user-level-tag"><span class="dot"></span>' + user_level + '</div>' +
        '</div>' +
        '<div class="pinlun-topic-content1">' +
        '<div class="pinlun-topic-header-row">' +
        '<div class="pinlun-comment-author-date">' + author + '&nbsp;' + date + '</div>' +
        '<div class="pinlun-topic-actions">' +
        '<button class="pinlun-comment-like" data-id="' + comment_id + '"><span class="pinlun-icon-like1">' + icons.like(16) + '</span><span class="pinlun-action-count">0</span></button>' +
        '<button class="pinlun-comment-dislike" data-id="' + comment_id + '"><span class="pinlun-icon-dislike">' + icons.dislike(16) + '</span><span class="pinlun-action-count">0</span></button>' +
        '<button class="pinlun-comment-reply" data-id="' + comment_id + '" data-username="' + author + '">' + icons.reply + '回复</button>' +
        (isAdmin ? '<button class="pinlun-comment-delete" data-id="' + comment_id + '">删除</button>' : '') +
        '</div>' +
        '</div>' +
        '<div class="pinlun-post01">' + content + '</div>' +
        '</div>' +
        '</div>';
      dom.latest?.insertAdjacentHTML('afterbegin', html);
      adjustCommentListHeight();
      syncTopicActionsVisibility(dom.latest || document);
      bindImagePreview();
      bindVideoFullscreen(dom.latest || document);
      applyLevelStyles(dom.latest);
      renderRandomAvatars();
    }

    function bindAjaxPagination() {
      simplifyPaginationForMobile();
      document.querySelectorAll(".pinlun-pagination a").forEach(link => {
        if (link.dataset.ajaxBound) return;
        try {
          link.href = applyOrderToUrl(link.href, currentOrder);
        } catch (e) {}
        link.dataset.ajaxBound = 'true';
        link.onclick = e => {
          e.preventDefault();
          loadPageByAjax(link.href);
        };
      });
      const select = document.querySelector(".pinlun-pagination select");
      if (select && !select.dataset.ajaxBound) {
        Array.from(select.options || []).forEach(opt => {
          if (!opt.value) return;
          try {
            opt.value = applyOrderToUrl(opt.value, currentOrder);
          } catch (e) {}
        });
        select.dataset.ajaxBound = 'true';
        select.onchange = () => loadPageByAjax(select.value);
      }
    }

    function simplifyPaginationForMobile() {
      if (!dom.pagination) return;
      if (!(window.matchMedia && window.matchMedia('(max-width: 768px)').matches)) return;

      const source = document.createElement('div');
      source.innerHTML = dom.pagination.innerHTML;

      const select = source.querySelector('select');
      const nodeList = Array.from(source.querySelectorAll('a, span, strong, b'));

      function pickByText(keys) {
        return nodeList.find(el => {
          const t = (el.textContent || '').trim();
          return keys.some(k => t.includes(k));
        }) || null;
      }

      const prevNode = pickByText(['上一页', '上一', '‹', '«']);
      const nextNode = pickByText(['下一页', '下一', '›', '»']);

      let newSelect = null;
      if (select) {
        const optMap = new Map();
        let currentPage = 0;
        let totalPage = 0;
        Array.from(select.options || []).forEach(opt => {
          const n = parseInt((opt.textContent || '').replace(/[^\d]/g, ''), 10);
          if (!n) return;
          optMap.set(n, {
            value: opt.value,
            label: opt.textContent
          });
          if (opt.selected) currentPage = n;
          totalPage = Math.max(totalPage, n);
        });

        if (!currentPage) {
          try {
            currentPage = parseInt(new URL(location.href).searchParams.get('page') || '1', 10) || 1;
          } catch (e) {
            currentPage = 1;
          }
        }

        const pageNums = [];
        if (totalPage <= 10) {
          for (let i = 1; i <= totalPage; i++) pageNums.push(i);
        } else {
          for (let i = 1; i <= 5; i++) pageNums.push(i);
          if (currentPage > 5 && currentPage < totalPage - 4) pageNums.push(currentPage);
          for (let i = totalPage - 4; i <= totalPage; i++) pageNums.push(i);
        }

        const uniq = Array.from(new Set(pageNums)).filter(n => n >= 1 && n <= totalPage).sort((a, b) => a - b);
        newSelect = select.cloneNode(false);
        newSelect.className = select.className;
        newSelect.name = select.name;
        newSelect.id = select.id;

        let last = 0;
        for (const n of uniq) {
          if (last && n - last > 1) {
            const gap = document.createElement('option');
            gap.textContent = '...';
            gap.disabled = true;
            gap.value = '';
            newSelect.appendChild(gap);
          }
          const meta = optMap.get(n);
          const o = document.createElement('option');
          o.value = meta ? meta.value : '';
          o.textContent = meta ? meta.label : String(n);
          if (n === currentPage) o.selected = true;
          newSelect.appendChild(o);
          last = n;
        }
      }

      const parts = [];
      if (prevNode) parts.push(prevNode.outerHTML);
      if (newSelect) parts.push(newSelect.outerHTML);
      if (nextNode) parts.push(nextNode.outerHTML);
      if (parts.length) dom.pagination.innerHTML = parts.join('');
    }

    async function loadPageByAjax(url) {
      if (!dom.latest || !url || url.includes('#') || url.includes('javascript:')) return;
      try {
        currentOrder = normalizeOrder(new URL(url, location.origin).searchParams.get('order'));
        syncSortButtons(currentOrder);
      } catch (e) {}

      const sortLoading = document.getElementById('pinlun-sort-loading');
      if (sortLoading) sortLoading.classList.add('show');

      dom.latest.style.opacity = "0.5";
      dom.latest.style.pointerEvents = "none";

      try {
        const html = await fetch(url, {
          headers: {
            "X-Requested-With": "XMLHttpRequest"
          }
        }).then(r => r.text());
        const doc = new DOMParser().parseFromString(html, "text/html");
        const newContent = doc.querySelector("#pinlun-latest");
        const newPagination = doc.querySelector(".pinlun-pagination");

        if (newContent) {
          dom.latest.innerHTML = newContent.innerHTML;
          dom.latest.style.opacity = "1";
          dom.latest.style.pointerEvents = "auto";
          adjustCommentListHeight();
          syncTopicActionsVisibility(dom.latest);
          bindImagePreview();
          bindVideoFullscreen(dom.latest);
          applyLevelStyles(dom.latest);
          if (window.pinlunjs && window.pinlunjs.likeAnimationEnabled) initFloatingLikes();
          renderRandomAvatars();
        }
        if (newPagination && dom.pagination) {
          dom.pagination.innerHTML = newPagination.innerHTML;
          bindAjaxPagination();
        }
        history.pushState(null, null, url);
      } catch (err) {
        location.href = url;
      } finally {
        if (sortLoading) sortLoading.classList.remove('show');
      }
    }

    document.addEventListener('click', e => {
      const sortBtn = e.target.closest('.pinlun-sort-btn');
      if (sortBtn) {
        e.preventDefault();
        const nextOrder = normalizeOrder(sortBtn.dataset.order);
        if (nextOrder === currentOrder) return;
        currentOrder = nextOrder;
        syncSortButtons(currentOrder);
        loadPageByAjax(buildOrderUrl(currentOrder));
        return;
      }

      const replyBtn = e.target.closest('.pinlun-comment-reply');
      if (replyBtn) return showReplyModal(replyBtn.dataset.id, replyBtn.dataset.username);

      const reactionBtn = e.target.closest('.pinlun-comment-like, .pinlun-reply-like, .pinlun-comment-dislike, .pinlun-reply-dislike');
      if (reactionBtn) {
        const isLike = reactionBtn.classList.contains('pinlun-comment-like') || reactionBtn.classList.contains('pinlun-reply-like');
        apiRequest(isLike ? 'toggle_like' : 'toggle_dislike', {
          content_type: 'comment',
          content_id: reactionBtn.dataset.id
        }, res => {
          reactionBtn.querySelector('.pinlun-action-count').textContent = res[isLike ? 'likes_count' : 'dislikes_count'];
          reactionBtn.classList.toggle(isLike ? 'pinlun-liked' : 'pinlun-disliked', res.action === (isLike ? 'like' : 'dislike'));
          showToast(res.message);
        });
        return;
      }

      const deleteBtn = e.target.closest('.pinlun-comment-delete, .pinlun-reply-delete');
      if (deleteBtn && checkPermission('删除评论')) {
        apiRequest('delete_comment', {
          comment_id: deleteBtn.dataset.id
        }, () => {
          showToast('删除成功');
          deleteBtn.closest('.pinlun-topic-item, .pinlun-comment-reply-item')?.remove();
        });
      }
    });

    dom.submitBtn?.addEventListener('click', () => {
      const content = window.pinlunjs.quill?.root.innerHTML;
      if (!content || content === '<p><br></p>') return showToast('请填写评论内容', true);
      currentReplyInfo = {
        parentId: 0,
        replyTo: ''
      };
      submitMainComment(content);
    });

    dom.closeModalBtn?.addEventListener('click', closeModal);
    dom.modalSubmitBtn?.addEventListener('click', submitModalComment);
    window.addEventListener('keydown', e => e.key === 'Escape' && closeModal());

    // 延迟初始化编辑器，确保页面其他元素（特别是页脚）加载完成后再初始化
    function initMainQuill() {
      const editorId = 'pinlun-comment-quill-editor';
      if (!document.getElementById(editorId)) return;
      if (typeof Quill === 'undefined') return;
      if (window.pinlunjs && window.pinlunjs.quill) return;
      registerMp4Blot();

      const enableImageUpload = !!window.pinlunjs.imageUploadEnabled;
      const enableVideoInsert = !!window.pinlunjs.videoInsertEnabled;
      const quill = new Quill('#' + editorId, {
        theme: 'snow',
        placeholder: '写下你的评论...',
        modules: {
          toolbar: {
            container: [
              ['bold', 'italic', 'underline'],
              [{
                'list': 'ordered'
              }, {
                'list': 'bullet'
              }],
              ['link'].concat(enableVideoInsert ? ['video'] : []).concat(enableImageUpload ? ['image'] : [])
            ],
            handlers: {
              video: () => {
                if (!window.pinlunjs.videoInsertEnabled) {
                  showToast('不允许插入视频', true);
                  return;
                }
                const url = (prompt('请输入MP4视频URL:') || '').trim();
                if (!url) return;
                const range = quill.getSelection(true);
                quill.insertEmbed(range.index, 'mp4', url, 'user');
                quill.setSelection(range.index + 1);
              },
              image: () => {
                if (!window.pinlunjs.imageUploadEnabled) {
                  showToast('不允许上传图片', true);
                  return;
                }
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.png,.gif,.jpg,.jpeg';
                input.multiple = true;
                input.onchange = () => {
                  const maxCount = parseInt(window.pinlunjs.imageUploadMaxCount || 5, 10);
                  const files = Array.from(input.files);
                  const currentImageCount = (quill.root.innerHTML.match(/<img/g) || []).length;
                  if (currentImageCount + files.length > maxCount) {
                    showToast('评论内容最多只能包含' + maxCount + '张图片，当前已有' + currentImageCount + '张', true);
                    return;
                  }
                  files.forEach(file => {
                    const maxKB = parseInt(window.pinlunjs.imageSizeLimitKB || 600, 10);
                    if (file.size > maxKB * 1024) {
                      showToast('图片大小不能超过' + maxKB + 'KB', true);
                      return;
                    }
                    const fd = new FormData();
                    fd.append('image', file);
                    fd.append('action', 'upload_comment_image');
                    fd.append('csrf_token', window.pinlunjs.csrfToken);
                    fetch(window.pinlunjs.ajaxUrl, {
                        method: 'POST',
                        body: fd,
                        credentials: 'include',
                        headers: {
                          'X-Requested-With': 'XMLHttpRequest',
                          'X-CSRF-Token': window.pinlunjs.csrfToken
                        }
                      })
                      .then(r => r.json()).then(data => {
                        if (data.success) {
                          const range = quill.getSelection(true);
                          quill.insertEmbed(range.index, 'image', data.url);
                          quill.setSelection(range.index + 1);
                          requestAnimationFrame(() => {
                            try {
                              const root = quill.root;
                              const imgs = root ? root.querySelectorAll('img') : null;
                              const img = imgs && imgs.length ? imgs[imgs.length - 1] : null;
                              if (img && root) {
                                const top = img.offsetTop;
                                root.scrollTop = Math.max(0, top - 8);
                              }
                            } catch (e) {}
                          });
                          setTimeout(() => bindImagePreview(), 100);
                        } else showToast(data.message || '图片上传失败', true);
                      });
                  });
                };
                input.click();
              }
            }
          }
        }
      });
      window.pinlunjs = window.pinlunjs || {};
      window.pinlunjs.quill = quill;
      requestAnimationFrame(function() {
        ensureMainEmojiTrigger(quill);
      });
      setTimeout(function() {
        ensureMainEmojiTrigger(quill);
      }, 80);

      const shell = document.getElementById('pinlun-comment-quill-shell');
      const editor = quill?.root;
      const baseHeight = 130;
      const maxHeight = 320;
      const editorBaseHeight = 78;

      function adjustHeight() {
        if (!editor || !shell) return;
        const scrollH = editor.scrollHeight;
        if (scrollH > editorBaseHeight + 5) {
          const newEditorH = Math.min(scrollH, maxHeight - 52);
          const newShellH = newEditorH + 52;
          editor.style.height = newEditorH + 'px';
          shell.style.height = newShellH + 'px';
        } else {
          editor.style.height = editorBaseHeight + 'px';
          shell.style.height = baseHeight + 'px';
        }
      }

      quill.on('text-change', adjustHeight);
      try {
        const shell = document.getElementById('pinlun-comment-quill-shell');
        if (shell) {
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
              shell.classList.add('qk-quill-ready');
            });
          });
        }
      } catch (e) {}
    }

    const editorShell = document.getElementById('pinlun-comment-quill-shell');
    if (editorShell) {
      const boot = () => initMainQuill();
      editorShell.addEventListener('mousedown', boot, {
        once: true
      });
      editorShell.addEventListener('touchstart', boot, {
        once: true
      });
      editorShell.addEventListener('focusin', boot, {
        once: true
      });
      window.addEventListener('DOMContentLoaded', () => {
        if (typeof requestIdleCallback === 'function') {
          requestIdleCallback(() => initMainQuill(), {
            timeout: 1200
          });
        } else {
          setTimeout(() => initMainQuill(), 0);
        }
      });
    }

    const overlay = document.createElement('div');
    overlay.className = 'pinlun-image-preview-overlay';
    overlay.innerHTML = '<div class="pinlun-image-preview-dialog">' +
      '<div class="pinlun-image-preview-container">' +
      '<div class="pinlun-image-preview-counter">1 / 1</div>' +
      '<div class="pinlun-image-preview-close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></div>' +
      '<button class="pinlun-image-preview-nav pinlun-image-preview-prev pinlun-hidden"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"></polyline></svg></button>' +
      '<img src="" alt="预览">' +
      '<button class="pinlun-image-preview-nav pinlun-image-preview-next pinlun-hidden"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"></polyline></svg></button>' +
      '</div>' +
      '<div class="pinlun-image-preview-content"><div class="pinlun-image-preview-content-text"></div></div>' +
      '</div>';
    document.body.appendChild(overlay);

    const previewImg = overlay.querySelector('img'),
      nav = {
        prev: overlay.querySelector('.pinlun-image-preview-prev'),
        next: overlay.querySelector('.pinlun-image-preview-next')
      },
      counter = overlay.querySelector('.pinlun-image-preview-counter'),
      contentText = overlay.querySelector('.pinlun-image-preview-content-text');
    let currentImages = [],
      currentIndex = 0;

    function isEmojiImage(img) {
      const src = img?.getAttribute('src') || '';
      return src.indexOf('/assets/emoji/') !== -1;
    }

    function updatePreview() {
      if (!currentImages.length) return;
      previewImg.style.opacity = '0';
      setTimeout(() => {
        previewImg.src = currentImages[currentIndex].src;
        previewImg.style.opacity = '1';
      }, 150);
      counter.textContent = (currentIndex + 1) + ' / ' + currentImages.length;
      nav.prev.classList.toggle('pinlun-hidden', currentImages.length <= 1);
      nav.next.classList.toggle('pinlun-hidden', currentImages.length <= 1);
    }

    nav.prev.onclick = () => {
      currentIndex = (currentIndex - 1 + currentImages.length) % currentImages.length;
      updatePreview();
    };
    nav.next.onclick = () => {
      currentIndex = (currentIndex + 1) % currentImages.length;
      updatePreview();
    };
    overlay.querySelector('.pinlun-image-preview-close').onclick = () => overlay.classList.remove('pinlun-image-preview-active');
    overlay.onclick = e => e.target === overlay && overlay.classList.remove('pinlun-image-preview-active');

    function bindImagePreview() {
      if (document.body.dataset.pinlunPreviewDelegated) return;
      document.body.dataset.pinlunPreviewDelegated = 'true';

      document.addEventListener('click', e => {
        const img = e.target.closest('.pinlun-post01 img, .pinlun-reply-body img');
        if (!img) return;
        if (isEmojiImage(img)) return;
        e.preventDefault();
        e.stopPropagation();
        const container = img.closest('.pinlun-topic-item, .pinlun-comment-reply-item');
        currentImages = Array.from(container?.querySelectorAll('.pinlun-post01 img, .pinlun-reply-body img') || [img]).filter(function(item) {
          return !isEmojiImage(item);
        });
        if (!currentImages.length) return;
        currentIndex = Math.max(0, currentImages.indexOf(img));
        contentText.textContent = container?.querySelector('.pinlun-post01, .pinlun-reply-body')?.textContent.trim() || '';
        updatePreview();
        overlay.classList.add('pinlun-image-preview-active');
      }, true);

      document.addEventListener('click', e => {
        const img = e.target.closest('#pinlun-modal-quill-editor .ql-editor img');
        if (!img) return;
        if (isEmojiImage(img)) return;
        e.preventDefault();
        e.stopPropagation();
        const container = document.querySelector('#pinlun-modal-quill-editor .ql-editor');
        currentImages = Array.from(container?.querySelectorAll('img') || [img]).filter(function(item) {
          return !isEmojiImage(item);
        });
        if (!currentImages.length) return;
        currentIndex = Math.max(0, currentImages.indexOf(img));
        contentText.textContent = container?.textContent.trim() || '';
        updatePreview();
        overlay.classList.add('pinlun-image-preview-active');
      }, true);
    }

    function bindVideoFullscreen(scope = document) {
      if (document.body.dataset.pinlunVideoDelegated) return;
      document.body.dataset.pinlunVideoDelegated = 'true';
      document.addEventListener('click', e => {
        const video = e.target.closest('.pinlun-post01 video, .pinlun-reply-body video');
        if (!video) return;
        e.preventDefault();
        e.stopPropagation();
        try {
          video.setAttribute('playsinline', '');
          video.setAttribute('webkit-playsinline', '');
          video.controls = true;
        } catch (err) {}
        try {
          const p = video.play();
          if (p && typeof p.catch === 'function') p.catch(() => {});
        } catch (err) {}
        try {
          if (video.requestFullscreen) video.requestFullscreen();
          else if (video.webkitEnterFullscreen) video.webkitEnterFullscreen();
          else if (video.webkitRequestFullscreen) video.webkitRequestFullscreen();
          else if (video.msRequestFullscreen) video.msRequestFullscreen();
        } catch (err) {}
      }, true);
    }

    function initFloatingLikes() {
      const stage = document.getElementById('pinlunjs-floating-likes');
      if (!stage) return;
      while (stage.firstChild) stage.removeChild(stage.firstChild);
      const palette = ['#60a5fa', '#34d399', '#f59e0b', '#f472b6', '#a78bfa', '#f87171', '#22d3ee', '#7dd3fc', '#86efac'];

      function anchorStage() {
        const host = document.querySelector('.pinlun-topics-content') || document.getElementById('latest');
        if (!host) return;
        const r = host.getBoundingClientRect();
        stage.style.left = Math.max(0, r.left) + 'px';
        stage.style.width = Math.max(0, r.width) + 'px';
        stage.style.bottom = Math.max(0, window.innerHeight - r.bottom) + 'px';
      }
      anchorStage();
      window.addEventListener('scroll', anchorStage, {
        passive: true
      });
      window.addEventListener('resize', anchorStage);
      apiRequest('latest_likes', {}, res => {
        const n = Math.max(8, Math.min(12, (res.items && res.items.length) ? res.items.length : 10));
        for (let i = 0; i < n; i++) {
          const b = document.createElement('div');
          b.className = 'pinlunjs-like-bubble';
          b.style.left = Math.floor(Math.random() * 92 + 4) + '%';
          const dur = (Math.random() * 2.8 + 3.8).toFixed(2) + 's';
          const delay = (Math.random() * 2.8).toFixed(2) + 's';
          b.style.animationDuration = dur;
          b.style.animationDelay = delay;
          b.style.color = palette[i % palette.length];
          const size = Math.floor(Math.random() * 15) + 22; // 22-36
          b.innerHTML = icons.like(size);
          const t = document.createElement('i');
          t.className = 'pinlunjs-tail';
          t.style.setProperty('--tailAngle', (Math.random() * 14 - 7) + 'deg');
          t.style.setProperty('--tailLen', Math.round(size * 1.2) + 'px');
          b.appendChild(t);
          stage.appendChild(b);
        }
      });
    }

    function applyLevelStyles(scope = document) {
      scope.querySelectorAll('.pinlun-user-level-tag').forEach(el => {
        const t = (el.textContent || '').trim();
        el.classList.remove('level-admin', 'level-vip', 'level-user', 'level-guest');
        let cls = 'level-user';
        if (/管理员|admin/i.test(t)) cls = 'level-admin';
        else if (/VIP|会员|高级/i.test(t)) cls = 'level-vip';
        else if (/游客|访客/i.test(t)) cls = 'level-guest';
        el.classList.add(cls);
      });
    }

    applyLevelStyles();

    window.addEventListener('DOMContentLoaded', () => {
      window.pinlunjs = window.pinlunjs || {};
      window.pinlunjs.initFloatingLikes = initFloatingLikes;
      window.pinlunjs.apiRequest = apiRequest;
      currentOrder = normalizeOrder(new URL(location.href).searchParams.get('order'));
      syncSortButtons(currentOrder);
      adjustCommentListHeight();
      bindAjaxPagination();
      bindImagePreview();
      bindVideoFullscreen();
      applyLevelStyles();
      syncTopicActionsVisibility();
      if (window.pinlunjs && window.pinlunjs.likeAnimationEnabled) initFloatingLikes();
      renderRandomAvatars();
    });
    window.addEventListener('resize', () => syncTopicActionsVisibility(dom.latest || document));
  })();

  window.pinlunjs = window.pinlunjs || {};
  window.pinlunjs.validateCommentContent = function(text) {
    const t = (text || '').trim();
    if (!t) {
      window.pinlunjs.validationError = '内容不能为空';
      return false;
    }
    if (t.startsWith('[图片评论：') || t.startsWith('[视频评论：') || t === '[媒体评论]') return true;
    if (window.pinlunjs.securityFilterEnabled) {
      if (/^\d+$/.test(t)) {
        window.pinlunjs.validationError = '不允许纯数字';
        return false;
      }
      if (t.length < 5) {
        window.pinlunjs.validationError = '内容过短';
        return false;
      }
    }
    window.pinlunjs.validationError = null;
    return true;
  };
</script>
