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

function batch_management_install(array $plugin): void
{
    $t = app_db_types();
    app_db_create_table('plugin_batch_management_trash', "id {$t['id']},table_name {$t['string']} NOT NULL,row_id {$t['uint']} NOT NULL,row_data {$t['text']} NOT NULL,deleted_by {$t['uint']} NOT NULL DEFAULT 0,created_at {$t['uint']} NOT NULL");
    app_db_create_index('idx_batch_management_trash_table_time', 'plugin_batch_management_trash(table_name,created_at DESC,id DESC)');
}

function batch_management_uninstall(array $plugin): void
{
    app_db_drop_index('idx_batch_management_trash_table_time', 'plugin_batch_management_trash');
    app_db_drop_table('plugin_batch_management_trash');
}

function batch_management_store_trash($value, array $ctx): void
{
    $table = (string)($ctx['table'] ?? '');
    $row = is_array($ctx['row'] ?? null) ? $ctx['row'] : [];
    if (!in_array($table, ['users', 'topics', 'replies'], true) || (int)($row['id'] ?? 0) < 1) return;
    q('INSERT INTO plugin_batch_management_trash(table_name,row_id,row_data,deleted_by,created_at) VALUES(?,?,?,?,?)', [
        $table,
        (int)$row['id'],
        json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
        uid(),
        now(),
    ]);
}

function batch_management_view(string $view = ''): string
{
    $view = $view !== '' ? $view : (string)($_GET['view'] ?? 'topics');
    return in_array($view, ['topics', 'replies', 'users', 'trash', 'user_edit'], true) ? $view : 'topics';
}

function batch_management_url(string $view = 'topics', array $params = []): string
{
    return admin_url(array_merge(['tab' => 'batch_management', 'view' => $view], $params));
}

function batch_management_attach_topics(array $rows, string $key = 'topic_id'): array
{
    $topics = rows_by_ids('app_topics', array_column($rows, $key), 'id,title');
    foreach ($rows as &$row) $row['topic_title'] = (string)($topics[(int)($row[$key] ?? 0)]['title'] ?? '主题已删除');
    unset($row);
    return $rows;
}

function batch_management_select_group(int $group_id): string
{
    return select_input('用户组', 'group_id', $group_id, array_column(groups_cache(), 'name', 'id'));
}

function batch_management_fts_rebuild_from(string $type, int $start_id): int
{
    $type = $type === 'replies' ? 'replies' : 'topics';
    $start_id = max(1, $start_id);
    $table = 'app_' . $type;
    $columns = $type === 'replies' ? 'id,body' : 'id,title,body';
    $rows = q("SELECT $columns FROM $table WHERE id>=? ORDER BY id", [$start_id])->fetchAll();
    foreach (array_chunk($rows, 200) as $chunk) {
        tx(function () use ($type, $chunk): void {
            foreach ($chunk as $row) {
                if ($type === 'topics') topic_fts_sync((int)$row['id'], (string)$row['title'], (string)$row['body']);
                else reply_fts_sync((int)$row['id'], (string)$row['body']);
            }
        });
    }
    return count($rows);
}

function batch_management_admin_tabs_hook($items, array $ctx): array
{
    $items = is_array($items) ? $items : [];
    $tab = ['label' => '内容', 'href' => batch_management_url()];
    $result = [];
    $inserted = false;
    foreach ($items as $key => $item) {
        if ($key === 'plugins') {
            $result['batch_management'] = $tab;
            $inserted = true;
        }
        $result[$key] = $item;
    }
    if (!$inserted) $result['batch_management'] = $tab;
    return $result;
}

function batch_management_tabs_html(string $active): string
{
    return tab_bar_html([
        'topics' => ['label' => '主题', 'href' => batch_management_url('topics')],
        'replies' => ['label' => '回帖', 'href' => batch_management_url('replies')],
        'users' => ['label' => '用户', 'href' => batch_management_url('users')],
        'trash' => ['label' => '回收站', 'href' => batch_management_url('trash')],
    ], $active, 'plugin-tabs');
}

function batch_management_search_like(string $query): string
{
    return '%' . strtr($query, ['!' => '!!', '%' => '!%', '_' => '!_']) . '%';
}

function batch_management_topic_field(string $field): string
{
    return in_array($field, ['title', 'body', 'author', 'id'], true) ? $field : 'title';
}

function batch_management_reply_field(string $field): string
{
    return in_array($field, ['body', 'author', 'id'], true) ? $field : 'body';
}

function batch_management_user_field(string $field): string
{
    return in_array($field, ['keyword', 'id'], true) ? $field : 'keyword';
}

function batch_management_filter_sql(string $type, string $query = '', string $field = 'title', int $group_id = 0, int $banned_filter = -1, int $muted_filter = -1, int $forum_id = 0): ?array
{
    $where = [];
    $params = [];
    $like = $query !== '' ? batch_management_search_like($query) : '';
    if ($type === 'users') {
        if ($query !== '') {
            $field = batch_management_user_field($field);
            if ($field === 'id') {
                if (!ctype_digit($query) || (int)$query < 1) return null;
                $where[] = 'id=?';
                $params[] = (int)$query;
            } else {
                $where[] = "(username LIKE ? ESCAPE '!' OR email LIKE ? ESCAPE '!' OR bio LIKE ? ESCAPE '!')";
                $params = [$like, $like, $like];
            }
        }
        if ($group_id > 0) {
            $where[] = 'group_id=?';
            $params[] = $group_id;
        }
        if ($banned_filter >= 0) {
            $where[] = 'is_banned=?';
            $params[] = $banned_filter;
        }
        if ($muted_filter >= 0) {
            $where[] = 'is_muted=?';
            $params[] = $muted_filter;
        }
    } elseif ($type === 'topics') {
        if ($forum_id > 0) {
            $where[] = 'forum_id=?';
            $params[] = $forum_id;
        }
        if ($query !== '') {
            $field = batch_management_topic_field($field);
            if ($field === 'id') {
                if (!ctype_digit($query) || (int)$query < 1) return null;
                $where[] = 'id=?';
                $params[] = (int)$query;
            } elseif ($field === 'author') {
                $user_ids = array_column(q("SELECT id FROM app_users WHERE username LIKE ? ESCAPE '!'", [$like])->fetchAll(), 'id');
                if (!$user_ids) return null;
                $where[] = 'user_id IN (' . sql_marks(count($user_ids)) . ')';
                $params = array_merge($params, $user_ids);
            } else {
                [$condition, $search_params] = content_search_condition($query, $field);
                $where[] = '(' . $condition . ')';
                $params = array_merge($params, $search_params);
            }
        }
    } elseif ($type === 'replies') {
        if ($query !== '') {
            $field = batch_management_reply_field($field);
            if ($field === 'id') {
                if (!ctype_digit($query) || (int)$query < 1) return null;
                $where[] = 'id=?';
                $params[] = (int)$query;
            } elseif ($field === 'author') {
                $user_ids = array_column(q("SELECT id FROM app_users WHERE username LIKE ? ESCAPE '!'", [$like])->fetchAll(), 'id');
                if (!$user_ids) return null;
                $where[] = 'user_id IN (' . sql_marks(count($user_ids)) . ')';
                $params = array_merge($params, $user_ids);
            } else {
                [$condition, $search_params] = content_search_condition($query, 'reply');
                $where[] = '(' . $condition . ')';
                $params = array_merge($params, $search_params);
            }
        }
    } else {
        return null;
    }
    return ['where' => $where ? ' WHERE ' . implode(' AND ', $where) : '', 'params' => $params];
}

function batch_management_count(string $type, int $group_id = 0, int $banned_filter = -1, int $muted_filter = -1, int $forum_id = 0): int
{
    $filter = batch_management_filter_sql($type, '', 'title', $group_id, $banned_filter, $muted_filter, $forum_id);
    if (!$filter) return 0;
    $sql = match ($type) {
        'users' => 'SELECT COUNT(*) FROM app_users',
        'topics' => 'SELECT COUNT(*) FROM app_topics',
        'replies' => 'SELECT COUNT(*) FROM app_replies',
        default => throw new InvalidArgumentException('无效的后台数据类型。'),
    };
    return (int)val($sql . $filter['where'], $filter['params']);
}

function batch_management_list(string $type, string $query, int $size, int $offset, array $filters = []): array
{
    [$table, $columns] = match ($type) {
        'users' => ['app_users', '*'],
        'topics' => ['app_topics', 'id,forum_id,title,highlight_style,user_id,created_at'],
        'replies' => ['app_replies', 'id,body,topic_id,user_id,created_at'],
        default => throw new InvalidArgumentException('无效的后台数据类型。'),
    };
    $field = (string)($filters['field'] ?? match ($type) {
        'users' => 'keyword',
        'replies' => 'body',
        default => 'title',
    });
    $filter = batch_management_filter_sql($type, $query, $field, (int)($filters['group_id'] ?? 0), (int)($filters['banned_filter'] ?? -1), (int)($filters['muted_filter'] ?? -1), (int)($filters['forum_id'] ?? 0));
    if (!$filter) return [];
    $rows = q("SELECT $columns FROM $table" . $filter['where'] . ' ORDER BY id DESC LIMIT ? OFFSET ?', array_merge($filter['params'], [$size, $offset]))->fetchAll();
    if ($type !== 'users') $rows = attach_users($rows);
    return $type === 'replies' ? batch_management_attach_topics($rows) : $rows;
}

function batch_management_search_form(string $view, string $query): string
{
    $field = batch_management_topic_field((string)($_GET['field'] ?? 'title'));
    $select = $view === 'topics' ? '<select class="admin-search-select" name="field"><option value="title"' . ($field === 'title' ? ' selected' : '') . '>标题</option><option value="body"' . ($field === 'body' ? ' selected' : '') . '>内容</option><option value="author"' . ($field === 'author' ? ' selected' : '') . '>作者</option><option value="id"' . ($field === 'id' ? ' selected' : '') . '>主题 ID</option></select>' : '';
    if ($view === 'topics') {
        $forum_id = max(0, (int)($_GET['forum_id'] ?? 0));
        $select .= '<select class="admin-search-select" name="forum_id"><option value="0">全部版块</option>';
        foreach (forums_cache() as $forum) $select .= '<option value="' . (int)$forum['id'] . '"' . ($forum_id === (int)$forum['id'] ? ' selected' : '') . '>' . h($forum['name']) . '</option>';
        $select .= '</select>';
    }
    $group_id = (int)($_GET['group_id'] ?? 0);
    if ($view === 'users') {
        $field = batch_management_user_field((string)($_GET['user_field'] ?? 'keyword'));
        $banned_filter = isset($_GET['is_banned']) && $_GET['is_banned'] !== '' ? (int)$_GET['is_banned'] : -1;
        $muted_filter = isset($_GET['is_muted']) && $_GET['is_muted'] !== '' ? (int)$_GET['is_muted'] : -1;
        $select = '<select class="admin-search-select" name="user_field"><option value="keyword"' . ($field === 'keyword' ? ' selected' : '') . '>关键词</option><option value="id"' . ($field === 'id' ? ' selected' : '') . '>用户 ID</option></select><select class="admin-search-select" name="group_id"><option value="0">全部用户组</option>';
        foreach (groups_cache() as $group) $select .= '<option value="' . (int)$group['id'] . '"' . ($group_id === (int)$group['id'] ? ' selected' : '') . '>' . h($group['name']) . '</option>';
        $select .= '</select><select class="admin-search-select" name="is_muted"><option value="">发言状态</option><option value="1"' . ($muted_filter === 1 ? ' selected' : '') . '>禁止发言</option><option value="0"' . ($muted_filter === 0 ? ' selected' : '') . '>允许发言</option></select><select class="admin-search-select" name="is_banned"><option value="">访问状态</option><option value="1"' . ($banned_filter === 1 ? ' selected' : '') . '>禁止访问</option><option value="0"' . ($banned_filter === 0 ? ' selected' : '') . '>允许访问</option></select>';
    } elseif ($view === 'replies') {
        $field = batch_management_reply_field((string)($_GET['reply_field'] ?? 'body'));
        $select = '<select class="admin-search-select" name="reply_field"><option value="body"' . ($field === 'body' ? ' selected' : '') . '>内容</option><option value="author"' . ($field === 'author' ? ' selected' : '') . '>作者</option><option value="id"' . ($field === 'id' ? ' selected' : '') . '>回帖 ID</option></select>';
    }
    $has_clear = $query !== '';
    if ($view === 'topics') $has_clear = $has_clear || (int)($_GET['forum_id'] ?? 0) > 0 || $field !== 'title';
    if ($view === 'replies') $has_clear = $has_clear || $field !== 'body';
    $hidden = hidden_inputs(['a' => 'admin', 'tab' => 'batch_management', 'view' => $view]);
    $is_id = $field === 'id';
    return '<form class="admin-table-search" method="get" action="' . h(index_url()) . '">' . $hidden . $select . '<div class="admin-search-field"><input name="q" value="' . h($query) . '" placeholder="' . ($is_id ? '输入精确 ID' : '搜索') . '" minlength="1"><button class="admin-search-submit" type="submit">搜索</button></div>' . ($has_clear ? '<a class="admin-search-clear" href="' . h(batch_management_url($view)) . '">清空</a>' : '') . '</form>';
}

function batch_management_bulk_form_open(string $view): string
{
    return '<form id="batch-management-form" method="post" action="' . h(batch_management_url($view)) . '" data-confirm="确定执行批量操作？">' . form_token() . hidden_inputs(['batch_management_action' => 'batch', 'view' => $view]) . '</form>';
}

function batch_management_rebuild_fts_form(string $type): string
{
    if (db_driver() !== 'sqlite' || !sqlite_fts_uses_trigram()) return '';
    $label = $type === 'replies' ? '回帖' : '主题';
    return '<form class="admin-rebuild-fts-form" method="post" action="' . h(batch_management_url($type)) . '" data-prompt-title="重建' . $label . '索引" data-prompt-message="请输入起始' . $label . ' ID，将重建该 ID 及之后的' . $label . '搜索索引。" data-prompt-field="start_id" data-prompt-value="1">' . form_token() . hidden_inputs(['batch_management_action' => 'rebuild_fts', 'fts_type' => $type, 'start_id' => 1]) . '<button class="admin-search-link" type="submit">重建索引</button></form>';
}

function batch_management_pagination(string $view, string $query, int $total, int $page, int $size, string $field, int $group_id, int $banned_filter, int $muted_filter, bool $simple, bool $has_next): string
{
    $params = [];
    if ($query !== '') $params['q'] = $query;
    if ($view === 'topics' && $field !== '') $params['field'] = batch_management_topic_field($field);
    if ($view === 'replies' && $field !== '') $params['reply_field'] = batch_management_reply_field($field);
    if ($view === 'users' && $field !== '') $params['user_field'] = batch_management_user_field($field);
    if ($view === 'topics' && (int)($_GET['forum_id'] ?? 0) > 0) $params['forum_id'] = (int)$_GET['forum_id'];
    if ($view === 'users' && $group_id > 0) $params['group_id'] = $group_id;
    if ($view === 'users' && $banned_filter >= 0) $params['is_banned'] = $banned_filter;
    if ($view === 'users' && $muted_filter >= 0) $params['is_muted'] = $muted_filter;
    $url = batch_management_url($view, $params);
    $html = $simple ? simple_paginate($page > 1, $has_next, $page, $url) : paginate($total, $page, $size, $url);
    return $html === '' ? '' : '<div class="pagination-bar">' . $html . '</div>';
}

function batch_management_row_check_html(int $id): string
{
    return '<input class="admin-row-check" type="checkbox" name="ids[]" value="' . $id . '" form="batch-management-form" aria-label="选择">';
}

function batch_management_user_row(array $user, bool $manageable): string
{
    $group = group_by_id((int)$user['group_id']) ?: ['name' => ''];
    $ops = $manageable ? '<div class="admin-inline-ops"><a href="' . h(batch_management_url('user_edit', ['id' => (int)$user['id']])) . '">编辑</a>' . post_action_form(batch_management_url('users'), '删除', ['batch_management_action' => 'delete', 'type' => 'users', 'id' => (int)$user['id']], 'danger', '确定删除？') . batch_management_row_check_html((int)$user['id']) . '</div>' : '';
    $states = array_filter([h($group['name']), '积分 ' . (int)($user['points'] ?? 0), (int)($user['is_banned'] ?? 0) ? '禁访' : '', (int)($user['is_muted'] ?? 0) ? '禁言' : '']);
    return '<li class="admin-list-item admin-object-row admin-user-row"><div class="admin-row-main"><div class="admin-user-cell">' . avatar_tag((int)$user['id'], (string)$user['username'], (string)($user['avatar_style'] ?? ''), 'table-avatar', (string)($user['avatar_seed'] ?? '')) . '<div class="admin-user-text"><strong>' . h($user['username']) . '</strong><span>' . implode(' / ', $states) . ' · ID ' . (int)$user['id'] . '</span></div></div></div>' . $ops . '</li>';
}

function batch_management_topic_row(array $topic, bool $manageable): string
{
    $url = route_url('topic', ['id' => (int)$topic['id']]);
    $ops = $manageable ? '<div class="admin-inline-ops"><a href="' . h(route_url('topic_edit', ['id' => (int)$topic['id']])) . '">编辑</a>' . post_action_form(batch_management_url('topics'), '删除', ['batch_management_action' => 'delete', 'type' => 'topics', 'id' => (int)$topic['id']], 'danger', '确定删除？') . batch_management_row_check_html((int)$topic['id']) . '</div>' : '';
    $forum = forum_by_id((int)($topic['forum_id'] ?? 0)) ?: ['name' => ''];
    $forum_tag = $forum['name'] !== '' ? '<span class="admin-forum-name">' . h($forum['name']) . '</span>' : '';
    return '<li class="admin-list-item admin-object-row admin-topic-row"><div class="admin-row-main"><a class="admin-content-title" href="' . h($url) . '" target="_blank" rel="noopener">' . h($topic['title']) . '</a><div class="admin-row-meta"><span class="admin-author-mini">' . avatar_tag((int)$topic['user_id'], (string)$topic['username'], (string)($topic['avatar_style'] ?? ''), 'table-avatar', (string)($topic['avatar_seed'] ?? '')) . h($topic['username']) . '</span><span>ID ' . (int)$topic['id'] . '</span><span>' . date('Y-m-d H:i', (int)$topic['created_at']) . '</span>' . $forum_tag . '</div></div>' . $ops . '</li>';
}

function batch_management_reply_row(array $reply, bool $manageable): string
{
    $topic_url = route_url('topic', ['id' => (int)$reply['topic_id'], 'replyid' => (int)$reply['id']]);
    $topic_title = (string)($reply['topic_title'] ?? '主题已删除');
    $ops = $manageable ? '<div class="admin-inline-ops"><a href="' . h(route_url('reply_edit', ['id' => (int)$reply['id']])) . '">编辑</a>' . post_action_form(batch_management_url('replies'), '删除', ['batch_management_action' => 'delete', 'type' => 'replies', 'id' => (int)$reply['id']], 'danger', '确定删除？') . batch_management_row_check_html((int)$reply['id']) . '</div>' : '';
    $excerpt = content_excerpt(content_preview_source_text((string)$reply['body']), 150);
    return '<li class="admin-list-item admin-object-row admin-reply-row"><div class="admin-row-main"><a class="admin-reply-topic-title" href="' . h($topic_url) . '" target="_blank" rel="noopener">' . h($topic_title) . '</a><div class="admin-content-text">' . h($excerpt) . '</div><div class="admin-row-meta"><span class="admin-author-mini">' . avatar_tag((int)$reply['user_id'], (string)$reply['username'], (string)($reply['avatar_style'] ?? ''), 'table-avatar', (string)($reply['avatar_seed'] ?? '')) . h($reply['username']) . '</span><span>回帖 #' . (int)$reply['id'] . '</span><span>主题 #' . (int)$reply['topic_id'] . '</span><span>' . date('Y-m-d H:i', (int)$reply['created_at']) . '</span></div></div>' . $ops . '</li>';
}

function batch_management_bulk_bar(string $view): string
{
    if ($view === 'users') {
        $actions = '<select class="bulk-action-select" name="batch_action" form="batch-management-form"><option value="delete">删除</option><option value="mute">禁止发言</option><option value="unmute">取消禁止发言</option><option value="ban">禁止访问</option><option value="unban">取消禁止访问</option></select>';
    } elseif ($view === 'topics') {
        $forum_select = '<span class="bulk-forum-wrap is-hidden" data-bulk-forum-wrap><select class="bulk-action-select" name="forum_id" form="batch-management-form">';
        foreach (forums_cache() as $forum) $forum_select .= '<option value="' . (int)$forum['id'] . '">' . h($forum['name']) . '</option>';
        $forum_select .= '</select></span>';
        $actions = '<div class="bulk-action-group"><select class="bulk-action-select" name="batch_action" form="batch-management-form" data-bulk-action><option value="delete">删除</option><option value="move">批量转移</option></select>' . $forum_select . '</div>';
    } elseif ($view === 'trash') {
        $actions = '<select class="bulk-action-select" name="batch_action" form="batch-management-form"><option value="restore">恢复</option></select>';
    } else {
        $actions = '<select class="bulk-action-select" name="batch_action" form="batch-management-form"><option value="delete">删除</option></select>';
    }
    return '<div class="bulk-bar"><label class="bulk-select-all"><input type="checkbox" data-select-all><span>全选</span></label>' . $actions . '<button class="danger bulk-delete" type="submit" form="batch-management-form">执行</button></div>';
}

function batch_management_object_list_html(string $view, string $query, bool $manageable, int $size, callable $count_rows, callable $load_rows, callable $render_pagination, string $head_right = ''): string
{
    $render_row = ['users' => 'batch_management_user_row', 'topics' => 'batch_management_topic_row', 'replies' => 'batch_management_reply_row'][$view] ?? throw new InvalidArgumentException('不支持的后台列表类型');
    $simple = $query !== '';
    $total = $simple ? 0 : (int)$count_rows();
    $rows = $load_rows();
    $has_next = $simple && count($rows) > $size;
    if ($has_next) $rows = array_slice($rows, 0, $size);
    $html = $manageable ? batch_management_bulk_form_open($view) : '';
    $html .= '<div class="admin-list-panel">' . admin_list_head(batch_management_search_form($view, $query), $head_right) . '<ul class="admin-manage-list">';
    foreach ($rows as $row) $html .= $render_row($row, $manageable);
    if (!$rows) $html .= '<li class="empty-state">暂无数据</li>';
    $html .= '</ul></div>';
    if ($manageable) $html .= batch_management_bulk_bar($view);
    return $html . $render_pagination($total, $simple, $has_next);
}

function batch_management_trash_restore(int $id): string
{
    $trash = one('SELECT * FROM plugin_batch_management_trash WHERE id=?', [$id]) ?: err('记录不存在');
    $table = (string)$trash['table_name'];
    if (!in_array($table, ['users', 'topics', 'replies'], true)) err('参数错误');
    $physical_table = match ($table) {
        'users' => 'app_users',
        'topics' => 'app_topics',
        'replies' => 'app_replies',
    };
    $row = json_decode((string)$trash['row_data'], true);
    if (!is_array($row)) err('数据错误');
    $columns = array_keys(app_db_columns(db(), db_driver(), $physical_table));
    $fields = [];
    $values = [];
    foreach ($columns as $column) {
        $name = (string)$column;
        $fields[] = $name;
        $values[] = $row[$name] ?? null;
    }
    q('DELETE FROM ' . $physical_table . ' WHERE id=?', [(int)$row['id']]);
    q('INSERT INTO ' . $physical_table . ' (' . implode(',', $fields) . ') VALUES(' . sql_marks(count($fields)) . ')', $values);
    if ($table === 'topics') topic_fts_sync((int)$row['id'], (string)($row['title'] ?? ''), (string)($row['body'] ?? ''));
    if ($table === 'replies') {
        reply_fts_sync((int)$row['id'], (string)($row['body'] ?? ''));
        refresh_topic_stats((int)($row['topic_id'] ?? 0));
    }
    q('DELETE FROM plugin_batch_management_trash WHERE id=?', [$id]);
    return $table;
}

function batch_management_trash_count(string $table): int
{
    return $table !== '' ? (int)val('SELECT COUNT(*) FROM plugin_batch_management_trash WHERE table_name=?', [$table]) : (int)val('SELECT COUNT(*) FROM plugin_batch_management_trash');
}

function batch_management_trash_list(string $table, int $size, int $offset): array
{
    $rows = $table !== '' ? q('SELECT * FROM plugin_batch_management_trash WHERE table_name=? ORDER BY id DESC LIMIT ? OFFSET ?', [$table, $size, $offset])->fetchAll() : q('SELECT * FROM plugin_batch_management_trash ORDER BY id DESC LIMIT ? OFFSET ?', [$size, $offset])->fetchAll();
    $users = rows_by_ids('app_users', array_column($rows, 'deleted_by'), 'id,username');
    foreach ($rows as &$row) $row['deleted_username'] = (string)($users[(int)($row['deleted_by'] ?? 0)]['username'] ?? '用户删除');
    unset($row);
    return $rows;
}

function batch_management_trash_search_form(string $table): string
{
    $html = '<form class="admin-table-search" method="get" action="' . h(index_url()) . '">' . hidden_inputs(['a' => 'admin', 'tab' => 'batch_management', 'view' => 'trash']) . '<select class="admin-search-select" name="table"><option value="">全部</option>';
    foreach (['users' => '用户', 'topics' => '主题', 'replies' => '回帖'] as $key => $label) $html .= '<option value="' . $key . '"' . ($table === $key ? ' selected' : '') . '>' . $label . '</option>';
    return $html . '</select><button class="admin-search-link" type="submit">筛选</button></form>';
}

function batch_management_trash_row(array $row): string
{
    $data = json_decode((string)$row['row_data'], true);
    $title = (['users' => '用户', 'topics' => '主题', 'replies' => '回帖'][(string)$row['table_name']] ?? (string)$row['table_name']) . ' #' . (int)$row['row_id'];
    $summary = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : (string)$row['row_data'];
    $restore = post_action_form(batch_management_url('trash'), '恢复', ['batch_management_action' => 'restore', 'id' => (int)$row['id']], '', '确定恢复？');
    return '<li class="admin-list-item admin-object-row admin-trash-row">' . batch_management_row_check_html((int)$row['id']) . '<div class="admin-row-main"><div class="admin-topic-user"><span class="admin-group-pill">' . h($title) . '</span><span class="admin-dot">·</span>删除人：' . h((string)$row['deleted_username']) . '<span class="admin-dot">·</span>删除时间：' . date('Y-m-d H:i', (int)$row['created_at']) . '</div><pre class="admin-trash-data">' . h($summary) . '</pre></div><div class="admin-inline-ops">' . $restore . '</div></li>';
}

function batch_management_handle_post(string $view): void
{
    require_post();
    need_manage();
    $action = (string)($_POST['batch_management_action'] ?? '');
    if ($action === 'save_user') {
        if (id() === 1 && !is_super_user()) err('无权限');
        save_user(true);
        go(batch_management_url('users'));
    }
    if ($action === 'delete') {
        $type = (string)($_POST['type'] ?? '');
        if (!in_array($type, ['users', 'topics', 'replies'], true)) err('参数错误');
        if (!can_admin_delete($type, id())) err('无权限');
        del($type, id());
        go(batch_management_url($type));
    }
    if ($action === 'restore') {
        if (batch_management_trash_restore(id()) === 'topics') home_stats_refresh_topics();
        go(batch_management_url('trash'));
    }
    if ($action === 'rebuild_fts') {
        $type = (string)($_POST['fts_type'] ?? 'topics') === 'replies' ? 'replies' : 'topics';
        if (db_driver() !== 'sqlite') {
            set_flash('当前数据库的搜索索引由数据库自动维护，无需重建');
            go(batch_management_url($type));
        }
        if (!sqlite_fts_uses_trigram()) {
            set_flash('当前 SQLite 不支持 trigram，搜索使用 LIKE，无需重建索引');
            go(batch_management_url($type));
        }
        $start_id = max(1, (int)($_POST['start_id'] ?? 1));
        $count = batch_management_fts_rebuild_from($type, $start_id);
        set_flash('已重建' . ($type === 'replies' ? '回帖' : '主题') . '索引：' . $count . ' 条');
        go(batch_management_url($type));
    }
    if ($action !== 'batch') err('参数错误');
    $view = in_array($view, ['users', 'topics', 'replies', 'trash'], true) ? $view : 'topics';
    $batch_action = (string)($_POST['batch_action'] ?? 'delete');
    $ids = array_values(array_unique(array_filter(array_map('intval', $_POST['ids'] ?? []))));
    if ($view === 'trash' && $batch_action === 'restore') {
        $topics_changed = false;
        foreach ($ids as $trash_id) if (batch_management_trash_restore($trash_id) === 'topics') $topics_changed = true;
        if ($topics_changed) home_stats_refresh_topics();
    } elseif ($view === 'users' && in_array($batch_action, ['mute', 'unmute', 'ban', 'unban'], true)) {
        $field = in_array($batch_action, ['ban', 'unban'], true) ? 'is_banned' : 'is_muted';
        $value = in_array($batch_action, ['ban', 'mute'], true) ? 1 : 0;
        $ids = array_values(array_diff($ids, [1, uid()]));
        if ($ids) q("UPDATE app_users SET $field=? WHERE id IN (" . sql_marks(count($ids)) . ')', array_merge([$value], $ids));
    } elseif ($view === 'topics' && $batch_action === 'move') {
        $forum_id = max(1, (int)($_POST['forum_id'] ?? 0));
        if (!forum_by_id($forum_id)) err('版块不存在');
        if ($ids) q('UPDATE app_topics SET forum_id=?,last_reply_at=? WHERE id IN (' . sql_marks(count($ids)) . ')', array_merge([$forum_id, now()], $ids));
    } elseif ($batch_action === 'delete') {
        foreach ($ids as $row_id) {
            if (!can_admin_delete($view, $row_id)) continue;
            del($view, $row_id);
        }
    }
    go(batch_management_url($view));
}

function batch_management_user_edit_page(): string
{
    need_manage();
    if (id() === 1 && !is_super_user()) err('无权限');
    $user = id() ? (user_by_id(id()) ?: err('用户不存在')) : ['id' => 0, 'username' => '', 'email' => '', 'bio' => '', 'avatar_style' => '', 'avatar_seed' => '', 'group_id' => (int)setting('default_group_id', '2'), 'points' => 0];
    $is_new = id() === 0;
    $body = input('用户名', 'username', $user['username'], 'text', true) . input('邮箱', 'email', $user['email'], 'email') . input($is_new ? '密码' : '新密码', 'password', '', 'password', $is_new) . input('确认密码', 'password2', '', 'password', $is_new) . avatar_picker_html($user) . batch_management_select_group((int)$user['group_id']) . number_input('积分', 'points', (int)($user['points'] ?? 0)) . checkbox('禁止访问', 'is_banned', (bool)(int)($user['is_banned'] ?? 0)) . checkbox('禁止发言', 'is_muted', (bool)(int)($user['is_muted'] ?? 0)) . textarea('简介', 'bio', $user['bio']);
    return '<div class="form-panel"><h2>编辑用户</h2><form method="post" action="' . h(batch_management_url('user_edit', ['id' => id()])) . '">' . form_token() . hidden_inputs(['batch_management_action' => 'save_user', 'id' => id()]) . $body . '<button>保存</button></form></div>';
}

function batch_management_admin_page(array $plugin): string
{
    $view = batch_management_view();
    if ($_SERVER['REQUEST_METHOD'] === 'POST') batch_management_handle_post($view);
    $active = $view === 'user_edit' ? 'users' : $view;
    $tabs = batch_management_tabs_html($active);
    if ($view === 'user_edit') return $tabs . batch_management_user_edit_page();
    $query = trim((string)($_GET['q'] ?? ''));
    $search_field = match ($view) {
        'users' => batch_management_user_field((string)($_GET['user_field'] ?? 'keyword')),
        'replies' => batch_management_reply_field((string)($_GET['reply_field'] ?? 'body')),
        default => batch_management_topic_field((string)($_GET['field'] ?? 'title')),
    };
    if ($search_field !== 'id') require_search_min_chars($query);
    $manageable = can_manage();
    $size = 50;
    $page = max(1, (int)($_GET['p'] ?? 1));
    $offset = ($page - 1) * $size;
    if ($view === 'users') {
        $field = batch_management_user_field((string)($_GET['user_field'] ?? 'keyword'));
        $group_id = max(0, (int)($_GET['group_id'] ?? 0));
        $banned_filter = isset($_GET['is_banned']) && $_GET['is_banned'] !== '' ? (int)$_GET['is_banned'] : -1;
        $muted_filter = isset($_GET['is_muted']) && $_GET['is_muted'] !== '' ? (int)$_GET['is_muted'] : -1;
        $body = batch_management_object_list_html('users', $query, $manageable, $size,
            fn(): int => batch_management_count('users', $group_id, $banned_filter, $muted_filter),
            fn(): array => batch_management_list('users', $query, $query !== '' ? $size + 1 : $size, $offset, ['field' => $field, 'group_id' => $group_id, 'banned_filter' => $banned_filter, 'muted_filter' => $muted_filter]),
            fn(int $total, bool $simple, bool $has_next): string => batch_management_pagination('users', $query, $total, $page, $size, $field, $group_id, $banned_filter, $muted_filter, $simple, $has_next)
        );
    } elseif ($view === 'replies') {
        $field = batch_management_reply_field((string)($_GET['reply_field'] ?? 'body'));
        $body = batch_management_object_list_html('replies', $query, $manageable, $size,
            fn(): int => batch_management_count('replies'),
            fn(): array => batch_management_list('replies', $query, $query !== '' ? $size + 1 : $size, $offset, ['field' => $field]),
            fn(int $total, bool $simple, bool $has_next): string => batch_management_pagination('replies', $query, $total, $page, $size, $field, 0, -1, -1, $simple, $has_next),
            $manageable ? batch_management_rebuild_fts_form('replies') : ''
        );
    } elseif ($view === 'trash') {
        $table = in_array((string)($_GET['table'] ?? ''), ['users', 'topics', 'replies'], true) ? (string)$_GET['table'] : '';
        $total = batch_management_trash_count($table);
        $body = $manageable ? batch_management_bulk_form_open('trash') : '';
        $body .= '<div class="admin-list-panel">' . admin_list_head(batch_management_trash_search_form($table), '') . '<ul class="admin-manage-list">';
        $rows = batch_management_trash_list($table, $size, $offset);
        foreach ($rows as $row) $body .= batch_management_trash_row($row);
        if (!$rows) $body .= '<li class="empty-state">暂无数据</li>';
        $body .= '</ul></div>';
        if ($manageable) $body .= batch_management_bulk_bar('trash');
        $pagination = paginate($total, $page, $size, batch_management_url('trash', ['table' => $table]));
        if ($pagination !== '') $body .= '<div class="pagination-bar">' . $pagination . '</div>';
    } else {
        $field = batch_management_topic_field((string)($_GET['field'] ?? 'title'));
        $forum_id = max(0, (int)($_GET['forum_id'] ?? 0));
        $tools = $manageable ? batch_management_rebuild_fts_form('topics') : '';
        $body = batch_management_object_list_html('topics', $query, $manageable, $size,
            fn(): int => batch_management_count('topics', 0, -1, -1, $forum_id),
            fn(): array => batch_management_list('topics', $query, $query !== '' ? $size + 1 : $size, $offset, ['field' => $field, 'forum_id' => $forum_id]),
            fn(int $total, bool $simple, bool $has_next): string => batch_management_pagination('topics', $query, $total, $page, $size, $field, 0, -1, -1, $simple, $has_next),
            $tools
        );
    }
    return $tabs . $body;
}

function batch_management_js(): string
{
    return <<<'JS'
document.addEventListener('change', event => {
    const selectAll = event.target.closest('[data-select-all]');
    if (selectAll) {
        document.querySelectorAll('input[type="checkbox"][name="ids[]"][form="batch-management-form"]').forEach(box => {
            box.checked = selectAll.checked;
        });
        return;
    }
    const action = event.target.closest('[data-bulk-action]');
    if (!action) return;
    const forum = action.closest('.bulk-action-group')?.querySelector('[data-bulk-forum-wrap]');
    if (forum) forum.classList.toggle('is-hidden', action.value !== 'move');
});
JS;
}

return [
    'id' => 'batch_management',
    'name' => '内容批量管理',
    'version' => '1.2.0',
    'description' => '集中管理主题、回帖和用户，并可批量处理或恢复已删除内容。',
    'author' => 'bbs1org',
    'assets' => ['js' => 'batch_management_js'],
    'hooks' => ['admin.tabs' => 'batch_management_admin_tabs_hook', 'content.before_delete' => 'batch_management_store_trash'],
    'admin_tabs' => ['batch_management' => 'batch_management_admin_page'],
    'install' => 'batch_management_install',
    'uninstall' => 'batch_management_uninstall',
];