#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""可复用「可编辑 + 自动保存 + 导出」HTML 图层（宝锐默认规则 2026-09-16）

用法：
    from html_editable_layer import EDIT_CSS, EDIT_JS, TOOLBAR, apply
    html = TEMPLATE.replace('__EDITCSS__', EDIT_CSS).replace('__EDITJS__', EDIT_JS).replace('__TOOLBAR__', TOOLBAR)
    html = apply(html, ls_key='baoyu-xxx-20260917', file_base='文件中文名')
要点：
  1) EDIT_JS 必须放在 <head>（恢复内容容器 innerHTML 不会覆盖 head 里的函数定义）
  2) 恢复只替换内容容器（默认 .page，可用 container 参数改），绝不替换 body，否则工具栏被抹掉
  3) 导出 HTML 时把 input.value 写回 value 属性 → 得到「带数据的可编辑文件」
  4) 文件名用常量 FILE_BASE，不要取 document.title（浏览器插件会注入 emoji）
"""

EDIT_CSS = """
/* ── 可编辑层（默认开启） ── */
.editbar{position:fixed;right:16px;bottom:16px;z-index:99;display:flex;flex-direction:column;gap:6px;align-items:stretch;
 background:rgba(255,255,255,.97);border:1px solid #cfe0f2;border-radius:12px;padding:9px;box-shadow:0 8px 26px rgba(16,42,80,.2)}
.editbar button{padding:7px 13px;border:1px solid #cfe0f2;background:#fff;color:#12325b;border-radius:8px;font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap}
.editbar button:hover{border-color:#1a73c9;color:#1a73c9;background:#f5f9ff}
.editbar button.pri{background:#1a73c9;color:#fff;border-color:#1a73c9}
.editbar #saveState{font-size:11px;color:#7286a0;text-align:center;padding-bottom:2px;border-bottom:1px solid #eef3f9;margin-bottom:3px}
.edithint{background:#e8f5ec;border:1px solid #a9dcbe;color:#14663c;border-radius:7px;padding:7px 11px;font-size:12.3px;margin-bottom:11px}
.edithint b{color:#0d4a2b}
body[contenteditable=true] .page :hover{box-shadow:inset 0 0 0 1px #e3ecf6}
body.locked .page :hover{box-shadow:none}
.ed:focus{background:#fffbe9;outline:none}
@media print{.editbar,.edithint{display:none!important}}
"""

EDIT_JS = r"""var LS_KEY='__LSKEY__';
var FILE_BASE='__FILEBASE__';
var CONT_SEL='__CONT__';
var EDIT_DEFAULT=__EDITDEFAULT__;
function pageEl(){return document.querySelector(CONT_SEL);}
function fieldsList(){return document.querySelectorAll(CONT_SEL+' input,'+CONT_SEL+' textarea,'+CONT_SEL+' select');}
function snapshot(){
 var vals=[],fs=fieldsList();
 for(var i=0;i<fs.length;i++){var el=fs[i];
  if(el.type==='checkbox'||el.type==='radio'){vals.push(el.checked?'1':'0');}else{vals.push(el.value);}}
 return {t:new Date().toISOString(),html:(pageEl()?pageEl().innerHTML:''),vals:vals};
}
function applyVals(vals){
 var fs=fieldsList();
 for(var i=0;i<fs.length&&i<vals.length;i++){var el=fs[i];
  if(el.type==='checkbox'||el.type==='radio'){el.checked=(vals[i]==='1'||vals[i]===true);}else{el.value=vals[i];}}
}
function saveNow(){
 var st=document.getElementById('saveState');
 try{localStorage.setItem(LS_KEY,JSON.stringify(snapshot()));
  if(st)st.textContent='已保存 '+new Date().toTimeString().slice(0,5);
 }catch(e){if(st)st.textContent='保存失败：浏览器存储已满';}
}
function autoSave(){
 var st=document.getElementById('saveState');
 try{localStorage.setItem(LS_KEY,JSON.stringify(snapshot()));
  if(st)st.textContent='自动保存 '+new Date().toTimeString().slice(0,5);
 }catch(e){}
}
function restore(){
 var raw=null;
 try{raw=localStorage.getItem(LS_KEY);}catch(e){return;}
 if(!raw)return;
 try{
  var d=JSON.parse(raw);
  if(d.html&&pageEl()){pageEl().innerHTML=d.html;}
  if(d.vals)applyVals(d.vals);
  var st=document.getElementById('saveState');
  if(st)st.textContent='已恢复上次填写内容';
 }catch(e){}
}
/* 🔗 链接保护：contenteditable 区域里点 <a> 只会落光标、不会跳转。
   ① 给所有 <a href> 标 contenteditable=false（光标不再吞掉链接）
   ② 编辑模式下点击由 JS 兜底 window.open（飞书 WebView 也稳） */
function protectLinks(){
 var as=document.querySelectorAll('a[href]');
 for(var i=0;i<as.length;i++){as[i].setAttribute('contenteditable','false');}
}
document.addEventListener('click',function(ev){
 if(!window.EDIT)return;                       // 阅读模式交还浏览器原生跳转
 var el=ev.target;
 while(el&&el.tagName!=='A'){el=el.parentNode;}
 if(!el||el.tagName!=='A')return;
 var href=el.getAttribute('href')||'';
 if(!/^(https?:|mailto:)/i.test(href))return;  // 页内锚点/空链接不拦
 ev.preventDefault();ev.stopPropagation();
 window.open(href,'_blank','noopener');
},true);
function setEditable(on){
 window.EDIT=on;
 document.body.setAttribute('contenteditable',on?'true':'false');
 var prot=document.querySelectorAll('.editbar,button,input,textarea,select,.bx');
 for(var i=0;i<prot.length;i++){prot[i].setAttribute('contenteditable','false');}
 protectLinks();
 var b=document.getElementById('btnEdit');
 if(b)b.textContent=on?'🔒 锁定':'✏️ 编辑';
 document.body.classList.toggle('locked',!on);
}
function toggleEdit(){setEditable(!window.EDIT);}
function download(name,mime,content){
 var b=new Blob([content],{type:mime+';charset=utf-8'});
 var a=document.createElement('a');
 a.href=URL.createObjectURL(b);
 a.download=name;
 document.body.appendChild(a);a.click();
 setTimeout(function(){a.remove();},500);
}
function exportHtml(){
 var src=fieldsList();
 var clone=document.documentElement.cloneNode(true);
 var dst=clone.querySelectorAll(CONT_SEL+' input,'+CONT_SEL+' textarea,'+CONT_SEL+' select');
 for(var i=0;i<dst.length;i++){
  var el=dst[i],v=(src[i]?src[i].value:'');
  if(el.tagName==='INPUT'){
   if(el.type==='checkbox'||el.type==='radio'){if(src[i]&&src[i].checked){el.setAttribute('checked','checked');}else{el.removeAttribute('checked');}}
   else{el.setAttribute('value',v);}
  }else if(el.tagName==='TEXTAREA'){el.textContent=v;}
 }
 var st=clone.querySelector('#saveState');
 if(st)st.textContent='已导出 '+new Date().toLocaleString();
 download(FILE_BASE+'_已填写.html','text/html','<!DOCTYPE html>'+clone.outerHTML);
}
function exportJson(){download(FILE_BASE+'_数据.json','application/json',JSON.stringify(snapshot(),null,1));}
function resetAll(){
 if(!confirm('确定清空本页所有填写内容并恢复初始状态？'))return;
 try{localStorage.removeItem(LS_KEY);}catch(e){}
 location.reload();
}
document.addEventListener('input',function(){autoSave();});
function boot(){restore();protectLinks();setEditable(EDIT_DEFAULT);}
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',boot);}else{boot();}"""

TOOLBAR = """<div class="editbar" contenteditable="false">
 <span id="saveState">未保存</span>
 <button class="pri" onclick="saveNow()">💾 保存</button>
 <button onclick="exportHtml()">⬇ 导出 HTML</button>
 <button onclick="exportJson()">⬇ 导出 JSON</button>
 <button onclick="window.print()">🖨 打印 / PDF</button>
 <button id="btnEdit" onclick="toggleEdit()">🔒 锁定</button>
 <button onclick="resetAll()">⟲ 重置</button>
</div>"""

EDITHINT = """<div class="edithint">✏️ <b>本页默认可编辑</b>：点击任意文字即可修改，填写内容<b>自动保存在本机浏览器</b>（关掉再开仍在）。<b>链接随时可点</b>（编辑模式下也能直接跳转）。改完点右下角「<b>⬇ 导出 HTML</b>」生成带数据的独立文件（可发给他人 / 存档），「🖨 打印 / PDF」出纸质版。</div>"""

EDITHINT_READ = """<div class="edithint">📖 <b>本页默认为阅读模式</b>：链接可直接点击跳转，不受编辑层影响。需要批注 / 改动时点右下角「<b>✏️ 编辑</b>」解锁；改完点「<b>⬇ 导出 HTML</b>」生成带数据的独立文件，「🖨 打印 / PDF」出纸质版。</div>"""


def apply(html, ls_key, file_base, container='.page', head_marker='</head>', hint_anchor=None,
          editable_default=True, hint=None):
    """注入可编辑层。html 需含 __EDITCSS__ / __EDITJS__ / __TOOLBAR__ 占位符。

    editable_default=False → 以阅读模式打开（链接随手可点，适合日报/报告类只读交付物）；
    表单类（信息收集表、拜访卡）保持 True，开箱即可填。
    无论哪种模式，<a> 都已保护（contenteditable=false + 点击兜底 window.open），编辑不再吃掉跳转。
    """
    js = (EDIT_JS.replace('__LSKEY__', ls_key).replace('__FILEBASE__', file_base)
                .replace('__CONT__', container)
                .replace('__EDITDEFAULT__', 'true' if editable_default else 'false'))
    out = (html.replace('__EDITCSS__', EDIT_CSS)
               .replace('__EDITJS__', js)
               .replace('__TOOLBAR__', TOOLBAR))
    hint_html = hint if hint is not None else (EDITHINT if editable_default else EDITHINT_READ)
    if hint_anchor and hint_anchor in out:
        out = out.replace(hint_anchor, hint_html + hint_anchor, 1)
    return out


if __name__ == '__main__':
    print('图层模块：EDIT_CSS %d 字符 ｜ EDIT_JS %d 字符 ｜ TOOLBAR %d 字符'
          % (len(EDIT_CSS), len(EDIT_JS), len(TOOLBAR)))
