// 全链路仿真: text/plain 回退恢复 → filterWord → 换行转换 → htmlparser → filterInputRule → toHtml
const fs = require('fs');
const PATH = '/opt/code-workspace/ly_ueditor_plus/ueditor-plus/ueditor.all.js';

global.window = global;
global.navigator = { userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" };
global.document = {
  createElement: () => ({ style: {}, setAttribute(){}, removeAttribute(){}, getAttribute(){ return null; }, appendChild(){}, childNodes: [] }),
  documentMode: 12, compatMode: "CSS1Compat", readyState: "complete",
  attachEvent: () => true, detachEvent: () => {}, addEventListener: () => {},
  getElementsByTagName: () => [], createTextNode: (t) => ({ nodeValue: t }),
  querySelectorAll: () => [], body: {}
};
global.attachEvent = () => true; global.detachEvent = () => {};
global.XMLHttpRequest = function(){};
global.FormData = function(){}; global.FileReader = function(){};
global.addEventListener = () => {}; global.removeEventListener = () => {};
global.localStorage = { getItem: () => null, setItem(){}, removeItem(){} };
global.location = { host: "x", protocol: "http:", href: "http://x/" };

require(PATH);
const UE = global.window.UE;
const src = fs.readFileSync(PATH, 'utf8');

// ---- ① 从真实文件抽取 text/plain 回退恢复表达式 ----
const a1 = 'i=e.innerHTML,';
const i1 = src.indexOf(a1);
const i2 = src.indexOf(',i=UE.filterWord(i),', i1);
if (i1 < 0 || i2 < 0) { console.error('恢复表达式锚点未找到!'); process.exit(1); }
const restoreExpr = src.slice(i1 + a1.length, i2);
console.log('恢复表达式长度:', restoreExpr.length, '| 前60:', restoreExpr.slice(0, 60));
const restoreFn = new Function('i', 'p', 'return (' + restoreExpr + ');');

// ---- ② 换行转换表达式(仍在) ----
const anchor2 = 'i=UE.filterWord(i),';
const x1 = src.indexOf(anchor2);
const x2 = src.indexOf(';var C=UE.htmlparser(i);', x1);
const pasteTransform = new Function('i', 'return (' + src.slice(x1 + anchor2.length, x2) + ');');

const ed = {
  options: {}, commands: {}, inputRules: [], outputRules: [],
  setOpt(o, v){ if (typeof o === 'string') this.options[o] = v; else Object.assign(this.options, o); },
  getOpt(k){ return this.options[k]; },
  addInputRule(f){ this.inputRules.push(f); }, addOutputRule(f){ this.outputRules.push(f); },
  addListener(){}, removeListener(){}, fireEvent(){},
  getPreferences(){ return {}; }, ready(){},
  queryCommandState(){ return 0; },
  selection: { getRange(){ return { collapsed: true, createBookmark: () => ({}), select(){} }; } }
};
UE.plugins.pasteplain.call(ed);
UE.plugins.insertcode.call(ed);
UE.plugins.autotypeset.call(ed);

function simulatePaste(rawHtml, plainText) {
  let i = restoreFn(rawHtml, plainText || '');   // ① text/plain 回退恢复 pre 换行
  i = UE.filterWord(i);                          // ② filterWord
  i = pasteTransform(i);                         // ③ 文本节点换行→<br/>
  let C = UE.htmlparser(i);                      // ④ htmlparser
  if (ed.options.filterRules) UE.filterNode(C, ed.options.filterRules);
  UE.Editor.prototype.filterInputRule.call(ed, C); // ⑤ 输入规则(insertcode pre 重建)
  return C.toHtml();                             // ⑥ 最终 insertHtml 内容
}

let pass = 0, fail = 0;
function check(name, cond, detail) {
  if (cond) { pass++; console.log('PASS ' + name); }
  else { fail++; console.log('FAIL ' + name + ' :: ' + detail); }
}

// ============ 用户实际场景: 来源网页 pre 内换行几乎全丢 ============
const plain19 = [
  '# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson',
  '#    (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)',
  '',
  '# 1. 环境准备(确保 source 仓库已启用)',
  'dnf repolist --enabled | grep -i source',
  '',
  '# 2. 获取源码包并安装构建依赖',
  'cd /root/rpmbuild/SRPMS',
  'dnf download --source rsyslog',
  'dnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm',
  '',
  '# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)',
  "rpmbuild --rebuild --nocheck \\",
  "    --define 'dist .oe2509' \\",
  "    --define 'build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm' \\",
  '    rsyslog-8.2312.0-8.oe2509.src.rpm',
  '',
  '# 4. 安装重建后的基础 RPM(同版本替换)',
  'cd /root/rpmbuild/RPMS/x86_64',
  'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm',
  '',
  '# 5. 验证',
  'rsyslogd -N1                          # 应 exit=0',
  'systemctl start rsyslog && systemctl is-active rsyslog   # 应 active',
  'logger "verification" && tail -3 /var/log/messages       # 应恢复写入'
].join('\n');

// 模拟来源网页: pre 内只有 5 处换行,其余行全部粘连(与用户贴回的 HTML 一致)
const brokenPre = '<pre class="codetheme-tomorrownight language-bash" tabindex="0" style="box-sizing:border-box">'
  + '# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson'
  + '#    (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)'
  + '# 1. 环境准备(确保 source 仓库已启用)'
  + 'dnf repolist --enabled | grep -i source'
  + '# 2. 获取源码包并安装构建依赖'
  + 'cd /root/rpmbuild/SRPMS'
  + '\ndnf download --source rsyslog'
  + '\ndnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm'
  + '# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)'
  + 'rpmbuild --rebuild --nocheck \\'
  + '\n    --define &#39;dist .oe2509&#39; \\'
  + '\n    --define &#39;build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm&#39; \\'
  + '\n    rsyslog-8.2312.0-8.oe2509.src.rpm'
  + '# 4. 安装重建后的基础 RPM(同版本替换)'
  + 'cd /root/rpmbuild/RPMS/x86_64'
  + 'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm'
  + '# 5. 验证'
  + 'rsyslogd -N1                          # 应 exit=0'
  + 'systemctl start rsyslog &amp;&amp; systemctl is-active rsyslog   # 应 active'
  + 'logger &quot;verification&quot; &amp;&amp; tail -3 /var/log/messages       # 应恢复写入'
  + '</pre>';

// R1: 用户场景全链路 — 恢复 + 管线后应逐行 <br/> (空行→双 <br/>)
const outR1 = simulatePaste(brokenPre, plain19);
check('R1 损坏pre+纯文本→换行恢复',
  /libfastjson<br\s*\/?>#    (关键/.test(outR1) &&
  /直接绑定)<br\s*\/?>(<br\s*\/?>)?# 1\./.test(outR1) &&
  /x86_64<br\s*\/?>rpm -Uvh/.test(outR1) &&
  /应恢复写入/.test(outR1),
  JSON.stringify(outR1).slice(0, 300));
const brCount1 = (outR1.match(/<br\s*\/?>/gi) || []).length;
check('R1 每行一个 <br/> (≥19)', brCount1 >= 19, 'br 数=' + brCount1);

// R2: 正常 pre(换行足够)→ 恢复跳过, 转换后 pre 内 \n 全转 <br/>(2个)
const outR2 = simulatePaste('<pre>x\ny\nz</pre>', 'x\ny\nz');
const brCount2 = (outR2.match(/<br\s*\/?>/gi) || []).length;
check('R2 正常pre恢复跳过', /x<br\s*\/?>y<br\s*\/?>z/.test(outR2) && brCount2 === 2, JSON.stringify(outR2) + ' br=' + brCount2);

// R3: pre 与纯文本不匹配 → 恢复跳过,不注入无关内容
const outR3 = simulatePaste('<pre>abc</pre>', '完全不同的内容\n第二行');
check('R3 不匹配不注入', /abc/.test(outR3) && outR3.indexOf('完全不同的内容') < 0, JSON.stringify(outR3));

// R4: 无 pre → 不动
const outR4 = simulatePaste('<p>hi</p>', 'hi');
check('R4 无pre不动', /<p>hi<\/p>/.test(outR4), JSON.stringify(outR4));

// R5: 纯文本单行 → 恢复跳过(仅 insertcode 尾部br)
const outR5 = simulatePaste('<pre>single</pre>', 'single');
check('R5 单行恢复跳过', /<pre>single<br\s*\/?><\/pre>/.test(outR5), JSON.stringify(outR5));

// R6: 代码含 $1 $& 等替换符 — 恢复后不被吃掉
const outR6 = simulatePaste('<pre>echo $1 $2 &amp;&amp; $&amp;</pre>', 'echo $1 $2 && $&');
check('R6 替换符不被吃掉', /echo \$1 \$2 &amp;&amp; \$&amp;/.test(outR6) || /echo \$1 \$2 && \$&/.test(outR6), JSON.stringify(outR6));

// ============ 用户真实场景: pre>code>span 逐行 + \n (htmlparser 曾剥掉 span 相邻换行) ============
const spanLines = [
  '# 0. 前置:先按 micoder/libfastjson 仓库重建并安装 libfastjson',
  '#    (关键:DT_NEEDED 包含 libm.so.6,modf IFUNC 直接绑定)',
  '',
  '# 1. 环境准备(确保 source 仓库已启用)',
  'dnf repolist --enabled | grep -i source',
  '',
  '# 2. 获取源码包并安装构建依赖',
  'cd /root/rpmbuild/SRPMS',
  'dnf download --source rsyslog',
  'dnf builddep -y rsyslog-8.2312.0-8.oe2509.src.rpm',
  '',
  '# 3. 重建 RPM(--nocheck 跳过耗时测试;关键参数见 build.sh 注释)',
  "rpmbuild --rebuild --nocheck \\",
  "    --define 'dist .oe2509' \\",
  "    --define 'build_ldflags -Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld -Wl,--no-as-needed -lm' \\",
  '    rsyslog-8.2312.0-8.oe2509.src.rpm',
  '',
  '# 4. 安装重建后的基础 RPM(同版本替换)',
  'cd /root/rpmbuild/RPMS/x86_64',
  'rpm -Uvh --replacepkgs rsyslog-8.2312.0-8.oe2509.x86_64.rpm',
  '',
  '# 5. 验证',
  'rsyslogd -N1                          # 应 exit=0',
  'systemctl start rsyslog && systemctl is-active rsyslog   # 应 active',
  'logger "verification" && tail -3 /var/log/messages       # 应恢复写入'
];
// 复刻来源网页: 每行包在 <span class="token ..."> 里, 行尾紧跟 \n
const spanHtml = '<pre class=" codetheme-tomorrownight language-bash" tabindex="0" style="color:rgb(204,204,204)"><code class="language-bash">'
  + spanLines.map(function (l) {
      return l ? '<span class="token comment" style="color:rgb(153,153,153)">' + l + '</span>\n' : '\n';
    }).join('')
  + '</code></pre>';
const outS1 = simulatePaste(spanHtml, spanLines.join('\n'));
const brCountS1 = (outS1.match(/<br\s*\/?>/gi) || []).length;
check('S1 span+换行代码块→逐行<br/> (≥19)',
  /前置:先按 micoder[\s\S]{0,100}?<br\s*\/?>[\s\S]{0,120}?#[\s\S]{0,20}?(关键/.test(outS1) &&
  /x86_64[\s\S]{0,100}?<br\s*\/?>[\s\S]{0,100}?rpm -Uvh/.test(outS1) &&
  brCountS1 >= 19,
  'br=' + brCountS1 + ' :: ' + JSON.stringify(outS1).slice(0, 260));
check('S1 pre 内无残留 \\n', outS1.indexOf('\n') < 0, JSON.stringify(outS1).slice(0, 120));
check('S1 span 结构保留', /<span class="token/.test(outS1), JSON.stringify(outS1).slice(0, 120));

// ============ 原回归用例(换行转换) ============
const outA = simulatePaste('# a\n# b\n\n# c\nd', '# a\n# b\n\n# c\nd');
check('纯文本多行→<br/>', /# a<br\s*\/?># b/.test(outA) && /# b<br\s*\/?><br\s*\/?># c/.test(outA), JSON.stringify(outA));

const outB = simulatePaste('<p>intro</p><pre class="brush:js">var a=1;\nvar b=2;</pre><p>outro</p>', '');
check('pre 内换行保留(回归)', /var a=1;(\n|<br\s*\/?>)var b=2/.test(outB), JSON.stringify(outB));

const outC = simulatePaste('<p>a</p>\n<p>b</p>', 'a\nb');
check('段落间换行不产生<br>(回归)', outC.indexOf('<br') < 0, JSON.stringify(outC));

console.log('\n结果: ' + pass + ' 通过, ' + fail + ' 失败');
process.exit(fail ? 1 : 0);