<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>文档管理</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: #1f2937; }
        .container { 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 { margin-top: 0; }
        p { color: #4b5563; line-height: 1.7; }
        .actions, .form-actions, .table-actions, .file-actions { display: flex; flex-wrap: wrap; gap: 12px; }
        .toolbar { display: grid; grid-template-columns: 1.4fr 1fr auto auto; gap: 10px; }
        .btn { border: none; border-radius: 12px; padding: 10px 16px; font-weight: 600; 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; }
        .status { margin-top: 14px; min-height: 24px; font-size: 14px; }
        .status.success { color: #15803d; }
        .status.error { color: #b91c1c; }
        .editor-panel { display: none; }
        .editor-panel.show { display: block; }
        .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
        .full { grid-column: 1 / -1; }
        label { display: block; margin-bottom: 8px; color: #334155; font-weight: 600; }
        input, textarea { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; }
        textarea { min-height: 96px; resize: vertical; }
        .hint { color: #64748b; font-size: 12px; margin-top: 6px; }
        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; }
        .empty { text-align: center; padding: 24px 16px; color: #64748b; }
        .muted { color: #64748b; font-size: 12px; }
        .doc-links a, .file-card a { display: inline-block; margin-right: 10px; color: #2563eb; text-decoration: none; font-weight: 600; }
        .btn-warning { background: #f59e0b; color: #fff; }
        .editor-banner { display: inline-flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; font-size: 13px; font-weight: 700; }
        .file-group { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 16px; }
        .file-box { border: 1px solid #dbe3f0; border-radius: 18px; padding: 16px; background: #f8fbff; }
        .option-box { border: 1px solid #dbe3f0; border-radius: 16px; background: #f8fbff; padding: 14px; }
        .option-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-top: 12px; }
        .option-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; background: #fff; border: 1px solid #e5e7eb; min-height: 48px; }
        .option-item input[type="checkbox"] { width: auto; margin: 0; }
        .option-item span { display: block; word-break: break-word; }
        .selection-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
        .selection-tag { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; font-size: 12px; font-weight: 700; }
        .choice-switch { display: inline-flex; gap: 8px; padding: 6px; border-radius: 14px; border: 1px solid #dbe3f0; background: #f8fbff; }
        .choice-switch button { border: 1px solid transparent; background: transparent; color: #475569; padding: 9px 18px; border-radius: 10px; font-weight: 700; cursor: pointer; }
        .choice-switch button.active { background: #2563eb; color: #fff; border-color: #2563eb; }
        .dropzone { margin-top: 8px; border: 2px dashed #bfdbfe; border-radius: 16px; background: #ffffff; padding: 16px; transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease; cursor: pointer; }
        .dropzone.dragover { border-color: #2563eb; background: #eff6ff; box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.08); }
        .dropzone-title { font-weight: 700; color: #1e3a8a; }
        .dropzone-text { color: #475569; font-size: 13px; margin-top: 6px; }
        .dropzone input[type="file"] { margin-top: 12px; }
        .file-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 12px; }
        .file-card { min-width: 0; min-height: 122px; border: 1px solid #dbe3f0; border-radius: 14px; padding: 8px; background: #fff; display: flex; flex-direction: column; gap: 6px; }
        .file-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
        .file-card-title { flex: 1; font-weight: 700; font-size: 12px; line-height: 1.3; word-break: break-all; }
        .file-card-icon { width: 24px; height: 24px; border-radius: 999px; border: 1px solid #dbe3f0; background: #fff; color: #475569; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; padding: 0; }
        .file-card-icon:hover { border-color: #94a3b8; background: #f8fafc; }
        .file-meta { display: none; }
        .thumb { width: 60px; height: 60px; border-radius: 10px; object-fit: cover; border: 1px solid #dbe3f0; background: #fff; cursor: zoom-in; }
        .file-preview { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #f8fafc; color: #334155; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; }
        .thumb-strip { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
        .image-viewer { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.82); z-index: 9999; }
        .image-viewer.show { display: flex; }
        .image-viewer img { max-width: min(92vw, 1600px); max-height: 88vh; border-radius: 18px; box-shadow: 0 24px 70px rgba(15, 23, 42, 0.38); background: #fff; }
        .image-viewer-close { position: absolute; top: 18px; right: 18px; min-width: 44px; min-height: 44px; border-radius: 999px; border: none; background: rgba(255,255,255,0.92); color: #0f172a; font-size: 24px; cursor: pointer; }
        .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: docSpin 0.9s linear infinite; }
        .loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
        .pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
        .pagination-info { color: #475569; font-size: 14px; }
        .pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
        .pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
        .pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
        .pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
        .pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
        .pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
        .pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
        .pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
        html[data-theme="dark"] .editor-banner { background: rgba(30, 41, 59, 0.86); color: #bfdbfe; }
        html[data-theme="dark"] .file-box { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.22); box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.08); }
        html[data-theme="dark"] .choice-switch { background: rgba(15, 23, 42, 0.86); border-color: rgba(148, 163, 184, 0.26); }
        html[data-theme="dark"] .choice-switch button { color: #cbd5e1; }
        html[data-theme="dark"] .choice-switch button.active { background: #3b82f6; color: #eff6ff; border-color: #3b82f6; }
        html[data-theme="dark"] .dropzone { background: rgba(15, 23, 42, 0.86); border-color: rgba(96, 165, 250, 0.45); }
        html[data-theme="dark"] .dropzone.dragover { background: rgba(30, 58, 138, 0.32); border-color: #60a5fa; box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.12); }
        html[data-theme="dark"] .dropzone-title { color: #bfdbfe; }
        html[data-theme="dark"] .dropzone-text { color: #94a3b8; }
        html[data-theme="dark"] .file-card,
        html[data-theme="dark"] .file-preview,
        html[data-theme="dark"] .file-card-icon { background: rgba(2, 6, 23, 0.9); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; }
        html[data-theme="dark"] .file-card-icon:hover { background: rgba(30, 41, 59, 0.82); border-color: rgba(148, 163, 184, 0.4); }
        html[data-theme="dark"] .pagination-info,
        html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
        html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
        html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
        html[data-theme="dark"] .pagination-btn.active,
        html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
        html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
        html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
        html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
        @keyframes docSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        @media (max-width: 960px) {
            .grid, .file-group { grid-template-columns: 1fr; }
            .option-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .file-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            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; justify-content: space-between; gap: 12px; }
            td::before { content: attr(data-label); font-weight: 700; color: #475569; }
        }
    </style>
    {{ template "theme_head" . }}
</head>
<body>
    <div class="container">
        <section class="panel">
            <h1>文档管理</h1>
            <div class="actions">
                <a class="btn btn-light" href="/pb/manage">返回主页</a>
                <button class="btn btn-light" id="reloadBtn" type="button">刷新列表</button>
                <button class="btn btn-light" id="createModeBtn" type="button">新建模式</button>
            </div>
            <div class="status" id="status"></div>
        </section>

        <section class="panel editor-panel" id="editorPanel">
            <div class="toolbar" style="justify-content:space-between;align-items:center;">
                <div>
                    <h2 id="formTitle">新增文档</h2>
                </div>
                <div class="editor-banner" id="editorMode">当前模式：新建</div>
            </div>
            <div class="grid" style="margin-top:16px;">
                <div>
                    <label for="documentTitle">文档标题</label>
                    <input id="documentTitle" placeholder="请输入文档标题" />
                </div>
                <div>
                    <label for="embeddingStatus">嵌入状态</label>
                    <input id="embeddingStatus" placeholder="可为空" />
                </div>
                <div class="full">
                    <label for="documentSubtitle">副标题</label>
                    <input id="documentSubtitle" placeholder="可选" />
                </div>
                <div>
                    <label for="documentTypeSource">文档类型</label>
                    <div class="option-box">
                        <select id="documentTypeSource">
                            <option value="">请选择数据来源字典</option>
                        </select>
                        <div class="option-list" id="documentTypeOptions"></div>
                        <div class="selection-tags" id="documentTypeTags"></div>
                    </div>
                </div>
                <div>
                    <label for="documentStatus">文档状态</label>
                    <input id="documentStatus" type="hidden" value="有效" />
                    <div class="choice-switch" id="documentStatusSwitch">
                        <button type="button" data-status-value="有效" class="active">有效</button>
                        <button type="button" data-status-value="过期">过期</button>
                    </div>
                    <div class="hint">系统会根据生效日期和到期日期自动切换状态；都不填写时默认有效。</div>
                </div>
                <div>
                    <label for="effectDate">生效日期</label>
                    <input id="effectDate" type="date" />
                </div>
                <div>
                    <label for="expiryDate">到期日期</label>
                    <input id="expiryDate" type="date" />
                </div>
                <div class="full">
                    <label for="documentSummary">摘要</label>
                    <textarea id="documentSummary" placeholder="请输入文档摘要"></textarea>
                </div>
                <div class="full">
                    <label for="documentContent">正文内容</label>
                    <textarea id="documentContent" placeholder="支持 Markdown 原文"></textarea>
                </div>
                <div>
                    <label for="relationModel">关联机型</label>
                    <input id="relationModel" placeholder="可选" />
                </div>
                <div>
                    <label for="vectorVersion">向量版本</label>
                    <input id="vectorVersion" placeholder="可选" />
                </div>
                <div>
                    <label>关键词</label>
                    <div class="option-box">
                        <div class="hint">可不选，也可多选。</div>
                        <div class="option-list" id="documentKeywordsOptions"></div>
                        <div class="selection-tags" id="documentKeywordsTags"></div>
                    </div>
                </div>
                <div>
                    <label>产品关联文档</label>
                    <div class="option-box">
                        <div class="hint">可不选，也可多选。</div>
                        <div class="option-list" id="productCategoriesOptions"></div>
                        <div class="selection-tags" id="productCategoriesTags"></div>
                    </div>
                </div>
                <div>
                    <label>筛选依据</label>
                    <div class="option-box">
                        <div class="hint">可不选，也可多选。</div>
                        <div class="option-list" id="applicationScenariosOptions"></div>
                        <div class="selection-tags" id="applicationScenariosTags"></div>
                    </div>
                </div>
                <div>
                    <label>适用场景</label>
                    <div class="option-box">
                        <div class="hint">可不选，也可多选。</div>
                        <div class="option-list" id="hotelTypeOptions"></div>
                        <div class="selection-tags" id="hotelTypeTags"></div>
                    </div>
                </div>
                <div class="full">
                    <label for="documentRemark">备注</label>
                    <textarea id="documentRemark" placeholder="可选"></textarea>
                </div>
            </div>
            <div class="file-group">
                <div class="file-box">
                    <h3>图片附件</h3>
                    <label for="imageFile">新增图片</label>
                    <div class="dropzone" id="imageDropzone">
                        <div class="dropzone-title">拖拽图片到这里，或点击选择文件</div>
                        <input id="imageFile" type="file" multiple />
                    </div>
                    <div class="file-list" id="imageCurrentList"></div>
                    <div class="file-list" id="imagePendingList"></div>
                </div>
                <div class="file-box">
                    <h3>视频附件</h3>
                    <label for="videoFile">新增视频</label>
                    <div class="dropzone" id="videoDropzone">
                        <div class="dropzone-title">拖拽视频到这里，或点击选择文件</div>
                        <input id="videoFile" type="file" multiple />
                    </div>
                    <div class="file-list" id="videoCurrentList"></div>
                    <div class="file-list" id="videoPendingList"></div>
                </div>
                <div class="file-box">
                    <h3>文件附件</h3>
                    <label for="documentFile">新增文件</label>
                    <div class="dropzone" id="documentFileDropzone">
                        <div class="dropzone-title">拖拽文件到这里，或点击选择文件</div>
                        <input id="documentFile" type="file" multiple />
                    </div>
                    <div class="file-list" id="documentFileCurrentList"></div>
                    <div class="file-list" id="documentFilePendingList"></div>
                </div>
            </div>
            <div class="form-actions" style="margin-top:16px;">
                <button class="btn btn-primary" id="submitBtn" type="button">上传附件并创建文档</button>
                <button class="btn btn-warning" id="cancelEditBtn" type="button">取消编辑</button>
                <button class="btn btn-light" id="resetBtn" type="button">重置表单</button>
            </div>
            <div class="status" id="editorStatus"></div>
        </section>

        <section class="panel">
            <h2>文档列表</h2>
            <div class="toolbar" style="margin-bottom: 12px;">
                <div>
                    <label for="listTitleKeyword">标题模糊搜索</label>
                    <input id="listTitleKeyword" placeholder="请输入标题关键字" />
                </div>
                <div>
                    <label for="listTypeFilter">类型筛选</label>
                    <select id="listTypeFilter" style="min-width: 220px; min-height: 44px; font-size: 16px;">
                        <option value="">全部类型</option>
                    </select>
                </div>
                <button class="btn btn-primary" id="searchBtn" type="button">查询</button>
                <button class="btn btn-light" id="clearSearchBtn" type="button">清空筛选</button>
            </div>
            <div style="overflow:auto;">
                <table>
                    <thead>
                        <tr>
                            <th>标题</th>
                            <th>类型/状态</th>
                            <th>附件链接</th>
                            <th>更新时间</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="tableBody">
                        <tr><td colspan="6" class="empty">暂无数据，请先刷新列表。</td></tr>
                    </tbody>
                </table>
            </div>
            <div class="pagination" id="listPagination"></div>
        </section>
    </div>

    <div class="image-viewer" id="imageViewer">
        <button class="image-viewer-close" id="closeImageViewerBtn" type="button">×</button>
        <img id="imageViewerImg" src="" alt="预览原图" />
    </div>

    <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 tokenKey = 'pb_manage_token'
        const API_BASE = '/pb/api'
        const statusEl = document.getElementById('status')
        const editorStatusEl = document.getElementById('editorStatus')
        const editorPanelEl = document.getElementById('editorPanel')
        const tableBody = document.getElementById('tableBody')
        const formTitleEl = document.getElementById('formTitle')
        const editorModeEl = document.getElementById('editorMode')
        const imageCurrentListEl = document.getElementById('imageCurrentList')
        const imagePendingListEl = document.getElementById('imagePendingList')
        const videoCurrentListEl = document.getElementById('videoCurrentList')
        const videoPendingListEl = document.getElementById('videoPendingList')
        const documentFileCurrentListEl = document.getElementById('documentFileCurrentList')
        const documentFilePendingListEl = document.getElementById('documentFilePendingList')
        const imageDropzoneEl = document.getElementById('imageDropzone')
        const videoDropzoneEl = document.getElementById('videoDropzone')
        const documentFileDropzoneEl = document.getElementById('documentFileDropzone')
        const documentTypeSourceEl = document.getElementById('documentTypeSource')
        const documentTypeOptionsEl = document.getElementById('documentTypeOptions')
        const documentTypeTagsEl = document.getElementById('documentTypeTags')
        const documentStatusSwitchEl = document.getElementById('documentStatusSwitch')
        const documentKeywordsOptionsEl = document.getElementById('documentKeywordsOptions')
        const documentKeywordsTagsEl = document.getElementById('documentKeywordsTags')
        const productCategoriesOptionsEl = document.getElementById('productCategoriesOptions')
        const productCategoriesTagsEl = document.getElementById('productCategoriesTags')
        const applicationScenariosOptionsEl = document.getElementById('applicationScenariosOptions')
        const applicationScenariosTagsEl = document.getElementById('applicationScenariosTags')
        const hotelTypeOptionsEl = document.getElementById('hotelTypeOptions')
        const hotelTypeTagsEl = document.getElementById('hotelTypeTags')
        const listTitleKeywordEl = document.getElementById('listTitleKeyword')
        const listTypeFilterEl = document.getElementById('listTypeFilter')
        const listPaginationEl = document.getElementById('listPagination')
        const imageViewerEl = document.getElementById('imageViewer')
        const imageViewerImgEl = document.getElementById('imageViewerImg')
        const loadingMaskEl = document.getElementById('loadingMask')
        const loadingTextEl = document.getElementById('loadingText')
        const loadingState = { count: 0 }
        const fields = {
            documentTitle: document.getElementById('documentTitle'),
            documentStatus: document.getElementById('documentStatus'),
            embeddingStatus: document.getElementById('embeddingStatus'),
            effectDate: document.getElementById('effectDate'),
            expiryDate: document.getElementById('expiryDate'),
            documentSubtitle: document.getElementById('documentSubtitle'),
            documentSummary: document.getElementById('documentSummary'),
            documentContent: document.getElementById('documentContent'),
            relationModel: document.getElementById('relationModel'),
            vectorVersion: document.getElementById('vectorVersion'),
            documentRemark: document.getElementById('documentRemark'),
            imageFile: document.getElementById('imageFile'),
            videoFile: document.getElementById('videoFile'),
            documentFile: document.getElementById('documentFile'),
        }
        const dictionaryFieldConfig = {
            documentKeywords: {
                dictName: '文档-关键词',
                optionsEl: documentKeywordsOptionsEl,
                tagsEl: documentKeywordsTagsEl,
            },
            productCategories: {
                dictName: '文档-产品关联文档',
                optionsEl: productCategoriesOptionsEl,
                tagsEl: productCategoriesTagsEl,
            },
            applicationScenarios: {
                dictName: '文档-筛选依据',
                optionsEl: applicationScenariosOptionsEl,
                tagsEl: applicationScenariosTagsEl,
            },
            hotelType: {
                dictName: '文档-适用场景',
                optionsEl: hotelTypeOptionsEl,
                tagsEl: hotelTypeTagsEl,
            },
        }
        const state = {
            list: [],
            fullList: [],
            filteredList: [],
            mode: 'idle',
            editingId: '',
            editingSource: null,
            currentImageAttachments: [],
            currentVideoAttachments: [],
            currentFileAttachments: [],
            pendingImageFiles: [],
            pendingVideoFiles: [],
            pendingDocumentFiles: [],
            removedAttachmentIds: [],
            dictionaries: [],
            dictionariesByName: {},
            dictionariesById: {},
            selections: {
                documentTypeSource: '',
                documentTypeValues: [],
                documentKeywords: [],
                productCategories: [],
                applicationScenarios: [],
                hotelType: [],
            },
            listFilters: {
                titleKeyword: '',
                type: '',
            },
            pagination: {
                page: 1,
                pageSize: 10,
                total: 0,
                totalPages: 1,
            },
        }

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

        function setEditorVisible(visible) {
            if (!editorPanelEl) {
                return
            }
            editorPanelEl.classList.toggle('show', !!visible)
        }

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

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

        function escapeJsString(value) {
            return String(value || '')
                .replace(/\\\\/g, '\\\\\\\\')
                .replace(/'/g, "\\'")
        }

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

        function getApiUrl(path) {
            return API_BASE + path
        }

        function toDateInputValue(value) {
            const text = String(value || '')
            const match = text.match(/^\\d{4}-\\d{2}-\\d{2}/)
            return match ? match[0] : ''
        }

        function getTodayDateString() {
            const now = new Date()
            const year = now.getFullYear()
            const month = String(now.getMonth() + 1).padStart(2, '0')
            const day = String(now.getDate()).padStart(2, '0')
            return year + '-' + month + '-' + day
        }

        function calculateDocumentStatus(effectDate, expiryDate, fallbackStatus) {
            const start = toDateInputValue(effectDate)
            const end = toDateInputValue(expiryDate)
            const today = getTodayDateString()

            if (!start && !end) {
                return fallbackStatus === '过期' ? '过期' : '有效'
            }
            if (start && today < start) {
                return '过期'
            }
            if (end && today > end) {
                return '过期'
            }
            return '有效'
        }

        function updateDocumentStatusSwitch() {
            const buttons = documentStatusSwitchEl ? documentStatusSwitchEl.querySelectorAll('[data-status-value]') : []
            const currentValue = fields.documentStatus.value || '有效'
            for (let i = 0; i < buttons.length; i += 1) {
                const button = buttons[i]
                const isActive = button.getAttribute('data-status-value') === currentValue
                button.classList.toggle('active', isActive)
            }
        }

        function syncDocumentStatus(preferredStatus) {
            fields.documentStatus.value = calculateDocumentStatus(fields.effectDate.value, fields.expiryDate.value, preferredStatus || fields.documentStatus.value || '有效')
            updateDocumentStatusSwitch()
        }

        function normalizeAttachmentList(attachments, ids, urls) {
            const attachmentArray = Array.isArray(attachments) ? attachments : []
            if (attachmentArray.length) {
                return attachmentArray.map(function (item) {
                    return {
                        attachments_id: item.attachments_id || '',
                        attachments_filename: item.attachments_filename || item.attachments_id || '',
                        attachments_url: item.attachments_url || '',
                        attachments_download_url: item.attachments_download_url || '',
                        attachments_filetype: item.attachments_filetype || '',
                        attachments_size: item.attachments_size || '',
                    }
                })
            }

            const idArray = Array.isArray(ids) ? ids : []
            const urlArray = Array.isArray(urls) ? urls : []
            return idArray.map(function (id, index) {
                return {
                    attachments_id: id,
                    attachments_filename: id,
                    attachments_url: urlArray[index] || '',
                    attachments_download_url: '',
                    attachments_filetype: '',
                    attachments_size: '',
                }
            })
        }

        function splitPipeValue(value) {
            return String(value || '')
                .split('|')
                .map(function (item) { return String(item || '').trim() })
                .filter(function (item) { return !!item })
        }

        function joinPipeValue(values) {
            return Array.from(new Set((values || []).map(function (item) {
                return String(item || '').trim()
            }).filter(function (item) {
                return !!item
            }))).join('|')
        }

        function findDictionaryByName(dictName) {
            return state.dictionariesByName[dictName] || null
        }

        function getDocumentTypeSourceDictionary() {
            const sourceId = String(state.selections.documentTypeSource || '')
            return sourceId ? (state.dictionariesById[sourceId] || null) : null
        }

        function normalizeDocumentTypeEnumValue(value) {
            const raw = String(value || '').trim()
            if (!raw) {
                return ''
            }

            const separatorIndex = raw.indexOf('@')
            return separatorIndex === -1 ? raw : raw.slice(separatorIndex + 1)
        }

        function buildDocumentTypeStorageValue() {
            const sourceDict = getDocumentTypeSourceDictionary()
            if (!sourceDict) {
                return ''
            }

            const sourceId = String(sourceDict.system_dict_id || '').trim()
            if (!sourceId) {
                return ''
            }

            const seen = {}
            const parts = []

            state.selections.documentTypeValues.forEach(function (value) {
                const enumValue = normalizeDocumentTypeEnumValue(value)
                if (!enumValue) {
                    return
                }

                const token = sourceId + '@' + enumValue
                if (seen[token]) {
                    return
                }

                seen[token] = true
                parts.push(token)
            })

            return joinPipeValue(parts)
        }

        function updateSelection(fieldName, value, checked) {
            const target = state.selections[fieldName] || []
            const next = target.filter(function (item) {
                return item !== value
            })

            if (checked) {
                next.push(value)
            }

            state.selections[fieldName] = next
            renderDictionarySelectors()
        }

        function renderSelectionTags(tagsEl, values, labelsMap) {
            if (!values.length) {
                tagsEl.innerHTML = '<span class="muted">未选择</span>'
                return
            }

            tagsEl.innerHTML = values.map(function (value) {
                return '<span class="selection-tag">' + escapeHtml(labelsMap[value] || value) + '</span>'
            }).join('')
        }

        function formatDocumentTypeDisplay(value) {
            const parts = splitPipeValue(value)
            if (!parts.length) {
                return ''
            }

            return parts.map(function (part) {
                const separatorIndex = part.indexOf('@')
                if (separatorIndex === -1) {
                    return part
                }

                const sourceId = part.slice(0, separatorIndex)
                const enumValue = part.slice(separatorIndex + 1)
                const dict = state.dictionariesById[sourceId]
                if (!dict || !Array.isArray(dict.items)) {
                    return enumValue
                }

                const matched = dict.items.find(function (item) {
                    return String(item.enum || '') === enumValue
                })

                return matched ? (matched.description || matched.enum || enumValue) : enumValue
            }).join(' | ')
        }

        function renderOptionList(optionsEl, selectedValues, items, fieldName) {
            if (!items || !items.length) {
                optionsEl.innerHTML = '<div class="muted">暂无可选项</div>'
                return {}
            }

            const labelsMap = {}
            optionsEl.innerHTML = items.map(function (item) {
                const optionValue = String(item.enum || '')
                const checked = selectedValues.indexOf(optionValue) !== -1
                labelsMap[optionValue] = item.description || item.enum || optionValue
                return '<label class="option-item">'
                    + '<input type="checkbox" data-selection-field="' + fieldName + '" value="' + escapeHtml(optionValue) + '"' + (checked ? ' checked' : '') + ' />'
                    + '<span>' + escapeHtml(item.description || item.enum || optionValue) + '</span>'
                    + '</label>'
            }).join('')

            return labelsMap
        }

        function renderDocumentTypeSourceOptions() {
            const currentValue = String(state.selections.documentTypeSource || '')
            documentTypeSourceEl.innerHTML = ['<option value="">请选择数据来源字典</option>']
                .concat(state.dictionaries.map(function (dict) {
                    return '<option value="' + escapeHtml(dict.system_dict_id) + '"' + (currentValue === dict.system_dict_id ? ' selected' : '') + '>' + escapeHtml(dict.dict_name) + '</option>'
                }))
                .join('')
        }

        function buildDocumentTypeFilterOptions() {
            return state.dictionaries
                .map(function (dict) {
                    const sourceId = String(dict && dict.system_dict_id ? dict.system_dict_id : '').trim()
                    if (!sourceId) {
                        return null
                    }

                    return {
                        value: sourceId,
                        label: dict.dict_name || sourceId,
                    }
                })
                .filter(function (item) {
                    return !!item
                })
        }

        function renderListTypeFilterOptions() {
            if (!listTypeFilterEl) {
                return
            }

            const currentValue = String(state.listFilters.type || '')
            const options = buildDocumentTypeFilterOptions()
            listTypeFilterEl.innerHTML = ['<option value="">全部类型</option>']
                .concat(options.map(function (item) {
                    return '<option value="' + escapeHtml(item.value) + '"' + (item.value === currentValue ? ' selected' : '') + '>' + escapeHtml(item.label) + '</option>'
                }))
                .join('')
        }

        function renderDictionarySelectors() {
            renderDocumentTypeSourceOptions()
            renderListTypeFilterOptions()

            const sourceDict = getDocumentTypeSourceDictionary()
            const sourceItems = sourceDict && Array.isArray(sourceDict.items) ? sourceDict.items : []
            const documentTypeLabels = renderOptionList(documentTypeOptionsEl, state.selections.documentTypeValues, sourceItems, 'documentTypeValues')
            renderSelectionTags(documentTypeTagsEl, state.selections.documentTypeValues, documentTypeLabels)

            Object.keys(dictionaryFieldConfig).forEach(function (fieldName) {
                const config = dictionaryFieldConfig[fieldName]
                const dict = findDictionaryByName(config.dictName)
                const items = dict && Array.isArray(dict.items) ? dict.items : []
                const labelsMap = renderOptionList(config.optionsEl, state.selections[fieldName], items, fieldName)
                renderSelectionTags(config.tagsEl, state.selections[fieldName], labelsMap)
            })
        }

        async function loadDictionaries() {
            showLoading('正在加载字典选项...')
            try {
                const data = await requestJson('/dictionary/list', {})
                state.dictionaries = Array.isArray(data.items) ? data.items : []
                state.dictionariesByName = {}
                state.dictionariesById = {}

                for (let i = 0; i < state.dictionaries.length; i += 1) {
                    const item = state.dictionaries[i]
                    state.dictionariesByName[item.dict_name] = item
                    state.dictionariesById[item.system_dict_id] = item
                }

                renderDictionarySelectors()
            } finally {
                hideLoading()
            }
        }

        function updateEditorMode() {
            setEditorVisible(state.mode === 'create' || state.mode === 'edit')

            if (state.mode === 'edit') {
                formTitleEl.textContent = '编辑文档'
                editorModeEl.textContent = '当前模式：编辑 ' + (state.editingSource && state.editingSource.document_title ? state.editingSource.document_title : state.editingId)
                document.getElementById('submitBtn').textContent = '保存文档修改'
            } else if (state.mode === 'create') {
                formTitleEl.textContent = '新增文档'
                editorModeEl.textContent = '当前模式：新建'
                document.getElementById('submitBtn').textContent = '上传附件并创建文档'
            } else {
                formTitleEl.textContent = '新增文档'
                editorModeEl.textContent = '当前模式：未打开编辑区'
                document.getElementById('submitBtn').textContent = '上传附件并创建文档'
            }
        }

        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) {
                window.location.replace('/pb/manage/login')
                throw new Error('登录状态已失效，请重新登录')
            }

            const res = await fetch(getApiUrl(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')
                    window.location.replace('/pb/manage/login')
                }
                const details = unwrapped.payload && typeof unwrapped.payload === 'object' ? unwrapped.payload : {}
                const detailMessage = details.originalMessage || details.body || ''
                const finalMessage = [unwrapped.errMsg || '请求失败', detailMessage].filter(function (item, index, arr) {
                    return item && arr.indexOf(item) === index
                }).join('：')
                throw new Error(finalMessage || '请求失败')
            }

            return unwrapped.payload
        }

        async function parseJsonSafe(res) {
            const contentType = String(res.headers.get('content-type') || '').toLowerCase()
            const rawText = await res.text()
            const isJson = contentType.indexOf('application/json') !== -1

            if (!rawText) {
                return {
                    json: null,
                    text: '',
                    isJson: false,
                }
            }

            if (isJson) {
                try {
                    return {
                        json: JSON.parse(rawText),
                        text: rawText,
                        isJson: true,
                    }
                } catch (_error) {}
            }

            return {
                json: null,
                text: rawText,
                isJson: false,
            }
        }

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

            const form = new FormData()
            form.append('attachments_link', file)
            form.append('attachments_filename', file.name || '')
            form.append('attachments_filetype', file.type || '')
            form.append('attachments_size', String(file.size || 0))
            form.append('attachments_status', 'active')
            form.append('attachments_remark', 'document-manage:' + label)

            const res = await fetch(getApiUrl('/attachment/upload'), {
                method: 'POST',
                headers: {
                    Authorization: 'Bearer ' + token,
                },
                body: form,
            })

            const parsed = await parseJsonSafe(res)
            const data = parsed.json
            const unwrapped = unwrapApiResponse(data, res)
            if (!res.ok || !data || unwrapped.code >= 400) {
                if (res.status === 413) {
                    throw new Error('上传' + label + '失败：文件已超过当前网关允许的请求体大小，或线上服务仍在运行旧版 hooks。')
                }

                if (!parsed.isJson && parsed.text) {
                    throw new Error('上传' + label + '失败：服务端返回了非 JSON 响应，通常表示网关或反向代理提前拦截了上传请求。')
                }

                throw new Error(unwrapped.errMsg || ('上传' + label + '失败'))
            }

            return unwrapped.payload
        }

        async function uploadPendingFiles(fileItems, label) {
            const uploaded = []

            for (let i = 0; i < fileItems.length; i += 1) {
                uploaded.push(await uploadAttachment(fileItems[i].file, label + '-' + (i + 1)))
            }

            return uploaded
        }

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

        function getAttachmentPreviewUrl(item, pending) {
            if (!item) {
                return ''
            }

            if (pending) {
                return item.previewUrl || ''
            }

            return item.attachments_url || ''
        }

        function renderImageThumb(url, title) {
            if (!url) {
                return ''
            }

            return '<img class="thumb" src="' + escapeHtml(url) + '" alt="' + escapeHtml(title || '') + '" onclick="window.__previewImage(\'' + escapeJsString(url) + '\')" />'
        }

        function renderAttachmentCards(container, items, category, pending) {
            if (!items.length) {
                container.innerHTML = '<div class="muted">' + (pending ? '暂无待上传附件。' : '暂无已绑定附件。') + '</div>'
                return
            }

            container.innerHTML = items.map(function (item, index) {
            const title = pending
                    ? (item.file && item.file.name ? item.file.name : ('待上传文件' + (index + 1)))
                    : (item.attachments_filename || item.attachments_id || ('附件' + (index + 1)))
                const previewUrl = getAttachmentPreviewUrl(item, pending)
                const previewHtml = category === 'image'
                    ? renderImageThumb(previewUrl, title)
                    : (previewUrl
                        ? ('<a class="file-preview" href="' + escapeHtml(previewUrl) + '" target="_blank" rel="noreferrer">' + (category === 'video' ? '🎬' : '📄') + '</a>')
                        : ('<div class="file-preview">' + (category === 'video' ? '🎬' : '📄') + '</div>'))
                const handler = pending ? '__removePendingAttachment' : '__removeCurrentAttachment'

                return '<div class="file-card">'
                    + '<div class="file-card-head"><div class="file-card-title">' + escapeHtml(title) + '</div><button class="file-card-icon" type="button" title="移除" onclick="window.' + handler + '(\'' + category + '\',' + index + ')">×</button></div>'
                    + previewHtml
                    + '</div>'
            }).join('')
        }

        function renderAttachmentEditors() {
            renderAttachmentCards(imageCurrentListEl, state.currentImageAttachments, 'image', false)
            renderAttachmentCards(videoCurrentListEl, state.currentVideoAttachments, 'video', false)
            renderAttachmentCards(documentFileCurrentListEl, state.currentFileAttachments, 'file', false)
            renderAttachmentCards(imagePendingListEl, state.pendingImageFiles, 'image', true)
            renderAttachmentCards(videoPendingListEl, state.pendingVideoFiles, 'video', true)
            renderAttachmentCards(documentFilePendingListEl, state.pendingDocumentFiles, 'file', true)
        }

        function renderLinks(item) {
            const imageUrls = Array.isArray(item.document_image_urls) ? item.document_image_urls : (item.document_image_url ? [item.document_image_url] : [])

            if (!imageUrls.length) {
                return '<span class="muted">无</span>'
            }

            return '<div class="thumb-strip">' + renderImageThumb(imageUrls[0], '文档图片预览') + '</div>'
        }

        function applyListFiltersToList(items) {
            const list = Array.isArray(items) ? items : []
            const titleKeyword = String(state.listFilters.titleKeyword || '').toLowerCase().trim()

            if (!titleKeyword) {
                return list.slice()
            }

            return list.filter(function (item) {
                const title = String((item && item.document_title) || '').toLowerCase()
                return title.indexOf(titleKeyword) !== -1
            })
        }

        function clampPage(page, totalPages) {
            const normalizedPage = Number(page) || 1
            const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
            return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
        }

        function getVisiblePageNumbers(page, totalPages) {
            const result = []
            const start = Math.max(1, page - 2)
            const end = Math.min(totalPages, start + 4)
            const adjustedStart = Math.max(1, end - 4)

            for (let current = adjustedStart; current <= end; current += 1) {
                result.push(current)
            }

            return result
        }

        function renderPageSizeSelector() {
            const options = [10, 20, 50, 100, 500]
            return '<label class="pagination-size">每页'
                + '<select class="pagination-select" onchange="window.__setDocumentPageSize(this.value)">'
                + options.map(function (size) {
                    return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
                }).join('')
                + '</select>'
                + '条</label>'
        }

        function renderPagination() {
            if (!listPaginationEl) {
                return
            }

            const total = Number(state.pagination.total || 0)
            const page = Number(state.pagination.page || 1)
            const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
            const sizeSelector = renderPageSizeSelector()

            if (!total) {
                listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
                    + '<div class="pagination-actions">' + sizeSelector + '</div>'
                return
            }

            const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
            const startIndex = (page - 1) * state.pagination.pageSize + 1
            const endIndex = Math.min(total, page * state.pagination.pageSize)
            const buttons = []

            buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')

            if (visiblePageNumbers[0] > 1) {
                buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(1)">1</button>')
                if (visiblePageNumbers[0] > 2) {
                    buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
                }
            }

            for (let i = 0; i < visiblePageNumbers.length; i += 1) {
                const current = visiblePageNumbers[i]
                buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoDocumentPage(' + current + ')">' + current + '</button>')
            }

            if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
                if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
                    buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
                }
                buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + totalPages + ')">' + totalPages + '</button>')
            }

            buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')

            listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条，显示第 ' + startIndex + '-' + endIndex + ' 条，第 ' + page + '/' + totalPages + ' 页</div>'
                + '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
        }

        function updateDocumentListView() {
            const filteredList = applyListFiltersToList(state.fullList)
            const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
            const total = filteredList.length
            const totalPages = Math.max(1, Math.ceil(total / pageSize))
            const currentPage = clampPage(state.pagination.page, totalPages)
            const startIndex = (currentPage - 1) * pageSize

            state.filteredList = filteredList
            state.pagination.page = currentPage
            state.pagination.total = total
            state.pagination.totalPages = totalPages
            state.list = filteredList.slice(startIndex, startIndex + pageSize)

            renderTable()
            renderPagination()
        }

        function renderTable() {
            if (!state.list.length) {
                tableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无文档数据。</td></tr>'
                return
            }

            tableBody.innerHTML = state.list.map(function (item) {
                return '<tr>'
                    + '<td data-label="标题"><div><strong>' + escapeHtml(item.document_title) + '</strong></div><div class="muted">' + escapeHtml(item.document_subtitle) + '</div><div class="muted">owner: ' + escapeHtml(item.document_owner) + '</div></td>'
                    + '<td data-label="类型/状态"><div>' + escapeHtml(formatDocumentTypeDisplay(item.document_type) || item.document_type) + '</div><div class="muted">' + escapeHtml(item.document_status) + ' / ' + escapeHtml(item.document_embedding_status) + '</div></td>'
                    + '<td data-label="附件链接">' + renderLinks(item) + '</td>'
                    + '<td data-label="更新时间"><span class="muted">' + escapeHtml(item.updated) + '</span></td>'
                    + '<td data-label="操作"><div class="table-actions">'
                    + '<button class="btn btn-light" type="button" onclick="window.__editDocument(\'' + encodeURIComponent(item.document_id) + '\')">编辑</button>'
                    + '<button class="btn btn-danger" type="button" onclick="window.__deleteDocument(\'' + encodeURIComponent(item.document_id) + '\')">删除</button>'
                    + '</div></td>'
                    + '</tr>'
            }).join('')
        }

        function appendPendingFiles(category, fileList) {
            const target = category === 'image'
                ? state.pendingImageFiles
                : (category === 'video' ? state.pendingVideoFiles : state.pendingDocumentFiles)
            const files = Array.from(fileList || [])
            for (let i = 0; i < files.length; i += 1) {
                target.push({
                    key: Date.now() + '-' + Math.random().toString(36).slice(2),
                    file: files[i],
                    previewUrl: category === 'image' && files[i] ? URL.createObjectURL(files[i]) : '',
                })
            }
            renderAttachmentEditors()
        }

        function revokePendingPreview(item) {
            if (item && item.previewUrl) {
                try {
                    URL.revokeObjectURL(item.previewUrl)
                } catch (_error) {}
            }
        }

        function clearPendingList(list) {
            for (let i = 0; i < list.length; i += 1) {
                revokePendingPreview(list[i])
            }
        }

        function bindDropzone(dropzoneEl, inputEl, category) {
            if (!dropzoneEl || !inputEl) {
                return
            }

            dropzoneEl.addEventListener('click', function (event) {
                const tagName = String((event.target && event.target.tagName) || '').toLowerCase()
                if (tagName === 'input') {
                    return
                }
                inputEl.click()
            })

            dropzoneEl.addEventListener('dragenter', function (event) {
                event.preventDefault()
                dropzoneEl.classList.add('dragover')
            })

            dropzoneEl.addEventListener('dragover', function (event) {
                event.preventDefault()
                dropzoneEl.classList.add('dragover')
            })

            dropzoneEl.addEventListener('dragleave', function (event) {
                if (dropzoneEl.contains(event.relatedTarget)) {
                    return
                }
                dropzoneEl.classList.remove('dragover')
            })

            dropzoneEl.addEventListener('drop', function (event) {
                event.preventDefault()
                dropzoneEl.classList.remove('dragover')
                const files = event.dataTransfer && event.dataTransfer.files ? event.dataTransfer.files : []
                appendPendingFiles(category, files)
            })
        }

        function removePendingAttachment(category, index) {
            const target = category === 'image'
                ? state.pendingImageFiles
                : (category === 'video' ? state.pendingVideoFiles : state.pendingDocumentFiles)
            if (index >= 0 && index < target.length) {
                revokePendingPreview(target[index])
                target.splice(index, 1)
            }
            renderAttachmentEditors()
        }

        function removeCurrentAttachment(category, index) {
            const target = category === 'image'
                ? state.currentImageAttachments
                : (category === 'video' ? state.currentVideoAttachments : state.currentFileAttachments)
            if (index < 0 || index >= target.length) {
                return
            }

            const removed = target.splice(index, 1)[0]
            if (removed && removed.attachments_id && state.removedAttachmentIds.indexOf(removed.attachments_id) === -1) {
                state.removedAttachmentIds.push(removed.attachments_id)
            }
            renderAttachmentEditors()
        }

        function syncListFiltersFromInputs() {
            state.listFilters.titleKeyword = listTitleKeywordEl ? String(listTitleKeywordEl.value || '').trim() : ''
            state.listFilters.type = listTypeFilterEl ? String(listTypeFilterEl.value || '').trim() : ''
        }

        function applyListFiltersToInputs() {
            if (listTitleKeywordEl) {
                listTitleKeywordEl.value = state.listFilters.titleKeyword || ''
            }
            if (listTypeFilterEl) {
                listTypeFilterEl.value = state.listFilters.type || ''
            }
        }

        function goToDocumentPage(page) {
            const nextPage = clampPage(page, state.pagination.totalPages)
            if (nextPage === state.pagination.page) {
                return
            }

            state.pagination.page = nextPage
            updateDocumentListView()
        }

        function setDocumentPageSize(pageSize) {
            const nextPageSize = Number(pageSize) || 10
            if (nextPageSize === state.pagination.pageSize) {
                return
            }

            state.pagination.pageSize = nextPageSize
            state.pagination.page = 1
            updateDocumentListView()
        }

        async function loadDocuments() {
            setStatus('正在加载文档列表...', '')
            showLoading('正在加载文档列表...')
            try {
                const data = await requestJson('/document/list', {
                    document_type: state.listFilters.type,
                })
                state.fullList = Array.isArray(data.items) ? data.items : []
                updateDocumentListView()
                setStatus('文档列表已刷新，共 ' + state.pagination.total + ' 条。', 'success')
            } catch (err) {
                setStatus(err.message || '加载列表失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function submitDocumentSilently() {
            if (state.mode !== 'create' && state.mode !== 'edit') {
                return true
            }

            if (!fields.documentTitle.value.trim()) {
                return false
            }

            if (!buildDocumentTypeStorageValue()) {
                return false
            }

            try {
                await persistDocumentWithoutReload()
                return true
            } catch (_err) {
                return false
            }
        }

        async function saveAndHideEditorBeforeListQuery() {
            if (state.mode === 'create' || state.mode === 'edit') {
                enterIdleMode()
            }
        }

        async function queryDocumentsWithAutoSave(resetPage) {
            syncListFiltersFromInputs()
            if (resetPage) {
                state.pagination.page = 1
            }
            await saveAndHideEditorBeforeListQuery()
            applyListFiltersToInputs()
            await loadDocuments()
        }

        function resetForm() {
            fields.documentTitle.value = ''
            fields.documentStatus.value = '有效'
            fields.embeddingStatus.value = ''
            fields.effectDate.value = ''
            fields.expiryDate.value = ''
            fields.documentSubtitle.value = ''
            fields.documentSummary.value = ''
            fields.documentContent.value = ''
            fields.relationModel.value = ''
            fields.vectorVersion.value = ''
            fields.documentRemark.value = ''
            fields.imageFile.value = ''
            fields.videoFile.value = ''
            fields.documentFile.value = ''
            state.selections.documentTypeSource = ''
            state.selections.documentTypeValues = []
            state.selections.documentKeywords = []
            state.selections.productCategories = []
            state.selections.applicationScenarios = []
            state.selections.hotelType = []
            renderDictionarySelectors()
            syncDocumentStatus('有效')
        }

        function enterCreateMode() {
            clearPendingList(state.pendingImageFiles)
            clearPendingList(state.pendingVideoFiles)
            clearPendingList(state.pendingDocumentFiles)
            state.mode = 'create'
            state.editingId = ''
            state.editingSource = null
            state.currentImageAttachments = []
            state.currentVideoAttachments = []
            state.currentFileAttachments = []
            state.pendingImageFiles = []
            state.pendingVideoFiles = []
            state.pendingDocumentFiles = []
            state.removedAttachmentIds = []
            resetForm()
            updateEditorMode()
            renderAttachmentEditors()
        }

        function enterIdleMode() {
            clearPendingList(state.pendingImageFiles)
            clearPendingList(state.pendingVideoFiles)
            clearPendingList(state.pendingDocumentFiles)
            state.mode = 'idle'
            state.editingId = ''
            state.editingSource = null
            state.currentImageAttachments = []
            state.currentVideoAttachments = []
            state.currentFileAttachments = []
            state.pendingImageFiles = []
            state.pendingVideoFiles = []
            state.pendingDocumentFiles = []
            state.removedAttachmentIds = []
            resetForm()
            updateEditorMode()
            renderAttachmentEditors()
        }

        function fillFormFromItem(item) {
            fields.documentTitle.value = item.document_title || ''
            fields.documentStatus.value = item.document_status || '有效'
            fields.embeddingStatus.value = item.document_embedding_status || ''
            fields.effectDate.value = toDateInputValue(item.document_effect_date)
            fields.expiryDate.value = toDateInputValue(item.document_expiry_date)
            fields.documentSubtitle.value = item.document_subtitle || ''
            fields.documentSummary.value = item.document_summary || ''
            fields.documentContent.value = item.document_content || ''
            fields.relationModel.value = item.document_relation_model || ''
            fields.vectorVersion.value = item.document_vector_version || ''
            fields.documentRemark.value = item.document_remark || ''
            fields.imageFile.value = ''
            fields.videoFile.value = ''
            fields.documentFile.value = ''

            const documentTypeParts = splitPipeValue(item.document_type)
            const firstDocumentType = documentTypeParts.length ? documentTypeParts[0] : ''
            const sourceId = firstDocumentType.indexOf('@') !== -1 ? firstDocumentType.split('@')[0] : ''
            state.selections.documentTypeSource = sourceId
            state.selections.documentTypeValues = documentTypeParts
                .map(function (part) {
                    return part.indexOf('@') !== -1 ? part.split('@').slice(1).join('@') : ''
                })
                .filter(function (part) {
                    return !!part
                })
            state.selections.documentKeywords = splitPipeValue(item.document_keywords)
            state.selections.productCategories = splitPipeValue(item.document_product_categories)
            state.selections.applicationScenarios = splitPipeValue(item.document_application_scenarios)
            state.selections.hotelType = splitPipeValue(item.document_hotel_type)
            renderDictionarySelectors()
            syncDocumentStatus(item.document_status || '有效')
        }

        function enterEditMode(documentId) {
            const target = state.list.find(function (item) {
                return item.document_id === documentId
            })
            if (!target) {
                setStatus('未找到待编辑文档。', 'error')
                return
            }

            state.mode = 'edit'
            state.editingId = target.document_id
            state.editingSource = target
            state.currentImageAttachments = normalizeAttachmentList(target.document_image_attachments, target.document_image_ids, target.document_image_urls)
            state.currentVideoAttachments = normalizeAttachmentList(target.document_video_attachments, target.document_video_ids, target.document_video_urls)
            state.currentFileAttachments = normalizeAttachmentList(target.document_file_attachments, target.document_file_ids, target.document_file_urls)
            state.pendingImageFiles = []
            state.pendingVideoFiles = []
            state.pendingDocumentFiles = []
            state.removedAttachmentIds = []

            fillFormFromItem(target)
            updateEditorMode()
            renderAttachmentEditors()
            setStatus('已进入编辑模式：' + (target.document_title || target.document_id), 'success')
            window.scrollTo({ top: 0, behavior: 'smooth' })
        }

        function buildMutationPayload(imageAttachments, videoAttachments, fileAttachments) {
            const source = state.editingSource || {}
            return {
                document_id: state.mode === 'edit' ? state.editingId : '',
                document_title: fields.documentTitle.value.trim(),
                document_type: buildDocumentTypeStorageValue(),
                document_status: fields.documentStatus.value.trim() || '有效',
                document_embedding_status: fields.embeddingStatus.value.trim(),
                document_effect_date: fields.effectDate.value,
                document_expiry_date: fields.expiryDate.value,
                document_subtitle: fields.documentSubtitle.value.trim(),
                document_summary: fields.documentSummary.value.trim(),
                document_content: fields.documentContent.value.trim(),
                document_image: imageAttachments.map(function (item) { return item.attachments_id }),
                document_video: videoAttachments.map(function (item) { return item.attachments_id }),
                document_file: fileAttachments.map(function (item) { return item.attachments_id }),
                document_relation_model: fields.relationModel.value.trim(),
                document_keywords: joinPipeValue(state.selections.documentKeywords),
                document_share_count: typeof source.document_share_count === 'undefined' || source.document_share_count === null ? '' : source.document_share_count,
                document_download_count: typeof source.document_download_count === 'undefined' || source.document_download_count === null ? '' : source.document_download_count,
                document_favorite_count: typeof source.document_favorite_count === 'undefined' || source.document_favorite_count === null ? '' : source.document_favorite_count,
                document_embedding_error: source.document_embedding_error || '',
                document_embedding_lasttime: source.document_embedding_lasttime || '',
                document_vector_version: fields.vectorVersion.value.trim(),
                document_product_categories: joinPipeValue(state.selections.productCategories),
                document_application_scenarios: joinPipeValue(state.selections.applicationScenarios),
                document_hotel_type: joinPipeValue(state.selections.hotelType),
                document_remark: fields.documentRemark.value.trim(),
            }
        }

        async function cleanupUploadedAttachments(uploadedAttachments) {
            for (let i = 0; i < uploadedAttachments.length; i += 1) {
                try {
                    await requestJson('/attachment/delete', { attachments_id: uploadedAttachments[i].attachments_id })
                } catch (_error) {}
            }
        }

        async function deleteRemovedAttachments() {
            const failed = []
            for (let i = 0; i < state.removedAttachmentIds.length; i += 1) {
                try {
                    await requestJson('/attachment/delete', { attachments_id: state.removedAttachmentIds[i] })
                } catch (error) {
                    failed.push(state.removedAttachmentIds[i] + '：' + (error.message || '删除失败'))
                }
            }
            return failed
        }

        async function submitDocument() {
            syncDocumentStatus()

            if (!fields.documentTitle.value.trim()) {
                setStatus('请先填写文档标题。', 'error')
                return
            }

            if (!buildDocumentTypeStorageValue()) {
                setStatus('请先填写文档类型。', 'error')
                return
            }

            setStatus(state.mode === 'edit' ? '正在保存文档修改...' : '正在上传附件并创建文档...', '')
            showLoading(state.mode === 'edit' ? '正在保存文档修改，请稍候...' : '正在上传附件并创建文档，请稍候...')

            const uploadedAttachments = []

            try {
                const newImageAttachments = await uploadPendingFiles(state.pendingImageFiles, 'image')
                const newVideoAttachments = await uploadPendingFiles(state.pendingVideoFiles, 'video')
                const newFileAttachments = await uploadPendingFiles(state.pendingDocumentFiles, 'file')
                uploadedAttachments.push.apply(uploadedAttachments, newImageAttachments)
                uploadedAttachments.push.apply(uploadedAttachments, newVideoAttachments)
                uploadedAttachments.push.apply(uploadedAttachments, newFileAttachments)

                const finalImageAttachments = state.currentImageAttachments.concat(newImageAttachments)
                const finalVideoAttachments = state.currentVideoAttachments.concat(newVideoAttachments)
                const finalFileAttachments = state.currentFileAttachments.concat(newFileAttachments)
                const payload = buildMutationPayload(finalImageAttachments, finalVideoAttachments, finalFileAttachments)

                if (state.mode === 'edit') {
                    const updated = await requestJson('/document/update', payload)
                    const deleteFailed = await deleteRemovedAttachments()
                    await loadDocuments()
                    state.editingId = (updated && updated.document_id) || state.editingId
                    enterEditMode(state.editingId)
                    if (deleteFailed.length) {
                        setStatus('文档已更新，但以下附件删除失败：' + deleteFailed.join('；'), 'error')
                        return
                    }
                    setStatus('文档修改成功。', 'success')
                    return
                }

                const created = await requestJson('/document/create', payload)
                await loadDocuments()
                if (created && created.document_id) {
                    enterEditMode(created.document_id)
                }
                setStatus('文档创建成功。', 'success')
            } catch (err) {
                if (uploadedAttachments.length) {
                    await cleanupUploadedAttachments(uploadedAttachments)
                }
                setStatus(err.message || (state.mode === 'edit' ? '修改文档失败' : '创建文档失败'), 'error')
            } finally {
                hideLoading()
            }
        }

        async function deleteDocument(documentId) {
            const target = decodeURIComponent(documentId)
            if (!window.confirm('确认删除文档「' + target + '」吗？')) {
                return
            }

            setStatus('正在删除文档...', '')
            showLoading('正在删除文档，请稍候...')
            try {
                await requestJson('/document/delete', { document_id: target })
                if (state.mode === 'edit' && state.editingId === target) {
                    enterCreateMode()
                }
                await loadDocuments()
                setStatus('文档删除成功。', 'success')
            } catch (err) {
                setStatus(err.message || '删除文档失败', 'error')
            } finally {
                hideLoading()
            }
        }

        window.__deleteDocument = deleteDocument
        window.__editDocument = function (documentId) {
            enterEditMode(decodeURIComponent(documentId))
        }
        window.__previewImage = function (url) {
            if (!url) {
                return
            }
            imageViewerImgEl.src = url
            imageViewerEl.classList.add('show')
        }
        window.__removePendingAttachment = removePendingAttachment
        window.__removeCurrentAttachment = removeCurrentAttachment

        documentTypeSourceEl.addEventListener('change', function (event) {
            state.selections.documentTypeSource = event.target.value || ''
            state.selections.documentTypeValues = []
            renderDictionarySelectors()
        })
        documentStatusSwitchEl.addEventListener('click', function (event) {
            const target = event.target
            if (!target || !target.getAttribute) {
                return
            }

            const nextValue = target.getAttribute('data-status-value')
            if (!nextValue) {
                return
            }

            fields.documentStatus.value = nextValue
            syncDocumentStatus(nextValue)
        })
        document.addEventListener('change', function (event) {
            const target = event.target
            if (!target || !target.getAttribute) {
                return
            }

            const fieldName = target.getAttribute('data-selection-field')
            if (!fieldName) {
                return
            }

            updateSelection(fieldName, target.value, !!target.checked)
        })

        window.__gotoDocumentPage = goToDocumentPage
        window.__setDocumentPageSize = setDocumentPageSize

        document.getElementById('reloadBtn').addEventListener('click', function () {
            queryDocumentsWithAutoSave(false)
        })
        document.getElementById('searchBtn').addEventListener('click', function () {
            queryDocumentsWithAutoSave(true)
        })
        document.getElementById('clearSearchBtn').addEventListener('click', function () {
            state.listFilters.titleKeyword = ''
            state.listFilters.type = ''
            state.pagination.page = 1
            applyListFiltersToInputs()
            queryDocumentsWithAutoSave(false)
        })
        if (listTitleKeywordEl) {
            listTitleKeywordEl.addEventListener('keydown', function (event) {
                if (event.key === 'Enter') {
                    queryDocumentsWithAutoSave(true)
                }
            })
        }
        if (listTypeFilterEl) {
            listTypeFilterEl.addEventListener('change', function () {
                queryDocumentsWithAutoSave(true)
            })
        }
        document.getElementById('createModeBtn').addEventListener('click', function () {
            enterCreateMode()
            setStatus('已切换到新建模式。', 'success')
        })
        document.getElementById('submitBtn').addEventListener('click', submitDocument)
        document.getElementById('cancelEditBtn').addEventListener('click', function () {
            enterIdleMode()
            setStatus('已关闭编辑区。', 'success')
        })
        document.getElementById('closeImageViewerBtn').addEventListener('click', function () {
            imageViewerEl.classList.remove('show')
            imageViewerImgEl.src = ''
        })
        imageViewerEl.addEventListener('click', function (event) {
            if (event.target === imageViewerEl) {
                imageViewerEl.classList.remove('show')
                imageViewerImgEl.src = ''
            }
        })
        document.addEventListener('keydown', function (event) {
            if (event.key === 'Escape' && imageViewerEl.classList.contains('show')) {
                imageViewerEl.classList.remove('show')
                imageViewerImgEl.src = ''
            }
        })
        document.getElementById('resetBtn').addEventListener('click', function () {
            if (state.mode === 'edit' && state.editingSource) {
                enterEditMode(state.editingId)
                setStatus('编辑表单已恢复到当前文档数据。', 'success')
                return
            }
            enterCreateMode()
            setStatus('表单已重置。', 'success')
        })
        fields.imageFile.addEventListener('change', function (event) {
            appendPendingFiles('image', event.target.files)
            fields.imageFile.value = ''
        })
        fields.effectDate.addEventListener('change', function () {
            syncDocumentStatus()
        })
        fields.expiryDate.addEventListener('change', function () {
            syncDocumentStatus()
        })
        fields.videoFile.addEventListener('change', function (event) {
            appendPendingFiles('video', event.target.files)
            fields.videoFile.value = ''
        })
        fields.documentFile.addEventListener('change', function (event) {
            appendPendingFiles('file', event.target.files)
            fields.documentFile.value = ''
        })
        bindDropzone(imageDropzoneEl, fields.imageFile, 'image')
        bindDropzone(videoDropzoneEl, fields.videoFile, 'video')
        bindDropzone(documentFileDropzoneEl, fields.documentFile, 'file')

        enterIdleMode()
        ;(async function initPage() {
            try {
                await loadDictionaries()
            } catch (err) {
                setStatus(err.message || '加载字典选项失败', 'error')
            }
            await loadDocuments()
        })()
    </script>
    {{ template "theme_body" . }}
</body>
</html>
