/**
 * 正从专项：层权令锁 + 黄金盘手算。正从断错则下游全错。
 * 运行：node website/app/js/kr_zhengcong_selftest.js
 * 也可被 kr_pipeline_selftest.js require。
 */
(function (global) {
  "use strict";
  var standalone = typeof module !== "undefined" && require.main === module;
  var fails = 0;
  function eq(name, a, b) {
    var ok = JSON.stringify(a) === JSON.stringify(b);
    if (!ok) { fails++; console.error("FAIL", name, "got", a, "want", b); }
    else console.log("ok", name);
  }
  function ok(name, cond) {
    if (!cond) { fails++; console.error("FAIL", name); }
    else console.log("ok", name);
  }
  function near(name, a, b, eps) {
    eps = eps == null ? 1e-9 : eps;
    if (Math.abs(a - b) > eps) { fails++; console.error("FAIL", name, "got", a, "want", b); }
    else console.log("ok", name);
  }

  if (!global.KRParams) require("./kr_params_2026_08_11.js");
  if (!global.KREngine) require("./kr_engine.js");
  if (!global.KRRelations) require("./kr_relations.js");
  if (!global.KRXinGong) require("./kr_xin_gong.js");

  var E = global.KREngine;
  var P = global.KRParams;
  var WX = ["木", "火", "土", "金", "水"];
  var xu = 10, shen = 8;

  // —— O19 戌／申层权令锁 ——
  near("戌月层权令·土", E.layerLingFactor(2, xu, P), 1.250);
  near("戌月层权令·金", E.layerLingFactor(3, xu, P), 1.2625);
  near("戌月层权令·火", E.layerLingFactor(1, xu, P), 0.8625);
  near("戌月层权令·木", E.layerLingFactor(0, xu, P), 0.7875);
  near("戌月层权令·水", E.layerLingFactor(4, xu, P), 0.8375);
  near("申月层权令·金", E.layerLingFactor(3, shen, P), 1.250);
  near("申月层权令·水", E.layerLingFactor(4, shen, P), 1.2625);
  near("申月层权令·土", E.layerLingFactor(2, shen, P), 0.8625);
  near("申月层权令·木", E.layerLingFactor(0, shen, P), 0.8375);
  near("申月层权令·火", E.layerLingFactor(1, shen, P), 0.7875);

  // 合化临界仍用月支本气，不吃层权令
  ok("合化尺：卯对土死（月支本气）", global.KRRelations.hehuaLingState("土", "卯").state === "死");
  ok("合化尺：巳对土相（月支本气）", global.KRRelations.hehuaLingState("土", "巳").state === "相");
  ok("合化尺：戌对木囚（月支本气，不是层权）", global.KRRelations.hehuaLingState("木", "戌").state === "囚");

  var natal = { year: "癸丑", month: "壬戌", day: "甲申", time: "庚午" };
  var ding = { year: "戊申", month: "壬戌", day: "丁丑", time: "辛丑" };
  var wu = { year: "癸酉", month: "丁巳", day: "戊戌", time: "壬子" };

  // 作者盘手算：先戌令 1−(1−L)×E戌，后午地 1−(1−D)×¼×E午。地打满盘。
  // 戌字距：同柱／同层邻柱 a=0；戌甲 a=1；戌午 a=1；戌庚 a=2。
  // 午字距：年干 a=3→⅛；年支 a=2→¼；月干 a=2→¼；月支 a=1→½；日干 a=1→½；日支／时柱 a=0→1。
  var Lxu = { 木: 0.7875, 火: 0.8625, 土: 1.250, 金: 1.2625, 水: 0.8375 };
  var Dwu = { 木: 0.7875, 火: 1.275, 土: 1.325, 金: 0.8125, 水: 0.8 };
  function lingApply(L, ev) { return 1 - (1 - L) * ev; }
  function diApply(D, ev) { return 1 - (1 - D) * 0.25 * ev; }
  function cell(w, L, eLing, D, eDi) { return w * lingApply(L, eLing) * diApply(D, eDi); }
  eq("天干本钱以日为中心", [E.stemBaseW(0), E.stemBaseW(1), E.stemBaseW(2), E.stemBaseW(3)], [0.75, 0.875, 1.0, 0.75]);
  eq("地支本钱以月令为中心", [E.branchBaseW(0), E.branchBaseW(1), E.branchBaseW(2), E.branchBaseW(3)], [0.75, 1.0, 0.875, 0.75]);
  var gW = E.stemBaseW, zY = E.branchBaseW;
  var wantAuthor = {
    木: cell(gW(2), Lxu.木, 0.5, Dwu.木, 0.5),
    火: cell(0.1 * zY(1), Lxu.火, 1, Dwu.火, 0.5) + cell(0.7 * zY(3), Lxu.火, 0.5, Dwu.火, 1),
    土: cell(0.6 * zY(0), Lxu.土, 1, Dwu.土, 0.25) + cell(0.6 * zY(1), Lxu.土, 1, Dwu.土, 0.5) + cell(0.1 * zY(2), Lxu.土, 1, Dwu.土, 1) + cell(0.3 * zY(3), Lxu.土, 0.5, Dwu.土, 1),
    金: cell(gW(3), Lxu.金, 0.25, Dwu.金, 1) + cell(0.1 * zY(0), Lxu.金, 1, Dwu.金, 0.25) + cell(0.3 * zY(1), Lxu.金, 1, Dwu.金, 0.5) + cell(0.6 * zY(2), Lxu.金, 1, Dwu.金, 1),
    水: cell(gW(0), Lxu.水, 0.5, Dwu.水, 0.125) + cell(gW(1), Lxu.水, 1, Dwu.水, 0.25) + cell(0.3 * zY(0), Lxu.水, 1, Dwu.水, 0.25) + cell(0.3 * zY(2), Lxu.水, 1, Dwu.水, 1),
  };
  near("手算作者令地·木", wantAuthor.木, 0.870009765625);
  near("手算作者令地·火", wantAuthor.火, 0.6117333984375);
  near("手算作者令地·土", wantAuthor.土, 1.74634765625);
  near("手算作者令地·金", wantAuthor.金, 1.856949462890625);
  near("手算作者令地·水", wantAuthor.水, 1.803341796875);
  near("戌土当令分项", 0.6 * 1.5 + 0.3 * 0.75 + 0.1 * 1.25, 1.250);
  near("戌金当令分项", 0.6 * 1.25 + 0.3 * 1.5 + 0.1 * 0.625, 1.2625);
  near("戌火当令分项", 0.6 * 0.75 + 0.3 * 0.875 + 0.1 * 1.5, 0.8625);
  near("戌木当令分项", 0.6 * 0.875 + 0.3 * 0.625 + 0.1 * 0.75, 0.7875);
  near("戌水当令分项", 0.6 * 0.625 + 0.3 * 1.25 + 0.1 * 0.875, 0.8375);
  near("令系数年癸", E.lingApply(Lxu.水, 0.5), 0.91875);
  near("午地水D", E.layerDiFactor(4, 6, P), 0.8);
  near("午地火D", E.layerDiFactor(1, 6, P), 1.275);
  near("地系数年癸", E.diApply(0.8, 0.125, 0.25), 0.99375);
  near("年癸先戌后午", cell(gW(0), Lxu.水, 0.5, Dwu.水, 0.125), 0.684755859375);
  near("月壬先戌后午", cell(gW(1), Lxu.水, 1, Dwu.水, 0.25), 0.72365234375);
  near("日甲先戌后午", cell(gW(2), Lxu.木, 0.5, Dwu.木, 0.5), 0.870009765625);
  near("时庚先戌后午", cell(gW(3), Lxu.金, 0.25, Dwu.金, 1), 0.76175537109375);
  near("戌戊先戌后午", cell(0.6 * zY(1), Lxu.土, 1, Dwu.土, 0.5), 0.78046875);
  near("丑己先戌后午", cell(0.6 * zY(0), Lxu.土, 1, Dwu.土, 0.25), 0.57392578125);
  near("申庚先戌后午", cell(0.6 * zY(2), Lxu.金, 1, Dwu.金, 1), 0.6317431640625);
  near("午丁先戌后午", cell(0.7 * zY(3), Lxu.火, 0.5, Dwu.火, 1), 0.5225185546875);
  near("戌→月支=1", E.lingFallE(1, "zhi"), 1);
  near("戌→年干=½", E.lingFallE(0, "gan"), 0.5);
  near("戌→年支=1", E.lingFallE(0, "zhi"), 1);
  near("戌→日干=½", E.lingFallE(2, "gan"), 0.5);
  near("戌→日支=1", E.lingFallE(2, "zhi"), 1);
  near("戌→时干=¼", E.lingFallE(3, "gan"), 0.25);
  near("戌→时支=½", E.lingFallE(3, "zhi"), 0.5);
  near("午→时支=1", E.diFallE(3, "zhi"), 1);
  near("午→年干=⅛", E.diFallE(0, "gan"), 0.125);
  near("午→年支=¼", E.diFallE(0, "zhi"), 0.25);
  near("午→月干=¼", E.diFallE(1, "gan"), 0.25);
  near("午→日干=½", E.diFallE(2, "gan"), 0.5);

  var acc = E.accumulateNatal(natal, 23.13, 113.26, P);
  WX.forEach(function (w) {
    near("引擎令地=" + w + "手算", acc.elLingDi[w], wantAuthor[w]);
  });
  ok("地理不进令地账", acc.el.水 !== acc.elLingDi.水 || acc.el.火 !== acc.elLingDi.火);

  var accChi = E.accumulateNatal(natal, 41.8781, -87.6298, P);
  WX.forEach(function (w) {
    near("同柱芝加哥令地同·" + w, acc.elLingDi[w], accChi.elLingDi[w]);
  });
  ok("地理不进字令地", JSON.stringify(acc.elLingDi) !== JSON.stringify(acc.el) && acc.geoBirth.火 > 0);
  ok("地理叠能进正从总量", JSON.stringify(acc.el) !== JSON.stringify(accChi.el));

  var gz = E.analyze({ pillars: natal, birthLat: 23.13, birthLon: 113.26 });
  near("作者字令地我", (gz.energy.lingDi.木 + gz.energy.lingDi.水), 2.6733515625);
  near("作者字令地客", (gz.energy.lingDi.火 + gz.energy.lingDi.土 + gz.energy.lingDi.金), 4.215030517578125);
  near("作者定体我含地理", gz.zhengDiHou.wo, 2.6733515625 + acc.geoBirth.木 + acc.geoBirth.水);
  near("作者定体客含地理", gz.zhengDiHou.ta, 4.215030517578125 + acc.geoBirth.火 + acc.geoBirth.土 + acc.geoBirth.金);
  eq("作者定体正格身弱用我", [gz.zhengDiHou.pattern, gz.zhengDiHou.strength, gz.zhengDiHou.orient, gz.zhengDiHou.useGuest],
    ["正格", "身弱", "用我忌客", false]);
  ok("作者二次不改正从身份", gz.zhengDiHou.pattern === "正格" && gz.zhengDiHou.strength === "身弱");
  ok("作者做功不改我客", !gz.zhengZhuan.gongDeLi && gz.zhengZhuan.wo === gz.zhengDiHou.wo && gz.zhengZhuan.ta === gz.zhengDiHou.ta);
  ok("令地旺衰不算合冲生克", JSON.stringify(gz.energy.lingDi) !== JSON.stringify(gz.energy.shengKeZi)
    && gz.zhengDiHou.strength === "身弱"
    && Math.abs(gz.zhengDiHou.wo - gz.energy.stages.beforeWork.wo) < 1e-12
    && Math.abs(gz.energy.stages.beforeYun.wo - gz.zhengDiHou.wo) > 0.1);
  eq("作者二次用我", [gz.zhengZhuan.orient, gz.xinGong.ledger.useGuest], ["用我忌客", false]);
  ok("作者断语正从冻结", /正从冻结/.test(gz.xinGong.judge.text) && /不改/.test(gz.xinGong.judge.text));
  ok("作者断语只改用忌", /只改用忌/.test(gz.xinGong.judge.text) && !/做功后身旺、用神得力/.test(gz.xinGong.judge.text));
  ok("作者我客有夭寿身体能力", /夭寿/.test(gz.xinGong.judge.text) && /身体/.test(gz.xinGong.judge.text) && /能力/.test(gz.xinGong.judge.text));
  ok("作者家内外定境不改", /自家实际量/.test(gz.xinGong.judge.text) && /可利用资源/.test(gz.xinGong.judge.text) && /做功不改写/.test(gz.xinGong.judge.text));
  ok("作者十神对自家日主利败", /对日主/.test(gz.xinGong.judge.text) && /对自家/.test(gz.xinGong.judge.text));
  ok("作者vitality能力平", gz.vitality && gz.vitality.neng && gz.vitality.neng.band === "持平");
  ok("作者vitality寿稳旺", gz.vitality && gz.vitality.shou && (gz.vitality.shou.band === "稳旺" || gz.vitality.shou.band === "安"));
  ok("作者家内外做功后等于定境", gz.xinGong.ledger.neiAfter === gz.xinGong.ledger.nei && gz.xinGong.ledger.waiAfter === gz.xinGong.ledger.wai);
  var tJia = global.KRXinGong.tagSeat("甲", "甲", "day", true);
  eq("日主甲木我忌内", [tJia.wx, tJia.ss, tJia.camp, tJia.yongJi, tJia.domain], ["木", "日主", "我", "忌", "内"]);
  var tGeng = global.KRXinGong.tagSeat("甲", "庚", "time", true);
  eq("时庚金七杀客用内", [tGeng.wx, tGeng.ss, tGeng.camp, tGeng.yongJi, tGeng.domain], ["金", "七杀", "客", "用", "内"]);
  var tRen = global.KRXinGong.tagSeat("甲", "壬", "month", true);
  eq("月壬水偏印我忌外", [tRen.wx, tRen.ss, tRen.camp, tRen.yongJi, tRen.domain], ["水", "偏印", "我", "忌", "外"]);
  ok("作者字能时辰在断", /【三、字能时辰】/.test(gz.xinGong.judge.text) && /不卖铁口/.test(gz.xinGong.judge.text) && /日干甲/.test(gz.xinGong.judge.text));
  var tj = gz.xinGong.taiji;
  function kin(id) { return (tj.rows || []).filter(function (r) { return r.id === id; })[0]; }
  ok("太极六亲在场", /【六、太极六亲】/.test(gz.xinGong.judge.text) && tj && tj.ok);
  ok("天意果因在场", /【零、天意】/.test(gz.xinGong.judge.text) && /不能代人逃因果/.test(gz.xinGong.judge.text));
  ok("母该星癸", kin("mu") && kin("mu").origin.ch === "癸" && kin("mu").origin.source === "该星");
  ok("父该星戊", kin("fu") && kin("fu").origin.ch === "戊" && kin("fu").origin.source === "该星");
  ok("子该星庚", kin("zi") && kin("zi").origin.ch === "庚" && kin("zi").origin.source === "该星");
  ok("妻该星己", kin("qi") && kin("qi").origin.ch === "己" && kin("qi").origin.source === "该星");
  ok("女该星辛库中", kin("nv") && kin("nv").origin.ch === "辛" && kin("nv").origin.vault);
  ok("兄代取壬", kin("xiong") && kin("xiong").origin.source === "代取" && kin("xiong").origin.ch === "壬");
  ok("母环境正格平衡", kin("mu").jing && kin("mu").jing.pattern === "正格" && kin("mu").jing.strength === "平衡");
  ok("父环境正格身弱", kin("fu").jing && kin("fu").jing.pattern === "正格" && kin("fu").jing.strength === "身弱");
  ok("子环境正格平衡", kin("zi").jing && kin("zi").jing.pattern === "正格" && kin("zi").jing.strength === "平衡");
  ok("太极不改正从", gz.zhengDiHou.pattern === "正格" && gz.zhengDiHou.strength === "身弱");
  ok("原日主在父环境为七杀", kin("fu").dayAs === "七杀");
  ok("印比我方财官伤客方", /印比＝我方/.test(gz.xinGong.judge.text) && /财官伤＝客方/.test(gz.xinGong.judge.text));
  ok("申甲杀根不克身", (gz.natalKeys || []).some(function (k) { return k.type === "genqi" && /申甲/.test((k.chars || []).join("")); }));
  ok("庚壬通根共振不走隔位生", (gz.natalKeys || []).some(function (k) {
    return (k.type === "genqi" || k.type === "tonggen") && (/[壬庚]{2}/.test((k.chars || []).join("")) || /壬/.test(k.note || "") && /庚/.test(k.note || ""));
  }));

  var engDing = E.analyze({ pillars: ding, birthLat: 23.13, birthLon: 113.26 });
  eq("丁盘定体正格身弱用我", [engDing.zhengDiHou.pattern, engDing.zhengDiHou.strength, engDing.zhengDiHou.orient],
    ["正格", "身弱", "用我忌客"]);
  ok("丁盘定体我>1", engDing.zhengDiHou.wo > 1);
  near("丁盘字令地我", engDing.energy.lingDi.火 + engDing.energy.lingDi.木, 0.9825234375);
  near("丁盘定体我", engDing.zhengDiHou.wo, 1.2425734375);
  ok("丁盘二次不改正从", /正从冻结/.test(engDing.xinGong.judge.text));

  var engWu = E.analyze({ pillars: wu, birthLat: 23.13, birthLon: 113.26 });
  eq("戊盘定体正格身强用客", [engWu.zhengDiHou.pattern, engWu.zhengDiHou.strength, engWu.zhengDiHou.orient],
    ["正格", "身强", "用客忌我"]);
  ok("戊盘我>客且差>⅛", engWu.zhengDiHou.wo > engWu.zhengDiHou.ta
    && !E.isBalanced(engWu.zhengDiHou.wo, engWu.zhengDiHou.ta));
  ok("戊盘二次不改正从", /正从冻结/.test(engWu.xinGong.judge.text) && engWu.zhengZhuan.strength === "身强");
  eq("戊盘二次用客", engWu.zhengZhuan.orient, "用客忌我");
  ok("戊盘家内外定境不改写", engWu.xinGong.ledger.neiAfter === engWu.xinGong.ledger.nei);

  // 流局＝外部环境：每字1.0，只乘与命主距离。无令地、无年权。
  near("流年距E=1", E.flowLayerE(0), 1);
  near("流月距E=½", E.flowLayerE(1), 0.5);
  near("流日距E=¼", E.flowLayerE(2), 0.25);
  near("流时距E=⅛", E.flowLayerE(3), 0.125);
  eq("流层序从标签", [E.flowLayerIndex("流年", 9), E.flowLayerIndex("流月", 9), E.flowLayerIndex("流日", 9), E.flowLayerIndex("流时", 9)], [0, 1, 2, 3]);
  var flowDemo = { year: "丙午", month: "丙申", day: "己未", time: "乙亥" };
  var flowAcc = E.accumulateFlow(flowDemo, 23.13, 113.26, P);
  near("流局字·木", flowAcc.el.木, 0.1875);
  near("流局字·火", flowAcc.el.火, 2.275);
  near("流局字·土", flowAcc.el.土, 0.75);
  near("流局字·金", flowAcc.el.金, 0.3);
  near("流局字·水", flowAcc.el.水, 0.2375);
  near("流局四柱字总和3.75", flowAcc.el.木 + flowAcc.el.火 + flowAcc.el.土 + flowAcc.el.金 + flowAcc.el.水, 3.75);
  ok("流局不吃令地", Math.abs(flowAcc.el.火 - 2.275) < 1e-12);
  var flowChi = E.accumulateFlow(flowDemo, 41.8781, -87.6298, P);
  WX.forEach(function (w) {
    near("流局字不吃现地·" + w, flowAcc.el[w], flowChi.el[w]);
  });
  var gzFlow = E.analyze({
    pillars: natal, birthLat: 23.13, birthLon: 113.26,
    standLat: 23.13, standLon: 113.26, flowPillars: flowDemo,
  });
  ok("流层不改正从", gzFlow.zhengDiHou.pattern === "正格" && gzFlow.zhengDiHou.strength === "身弱");
  near("③家外叠流局1.0×距", gzFlow.energy.stages.afterFlow.wai - gzFlow.energy.stages.beforeYun.wai,
    3.75 + flowAcc.geoStand.木 + flowAcc.geoStand.火 + flowAcc.geoStand.土 + flowAcc.geoStand.金 + flowAcc.geoStand.水);

  var heBan = { year: "癸未", month: "乙卯", day: "甲子", time: "己巳" };
  var heHua = { year: "癸未", month: "丁巳", day: "甲子", time: "己巳" };
  var engBan = E.analyze({ pillars: heBan, birthLat: 23.13, birthLon: 113.26 });
  var engHua = E.analyze({ pillars: heHua, birthLat: 23.13, birthLon: 113.26 });
  ok("争议盘合而不化仍在", engBan.xinGong.hehua.some(function (h) { return h.hua === "土" && h.mode === "ban"; }));
  ok("对照盘合而化仍在", engHua.xinGong.hehua.some(function (h) { return h.hua === "土" && h.mode === "hua"; }));
  ok("争议盘不改正从", /正从冻结/.test(engBan.xinGong.judge.text));

  if (standalone) {
    if (fails) { console.error("ZHENGCONG_FAILED", fails); process.exit(1); }
    console.log("ZHENGCONG_OK");
  } else {
    global.__zhengcongFails = fails;
  }
})(typeof window !== "undefined" ? window : globalThis);
