<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SDK 权限管理</title>
    <script>
        (function () {
            var token = localStorage.getItem('pb_manage_token') || ''
            var isLoggedIn = localStorage.getItem('pb_manage_logged_in') === '1'
            if (!token || !isLoggedIn) {
                window.location.replace('/pb/manage/login')
            }
        })()
    </script>
    <style>
        * { box-sizing: border-box; }
        body { margin: 0; font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(180deg, #eef6ff 0%, #f8fafc 100%); color: #0f172a; }
        .wrap { max-width: 1440px; margin: 0 auto; padding: 24px 14px 40px; }
        .panel { background: rgba(255,255,255,0.96); border: 1px solid #e5e7eb; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); border-radius: 20px; padding: 18px; }
        .panel + .panel { margin-top: 14px; }
        h1, h2, h3 { margin-top: 0; }
        p { color: #475569; line-height: 1.7; }
        .actions, .toolbar, .row-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
        .btn { border: none; border-radius: 12px; padding: 10px 16px; font-weight: 700; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }
        .btn-primary { background: #2563eb; color: #fff; }
        .btn-light { background: #f8fafc; color: #334155; border: 1px solid #dbe3f0; }
        .btn-danger { background: #dc2626; color: #fff; }
        .btn-warning { background: #f59e0b; color: #fff; }
        .btn-success { background: #16a34a; color: #fff; }
        .status { margin-top: 12px; min-height: 24px; font-size: 14px; }
        .status.success { color: #15803d; }
        .status.error { color: #b91c1c; }
        .note { padding: 14px 16px; border-radius: 16px; background: #eff6ff; color: #1d4ed8; font-size: 14px; line-height: 1.7; }
        table { width: 100%; border-collapse: collapse; }
        thead { background: #eff6ff; }
        th, td { padding: 14px 12px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: top; }
        th { font-size: 13px; color: #475569; }
        tr:hover td { background: #f8fafc; }
        input, textarea, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 9px 11px; font-size: 14px; background: #fff; }
        textarea { min-height: 80px; resize: vertical; }
        .empty { text-align: center; padding: 24px 16px; color: #64748b; }
        .muted { color: #64748b; font-size: 12px; }
        .grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
        .full { grid-column: 1 / -1; }
        .rule-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
        .rule-card { border: 1px solid #dbe3f0; border-radius: 16px; padding: 12px; background: #f8fbff; }
        .rule-card h4 { margin: 0 0 10px; font-size: 14px; display: flex; align-items: center; gap: 8px; }
        .rule-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; color: #475569; font-size: 12px; }
        .rule-meta input[type="checkbox"] { width: auto; margin: 0; }
        .rule-toggle { display: inline-flex; align-items: center; gap: 6px; color: #0f172a; font-size: 13px; font-weight: 600; }
        .rule-toggle input[type="checkbox"] { width: auto; margin: 0; }
        .split { display: grid; grid-template-columns: 1.15fr 1fr; gap: 14px; }
        .table-wrap { overflow: auto; }
        .collection-table { table-layout: fixed; }
        .collection-col { width: 264px; }
        .rule-col { width: calc(100% - 264px); }
        .collection-meta { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
        .loading-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.42); backdrop-filter: blur(4px); z-index: 9998; }
        .loading-mask.show { display: flex; }
        .loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
        .loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: sdkSpin 0.9s linear infinite; }
        .loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
        @keyframes sdkSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        @media (max-width: 1100px) {
            .split, .rule-grid, .grid { grid-template-columns: 1fr; }
            .collection-col, .rule-col { width: auto; }
            table, thead, tbody, th, td, tr { display: block; }
            thead { display: none; }
            tr { margin-bottom: 14px; border: 1px solid #e5e7eb; border-radius: 16px; overflow: hidden; }
            td { display: flex; flex-direction: column; gap: 8px; }
        }
    </style>
    {{ template "theme_head" . }}
</head>
<body>
    <main class="wrap">
        <section class="panel">
            <h1>SDK 权限管理</h1>
            <p>这里管理的是 <code>tbl_auth_users</code> 用户通过 PocketBase SDK 直连数据库时的业务权限。<strong>ManagePlatform</strong> 会被视为你的业务管理员，但不会自动变成 PocketBase 原生 <code>_superusers</code>。</p>
            <div class="actions">
                <a class="btn btn-light" href="/pb/manage">返回主页</a>
                <button class="btn btn-light" id="refreshBtn" type="button">刷新数据</button>
                <button class="btn btn-success" id="syncManageBtn" type="button">同步 ManagePlatform 全权限</button>
            </div>
            <div class="status" id="status"></div>
        </section>

        <section class="panel">
            <div class="note" id="noteBox">加载中...</div>
        </section>

        <section class="panel">
            <h2>角色管理</h2>
            <div class="grid">
                <input id="newRoleName" placeholder="角色名称" />
                <input id="newRoleCode" placeholder="角色编码，可为空" />
                <input id="newRoleStatus" type="number" placeholder="状态，默认1" value="1" />
                <button class="btn btn-primary" id="createRoleBtn" type="button">新增角色</button>
                <div class="muted">角色 ID 由系统自动生成，页面不显示。</div>
                <textarea id="newRoleRemark" class="full" placeholder="备注"></textarea>
            </div>
            <div class="table-wrap" style="margin-top:16px;">
                <table>
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>编码</th>
                            <th>状态</th>
                            <th>备注</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="roleTableBody">
                        <tr><td colspan="5" class="empty">暂无角色。</td></tr>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="panel">
            <h2>用户授权</h2>
            <div class="toolbar">
                <input id="userKeywordInput" placeholder="按姓名、手机号、openid、角色搜索" />
                <button class="btn btn-light" id="searchUserBtn" type="button">查询用户</button>
            </div>
            <div class="table-wrap" style="margin-top:16px;">
                <table>
                    <thead>
                        <tr>
                            <th>用户</th>
                            <th>身份类型</th>
                            <th>当前角色</th>
                            <th>授权</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="userTableBody">
                        <tr><td colspan="5" class="empty">暂无用户。</td></tr>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="panel">
            <h2>Collection 直连权限</h2>
            <div class="toolbar">
                <select id="permissionRoleSelect"></select>
                <div class="muted">这里是按“当前配置角色”逐表分配 CRUD 权限。下面依次对应“列表、详情、新增、修改、删除”五种权限，勾选后会立即保存。公开访问或自定义规则的操作不会显示授权勾选框。</div>
            </div>
            <div class="table-wrap" style="margin-top:16px;">
                <table class="collection-table">
                    <thead>
                        <tr>
                            <th class="collection-col">集合</th>
                            <th class="rule-col">当前角色权限</th>
                        </tr>
                    </thead>
                    <tbody id="collectionTableBody">
                        <tr><td colspan="2" class="empty">暂无集合。</td></tr>
                    </tbody>
                </table>
            </div>
        </section>
    </main>

    <div class="loading-mask" id="loadingMask">
        <div class="loading-card">
            <div class="loading-spinner"></div>
            <div class="loading-text" id="loadingText">处理中，请稍候...</div>
        </div>
    </div>

    <script>
        const API_BASE = '/pb/api/sdk-permission'
        const tokenKey = 'pb_manage_token'
        const statusEl = document.getElementById('status')
        const noteBox = document.getElementById('noteBox')
        const roleTableBody = document.getElementById('roleTableBody')
        const userTableBody = document.getElementById('userTableBody')
        const collectionTableBody = document.getElementById('collectionTableBody')
        const permissionRoleSelect = document.getElementById('permissionRoleSelect')
        const loadingMask = document.getElementById('loadingMask')
        const loadingText = document.getElementById('loadingText')
        const loadingState = { count: 0 }
        const state = {
            roles: [],
            users: [],
            collections: [],
            userKeyword: '',
            selectedPermissionRoleId: '',
        }

        function setStatus(message, type) {
            statusEl.textContent = message || ''
            statusEl.className = 'status' + (type ? ' ' + type : '')
        }

        function showLoading(message) {
            loadingState.count += 1
            loadingText.textContent = message || '处理中，请稍候...'
            loadingMask.classList.add('show')
        }

        function hideLoading() {
            loadingState.count = Math.max(0, loadingState.count - 1)
            if (!loadingState.count) {
                loadingMask.classList.remove('show')
                loadingText.textContent = '处理中，请稍候...'
            }
        }

        function getToken() {
            return localStorage.getItem(tokenKey) || ''
        }

        function escapeHtml(value) {
            return String(value || '')
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;')
        }

        function getRoleById(roleId) {
            const target = String(roleId || '')
            return state.roles.find(function (role) {
                return role.role_id === target
            }) || null
        }

        function getRoleName(roleId) {
            const role = getRoleById(roleId)
            return role ? role.role_name : ''
        }

        function syncSelectedPermissionRole() {
            const exists = state.roles.some(function (role) {
                return role.role_id === state.selectedPermissionRoleId
            })
            if (!exists) {
                state.selectedPermissionRoleId = state.roles.length ? state.roles[0].role_id : ''
            }
        }

        function decodeErrMsg(value) {
            if (!value) {
                return ''
            }

            try {
                return decodeURIComponent(value)
            } catch (_err) {
                return String(value)
            }
        }

        function unwrapApiResponse(data, res) {
            const safe = data && typeof data === 'object' ? data : {}
            const headerCode = Number(res && res.headers ? (res.headers.get('X-Status-Code') || 0) : 0)
            const headerErrMsg = decodeErrMsg(res && res.headers ? res.headers.get('X-Err-Msg') : '')
            const code = Number(safe.statusCode || safe.code || headerCode || 0)
            const errMsg = safe.errMsg || safe.msg || headerErrMsg || ''
            const hasLegacyEnvelope = Object.prototype.hasOwnProperty.call(safe, 'data')
                && (Object.prototype.hasOwnProperty.call(safe, 'code')
                    || Object.prototype.hasOwnProperty.call(safe, 'statusCode')
                    || Object.prototype.hasOwnProperty.call(safe, 'msg')
                    || Object.prototype.hasOwnProperty.call(safe, 'errMsg'))
            const payload = hasLegacyEnvelope ? (safe.data || {}) : safe
            return { code: code, errMsg: errMsg, payload: payload }
        }

        async function requestJson(path, payload) {
            const token = getToken()
            if (!token) {
                localStorage.removeItem('pb_manage_logged_in')
                window.location.replace('/pb/manage/login')
                throw new Error('登录状态已失效，请重新登录')
            }

            const res = await fetch(API_BASE + path, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: 'Bearer ' + token,
                },
                body: JSON.stringify(payload || {}),
            })

            const data = await res.json()
            const unwrapped = unwrapApiResponse(data, res)
            if (!res.ok || !data || unwrapped.code >= 400) {
                if (res.status === 401 || res.status === 403 || unwrapped.code === 401 || unwrapped.code === 403) {
                    localStorage.removeItem('pb_manage_token')
                    localStorage.removeItem('pb_manage_logged_in')
                    localStorage.removeItem('pb_manage_login_account')
                    localStorage.removeItem('pb_manage_login_time')
                    window.location.replace('/pb/manage/login')
                }
                throw new Error(unwrapped.errMsg || '请求失败')
            }

            return unwrapped.payload
        }

        function roleOptionsHtml(selectedRoleId) {
            const current = String(selectedRoleId || '')
            return ['<option value="">未分配</option>'].concat(state.roles.map(function (role) {
                return '<option value="' + escapeHtml(role.role_id) + '"' + (current === role.role_id ? ' selected' : '') + '>' + escapeHtml(role.role_name) + '</option>'
            })).join('')
        }

        function renderRoles() {
            if (!state.roles.length) {
                roleTableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无角色。</td></tr>'
                return
            }

            roleTableBody.innerHTML = state.roles.map(function (role) {
                return '<tr data-role-id="' + escapeHtml(role.role_id) + '">'
                    + '<td><input data-role-field="role_name" value="' + escapeHtml(role.role_name) + '" /></td>'
                    + '<td><input data-role-field="role_code" value="' + escapeHtml(role.role_code) + '" /></td>'
                    + '<td><input data-role-field="role_status" type="number" value="' + escapeHtml(role.role_status) + '" /></td>'
                    + '<td><textarea data-role-field="role_remark">' + escapeHtml(role.role_remark) + '</textarea></td>'
                    + '<td><div class="row-actions"><button class="btn btn-light" type="button" onclick="window.__saveRoleRow(\'' + encodeURIComponent(role.role_id) + '\')">保存</button><button class="btn btn-danger" type="button" onclick="window.__deleteRoleRow(\'' + encodeURIComponent(role.role_id) + '\')">删除</button></div></td>'
                    + '</tr>'
            }).join('')
        }

        function renderUsers() {
            if (!state.users.length) {
                userTableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无匹配用户。</td></tr>'
                return
            }

            userTableBody.innerHTML = state.users.map(function (user) {
                const name = user.users_name || '未命名用户'
                const phone = user.users_phone || '无手机号'
                const roleName = user.role_name || getRoleName(user.usergroups_id) || '未分配'
                return '<tr data-user-id="' + escapeHtml(user.pb_id) + '">'
                    + '<td><div><strong>' + escapeHtml(name) + '</strong></div><div class="muted">' + escapeHtml(phone) + '</div><div class="muted">' + escapeHtml(user.openid) + '</div></td>'
                    + '<td><div>' + escapeHtml(user.users_idtype || '') + '</div><div class="muted">' + escapeHtml(user.users_type || '') + '</div></td>'
                    + '<td>' + escapeHtml(roleName) + '</td>'
                    + '<td><select data-user-role-select="1">' + roleOptionsHtml(user.usergroups_id) + '</select></td>'
                    + '<td><button class="btn btn-light" type="button" onclick="window.__saveUserRole(\'' + escapeHtml(user.pb_id) + '\')">保存角色</button></td>'
                    + '</tr>'
            }).join('')
        }

        function renderPermissionRoleOptions() {
            if (!state.roles.length) {
                permissionRoleSelect.innerHTML = '<option value="">暂无角色</option>'
                permissionRoleSelect.disabled = true
                return
            }

            permissionRoleSelect.disabled = false
            permissionRoleSelect.innerHTML = state.roles.map(function (role) {
                return '<option value="' + escapeHtml(role.role_id) + '"' + (role.role_id === state.selectedPermissionRoleId ? ' selected' : '') + '>' + escapeHtml(role.role_name) + '</option>'
            }).join('')
        }

        function getOperationLabel(operation) {
            const map = {
                list: '列表',
                view: '详情',
                create: '新增',
                update: '修改',
                delete: '删除',
            }
            return map[operation] || operation
        }

        function getRuleSummary(config, selectedRoleId) {
            const current = config || { mode: 'locked', includeManagePlatform: false, roles: [], rawExpression: '' }
            const items = []
            if (current.mode === 'public') items.push('公开可访问')
            if (current.mode === 'authenticated') items.push('登录用户可访问')
            if (current.includeManagePlatform) items.push('含 ManagePlatform')
            if (current.mode === 'custom') items.push('自定义规则')
            if (Array.isArray(current.roles) && current.roles.length) {
                items.push('已分配角色数：' + current.roles.length)
            }
            return items.length ? items.join('，') : '当前无额外说明'
        }

        function canControlRule(config) {
            const current = config || { mode: 'locked', includeManagePlatform: false, roles: [], rawExpression: '' }
            return !!state.selectedPermissionRoleId && current.mode !== 'custom' && current.mode !== 'public'
        }

        function isCollectionFullyChecked(collection) {
            if (!collection || !collection.parsedRules) {
                return false
            }

            const operations = ['list', 'view', 'create', 'update', 'delete']
            let controllableCount = 0
            let checkedCount = 0

            for (let i = 0; i < operations.length; i += 1) {
                const config = collection.parsedRules[operations[i]]
                if (!canControlRule(config)) {
                    continue
                }
                controllableCount += 1
                if (config && Array.isArray(config.roles) && config.roles.indexOf(state.selectedPermissionRoleId) !== -1) {
                    checkedCount += 1
                }
            }

            return controllableCount > 0 && controllableCount === checkedCount
        }

        function getCollectionControllableCount(collection) {
            if (!collection || !collection.parsedRules) {
                return 0
            }

            const operations = ['list', 'view', 'create', 'update', 'delete']
            let controllableCount = 0

            for (let i = 0; i < operations.length; i += 1) {
                if (canControlRule(collection.parsedRules[operations[i]])) {
                    controllableCount += 1
                }
            }

            return controllableCount
        }

        function renderRuleCard(collectionName, operation, config) {
            const current = config || { mode: 'locked', includeManagePlatform: false, roles: [], rawExpression: '' }
            const selectedRoleId = state.selectedPermissionRoleId
            const checked = selectedRoleId && Array.isArray(current.roles) && current.roles.indexOf(selectedRoleId) !== -1
            const canControl = canControlRule(current)
            const summary = current.mode === 'public'
                ? '可公开访问'
                : getRuleSummary(current, selectedRoleId)
            return '<div class="rule-card" data-collection="' + escapeHtml(collectionName) + '" data-op="' + operation + '">'
                + '<h4>' + (canControl ? '<label class="rule-toggle"><input type="checkbox" data-rule-field="allowSelectedRole"' + (checked ? ' checked' : '') + ' /></label>' : '') + '<span>' + getOperationLabel(operation) + '</span></h4>'
                + '<div class="muted" style="margin-top:8px;">' + escapeHtml(summary) + '</div>'
                + (current.mode === 'custom' ? '<div class="muted" style="margin-top:8px;">当前操作使用 custom 规则，禁止修改</div>' : '')
                + '</div>'
        }

        function renderCollections() {
            if (!state.collections.length) {
                collectionTableBody.innerHTML = '<tr><td colspan="2" class="empty">暂无可管理集合。</td></tr>'
                return
            }

            collectionTableBody.innerHTML = state.collections.map(function (collection) {
                const allChecked = isCollectionFullyChecked(collection)
                const controllableCount = getCollectionControllableCount(collection)
                return '<tr data-collection-row="' + escapeHtml(collection.name) + '">'
                    + '<td class="collection-col"><div class="collection-meta"><div><strong>' + escapeHtml(collection.name) + '</strong></div><div class="muted">' + escapeHtml(collection.type) + '</div><label class="rule-toggle"><input type="checkbox" data-rule-field="toggleCollection"' + (allChecked ? ' checked' : '') + (state.selectedPermissionRoleId && controllableCount > 0 ? '' : ' disabled') + ' /><span>全选</span></label></div></td>'
                    + '<td><div class="rule-grid">'
                    + renderRuleCard(collection.name, 'list', collection.parsedRules.list)
                    + renderRuleCard(collection.name, 'view', collection.parsedRules.view)
                    + renderRuleCard(collection.name, 'create', collection.parsedRules.create)
                    + renderRuleCard(collection.name, 'update', collection.parsedRules.update)
                    + renderRuleCard(collection.name, 'delete', collection.parsedRules.delete)
                    + '</div></td>'
                    + '</tr>'
            }).join('')
        }

        async function loadContext() {
            showLoading('正在加载权限管理数据...')
            setStatus('正在加载权限管理数据...', '')
            try {
                const data = await requestJson('/context', { keyword: state.userKeyword })
                state.roles = Array.isArray(data.roles) ? data.roles : []
                state.users = Array.isArray(data.users) ? data.users : []
                state.collections = Array.isArray(data.collections) ? data.collections : []
                syncSelectedPermissionRole()
                noteBox.textContent = data.note || '权限管理说明已加载。'
                renderRoles()
                renderUsers()
                renderPermissionRoleOptions()
                renderCollections()
                setStatus('权限管理数据已刷新。', 'success')
            } catch (err) {
                setStatus(err.message || '加载权限管理数据失败', 'error')
            } finally {
                hideLoading()
            }
        }

        function getRoleRowPayload(roleId) {
            const targetId = decodeURIComponent(roleId)
            const row = roleTableBody.querySelector('[data-role-id="' + targetId.replace(/"/g, '\\"') + '"]')
            if (!row) {
                throw new Error('未找到对应角色行')
            }

            const find = function (fieldName) {
                const input = row.querySelector('[data-role-field="' + fieldName + '"]')
                return input ? input.value : ''
            }

            return {
                original_role_id: targetId,
                role_name: find('role_name'),
                role_code: find('role_code'),
                role_status: find('role_status'),
                role_remark: find('role_remark'),
            }
        }

        function getRuleBoxConfig(collectionName, operation) {
            const collection = state.collections.find(function (item) {
                return item.name === collectionName
            })
            const current = collection && collection.parsedRules ? collection.parsedRules[operation] : null
            const base = current || {
                mode: 'locked',
                includeManagePlatform: false,
                roles: [],
                rawExpression: '',
            }
            if (base.mode === 'custom') {
                return {
                    mode: 'custom',
                    includeManagePlatform: !!base.includeManagePlatform,
                    roles: Array.isArray(base.roles) ? base.roles.slice() : [],
                    rawExpression: base.rawExpression || '',
                }
            }

            const box = collectionTableBody.querySelector('.rule-card[data-collection="' + collectionName.replace(/"/g, '\\"') + '"][data-op="' + operation + '"]')
            const allowEl = box ? box.querySelector('[data-rule-field="allowSelectedRole"]') : null
            const roles = Array.isArray(base.roles) ? base.roles.slice() : []
            const selectedRoleId = state.selectedPermissionRoleId
            const nextRoles = roles.filter(function (roleId) {
                return roleId !== selectedRoleId
            })

            if (selectedRoleId && allowEl && allowEl.checked && nextRoles.indexOf(selectedRoleId) === -1) {
                nextRoles.push(selectedRoleId)
            }

            let nextMode = base.mode
            if (nextMode === 'locked' && nextRoles.length) {
                nextMode = 'roleBased'
            } else if (nextMode === 'roleBased' && !nextRoles.length && !base.includeManagePlatform) {
                nextMode = 'locked'
            }

            return {
                mode: nextMode,
                includeManagePlatform: !!base.includeManagePlatform,
                roles: nextRoles,
                rawExpression: base.rawExpression || '',
            }
        }

        async function createRole() {
            const payload = {
                role_name: document.getElementById('newRoleName').value.trim(),
                role_code: document.getElementById('newRoleCode').value.trim(),
                role_status: document.getElementById('newRoleStatus').value.trim() || '1',
                role_remark: document.getElementById('newRoleRemark').value.trim(),
            }

            showLoading('正在新增角色...')
            try {
                await requestJson('/role-save', payload)
                document.getElementById('newRoleName').value = ''
                document.getElementById('newRoleCode').value = ''
                document.getElementById('newRoleStatus').value = '1'
                document.getElementById('newRoleRemark').value = ''
                await loadContext()
                setStatus('角色新增成功。', 'success')
            } catch (err) {
                setStatus(err.message || '新增角色失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function saveRoleRow(roleId) {
            showLoading('正在保存角色...')
            try {
                await requestJson('/role-save', getRoleRowPayload(roleId))
                await loadContext()
                setStatus('角色保存成功。', 'success')
            } catch (err) {
                setStatus(err.message || '保存角色失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function deleteRoleRow(roleId) {
            const targetId = decodeURIComponent(roleId)
            if (!window.confirm('确认删除角色「' + targetId + '」吗？这会清空绑定该角色的用户，并从已解析的集合规则中移除它。')) {
                return
            }

            showLoading('正在删除角色...')
            try {
                await requestJson('/role-delete', { role_id: targetId })
                await loadContext()
                setStatus('角色删除成功。', 'success')
            } catch (err) {
                setStatus(err.message || '删除角色失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function saveUserRole(pbId) {
            const row = userTableBody.querySelector('[data-user-id="' + pbId.replace(/"/g, '\\"') + '"]')
            const select = row ? row.querySelector('[data-user-role-select="1"]') : null
            const payload = {
                pb_id: pbId,
                usergroups_id: select ? select.value : '',
            }

            showLoading('正在保存用户角色...')
            try {
                await requestJson('/user-role-update', payload)
                await loadContext()
                setStatus('用户角色已更新。', 'success')
            } catch (err) {
                setStatus(err.message || '更新用户角色失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function saveCollectionRules(collectionName) {
            const targetName = decodeURIComponent(collectionName)
            if (!state.selectedPermissionRoleId) {
                setStatus('请先选择一个要配置权限的角色。', 'error')
                return
            }
            const payload = {
                collection_name: targetName,
                rules: {
                    list: getRuleBoxConfig(targetName, 'list'),
                    view: getRuleBoxConfig(targetName, 'view'),
                    create: getRuleBoxConfig(targetName, 'create'),
                    update: getRuleBoxConfig(targetName, 'update'),
                    delete: getRuleBoxConfig(targetName, 'delete'),
                },
            }

            showLoading('正在同步集合权限...')
            try {
                await requestJson('/collection-save', payload)
                await loadContext()
                setStatus('已保存角色「' + (getRoleName(state.selectedPermissionRoleId) || '未命名角色') + '」在集合「' + targetName + '」上的权限。', 'success')
            } catch (err) {
                setStatus(err.message || '保存集合权限失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function syncManagePlatform() {
            if (!window.confirm('确认将 ManagePlatform 同步为所有业务集合的全权限吗？这不会创建 _superusers，但会为业务表开放全部 CRUD。')) {
                return
            }

            showLoading('正在同步 ManagePlatform 全权限...')
            try {
                const data = await requestJson('/manageplatform-sync', {})
                await loadContext()
                setStatus('已同步 ManagePlatform 全权限，共处理 ' + String((data && data.count) || 0) + ' 个集合。', 'success')
            } catch (err) {
                setStatus(err.message || '同步 ManagePlatform 全权限失败', 'error')
            } finally {
                hideLoading()
            }
        }

        window.__saveRoleRow = saveRoleRow
        window.__deleteRoleRow = deleteRoleRow
        window.__saveUserRole = saveUserRole
        window.__saveCollectionRules = saveCollectionRules

        document.getElementById('createRoleBtn').addEventListener('click', createRole)
        document.getElementById('refreshBtn').addEventListener('click', loadContext)
        document.getElementById('syncManageBtn').addEventListener('click', syncManagePlatform)
        permissionRoleSelect.addEventListener('change', function () {
            state.selectedPermissionRoleId = permissionRoleSelect.value || ''
            renderPermissionRoleOptions()
            renderCollections()
        })
        collectionTableBody.addEventListener('change', function (event) {
            const target = event.target
            if (!target) {
                return
            }

            const field = target.getAttribute('data-rule-field')
            if (field === 'allowSelectedRole') {
                const box = target.closest('.rule-card')
                if (!box) {
                    return
                }
                const collectionName = box.getAttribute('data-collection') || ''
                saveCollectionRules(collectionName)
                return
            }

            if (field === 'toggleCollection') {
                const row = target.closest('[data-collection-row]')
                if (!row) {
                    return
                }
                const checkboxes = row.querySelectorAll('[data-rule-field="allowSelectedRole"]')
                for (let i = 0; i < checkboxes.length; i += 1) {
                    if (!checkboxes[i].disabled) {
                        checkboxes[i].checked = !!target.checked
                    }
                }
                const collectionName = row.getAttribute('data-collection-row') || ''
                saveCollectionRules(collectionName)
            }
        })
        document.getElementById('searchUserBtn').addEventListener('click', function () {
            state.userKeyword = document.getElementById('userKeywordInput').value.trim()
            loadContext()
        })

        loadContext()
    </script>
    {{ template "theme_body" . }}
</body>
</html>