<?php
if (!defined('APP_ROOT')) exit;

/**
 * topic_vote —— 顶踩与举报
 * 给主题和回帖点顶踩；举报由管理员处理；踩到阈值自动临时隐藏、顶到阈值自动置顶、举报到阈值自动进回收站。
 */

function topic_vote_default_reasons(): array
{
    return ['广告或垃圾信息', '人身攻击', '色情低俗内容', '政治敏感', '刷屏灌水', '其他违规'];
}

function topic_vote_config(): array
{
    $raw = plugin_config('topic_vote', []);
    $reasons = array_values(array_filter(array_map('trim', explode("\n", str_replace(["\r\n", "\r"], "\n", (string)($raw['report_reasons'] ?? ''))))));
    if (!$reasons) $reasons = topic_vote_default_reasons();
    return [
        'vote_enabled' => (int)($raw['vote_enabled'] ?? 1) === 1,
        'report_enabled' => (int)($raw['report_enabled'] ?? 1) === 1,
        'show_counts_guest' => (int)($raw['show_counts_guest'] ?? 1) === 1,
        'vote_self' => (int)($raw['vote_self'] ?? 0) === 1,
        'report_reasons' => $reasons,
        'hide_down_threshold' => min(1000, max(0, (int)($raw['hide_down_threshold'] ?? 5))),
        'hide_minutes' => min(1440, max(1, (int)($raw['hide_minutes'] ?? 5))),
        'pin_up_threshold' => min(1000, max(0, (int)($raw['pin_up_threshold'] ?? 10))),
        'trash_report_threshold' => min(1000, max(0, (int)($raw['trash_report_threshold'] ?? 3))),
        'mute_default_minutes' => min(525600, max(0, (int)($raw['mute_default_minutes'] ?? 0))),
        'allow_guest_actions' => (int)($raw['allow_guest_actions'] ?? 0) === 1,
    ];
}

function topic_vote_migrate_legacy_tables(): void
{
    $db = db();
    $driver = db_driver();
    if (app_db_table_exists($db, $driver, 'plugin_topic_votes')) {
        $columns = app_db_columns($db, $driver, 'plugin_topic_votes');
        $guest = isset($columns['guest_key']) ? 'guest_key' : "'' guest_key";
        foreach (q("SELECT user_id,$guest,target_type,target_id,vote,created_at FROM plugin_topic_votes ORDER BY id")->fetchAll() as $row) {
            app_db_insert_ignore('plugin_topic_vote_votes', [
                'user_id' => (int)$row['user_id'],
                'guest_key' => (string)($row['guest_key'] ?? ''),
                'target_type' => (string)$row['target_type'],
                'target_id' => (int)$row['target_id'],
                'vote' => (int)$row['vote'],
                'created_at' => (int)$row['created_at'],
            ], ['user_id', 'guest_key', 'target_type', 'target_id']);
        }
    }
    if (app_db_table_exists($db, $driver, 'plugin_topic_reports')) {
        $columns = app_db_columns($db, $driver, 'plugin_topic_reports');
        $guest = isset($columns['guest_key']) ? 'guest_key' : "'' guest_key";
        foreach (q("SELECT user_id,$guest,target_type,target_id,reason,status,created_at,handled_at,handler_id FROM plugin_topic_reports ORDER BY id")->fetchAll() as $row) {
            app_db_insert_ignore('plugin_topic_vote_reports', [
                'user_id' => (int)$row['user_id'],
                'guest_key' => (string)($row['guest_key'] ?? ''),
                'target_type' => (string)$row['target_type'],
                'target_id' => (int)$row['target_id'],
                'reason' => (string)$row['reason'],
                'status' => (int)$row['status'],
                'created_at' => (int)$row['created_at'],
                'handled_at' => (int)$row['handled_at'],
                'handler_id' => (int)$row['handler_id'],
            ], ['user_id', 'guest_key', 'target_type', 'target_id']);
        }
    }
}

function topic_vote_schema(): void
{
    static $ready = false;
    if ($ready) return;
    $t = app_db_types();
    app_db_create_table('plugin_topic_vote_votes', "id {$t['id']},user_id {$t['uint']} NOT NULL,guest_key VARCHAR(32) NOT NULL DEFAULT '',target_type {$t['string']} NOT NULL,target_id {$t['uint']} NOT NULL,vote INTEGER NOT NULL,created_at {$t['uint']} NOT NULL,UNIQUE(user_id,guest_key,target_type,target_id)");
    app_db_create_table('plugin_topic_vote_reports', "id {$t['id']},user_id {$t['uint']} NOT NULL,guest_key VARCHAR(32) NOT NULL DEFAULT '',target_type {$t['string']} NOT NULL,target_id {$t['uint']} NOT NULL,reason {$t['text']} NOT NULL,status INTEGER NOT NULL DEFAULT 0,created_at {$t['uint']} NOT NULL,handled_at {$t['uint']} NOT NULL DEFAULT 0,handler_id {$t['uint']} NOT NULL DEFAULT 0,UNIQUE(user_id,guest_key,target_type,target_id)");
    app_db_create_index('idx_plugin_topic_vote_reports_status', 'plugin_topic_vote_reports(status,created_at)');
    topic_vote_migrate_legacy_tables();
    app_db_ensure_columns('app_users', ['plugin_topic_vote_muted_until' => 'INTEGER NOT NULL DEFAULT 0']);
    foreach (['app_topics', 'app_replies'] as $table) {
        app_db_ensure_columns($table, [
            'plugin_topic_vote_up' => 'INTEGER NOT NULL DEFAULT 0',
            'plugin_topic_vote_down' => 'INTEGER NOT NULL DEFAULT 0',
            'plugin_topic_report_count' => 'INTEGER NOT NULL DEFAULT 0',
            'plugin_topic_vote_hidden_until' => 'INTEGER NOT NULL DEFAULT 0',
            'plugin_topic_vote_trashed' => 'INTEGER NOT NULL DEFAULT 0',
        ]);
    }
    $ready = true;
}

function topic_vote_install(array $plugin): void
{
    topic_vote_schema();
}

function topic_vote_uninstall(array $plugin): void
{
    app_db_drop_table('plugin_topic_vote_votes');
    app_db_drop_table('plugin_topic_vote_reports');
    app_db_drop_table('plugin_topic_votes');
    app_db_drop_table('plugin_topic_reports');
    app_db_drop_column('app_users', 'plugin_topic_vote_muted_until');
    foreach (['app_topics', 'app_replies'] as $table) {
        app_db_drop_column($table, 'plugin_topic_vote_up');
        app_db_drop_column($table, 'plugin_topic_vote_down');
        app_db_drop_column($table, 'plugin_topic_report_count');
        app_db_drop_column($table, 'plugin_topic_vote_hidden_until');
        app_db_drop_column($table, 'plugin_topic_vote_trashed');
    }
}

function topic_vote_target_table(string $type): string
{
    return $type === 'reply' ? 'app_replies' : 'app_topics';
}

// 游客身份标识：无登录用户时用 cookie 匿名指纹（32 位 hex）
function topic_vote_guest_key(): string
{
    static $key = null;
    if ($key !== null) return $key;
    $k = (string)($_COOKIE['tv_guest'] ?? '');
    if ($k === '' || !preg_match('/^[a-f0-9]{32}$/', $k)) {
        $k = bin2hex(random_bytes(16));
        setcookie('tv_guest', $k, time() + 31536000, '/', '', false, true);
    }
    return $key = $k;
}

function topic_vote_target_row(string $type, int $id): ?array
{
    if ($id <= 0 || !in_array($type, ['topic', 'reply'], true)) return null;
    return row(topic_vote_target_table($type), 'id', $id);
}

function topic_vote_target_topic_id(string $type, int $id): int
{
    if ($type === 'reply') {
        $r = topic_vote_target_row('reply', $id);
        return $r ? max(1, (int)$r['topic_id']) : 0;
    }
    return max(1, $id);
}

// 详情页加载后收集：计数 + 我的投票状态 + 隐藏/回收站标记（一次批量查询）
function topic_vote_after_view($value, array $ctx): void
{
    $GLOBALS['__topic_vote_data'] = null;
    $config = topic_vote_config();
    if (!$config['vote_enabled'] && !$config['report_enabled']) return;
    $topic = is_array($ctx['topic'] ?? null) ? $ctx['topic'] : [];
    $replies = (array)($ctx['replies'] ?? []);
    $ids = ['topic' => [], 'reply' => []];
    foreach (array_merge([$topic], $replies) as $row) {
        if (!is_array($row) || (int)($row['id'] ?? 0) <= 0) continue;
        $ids[array_key_exists('topic_id', $row) ? 'reply' : 'topic'][] = (int)$row['id'];
    }
    $counts = [];
    foreach ($ids as $type => $target_ids) {
        $target_ids = array_values(array_unique($target_ids));
        if (!$target_ids) continue;
        $table = topic_vote_target_table($type);
        $marks = sql_marks(count($target_ids));
        foreach (q("SELECT id,plugin_topic_vote_up,plugin_topic_vote_down,plugin_topic_report_count,plugin_topic_vote_hidden_until,plugin_topic_vote_trashed FROM $table WHERE id IN ($marks)", $target_ids)->fetchAll() as $r) {
            $counts[$type . ':' . (int)$r['id']] = $r;
        }
    }
    $my_votes = [];
    $uid = uid();
    $guest_key = '';
    if (!$uid && $config['allow_guest_actions']) $guest_key = topic_vote_guest_key();
    if ($uid || $guest_key !== '') {
        $w = [];
        $p = [];
        foreach ($ids as $type => $target_ids) {
            foreach ($target_ids as $tid) {
                $w[] = '(target_type=? AND target_id=?)';
                $p[] = $type;
                $p[] = $tid;
            }
        }
        if ($w) {
            $marks = implode(' OR ', $w);
            foreach (q("SELECT target_type,target_id,vote FROM plugin_topic_vote_votes WHERE user_id=? AND guest_key=? AND ($marks)", array_merge([$uid, $guest_key], $p))->fetchAll() as $r) {
                $my_votes[(string)$r['target_type'] . ':' . (int)$r['target_id']] = (int)$r['vote'];
            }
        }
    }
    $GLOBALS['__topic_vote_data'] = ['counts' => $counts, 'my_votes' => $my_votes];
}

function topic_vote_render_state(string $type, int $trashed, int $hidden_until, string $html, bool $is_list): ?string
{
    $now = time();
    if ($hidden_until > $now) {
        $mins = max(1, (int)ceil(($hidden_until - $now) / 60));
        $box = '<div class="topic-vote-hidden-row">该内容因被较多用户踩，暂时隐藏，约 ' . $mins . ' 分钟后自动恢复。</div>';
        return $is_list ? '<li class="post-item"><div class="post-body">' . $box . '</div></li>' : $box;
    }
    if ($trashed === 1) return topic_vote_trash_html($type, $html, $is_list);
    return null;
}

// 回收站渲染策略：主题整行隐藏；回帖保留作者信息、正文替换为提示
function topic_vote_trash_html(string $type, string $html, bool $is_list): string
{
    if ($type === 'topic') {
        return $is_list ? '' : '<div class="topic-vote-trashed-row">该主题已被移入回收站，等待管理员审核。</div>';
    }
    $msg = '<div class="topic-vote-trashed-row">该内容因被多次举报，已移入回收站等待管理员审核。</div>';
    $pattern = $is_list ? '/<div class="profile-reply-excerpt"[ >]/' : '/<div class="post-content"[ >]/';
    $replaced = preg_replace($pattern, $msg . '<div class="topic-vote-hidden-content"', $html, 1);
    if (!is_string($replaced) || $replaced === $html) return $html . $msg;
    return $replaced;
}

function topic_vote_after_render($html, array $ctx): string
{
    $html = is_string($html) ? $html : '';
    if ($html === '') return $html;
    if (str_contains($html, 'topic-vote-bar') || str_contains($html, 'topic-vote-hidden-row') || str_contains($html, 'topic-vote-trashed-row') || str_contains($html, 'topic-vote-hidden-content')) return $html;
    $row = is_array($ctx['row'] ?? null) ? $ctx['row'] : [];
    $id = (int)($row['id'] ?? 0);
    if ($id <= 0) return $html;
    $type = array_key_exists('topic_id', $row) ? 'reply' : 'topic';
    $config = topic_vote_config();
    $key = $type . ':' . $id;
    $data = $GLOBALS['__topic_vote_data'] ?? null;
    $row_counts = is_array($data) ? ($data['counts'][$key] ?? null) : null;
    if (!is_array($row_counts) && empty($ctx['list'])) return $html;
    if (!is_array($row_counts)) {
        // 列表页无 after_view 收集：主键单行轻量查询隐藏/回收站状态
        $state = one("SELECT plugin_topic_vote_hidden_until,plugin_topic_vote_trashed FROM " . topic_vote_target_table($type) . " WHERE id=?", [$id]);
        if (!is_array($state)) return $html;
        $r = topic_vote_render_state($type, (int)($state['plugin_topic_vote_trashed'] ?? 0), (int)($state['plugin_topic_vote_hidden_until'] ?? 0), $html, true);
        return $r !== null ? $r : $html;
    }
    $r = topic_vote_render_state($type, (int)($row_counts['plugin_topic_vote_trashed'] ?? 0), (int)($row_counts['plugin_topic_vote_hidden_until'] ?? 0), $html, !empty($ctx['list']));
    if ($r !== null) return $r;
    $bar = topic_vote_bar_html($type, $id, (int)($row_counts['plugin_topic_vote_up'] ?? 0), (int)($row_counts['plugin_topic_vote_down'] ?? 0), (int)($data['my_votes'][$key] ?? 0), $config);
    return $bar === '' ? $html : $html . $bar;
}

function topic_vote_bar_html(string $type, int $id, int $up, int $down, int $my, array $config): string
{
    if (!$config['vote_enabled'] && !$config['report_enabled']) return '';
    $login_url = h(route_url('login'));
    $out = '<div class="topic-vote-bar" data-topic-vote-type="' . h($type) . '" data-topic-vote-id="' . $id . '">';
    if (!uid() && !$config['allow_guest_actions']) {
        $out .= '<span class="topic-vote-counts">顶 ' . $up . ' · 踩 ' . $down . '</span>';
        if ($config['vote_enabled'] || $config['report_enabled']) {
            $out .= '<a class="topic-vote-login" href="' . $login_url . '">登录后顶踩 / 举报</a>';
        }
        return $out . '</div>';
    }
    if ($config['vote_enabled']) {
        $up_active = $my === 1;
        $down_active = $my === -1;
        $out .= '<form class="topic-vote-form" method="post" action="' . h(route_url('topic_vote')) . '">' . form_token() . hidden_inputs(['target_type' => $type, 'target_id' => $id, 'vote' => 1]) . '<button class="topic-vote-btn' . ($up_active ? ' is-active-up' : '') . '" type="submit" title="顶一下">顶 <span class="topic-vote-count">' . $up . '</span></button></form>';
        $out .= '<form class="topic-vote-form" method="post" action="' . h(route_url('topic_vote')) . '">' . form_token() . hidden_inputs(['target_type' => $type, 'target_id' => $id, 'vote' => -1]) . '<button class="topic-vote-btn' . ($down_active ? ' is-active-down' : '') . '" type="submit" title="踩一下">踩 <span class="topic-vote-count">' . $down . '</span></button></form>';
    } elseif ($config['show_counts_guest']) {
        $out .= '<span class="topic-vote-counts">顶 ' . $up . ' · 踩 ' . $down . '</span>';
    }
    if ($config['report_enabled']) {
        $reasons = $config['report_reasons'];
        $opts = '<option value="">请选择举报理由</option>';
        foreach ($reasons as $r) {
            $opts .= '<option value="' . h($r) . '">' . h($r) . '</option>';
        }
        $out .= '<details class="topic-vote-report"><summary class="topic-vote-btn">举报</summary><form class="topic-vote-report-form" method="post" action="' . h(route_url('topic_report')) . '">' . form_token() . hidden_inputs(['target_type' => $type, 'target_id' => $id]) . '<select class="topic-vote-reason" name="reason" required>' . $opts . '</select><button type="submit">提交举报</button></form></details>';
    }
    return $out . '</div>';
}

function topic_vote_css(): string
{
    return '.topic-vote-bar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:6px 0 22px;padding:0}.topic-vote-form{display:inline-flex;margin:0}.topic-vote-btn{display:inline-flex;align-items:center;gap:4px;margin:0;padding:4px 10px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:var(--text-muted);font-size:12px;line-height:1.4;cursor:pointer;transition:color .15s,background .15s,border-color .15s}.topic-vote-btn:hover{color:var(--brand);border-color:var(--brand-soft);background:var(--brand-soft)}.topic-vote-btn.is-active-up{color:var(--success);border-color:var(--success);background:var(--success-soft)}.topic-vote-btn.is-active-down{color:var(--danger);border-color:var(--danger);background:var(--danger-soft)}.topic-vote-count{font-weight:600}.topic-vote-counts{color:var(--text-muted);font-size:12px}.topic-vote-login{color:var(--brand);font-size:12px}.topic-vote-report{display:inline-flex;align-items:center;gap:6px}.topic-vote-report summary{list-style:none;cursor:pointer}.topic-vote-report summary::-webkit-details-marker{display:none}.topic-vote-report-form{display:inline-flex;align-items:center;gap:6px;margin:0;padding:4px 6px;border:1px solid var(--line);border-radius:8px;background:var(--panel);box-shadow:var(--shadow-medium)}.topic-vote-reason{min-width:180px;max-width:240px}.topic-vote-hidden-row,.topic-vote-trashed-row{display:block;grid-column:1/-1;flex:0 0 100%;width:100%;min-width:0;box-sizing:border-box;margin:6px 0 22px;padding:10px 14px;border-radius:8px;font-size:13px}.topic-vote-hidden-row{background:var(--warning-soft);color:var(--warning)}.topic-vote-trashed-row{background:var(--danger-soft);color:var(--danger)}.topic-vote-hidden-content{display:none}.topic-vote-report-row .admin-row-actions{display:flex;flex-wrap:wrap;gap:6px}.topic-vote-collapse{display:block}.topic-vote-collapse summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;padding:14px 16px}.topic-vote-collapse summary::-webkit-details-marker{display:none}.topic-vote-collapse-summary{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.topic-vote-collapse-arrow{flex:0 0 auto;color:var(--text-muted);transition:transform .15s}.topic-vote-collapse[open] .topic-vote-collapse-arrow{transform:rotate(90deg)}.topic-vote-collapse-body{border-top:1px solid var(--line-soft)}';
}

function topic_vote_route(array $plugin): void
{
    require_post();
    $config = topic_vote_config();
    if (!uid() && !$config['allow_guest_actions']) need_login();
    if (!$config['vote_enabled']) err('顶踩功能已关闭');
    $type = (string)($_POST['target_type'] ?? '');
    $target_id = max(1, (int)($_POST['target_id'] ?? 0));
    $vote = (int)($_POST['vote'] ?? 0);
    if (!in_array($type, ['topic', 'reply'], true) || !in_array($vote, [1, -1], true)) err('参数错误');
    $uid = uid();
    $guest_key = '';
    if (!$uid) $guest_key = topic_vote_guest_key();
    $target = topic_vote_target_row($type, $target_id) ?: err('内容不存在');
    if ((int)$target['user_id'] === $uid && !$config['vote_self']) err('不能对自己的内容操作');
    $topic_id = topic_vote_target_topic_id($type, $target_id);
    if ($topic_id <= 0) err('内容不存在');
    tx(function () use ($uid, $guest_key, $type, $target_id, $vote, $config): void {
        $table = topic_vote_target_table($type);
        $old = one("SELECT vote FROM plugin_topic_vote_votes WHERE user_id=? AND guest_key=? AND target_type=? AND target_id=?", [$uid, $guest_key, $type, $target_id]);
        $old_vote = $old ? (int)$old['vote'] : 0;
        if ($old_vote === $vote) {
            q("DELETE FROM plugin_topic_vote_votes WHERE user_id=? AND guest_key=? AND target_type=? AND target_id=?", [$uid, $guest_key, $type, $target_id]);
            $final_vote = 0;
        } elseif ($old_vote === 0) {
            app_db_upsert('plugin_topic_vote_votes', ['user_id' => $uid, 'guest_key' => $guest_key, 'target_type' => $type, 'target_id' => $target_id, 'vote' => $vote, 'created_at' => now()], ['user_id', 'guest_key', 'target_type', 'target_id']);
            $final_vote = $vote;
        } else {
            q("UPDATE plugin_topic_vote_votes SET vote=? WHERE user_id=? AND guest_key=? AND target_type=? AND target_id=?", [$vote, $uid, $guest_key, $type, $target_id]);
            $final_vote = $vote;
        }
        $delta_up = ($final_vote === 1 ? 1 : 0) - ($old_vote === 1 ? 1 : 0);
        $delta_down = ($final_vote === -1 ? 1 : 0) - ($old_vote === -1 ? 1 : 0);
        $counts = one("SELECT plugin_topic_vote_up,plugin_topic_vote_down FROM $table WHERE id=?", [$target_id]);
        $old_up = (int)($counts['plugin_topic_vote_up'] ?? 0);
        $old_down = (int)($counts['plugin_topic_vote_down'] ?? 0);
        $new_up = max(0, $old_up + $delta_up);
        $new_down = max(0, $old_down + $delta_down);
        if ($delta_up !== 0) q("UPDATE $table SET plugin_topic_vote_up=? WHERE id=?", [$new_up, $target_id]);
        if ($delta_down !== 0) q("UPDATE $table SET plugin_topic_vote_down=? WHERE id=?", [$new_down, $target_id]);
        // 治理：顶到阈值自动置顶（仅主题）
        if ($type === 'topic' && $config['pin_up_threshold'] > 0 && $old_up < $config['pin_up_threshold'] && $new_up >= $config['pin_up_threshold']) {
            set_pinned_topic($target_id, true);
        }
        // 治理：踩到阈值自动临时隐藏（跨越阈值才触发，避免恢复后死循环）
        if ($config['hide_down_threshold'] > 0 && $old_down < $config['hide_down_threshold'] && $new_down >= $config['hide_down_threshold']) {
            q("UPDATE $table SET plugin_topic_vote_hidden_until=? WHERE id=?", [now() + $config['hide_minutes'] * 60, $target_id]);
        }
    });
    go(route_url('topic', ['id' => $topic_id]));
}

function topic_report_route(array $plugin): void
{
    require_post();
    $config = topic_vote_config();
    if (!uid() && !$config['allow_guest_actions']) need_login();
    if (!$config['report_enabled']) err('举报功能已关闭');
    $type = (string)($_POST['target_type'] ?? '');
    $target_id = max(1, (int)($_POST['target_id'] ?? 0));
    if (!in_array($type, ['topic', 'reply'], true)) err('参数错误');
    $reason = trim((string)post('reason', 100));
    if (!in_array($reason, $config['report_reasons'], true)) err('请选择有效的举报理由');
    $uid = uid();
    $guest_key = '';
    if (!$uid) $guest_key = topic_vote_guest_key();
    $target = topic_vote_target_row($type, $target_id) ?: err('内容不存在');
    if ((int)$target['user_id'] === $uid) err('不能举报自己');
    $topic_id = topic_vote_target_topic_id($type, $target_id);
    if ($topic_id <= 0) err('内容不存在');
    $exists = val("SELECT 1 FROM plugin_topic_vote_reports WHERE user_id=? AND guest_key=? AND target_type=? AND target_id=?", [$uid, $guest_key, $type, $target_id]);
    if (!$exists) {
        tx(function () use ($uid, $guest_key, $type, $target_id, $reason, $config): void {
            app_db_insert_ignore('plugin_topic_vote_reports', ['user_id' => $uid, 'guest_key' => $guest_key, 'target_type' => $type, 'target_id' => $target_id, 'reason' => $reason, 'status' => 0, 'created_at' => now(), 'handled_at' => 0, 'handler_id' => 0], ['user_id', 'guest_key', 'target_type', 'target_id']);
            $table = topic_vote_target_table($type);
            $old_count = (int)val("SELECT plugin_topic_report_count FROM $table WHERE id=?", [$target_id]);
            $new_count = $old_count + 1;
            q("UPDATE $table SET plugin_topic_report_count=? WHERE id=?", [$new_count, $target_id]);
            // 治理：举报到阈值自动进回收站
            if ($config['trash_report_threshold'] > 0 && $old_count < $config['trash_report_threshold'] && $new_count >= $config['trash_report_threshold']) {
                q("UPDATE $table SET plugin_topic_vote_trashed=1 WHERE id=?", [$target_id]);
            }
        });
    }
    set_flash('举报已提交，管理员会尽快处理');
    go(route_url('topic', ['id' => $topic_id]));
}

// cron：到期的临时隐藏自动恢复（幂等）
function topic_vote_restore_hidden_cron(array $plugin, array $task): string
{
    $now = now();
    q("UPDATE app_topics SET plugin_topic_vote_hidden_until=0 WHERE plugin_topic_vote_hidden_until>0 AND plugin_topic_vote_hidden_until<=?", [$now]);
    q("UPDATE app_replies SET plugin_topic_vote_hidden_until=0 WHERE plugin_topic_vote_hidden_until>0 AND plugin_topic_vote_hidden_until<=?", [$now]);
    return 'done';
}

// cron：禁言到期自动解除（幂等；0=永久不禁言，核心入口置 1 未写到期时间的视为永久）
function topic_vote_unmute_cron(array $plugin, array $task): string
{
    $now = now();
    $n = q("UPDATE app_users SET is_muted=0,plugin_topic_vote_muted_until=0 WHERE is_muted=1 AND plugin_topic_vote_muted_until>0 AND plugin_topic_vote_muted_until<=?", [$now])->rowCount();
    return $n > 0 ? "unmuted {$n}" : 'done';
}

function topic_vote_admin_page(array $plugin): string
{
    need_admin();
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = (string)($_POST['topic_vote_admin_action'] ?? '');
        if ($action === 'save_config') {
            $reasons = implode("\n", array_values(array_filter(array_map('trim', explode("\n", str_replace(["\r\n", "\r"], "\n", (string)($_POST['report_reasons'] ?? '')))))));
            plugin_save_config('topic_vote', [
                'vote_enabled' => isset($_POST['vote_enabled']) ? '1' : '0',
                'report_enabled' => isset($_POST['report_enabled']) ? '1' : '0',
                'show_counts_guest' => isset($_POST['show_counts_guest']) ? '1' : '0',
                'vote_self' => isset($_POST['vote_self']) ? '1' : '0',
                'report_reasons' => $reasons,
                'hide_down_threshold' => (string)max(0, min(1000, (int)($_POST['hide_down_threshold'] ?? 0))),
                'hide_minutes' => (string)max(1, min(1440, (int)($_POST['hide_minutes'] ?? 5))),
                'pin_up_threshold' => (string)max(0, min(1000, (int)($_POST['pin_up_threshold'] ?? 0))),
                'trash_report_threshold' => (string)max(0, min(1000, (int)($_POST['trash_report_threshold'] ?? 0))),
                'mute_default_minutes' => (string)max(0, min(525600, (int)($_POST['mute_default_minutes'] ?? 0))),
                'allow_guest_actions' => isset($_POST['allow_guest_actions']) ? '1' : '0',
            ]);
            set_flash('插件设置已保存');
        } elseif ($action === 'handle_report') {
            $report_id = max(1, (int)($_POST['report_id'] ?? 0));
            $handle = (string)($_POST['handle'] ?? '');
            $report = row('plugin_topic_vote_reports', 'id', $report_id) ?: err('举报不存在');
            if ($handle === 'mute_author') {
                $target = topic_vote_target_row((string)$report['target_type'], (int)$report['target_id']) ?: err('内容不存在');
                if ((int)$target['user_id'] === 1) err('不能操作超级管理员');
                $mute_minutes = (int)(topic_vote_config()['mute_default_minutes'] ?? 0);
                $muted_until = $mute_minutes > 0 ? now() + $mute_minutes * 60 : 0;
                q("UPDATE app_users SET is_muted=1,plugin_topic_vote_muted_until=? WHERE id=?", [$muted_until, (int)$target['user_id']]);
                q("UPDATE plugin_topic_vote_reports SET status=1,handled_at=?,handler_id=? WHERE id=?", [now(), uid(), $report_id]);
                set_flash('已处理：禁言作者' . ($mute_minutes > 0 ? '（' . $mute_minutes . ' 分钟）' : '（永久）'));
            } elseif ($handle === 'approve') {
                $rtable = topic_vote_target_table((string)$report['target_type']);
                q("UPDATE $rtable SET plugin_topic_vote_trashed=1 WHERE id=?", [(int)$report['target_id']]);
                q("UPDATE plugin_topic_vote_reports SET status=1,handled_at=?,handler_id=? WHERE id=?", [now(), uid(), $report_id]);
                set_flash('已确认违规，内容已移入回收站');
            } elseif ($handle === 'ignore') {
                $rtable = topic_vote_target_table((string)$report['target_type']);
                q("UPDATE $rtable SET plugin_topic_vote_trashed=0,plugin_topic_report_count=0 WHERE id=?", [(int)$report['target_id']]);
                q("UPDATE plugin_topic_vote_reports SET status=2,handled_at=?,handler_id=? WHERE id=?", [now(), uid(), $report_id]);
                set_flash('已标记不违规，内容已恢复，举报数已清零');
            } else err('参数错误');
        } elseif ($action === 'trash_action') {
            $type = (string)($_POST['trash_type'] ?? '');
            $tid = max(1, (int)($_POST['trash_id'] ?? 0));
            $do = (string)($_POST['trash_do'] ?? '');
            $table = topic_vote_target_table($type === 'reply' ? 'reply' : 'topic');
            if (!in_array($type, ['topic', 'reply'], true) || !topic_vote_target_row($type === 'reply' ? 'reply' : 'topic', $tid)) err('内容不存在');
            if ($do === 'restore') {
                q("UPDATE $table SET plugin_topic_vote_trashed=0 WHERE id=?", [$tid]);
                set_flash('已恢复内容');
            } elseif ($do === 'delete') {
                del($type === 'reply' ? 'replies' : 'topics', $tid);
                set_flash('已彻底删除');
            } else err('参数错误');
        } elseif ($action === 'unmute') {
            $user_id = max(1, (int)($_POST['user_id'] ?? 0));
            $u = row('app_users', 'id', $user_id) ?: err('用户不存在');
            q("UPDATE app_users SET is_muted=0,plugin_topic_vote_muted_until=0 WHERE id=?", [$user_id]);
            set_flash('已解除禁言：' . (string)$u['username']);
        } elseif ($action === 'unhide') {
            $type = (string)($_POST['trash_type'] ?? '');
            $tid = max(1, (int)($_POST['trash_id'] ?? 0));
            $table = topic_vote_target_table($type === 'reply' ? 'reply' : 'topic');
            if (!in_array($type, ['topic', 'reply'], true)) err('参数错误');
            q("UPDATE $table SET plugin_topic_vote_hidden_until=0 WHERE id=?", [$tid]);
            set_flash('已解除隐藏');
        } else err('参数错误');
        go(admin_url(['tab' => 'topic_vote']));
    }
    $config = topic_vote_config();
    $html = topic_vote_admin_settings_html($config);
    $html .= topic_vote_admin_reports_html();
    $html .= topic_vote_admin_trash_html();
    $html .= topic_vote_admin_hidden_html();
    $html .= topic_vote_admin_mutes_html();
    return $html;
}

function topic_vote_admin_settings_html(array $config): string
{
    $reasons_text = implode("\n", $config['report_reasons']);
    $settings = '<form class="plugin-settings-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'save_config']) . '<label class="grid"><span>启用顶踩</span><input type="checkbox" name="vote_enabled" value="1"' . ($config['vote_enabled'] ? ' checked' : '') . '></label><label class="grid"><span>启用举报</span><input type="checkbox" name="report_enabled" value="1"' . ($config['report_enabled'] ? ' checked' : '') . '></label><label class="grid"><span>未登录可见顶踩计数</span><input type="checkbox" name="show_counts_guest" value="1"' . ($config['show_counts_guest'] ? ' checked' : '') . '></label><label class="grid"><span>允许顶踩自己的内容</span><input type="checkbox" name="vote_self" value="1"' . ($config['vote_self'] ? ' checked' : '') . '></label>' . textarea('举报理由（每行一个，用户举报时选择）', 'report_reasons', $reasons_text) . number_input('踩到阈值自动隐藏（0 关闭）', 'hide_down_threshold', $config['hide_down_threshold'], 0, 1000, true) . number_input('隐藏时长（分钟）', 'hide_minutes', $config['hide_minutes'], 1, 1440, true) . number_input('顶到阈值自动置顶（0 关闭，仅主题）', 'pin_up_threshold', $config['pin_up_threshold'], 0, 1000, true) . number_input('举报到阈值自动进回收站（0 关闭）', 'trash_report_threshold', $config['trash_report_threshold'], 0, 1000, true) . '<label class="grid"><span>允许游客顶踩 / 举报</span><input type="checkbox" name="allow_guest_actions" value="1"' . ($config['allow_guest_actions'] ? ' checked' : '') . '></label>' . number_input('默认禁言时长（分钟，0=永久）', 'mute_default_minutes', $config['mute_default_minutes'], 0, 525600, true) . '<p class="muted">跨越阈值时触发一次：例如踩数从 4 变 5 且阈值 5，则隐藏 ' . $config['hide_minutes'] . ' 分钟，到期由计划任务自动恢复。</p><button>保存设置</button></form>';
    return '<div class="admin-list-panel plugin-manage-panel">' . admin_list_head('<div class="admin-plugin-summary"><strong>顶踩与举报设置</strong><span>顶踩一人一票，可改票或取消；举报结果进入下方待处理列表。</span></div>', '') . '<div class="plugin-panel-body">' . $settings . '</div></div>';
}

function topic_vote_collapse_head(string $title, string $meta): string
{
    return '<details class="topic-vote-collapse"><summary class="topic-vote-collapse-head"><span class="admin-plugin-summary topic-vote-collapse-summary"><strong>' . h($title) . '</strong><span>' . h($meta) . '</span></span><span class="topic-vote-collapse-arrow" aria-hidden="true">▸</span></summary><div class="topic-vote-collapse-body">';
}

function topic_vote_admin_reports_html(): string
{
    $pending = q("SELECT * FROM plugin_topic_vote_reports WHERE status=0 ORDER BY id DESC LIMIT 100")->fetchAll();
    $html = '<div class="admin-list-panel plugin-manage-panel">' . topic_vote_collapse_head('待处理举报', '共 ' . count($pending) . ' 条') . '<ul class="admin-manage-list">';
    if (!$pending) $html .= '<li class="empty-state">暂无待处理举报</li>';
    foreach ($pending as $row) {
        $type = (string)$row['target_type'];
        $target = topic_vote_target_row($type, (int)$row['target_id']);
        $target_label = '（内容已被删除）';
        if ($target) {
            if ($type === 'topic') {
                $target_label = '<a href="' . h(route_url('topic', ['id' => (int)$row['target_id']])) . '" target="_blank" rel="noopener">' . h(cut((string)$target['title'], 60)) . '</a>';
            } else {
                $target_label = '<a href="' . h(route_url('topic', ['id' => (int)$target['topic_id'], 'replyid' => (int)$row['target_id']])) . '" target="_blank" rel="noopener">回复 #' . (int)$row['target_id'] . '：' . h(cut(strip_tags((string)$target['body']), 60)) . '</a>';
            }
        }
        $reporter = rows_by_ids('app_users', [(int)$row['user_id']], 'id,username');
        $reporter_name = (string)($reporter[(int)$row['user_id']]['username'] ?? '未知');
        $actions = '<form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'handle_report', 'report_id' => (int)$row['id'], 'handle' => 'approve']) . '<button type="submit">确认违规</button></form><form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'handle_report', 'report_id' => (int)$row['id'], 'handle' => 'ignore']) . '<button type="submit">不违规</button></form>';
        if ($target && (int)$target['user_id'] !== 1) {
            $actions .= '<form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '" data-confirm="确定禁言该作者？">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'handle_report', 'report_id' => (int)$row['id'], 'handle' => 'mute_author']) . '<button type="submit">禁言作者</button></form>';
        }
        $html .= '<li class="admin-list-item admin-object-row topic-vote-report-row"><div class="admin-row-main"><strong class="admin-content-title">' . $target_label . '</strong><div class="admin-row-meta"><span>' . ($type === 'topic' ? '主题' : '回复') . ' #' . (int)$row['target_id'] . '</span><span>举报人 ' . h($reporter_name) . '</span><span>理由：' . h((string)$row['reason']) . '</span><span>' . date('Y-m-d H:i', (int)$row['created_at']) . '</span></div><div class="admin-content-text">' . h((string)$row['reason']) . '</div></div><div class="admin-row-actions">' . $actions . '</div></li>';
    }
    return $html . '</ul></div></details></div>';
}

function topic_vote_admin_trash_html(): string
{
    $html = '<div class="admin-list-panel plugin-manage-panel">' . topic_vote_collapse_head('回收站（举报自动移入）', '可恢复或彻底删除') . '<ul class="admin-manage-list">';
    $topics = q("SELECT t.id,t.title,t.user_id,t.plugin_topic_report_count,t.created_at,u.username FROM app_topics t LEFT JOIN app_users u ON u.id=t.user_id WHERE t.plugin_topic_vote_trashed=1 ORDER BY t.id DESC LIMIT 50")->fetchAll();
    $replies = q("SELECT r.id,r.topic_id,r.user_id,r.body,r.plugin_topic_report_count,r.created_at,u.username FROM app_replies r LEFT JOIN app_users u ON u.id=r.user_id WHERE r.plugin_topic_vote_trashed=1 ORDER BY r.id DESC LIMIT 50")->fetchAll();
    $topic_ids = array_values(array_unique(array_map('intval', array_column($replies, 'topic_id'))));
    $topics_map = $topic_ids ? rows_by_ids('app_topics', $topic_ids, 'id,title') : [];
    $found = false;
    foreach ($topics as $row) {
        $found = true;
        $label = '<a href="' . h(route_url('topic', ['id' => (int)$row['id']])) . '" target="_blank" rel="noopener">' . h(cut((string)$row['title'], 60)) . '</a>';
        $html .= topic_vote_admin_trash_item('topic', $row, $label, (string)$row['username'], (int)$row['plugin_topic_report_count'], (int)$row['created_at']);
    }
    foreach ($replies as $row) {
        $found = true;
        $tid = (int)$row['topic_id'];
        $topic_title = (string)($topics_map[$tid]['title'] ?? '');
        $label = '回复 ' . ($topic_title !== '' ? '《' . h(cut($topic_title, 30)) . '》' : '#' . $tid) . '：' . h(cut(strip_tags((string)$row['body']), 60));
        $label = '<a href="' . h(route_url('topic', ['id' => $tid, 'replyid' => (int)$row['id']])) . '" target="_blank" rel="noopener">' . $label . '</a>';
        $html .= topic_vote_admin_trash_item('reply', $row, $label, (string)$row['username'], (int)$row['plugin_topic_report_count'], (int)$row['created_at']);
    }
    if (!$found) $html .= '<li class="empty-state">回收站为空</li>';
    return $html . '</ul></div></details></div>';
}

function topic_vote_admin_trash_item(string $type, array $row, string $label, string $username, int $reports, int $created_at): string
{
    $actions = '<form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'trash_action', 'trash_type' => $type, 'trash_id' => (int)$row['id'], 'trash_do' => 'restore']) . '<button type="submit">恢复</button></form><form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '" data-confirm="彻底删除后不可恢复，确定？">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'trash_action', 'trash_type' => $type, 'trash_id' => (int)$row['id'], 'trash_do' => 'delete']) . '<button type="submit">彻底删除</button></form>';
    return '<li class="admin-list-item admin-object-row topic-vote-report-row"><div class="admin-row-main"><strong class="admin-content-title">' . $label . '</strong><div class="admin-row-meta"><span>' . ($type === 'topic' ? '主题' : '回复') . ' #' . (int)$row['id'] . '</span><span>作者 ' . h($username) . '</span><span>举报 ' . $reports . ' 次</span><span>' . date('Y-m-d H:i', $created_at) . '</span></div></div><div class="admin-row-actions">' . $actions . '</div></li>';
}

function topic_vote_admin_hidden_html(): string
{
    $now = now();
    $html = '<div class="admin-list-panel plugin-manage-panel">' . topic_vote_collapse_head('临时隐藏中（踩阈值触发）', '到期自动恢复，也可手动解除') . '<ul class="admin-manage-list">';
    $topics = q("SELECT id,title,user_id,plugin_topic_vote_hidden_until FROM app_topics WHERE plugin_topic_vote_hidden_until>0 ORDER BY plugin_topic_vote_hidden_until ASC LIMIT 50")->fetchAll();
    $replies = q("SELECT id,topic_id,body,user_id,plugin_topic_vote_hidden_until FROM app_replies WHERE plugin_topic_vote_hidden_until>0 ORDER BY plugin_topic_vote_hidden_until ASC LIMIT 50")->fetchAll();
    $found = false;
    foreach ($topics as $row) {
        $found = true;
        $remain = max(0, (int)$row['plugin_topic_vote_hidden_until'] - $now);
        $label = '<a href="' . h(route_url('topic', ['id' => (int)$row['id']])) . '" target="_blank" rel="noopener">' . h(cut((string)$row['title'], 60)) . '</a>';
        $html .= topic_vote_admin_hidden_item('topic', (int)$row['id'], $label, (int)ceil($remain / 60));
    }
    foreach ($replies as $row) {
        $found = true;
        $remain = max(0, (int)$row['plugin_topic_vote_hidden_until'] - $now);
        $label = '<a href="' . h(route_url('topic', ['id' => (int)$row['topic_id'], 'replyid' => (int)$row['id']])) . '" target="_blank" rel="noopener">回复 #' . (int)$row['id'] . '：' . h(cut(strip_tags((string)$row['body']), 60)) . '</a>';
        $html .= topic_vote_admin_hidden_item('reply', (int)$row['id'], $label, (int)ceil($remain / 60));
    }
    if (!$found) $html .= '<li class="empty-state">当前没有处于隐藏状态的内容</li>';
    return $html . '</ul></div></details></div>';
}

function topic_vote_admin_hidden_item(string $type, int $id, string $label, int $remain_minutes): string
{
    $actions = '<form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'unhide', 'trash_type' => $type, 'trash_id' => $id]) . '<button type="submit">立即解除</button></form>';
    return '<li class="admin-list-item admin-object-row topic-vote-report-row"><div class="admin-row-main"><strong class="admin-content-title">' . $label . '</strong><div class="admin-row-meta"><span>' . ($type === 'topic' ? '主题' : '回复') . ' #' . $id . '</span><span>剩余约 ' . $remain_minutes . ' 分钟</span></div></div><div class="admin-row-actions">' . $actions . '</div></li>';
}

function topic_vote_admin_mutes_html(): string
{
    $now = now();
    $muted = q("SELECT id,username,plugin_topic_vote_muted_until FROM app_users WHERE is_muted=1 ORDER BY plugin_topic_vote_muted_until ASC,id DESC LIMIT 100")->fetchAll();
    $html = '<div class="admin-list-panel plugin-manage-panel">' . topic_vote_collapse_head('禁言管理', '共 ' . count($muted) . ' 人，可手动解除') . '<ul class="admin-manage-list">';
    if (!$muted) $html .= '<li class="empty-state">当前没有禁言的用户</li>';
    foreach ($muted as $row) {
        $until = (int)$row['plugin_topic_vote_muted_until'];
        if ($until > 0 && $until <= $now) {
            $state = '已到期，等待自动解除';
        } elseif ($until > 0) {
            $state = '剩余约 ' . max(1, (int)ceil(($until - $now) / 60)) . ' 分钟';
        } else {
            $state = '永久禁言';
        }
        $actions = '<form class="post-action-form" method="post" action="' . h(admin_url(['tab' => 'topic_vote'])) . '" data-confirm="确定解除该用户的禁言？">' . form_token() . hidden_inputs(['topic_vote_admin_action' => 'unmute', 'user_id' => (int)$row['id']]) . '<button type="submit">解除禁言</button></form>';
        $html .= '<li class="admin-list-item admin-object-row topic-vote-report-row"><div class="admin-row-main"><strong class="admin-content-title">' . h((string)$row['username']) . '</strong><div class="admin-row-meta"><span>用户 #' . (int)$row['id'] . '</span><span>' . h($state) . '</span></div></div><div class="admin-row-actions">' . $actions . '</div></li>';
    }
    return $html . '</ul></div></details></div>';
}

return [
    'id' => 'topic_vote',
    'name' => '顶踩与举报',
    'version' => '1.4.0',
    'description' => '支持给主题和回帖点顶踩，举报内容由管理员处理；踩到阈值自动隐藏、顶到阈值自动置顶。',
    'author' => 'rdzn',
    'assets' => ['css' => 'topic_vote_css'],
    'hooks' => [
        'topic.after_view' => 'topic_vote_after_view',
        'topic.after_render' => 'topic_vote_after_render',
        'reply.after_render' => 'topic_vote_after_render',
    ],
    'routes' => [
        'topic_vote' => 'topic_vote_route',
        'topic_report' => 'topic_report_route',
    ],
    'admin_tabs' => ['topic_vote' => 'topic_vote_admin_page'],
    'cron' => ['restore_hidden' => ['callback' => 'topic_vote_restore_hidden_cron', 'interval' => 60], 'unmute_expired' => ['callback' => 'topic_vote_unmute_cron', 'interval' => 60]],
    'install' => 'topic_vote_install',
    'uninstall' => 'topic_vote_uninstall',
];