/**
 * 教材口径不得回潮旧 AI 句。运行：node website/app/js/kr_copy_selftest.js
 */
(function () {
  "use strict";
  var fs = require("fs");
  var path = require("path");
  var fails = 0;
  function ok(name, cond) {
    if (!cond) { fails++; console.error("FAIL", name); }
    else console.log("ok", name);
  }
  function read(rel) {
    return fs.readFileSync(path.join(__dirname, rel), "utf8");
  }
  function bare(s, needle, allow) {
    allow = allow || ["废止", "禁断", "不作", "不是", "已改", "勿写", "禁：", "勿断", "Abolished", "Abolido", "廃止", "أُلغي", "历史冻存"];
    var i = 0;
    while ((i = s.indexOf(needle, i)) >= 0) {
      var ctx = s.slice(Math.max(0, i - 24), i + needle.length + 8);
      if (!allow.some(function (a) { return ctx.indexOf(a) >= 0; })) return ctx;
      i += needle.length;
    }
    return "";
  }

  var tut = read("kr_tutorial.js");
  var sop = read("../../docs/XIN_GONG_FEED.md");
  var canon = read("../canon.html");
  var kanju = read("../kanju.html");
  var feed = read("../../AI_FEED_CANON.md");
  var lock = read("../../docs/IRON_LOCK.md");

  ok("铁律对照册在场", /定体正从/.test(lock) && /身旺＝福气/.test(lock) || /身旺/.test(lock));
  ok("铁律天干特关四冲五合", /四冲／五合/.test(lock) && /按普通克/.test(lock) && /五合解冲/.test(lock));
  ok("铁律地支特关两类", /开库/.test(lock) && /先合冲再生克/.test(lock) && /火量相加/.test(lock) && /a=1/.test(lock));
  ok("铁律普通生克紧贴含特殊", /必须紧贴/.test(lock) && /先算藏干特关/.test(lock) && /先合冲再生克/.test(lock) && /目字十神生链/.test(lock));
  ok("铁律小量基数", /小量/.test(lock) && /特关不算同行/.test(lock));
  ok("SOP五合绊按普通克", /按普通克/.test(sop) && /四冲/.test(sop) && /隔位遥感/.test(sop));
  ok("SOP地支特关两类", /先合冲再生克/.test(sop) && /火量相加/.test(sop) && /同行不作用/.test(sop));
  ok("教程天干特关", tut.indexOf("四冲／五合") > 0 && tut.indexOf("按普通克") > 0);
  ok("教程地支特关两类", tut.indexOf("会合") > 0 && tut.indexOf("刑冲害破") > 0 && tut.indexOf("先合再冲") > 0 && tut.indexOf("先冲再合") > 0);
  ok("教程生克时序", tut.indexOf("先算藏干特关") > 0 && tut.indexOf("相生先生后克") > 0 && tut.indexOf("相克先克后生") > 0 && tut.indexOf("目字十神生链") > 0);
  ok("教程同行只传递", tut.indexOf("同行不生克、只做能量传递") > 0);
  ok("教程做功只改用忌", tut.indexOf("做功后只改第二次实际用忌") > 0 && tut.indexOf("不改正从、我客、家内外") > 0);
  ok("教程令值新尺", tut.indexOf("囚⅞／死⅝") > 0 && tut.indexOf("休囚同0.75／死0.25") > 0);
  var prin = read("../../docs/PRINCIPLES.md");
  ok("原理总册在场", /火量相加/.test(prin) && /先合后冲/.test(prin) && /先冲后合/.test(prin) && /字内藏干不作用/.test(prin) && /先算藏干特关/.test(prin));
  ok("总册流层入局", /流年与原局的字分隔/.test(prin) && /单字共振/.test(prin) && /流年与大运/.test(prin) && /没形成流动/.test(prin));
  ok("铁律流层入局", /流层对原局分隔不生克/.test(lock) && /a≥1 只特关/.test(lock) && /流年对大运/.test(lock));
  ok("总册参数列表", /令·囚/.test(prin) && /7\/8＝0.875/.test(prin) && /只改用忌/.test(prin) && /水1\.2625/.test(prin));
  ok("总册令地逐字表", /令地计算详细参数表/.test(prin) && /戊土当令/.test(prin) && /0\.684755859375/.test(prin) && /1−\(1−L\)×E戌/.test(prin) && /1−\(1−D\)×¼×E午/.test(prin) && /地打满盘/.test(prin) && /2\.6733515625/.test(prin) && /字距不是柱距/.test(prin) && /先戌后午/.test(prin) && /地支以月令为中心/.test(prin) && /年3\/4/.test(prin) && /月7\/8/.test(prin));
  ok("铁律地支以月令为中心", /地支以\*\*月令\*\*为中心/.test(lock) || /地支以月令为中心/.test(lock));
  ok("铁律本钱年3/4月7/8", /年3\/4／月7\/8／日1\.0／时3\/4/.test(lock) && /年3\/4／月1\.0／日7\/8／时3\/4/.test(lock));
  ok("总册条图三阶段", /条图三阶段/.test(prin) && /①作用前/.test(prin) && /②运前/.test(prin) && /③流层后/.test(prin));
  ok("铁律条图三阶段", /条图三阶段/.test(lock) && /①作用前/.test(lock) && /②运前/.test(lock) && /③流层后/.test(lock));
  ok("总册令地旺衰不算合冲", /令地旺衰不算合冲生克/.test(prin));
  ok("铁律令地旺衰不算合冲", /令地旺衰不算合冲生克/.test(lock));
  ok("铁律流局外部环境每字1.0", /流局/.test(lock) && /外部环境/.test(lock) && /每字 \*\*1\.0\*\*/.test(lock) && /人类盘/.test(lock));
  ok("总册流局外部环境每字1.0", /流局是外部环境/.test(prin) && /每字1\.0/.test(prin) && /11\.6 流局/.test(prin) && /3\.75/.test(prin));
  ok("教程流局外部环境", tut.indexOf("流局是外部环境") > 0 && tut.indexOf("令地／年权只给人类盘") > 0);
  ok("教程字距不是柱距", tut.indexOf("不是柱距") > 0 && tut.indexOf("同柱／同层邻柱 a=0") > 0);
  ok("教程无裸柱距a=-1", !bare(tut, "a=-1 同柱"));
  ok("教程无裸地＝时支令后", !bare(tut, "地＝时支令后"));
  ok("教程无裸休囚同0.75死0.25现行", !bare(tut, "休囚同 0.75、死 0.25"));
  ok("教程无裸同柱E=2现行", tut.indexOf("能量：最大（同柱 E=2）") < 0 && tut.indexOf("同柱／同层邻柱 a=0，E=1") > 0);
  ok("典章无裸流局内同命局柱距", !bare(canon, "流局内：同命局柱距"));
  ok("典章流局外部环境", canon.indexOf("流局是外部环境") > 0 && canon.indexOf("每字1.0") > 0);
  var params = read("../../PARAMS_2026_08_11.md");
  ok("PARAMS地系数现行", params.indexOf("地系数＝1−(1−D)×¼×E午") > 0 && !bare(params, "地＝令后 **1.0×1/4**"));
  ok("PARAMS本钱年3/4月7/8", /天干年3\/4／月7\/8／日1\.0／时3\/4/.test(params) && /地支年3\/4／月1\.0／日7\/8／时3\/4/.test(params));
  var rules = read("../../model_rules_params.html");
  ok("定型参数表本钱尺", /本钱·天干/.test(rules) && /年3\/4／月7\/8／日1\.0／时3\/4/.test(rules) && /年3\/4／月1\.0／日7\/8／时3\/4/.test(rules) && /二附丙、本钱/.test(rules));
  var huoye = read("../../author_xin_pan_huoye.html");
  ok("预览活页定型本钱", /定型本钱/.test(huoye) && /干年3\/4月7\/8日1\.0时3\/4/.test(huoye) && /支年3\/4月1\.0日7\/8时3\/4/.test(huoye));
  ok("活页能排盘", /function runSheet/.test(huoye) && /KREngine\.analyze/.test(huoye) && /澳门丁/.test(huoye) && /philadelphia/.test(huoye));
  ok("活页锁句不叠整段参数一行", huoye.indexOf("id=\"paramLock\"") > 0 && !/paramLock[^>]*>[\s\S]{0,80}五合化A/.test(huoye));
  var zeisen = read("zeisen.js");
  var cities = read("kr_cities.js");
  var i18nUi = read("kr_i18n.js");
  ok("排盘深链可直开", /function applyUrlChart/.test(zeisen) && /go === "1"/.test(zeisen));
  ok("洋葱干上支下", /function gzStack/.test(zeisen) && /class="ogan/.test(zeisen) && /class="ozhi/.test(zeisen));
  ok("洋葱一层一行", /class="orow"/.test(zeisen) && zeisen.indexOf("orow up") < 0 && zeisen.indexOf("orow dn") < 0 && zeisen.indexOf("opager") < 0);
  ok("洋葱九柱同行", /function focusOnionDefault/.test(zeisen) && /natal-year/.test(zeisen) && /liushi/.test(zeisen) && /dayun/.test(zeisen));
  ok("九键均可选区间时段", /function togglePick/.test(zeisen) && /function lifeYears/.test(zeisen) && /pick-natal-year/.test(zeisen) && /pick-natal-month/.test(zeisen) && /pick-natal-day/.test(zeisen) && /pick-natal-time/.test(zeisen) && /pick-liushi/.test(zeisen));
  var zeisenCss = read("../zeisen.html");
  ok("洋葱一行九柱同屏", /flex:1 1 0/.test(zeisenCss) && /一行九柱同屏/.test(zeisenCss) && /flex-direction:column/.test(zeisenCss));
  var indexHtml = read("../../index.html");
  ok("入口有直接打开排盘预测", indexHtml.indexOf("zeisen.html?unlock=master&theme=light&go=1") > 0 && indexHtml.indexOf("直接打开 · 排盘预测") > 0);
  var dl = read("../../download.html");
  ok("下载页有完整工程包", dl.indexOf("KEY_RESONANCE_FULL.zip") > 0 && dl.indexOf("下载完整工程包") > 0);
  ok("下载页有各类参数文件", dl.indexOf("KR_PARAMS_2026_08_11.zip") > 0 && dl.indexOf("kr_params_2026_08_11.js") > 0 && dl.indexOf("PARAMS_2026_08_11.md") > 0 && dl.indexOf("ling_di_yun_params_2026_08_11.dart") > 0 && dl.indexOf("AI_FEED_PACK.zip") > 0 && dl.indexOf("AI_FEED_CANON.json") > 0);
  ok("网站主页打开即连", indexHtml.indexOf("键共振 · 网站主页") > 0 && indexHtml.indexOf("q.set(\"unlock\", \"master\")") > 0 && indexHtml.indexOf("author_unlock.js") > 0);
  ok("入口接参数与新盘", indexHtml.indexOf("作者输入新盘 · 测试预览") > 0 && indexHtml.indexOf("new=1") > 0 && indexHtml.indexOf("open === \"huoye\"") > 0 && indexHtml.indexOf("paramConn") > 0 && indexHtml.indexOf("benQian") > 0 && indexHtml.indexOf("KRParams.oneLinerZh") < 0);
  ok("活页空盘新盘", huoye.indexOf("isNew=q.get('new')==='1'") > 0 && huoye.indexOf("['新盘','',") > 0);
  ok("泽森空盘新盘", /isNew = q.get\("new"\) === "1"/.test(zeisen) && /isNew && !date/.test(zeisen));
  ok("总站一节引连载", indexHtml.indexOf("作者体验活动 · 连载") > 0 && indexHtml.indexOf("①目录 → ②公农历 → ③干支 → ④活页 → ⑤排盘") > 0);
  ok("性别只写男女", i18nUi.indexOf('male: "男"') > 0 && i18nUi.indexOf('sexMale: "男"') > 0 && i18nUi.indexOf("乾造·男") < 0 && i18nUi.indexOf("男 · 乾造") < 0 && i18nUi.indexOf("乾造甲木") < 0);
  var appIndex = read("../index.html");
  ok("app入口转泽森", appIndex.indexOf("location.replace(\"./zeisen.html?\"") > 0 && appIndex.indexOf("legacy") > 0);
  var previewIdx = read("../../preview/index.html");
  var serialJs = read("../../preview/kr_serial.js");
  ok("活动连载五节", previewIdx.indexOf("作者体验活动 · 连载") > 0 && previewIdx.indexOf("下一节") > 0 && serialJs.indexOf("id: \"hub\"") > 0 && serialJs.indexOf("id: \"cal\"") > 0 && serialJs.indexOf("id: \"gz\"") > 0 && serialJs.indexOf("id: \"huoye\"") > 0 && serialJs.indexOf("id: \"zeisen\"") > 0 && serialJs.indexOf("kr_serial") > 0);
  ok("活动入口接泽森", previewIdx.indexOf("zeisen.html?unlock=master&theme=light&go=1") > 0 && (previewIdx.indexOf("打开排盘预测") > 0 || previewIdx.indexOf("⑤ 泽森排盘预测") > 0));
  ok("活页接连载", huoye.indexOf("kr_serial.js") > 0 && huoye.indexOf("serialMount") > 0 && huoye.indexOf("current:\"huoye\"") > 0);
  var zeisenHtml = read("../zeisen.html");
  ok("泽森接连载", zeisenHtml.indexOf("kr_serial.js") > 0 && zeisenHtml.indexOf("serialHome") > 0 && zeisen.indexOf("function paintSerial") > 0 && zeisen.indexOf("stashGet") > 0);
  var calAct = read("../../preview/calendar_bazi_activity.html");
  ok("公农历检测接泽森", calAct.indexOf("toPaipan") > 0 && calAct.indexOf("zeisen.html?unlock=master&theme=light&go=1") > 0 && calAct.indexOf("打开基／专／键") < 0);
  var gzAct = read("../../preview/author_experience_check.html");
  ok("干支检测接活页排盘", gzAct.indexOf("author_xin_pan_huoye.html?unlock=master") > 0 && gzAct.indexOf("打开基／专／键排盘") < 0);
  var docsIdx = read("../../docs/index.html");
  ok("文档排盘主站接泽森", docsIdx.indexOf("zeisen.html?unlock=master&theme=light&go=1") > 0);
  ok("活盘无旧定位键现地此刻", zeisen.indexOf("定位键·现地此刻") < 0);
  ok("活盘实地时刻走系统钟与定位", /t\("locateNow"\)/.test(zeisen) && /applyShiDiShiKe/.test(zeisen) && /geolocation/.test(zeisen) && /nowDT\(\)/.test(zeisen));
  ok("活盘选现地同步当地墙钟", /applyCityLocalNow/.test(zeisen) && /commitLive/.test(zeisen) && /applyCoordsLocalNow/.test(zeisen) && /liveStandName/.test(zeisen));
  ok("活盘三步时地锚", /liveAnchors/.test(zeisen) && /threeStepHTML/.test(zeisen) && /stepBirth/.test(zeisen));
  ok("条图三阶段合一", /function triBar/.test(zeisen) && /class="merge"/.test(zeisen) && zeisen.indexOf('class="stage-h"') < 0);
  ok("三档条图身份", /function tchip/.test(zeisen) && /function tierBanner/.test(zeisen) && /function colOpen/.test(zeisen) && /tier-base/.test(zeisen) && /tier-pro/.test(zeisen) && /tier-key/.test(zeisen) && /page: "base"/.test(zeisen) && /page: "pro"/.test(zeisen) && /page: "key"/.test(zeisen));
  ok("未付列不露数", /open \? v\.toFixed\(2\) : "🔒"/.test(zeisen) || /num\(v1, o1\)/.test(zeisen));
  ok("五语三档付款", i18nUi.indexOf("①基础【初断】") > 0 && i18nUi.indexOf("付款内容分三部") > 0 && i18nUi.indexOf("未付档只显示锁") > 0 && i18nUi.indexOf("tierBasePay: \"免费\"") > 0);
  ok("预览档刷新全页", /if \(lastCtx\) render\(lastCtx\)/.test(zeisen));
  ok("实地时刻还按钮", /opts.onDone/.test(zeisen) && /locateNow/.test(zeisen));
  ok("存盘跟现地墙钟", /standLon: liveStandLon/.test(zeisen));
  ok("载入案件还原现地", /pendingStandRestore/.test(zeisen));
  ok("城市库nearest与设备时区", /function nearest/.test(cities) && /function byDeviceTz/.test(cities));
  ok("城市库有费城", /philadelphia/.test(cities) && /费城/.test(cities) && /America\/New_York/.test(cities));
  ok("五语实地时刻", i18nUi.indexOf('locateNow: "实地时刻"') > 0 && i18nUi.indexOf("Field time") > 0);
  var pjs = read("kr_params_2026_08_11.js");
  ok("KRParams一行含本钱", /本钱天干年3\/4月7\/8日1时3\/4/.test(pjs) && /benQian/.test(pjs));
  var i18n = read("kr_tutorial_i18n.js");
  ok("五语无裸柱距a=-1现行", i18n.indexOf("a=-1 same pillar (year stem") < 0 && i18n.indexOf("a=-1 同柱（年干-年支）") < 0 && i18n.indexOf("a=-1 mismo pilar (tallo") < 0);
  ok("五语流局1.0", i18n.indexOf("each character 1.0") > 0 && i18n.indexOf("各字1.0") > 0);
  var oTab = read("../../docs/O_PROGRAM_MASTER_TABLE.md");
  ok("O程序申戌新锁", /水1\.2625/.test(oTab) && oTab.indexOf("水1.225") < 0 && oTab.indexOf("囚0.75／死0.25") < 0);
  ok("总册无裸六合必合", !bare(feed, "地支六合仍相遇必合"));
  ok("总册无裸字内互生克", !bare(feed, "在每字内与其它藏干"));
  ok("教程首页是铁律对照", tut.indexOf("铁律对照（先读）") > 0);
  ok("教程无裸得令±1/8", !bare(tut, "得令±1/8"));
  ok("教程无裸小运1/10", !bare(tut, "1/10几乎可忽略") && tut.indexOf("叠能=1/10") < 0);
  ok("教程无庚克甲成立", tut.indexOf("天干庚克甲（金克日主）") < 0);
  ok("教程无直取即人", tut.indexOf("按十神相对日主直取") < 0);
  ok("SOP丁盘定体正格身弱", /定体（＋广州）我 1\.24＞1 → \*\*正格·身弱/.test(sop) || /定体（＋广州）我 1\.24＞1、我＜客 → \*\*正格·身弱/.test(sop));
  ok("铁律正格平衡", /正格·平衡/.test(lock) && /用忌看家内外做功/.test(lock) && /\|我−客\|／max/.test(lock));
  ok("教程正格平衡", tut.indexOf("正格·平衡") > 0 && tut.indexOf("用忌看家内外做功") > 0);
  ok("SOP正格平衡尺", /正格·平衡/.test(sop) && /用忌看家内外做功/.test(sop));
  ok("SOP戊盘正格身强", /4\.23∶客 3\.36/.test(sop) && /正格·身强/.test(sop));
  ok("典章小运1/8", /小运×1\/8/.test(canon) && canon.indexOf("小运=1/10") < 0);
  ok("典章无裸字内互生克", canon.indexOf("每字内与他藏干合冲生克") < 0);
  ok("看局小运1/8", /小运×1\/8/.test(kanju));
  ok("总册家内外新口径", /家内＝日时字＋出生地理/.test(feed) && !bare(feed, "家内≈原局+大小运"));

  require("./kr_tutorial.js");
  var parts = global.KRTutorial && global.KRTutorial.parts;
  ok("教程模块可载", !!(parts && parts.length === 3));
  ok("三部都先读铁律", parts && parts.every(function (p) { return p.pages[0] && p.pages[0].h === "铁律对照（先读）"; }));

  var vm = require("vm");
  var store = {};
  var ctx = {
    window: {},
    document: {
      getElementById: function () { return null; },
      createElement: function () { return { textContent: "" }; },
      head: { appendChild: function () {} },
      querySelector: function () { return { insertBefore: function () {} }; }
    },
    sessionStorage: {
      getItem: function (k) { return store[k] || null; },
      setItem: function (k, v) { store[k] = String(v); }
    }
  };
  ctx.window = ctx;
  vm.runInNewContext(serialJs, ctx);
  ctx.KRSerial.stashSet({ name: "作者预览", date: "1984-02-02", time: "00:30", pillars: "甲子乙丑丙寅丁卯", gender: "M" });
  var nextGz = ctx.KRSerial.hrefOf("preview", "gz");
  var nextHy = ctx.KRSerial.hrefOf("preview", "huoye");
  var nextZs = ctx.KRSerial.hrefOf("app", "zeisen");
  ok("连载携带名号日期四柱", nextGz.indexOf("name=") > 0 && nextGz.indexOf("1984-02-02") > 0 && nextGz.indexOf("00%3A30") > 0 && nextGz.indexOf("pillars=") > 0);
  ok("连载下一节指向活页与排盘", nextHy.indexOf("author_xin_pan_huoye.html") > 0 && nextZs.indexOf("zeisen.html") > 0 && nextZs.indexOf("go=1") > 0);
  ok("连载五步齐全", ctx.KRSerial.steps.length === 5);

  if (fails) { console.error("FAILED", fails); process.exit(1); }
  console.log("COPY_OK");
})();
