# -*- coding: utf-8 -*-
"""批次 A：
 ① 后台「任免表及考察材料」增加 汇报材料 / 说明材料 页签（与考察材料同级、同一套导入方式）
 ④ 考察/汇报/说明 三类页签下只保留「选择干部 → 导入xx材料」
 ⑥ 后端只保留「下载数据包 + 导入」：去掉内网分发演示按钮，数据包下载/校验接真实接口
 另：PAD 预览支持 report/note（pad.html embed）
"""
import re, shutil, time

BK = time.strftime('%Y%m%d%H%M%S')
A = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/wwwroot/admin.html'
API = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/Endpoints/AdminEndpoints.cs'
PADS = ['/data/www/files/rmsys-app/backend/src/Rmsys.Api/wwwroot/pad.html',
        '/data/www/files/rmsys-app/android/app/src/main/assets/web/pad.html']

def backup(p):
    shutil.copy2(p, p + '.bakA.' + BK)

# ============================ 1) 后端：cada 补 mat 元数据 ============================
s = open(API, encoding='utf-8').read(); backup(API)
old = """                cadreOut.Add(new
                {"""
new = """                // 各类型材料的「最新一份」，供后台三个材料页签显示真实上传人/时间/文件名/页数
                var matMap = new Dictionary<string, object>();
                foreach (var tp in MaterialType.All)
                {
                    var mm = mine.Where(x => x.Type == tp).OrderByDescending(x => x.Id).FirstOrDefault();
                    matMap[tp] = mm == null
                        ? new { exists = false }
                        : (object)new
                        {
                            exists = true,
                            id = mm.Id,
                            fileName = mm.FileName ?? "",
                            uploadedBy = mm.UploadedBy ?? "",
                            uploadedAt = mm.UploadedAt?.ToString(DateFmt) ?? "",
                            pages = mm.Pages,
                            version = mm.Version ?? "v1",
                            status = mm.Status ?? "",
                            sizeText = Size(mm.FileSize)
                        };
                }

                cadreOut.Add(new
                {"""
assert s.count(old) == 1
s = s.replace(old, new, 1)
old2 = """                    notes = noteMap.TryGetValue(c.Id, out var n) ? n : 0
                });"""
new2 = """                    notes = noteMap.TryGetValue(c.Id, out var n) ? n : 0,
                    mat = matMap
                });"""
assert s.count(old2) == 1
s = s.replace(old2, new2, 1)
# 数据包带上会议 id，前端才能拼出真实下载地址
old3 = """                    id = p.PkgNo,
                    meet = meetName.GetValueOrDefault(p.MeetingId, ""),"""
new3 = """                    id = p.PkgNo,
                    meeting = p.MeetingId,
                    meet = meetName.GetValueOrDefault(p.MeetingId, ""),"""
assert s.count(old3) == 1
s = s.replace(old3, new3, 1)
open(API, 'w', encoding='utf-8').write(s)
print("✓ 后端：cadre.mat 元数据 + pkg.meeting")

# ============================ 2) 后台：页签 + 按钮 + 表格 ============================
s = open(A, encoding='utf-8').read(); backup(A)

# --- 2.1 类型页签与功能按钮 ---
i1 = s.index("  /* 类型页签 */")
i2 = s.index("\n  if(ROLE.matType==='doc'){", i1)
new_block = """  /* 类型页签：任免文件 / 任免表 / 考察材料 / 汇报材料 / 说明材料（后三类同级、同一套导入方式） */
  var list=matList();
  var mt=ROLE.matType;
  var nMat={ form:nOf(list,'t30'), exam:nOf(list,'exam'), report:nOf(list,'report'), note:nOf(list,'note') };
  var nDoc=docList().length;
  var typeName = mt==='doc' ? '任免文件' : (mt==='form' ? '任免表' : MAT_TYPE_NAME[mt]);
  h+='<div class="bar">'
   + '<div class="segs">'
   +   '<button class="'+(mt==='doc'?'on':'')+'" onclick="pickMatType(\\'doc\\')">任免文件（'+nDoc+'）</button>'
   +   '<button class="'+(mt==='form'?'on':'')+'" onclick="pickMatType(\\'form\\')">任免表（'+nMat.form+'）</button>'
   +   '<button class="'+(mt==='exam'?'on':'')+'" onclick="pickMatType(\\'exam\\')">考察材料（'+nMat.exam+'）</button>'
   +   '<button class="'+(mt==='report'?'on':'')+'" onclick="pickMatType(\\'report\\')">汇报材料（'+nMat.report+'）</button>'
   +   '<button class="'+(mt==='note'?'on':'')+'" onclick="pickMatType(\\'note\\')">说明材料（'+nMat.note+'）</button>'
   + '</div>'
   + '<div class="spacer"></div>'
   + '<span style="font-size:12.5px;color:var(--ink3);">'+(hasDept?deptName(ROLE.dept):'全部上会干部')+' · '+typeName+'</span>'
   + '</div>';

  /* 功能按钮：考察 / 汇报 / 说明 三类只保留「选干部 → 导入」；任免表保留 3.0 导入与批量上传 */
  if(mt==='exam'||mt==='report'||mt==='note'){
    h+='<div class="bar">'
     + '<button class="btn pri" onclick="pickCadreForMat()">选择干部导入'+MAT_TYPE_NAME[mt]+'</button>'
     + '<div class="spacer"></div>'+matSearchHTML()+'</div>';
  } else if(mt==='form'){
    h+='<div class="bar">'
     + '<button class="btn pri" onclick="wizImportForm()">任免表导入</button>'
     + '<button class="btn" onclick="wizUpload()">批量上传个人材料</button>'
     + '<div class="spacer"></div>'+matSearchHTML()+'</div>';
  }"""
s = s[:i1] + new_block + s[i2:]

# --- 2.2 考察/汇报/说明 三合一表格 ---
i3 = s.index("  } else {\n    h+='<div class=\"tbl-wrap\"><table class=\"grid slim\"")
i4 = s.index("\n  h+='</div></div>';", i3)
new_tbl = """  } else {   /* 考察材料 / 汇报材料 / 说明材料：同一套渲染，取该类型最新一份材料 */
    var tk=mt;
    h+='<div class="tbl-wrap"><table class="grid slim" style="min-width:1060px;"><thead><tr>'
     + '<th class="opcell">操作</th><th>上传人</th><th>上传时间</th>'
     + '<th>序号</th><th>姓名</th><th>所属处室</th><th>'+MAT_TYPE_NAME[tk]+'</th><th>页数</th><th>版本</th>'
     + '</tr></thead><tbody>';
    if(!list.length) h+='<tr><td colspan="9"><div class="empty">当前处室 / 条件下暂无数据</div></td></tr>';
    list.forEach(function(c,i){
      var f=matOf(c,tk);
      var ops=[['预览','previewMat('+c.id+',\\''+tk+'\\')','pri'],['上传','uploadFor('+c.id+',\\''+tk+'\\')']];
      if(f) ops.push(['下载','dlMat('+f.id+')']);
      ops.push(['删除','delCadre('+c.id+')','del']);
      h+='<tr>'
       + opsCell(ops)
       + '<td>'+(f?esc(f.uploadedBy):'—')+'</td>'
       + '<td>'+(f?esc(f.uploadedAt):'—')+'</td>'
       + '<td>'+(i+1)+'</td><td class="nm">'+esc(c.name)+'</td><td>'+deptName(c.dept)+'</td>'
       + '<td>'+(f?esc(f.fileName):'<span style="color:#8E6F14;">未上传</span>')+'</td>'
       + '<td>'+(f&&f.pages?(f.pages+' 页'):'—')+'</td>'
       + '<td>'+(f?esc(f.version):'—')+'</td>'
       + '</tr>';
    });
    h+='</tbody></table></div>';
  }"""
s = s[:i3] + new_tbl + s[i4:]

# --- 2.3 辅助函数（材料读取 / 下载 / 查询框 / 预览 / 选干部）---
anchor = "function previewCadre(id){"
helpers = """/* ---- 材料元数据助手：数据来自 bootstrap 的 cadre.mat[类型] ---- */
function matOf(c, t){ return (c && c.mat && c.mat[t] && c.mat[t].exists) ? c.mat[t] : null; }
function nOf(list, t){ return list.filter(function(c){ return !!matOf(c,t); }).length; }
function dlMat(id, name){ saveFile(API_BASE + '/api/admin/materials/' + id + '/download', name || '材料'); }
function matSearchHTML(){
  return '<div class="searchbox">'
   + '<input type="text" id="mqn" placeholder="姓名 / 现任职务 / 拟任职务" value="'+esc(MAT_Q)+'" onkeydown="if(event.key===\\'Enter\\')doMatSearch()">'
   + '<select id="mst" onchange="MAT_STATUS=this.value;renderMaterials();">'
   + ['全部','已审核','待补件','已上会','已回写'].map(function(s){ return '<option'+(MAT_STATUS===s?' selected':'')+'>'+s+'</option>'; }).join('')
   + '</select>'
   + '<button class="btn pri" onclick="doMatSearch()">查询</button>'
   + '<button class="btn" onclick="MAT_Q=\\'\\';MAT_STATUS=\\'全部\\';renderMaterials();">重置</button>'
   + '</div>';
}
function previewMat(id, t){
  var c = null; CADRES.forEach(function(x){ if(x.id === id) c = x; });
  if(!c) return;
  if(!matOf(c, t)){ toast('该同志尚未上传' + MAT_TYPE_NAME[t] + '，请先上传', 'wr'); return; }
  openPreview(MAT_TYPE_NAME[t] + '预览 · ' + c.name, 'pad.html?embed=' + t + '&id=' + id);
}
/* 选干部 → 导入材料（考察 / 汇报 / 说明 三类共用） */
function pickCadreForMat(){
  var t = ROLE.matType;
  if(t !== 'exam' && t !== 'report' && t !== 'note') t = 'exam';
  var list = matList();
  ensureAuth().then(function(ok){
    if(!ok) return;
    var h = '<div class="modal-h"><h3>选择干部 · 导入' + MAT_TYPE_NAME[t] + '</h3><div class="spacer"></div><button class="x" onclick="closeModal()">&times;</button></div>';
    h += '<div class="modal-b"><div class="sec-note">点选干部后上传该同志的' + MAT_TYPE_NAME[t] + '（Word / PDF / OFD，单份不超过 50MB）。已有材料会自动换版，旧版保留可追溯。</div>';
    h += '<div style="max-height:52vh;overflow:auto;border:1px solid var(--line);border-radius:8px;">';
    if(!list.length) h += '<div class="empty">当前处室 / 条件下暂无干部</div>';
    list.forEach(function(c){
      var f = matOf(c, t);
      h += '<div style="display:flex;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--line);cursor:pointer;"'
         + ' onmouseover="this.style.background=\\'#FAF8F4\\'" onmouseout="this.style.background=\\'\\'"'
         + ' onclick="closeModal();uploadFor(' + c.id + ',\\'' + t + '\\')">'
         + '<b style="min-width:76px;">' + esc(c.name) + '</b>'
         + '<span style="color:var(--ink2);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + deptName(c.dept) + '　' + esc(c.cur || '') + '</span>'
         + '<span style="font-size:12.5px;color:' + (f ? 'var(--green)' : '#8E6F14') + ';">' + (f ? ('已上传 ' + esc(f.version)) : '未上传') + '</span>'
         + '</div>';
    });
    h += '</div></div>';
    h += '<div class="modal-f"><span style="font-size:12.5px;color:var(--ink3);">共 ' + list.length + ' 人</span><div class="spacer"></div><button class="btn" onclick="closeModal()">取消</button></div>';
    openModal(h, 'wide');
  });
}
"""
assert s.count(anchor) == 1
s = s.replace(anchor, helpers + anchor, 1)

# --- 2.4 去掉内网分发演示，改为真实下载/校验 ---
old_btn = """   + '<button class="btn" onclick="toast(\\'演示环境：已导出至内网分发目录，导出已留痕\\')">导出数据</button>'"""
new_btn = """   + '<button class="btn" onclick="downloadPkg(ROLE.meeting||1)">下载数据包</button>'"""
assert s.count(old_btn) == 1, "内网分发按钮未命中"
s = s.replace(old_btn, new_btn, 1)

old_ops = """     + '<td class="ops"><a class="lnk" onclick="toast(\\'演示环境：已生成下载链接，下载已留痕\\')">下载</a>'
     + '<a class="lnk" onclick="toast(\\'演示环境：数据包完整性与签名校验通过\\')">校验</a>'"""
new_ops = """     + '<td class="ops"><a class="lnk" onclick="downloadPkg('+(p.meeting||1)+')">下载</a>'
     + '<a class="lnk" onclick="checkPkg('+(p.meeting||1)+')">校验</a>'"""
assert s.count(old_ops) == 1, "数据包操作链接未命中"
s = s.replace(old_ops, new_ops, 1)

pkg_helpers = """/* 数据包只走「下载文件 → 拷到平板 → App 导入」这一条通道（不再有局域网远程分发） */
function downloadPkg(meetingId){
  saveFile(API_BASE + '/api/admin/packages/' + meetingId + '/export', 'PK' + meetingId + '.pkg');
}
function checkPkg(meetingId){
  apiCall(API_BASE + '/api/admin/packages/' + meetingId + '/check', 'GET').then(function(d){
    if(!d || d.__needLogin){ toast('登录已失效，请重新登录', 'wr'); ensureAuth(); return; }
    toast(d.ok ? ('校验通过：' + (d.message || '包内容完整')) : ('校验未通过：' + (d.message || '')), d.ok ? 'ok' : 'wr');
  });
}
"""
anchor2 = "function previewMat(id, t){"
assert s.count(anchor2) == 1
s = s.replace(anchor2, pkg_helpers + anchor2, 1)
open(A, 'w', encoding='utf-8').write(s)
print("✓ 后台：5 页签 + 三类共用导入 + 选干部弹窗 + 去内网分发 + 真实下载/校验")

# ============================ 3) PAD：预览支持 report/note ============================
old_embed = "  var pane='pane-'+(q.embed==='exam'?'exam':'t30');"
new_embed = "  var pane='pane-'+(({'exam':1,'report':1,'note':1,'att':1}[q.embed])?q.embed:'t30');"
for p in PADS:
    t = open(p, encoding='utf-8').read(); backup(p)
    assert t.count(old_embed) == 1, p
    open(p, 'w', encoding='utf-8').write(t.replace(old_embed, new_embed, 1))
    print("✓ PAD 预览支持 report/note：", p.split('/')[-3], p.split('/')[-1])
print("\n备份后缀：.bakA." + BK)
