/**
 * 整条链路自测：输新盘 → 档位／付款门控 → 时地同步 → 输出 → 教学 → 语音师傅 → 存盘。
 * 运行：node website/app/js/kr_flow_selftest.js
 */
(function () {
  "use strict";
  var fails = 0;
  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 ? 0.02 : eps;
    if (Math.abs(Number(a) - Number(b)) > eps) { fails++; console.error("FAIL", name, "got", a, "want", b); }
    else console.log("ok", name);
  }

  global.KRAuthor = { unlocked: false, ensure: function () { return this.unlocked; } };
  require("./kr_params_2026_08_11.js");
  require("./kr_relations.js");
  require("./kr_xin_gong.js");
  require("./kr_engine.js");
  require("./kr_cities.js");
  require("./kr_pricing.js");
  require("./kr_tutorial.js");
  require("./kr_shifu.js");

  var E = global.KREngine;
  var P = global.KRPricing;
  var C = global.KRCities;
  var T = global.KRTutorial;
  var S = global.KRShifu;

  // ① 输新盘：作者盘广州 → 定体正格身弱
  var natal = { year: "癸丑", month: "壬戌", day: "甲申", time: "庚午" };
  var A = E.analyze({ pillars: natal, birthLat: 23.13, birthLon: 113.26, params: global.KRParams });
  ok("新盘四柱", A.pillars.year === "癸丑" && A.pillars.day === "甲申");
  near("定体我", A.zhengDiHou.wo, 2.76);
  near("定体客", A.zhengDiHou.ta, 4.49);
  ok("正格身弱用我", A.zhengDiHou.pattern === "正格" && A.zhengDiHou.strength === "身弱" && A.zhengDiHou.orient === "用我忌客");
  ok("做功不改正从我客", A.zhengZhuan.pattern === "正格" && A.zhengZhuan.wo === A.zhengDiHou.wo);

  var ding = E.analyze({ pillars: { year: "戊申", month: "壬戌", day: "丁丑", time: "辛丑" }, birthLat: 23.13, birthLon: 113.26, params: global.KRParams });
  ok("第二盘丁正格身弱", ding.zhengDiHou.pattern === "正格" && ding.zhengDiHou.strength === "身弱");
  near("丁定体我", ding.zhengDiHou.wo, 1.24, 0.05);

  var macauDing = E.analyze({ pillars: { year: "戊申", month: "壬戌", day: "丁丑", time: "辛丑" }, birthLat: 22.1987, birthLon: 113.5439, params: global.KRParams });
  ok("澳门丁盘正格身弱用我", macauDing.zhengDiHou.pattern === "正格" && macauDing.zhengDiHou.strength === "身弱" && macauDing.zhengDiHou.orient === "用我忌客");
  near("澳门丁定体我", macauDing.zhengDiHou.wo, 1.25, 0.02);
  near("澳门丁定体客", macauDing.zhengDiHou.ta, 6.37, 0.02);
  ok("澳门丁做功不改正从", macauDing.zhengZhuan.pattern === "正格" && macauDing.zhengZhuan.wo === macauDing.zhengDiHou.wo);
  ok("城市库有费城", C.byId.philadelphia && C.byId.philadelphia.name === "费城");
  ok("费城nearest", C.nearest(39.9526, -75.1652, 50).city && C.nearest(39.9526, -75.1652, 50).city.id === "philadelphia");

  // ② 付款门控：结账未接通；三档配额／导出
  var pay = P.checkout("pro");
  ok("结账未接通", pay.ok === false && pay.message === "结账链接未接通" && P.checkoutConnected === false);
  ok("访客免费", P.currentTier() === "free" && P.hasPro() === false && P.casePolicy().quota === 3);
  ok("访客无细断无详断", P.allows("proDetail") === false && P.allows("keyResonanceFull") === false);

  global.KRAuthor.unlocked = true;
  P.setPreviewTier("free");
  ok("预览免费锁专业键振", P.hasPro() === false && P.hasResonance() === false && P.casePolicy().exportJson === false);
  P.setPreviewTier("pro");
  ok("预览专业开细断开JSON", P.hasPro() === true && P.hasResonance() === false && P.casePolicy().exportJson === true && P.casePolicy().exportJudgment === false);
  P.setPreviewSingle(true);
  ok("单次24h只开结论导出", P.currentTier() === "pro" && P.casePolicy().exportJudgment === true && P.allows("keyResonanceFull") === false);
  P.setPreviewSingle(false);
  P.setPreviewTier("resonance");
  ok("预览键振全开", P.hasResonance() === true && P.casePolicy().quota === 300 && P.casePolicy().events === true);

  var rawCase = { name: "甲", pillars: "癸丑 壬戌 甲申 庚午", intensity: "2.40", events: [{ date: "2024", text: "x" }], pattern: "正格·身弱", yong: "水", ji: "火", note: "试" };
  P.setPreviewTier("pro");
  var red = P.sanitizeCase(rawCase, false);
  ok("专业导出脱敏无烈度无事件", red.pattern === "正格·身弱" && red.intensity == null && red.events == null);

  // ③ 时地同步：选城写墙钟；广州≠芝加哥
  var gz = C.nowAtCity("guangzhou");
  var chi = C.nowAtCity("chicago");
  ok("广州芝加哥墙钟字段齐", gz.y && chi.y && gz.mo && chi.h != null);
  ok("广州芝加哥时区不同", gz.tz !== chi.tz);
  var rawH = Math.abs((gz.h + gz.mi / 60) - (chi.h + chi.mi / 60)) % 24;
  var hourDelta = Math.min(rawH, 24 - rawH);
  ok("广州芝加哥至少差6小时", hourDelta >= 6);
  var nearChi = C.nearest(41.8781, -87.6298, 50);
  ok("芝加哥经纬nearest", nearChi.city && nearChi.city.id === "chicago");
  var lonlat = C.nowAtLonLat(41.8781, -87.6298);
  ok("经纬当地钟跟芝加哥", lonlat.tz === chi.tz || Math.abs(lonlat.h - chi.h) <= 1);

  // ④ 输出：三阶段在场；流层后家外可动
  ok("三阶段账本", A.energy.stages && A.energy.stages.beforeWork && A.energy.stages.beforeYun && A.energy.stages.afterFlow);
  ok("①定体我客锁在作用前", Math.abs(A.energy.stages.beforeWork.wo - A.zhengDiHou.wo) < 1e-9);
  ok("②做功改能量不改正从", Math.abs(A.energy.stages.beforeYun.wo - A.zhengDiHou.wo) > 0.1 && A.zhengDiHou.strength === "身弱");

  // ⑤ 教学三部
  ok("教程三部", T && T.parts && T.parts.length === 3);
  ok("教程档位", T.parts[0].tier.indexOf("免费") >= 0 && T.parts[1].tier.indexOf("专业") >= 0 && T.parts[2].tier.indexOf("键共振") >= 0);
  ok("教程先读铁律", T.parts.every(function (p) { return p.pages[0] && p.pages[0].h === "铁律对照（先读）"; }));

  // ⑥ 语音／师傅：禁庚克甲；有听／停接口
  var pack = A.xinGong;
  var sha = S.ask("庚金克不克日主？", pack);
  ok("师傅拒庚克甲", sha && sha.answer && sha.answer.indexOf("勿断") >= 0 && sha.answer.indexOf("庚克甲") >= 0 && sha.answer.indexOf("不克身") >= 0);
  var sop = S.ask("这盘怎么断？", pack);
  ok("师傅有闭环答", sop && sop.answer && sop.answer.length > 20);
  ok("师傅有朗读函数", typeof S.speak === "function" && typeof S.stopSpeak === "function");
  var tape = S.tapeScript(pack);
  ok("试听脚本在场", tape && tape.ok && /四柱/.test(tape.text) && tape.tracks && tape.tracks.length >= 3);

  // ⑦ 存盘规定
  ok("免费可存3条", P.casePolicies.free.quota === 3 && P.casePolicies.free.save === true);
  ok("专业30条有案情", P.casePolicies.pro.quota === 30 && P.casePolicies.pro.note === true);
  ok("键振300条有事件", P.casePolicies.resonance.quota === 300 && P.casePolicies.resonance.events === true);

  var fs = require("fs");
  var path = require("path");
  var zeisen = fs.readFileSync(path.join(__dirname, "zeisen.js"), "utf8");
  ok("预览档刷新全页", /if \(lastCtx\) render\(lastCtx\)/.test(zeisen) && /setPreviewTier/.test(zeisen));
  ok("实地时刻还按钮", /onDone/.test(zeisen) && /btn.textContent = t\("locateNow"\)/.test(zeisen));
  ok("存盘跟现地", /liveStandName\(c\)/.test(zeisen) && /standLon: liveStandLon/.test(zeisen));
  ok("基本信息现地跟活锚", /liveStandLon\(c\)/.test(zeisen) && /liveStandLat\(c\)/.test(zeisen));
  ok("载入案件还原现地", /pendingStandRestore/.test(zeisen) && /adopt: live0\.source !== "place"/.test(zeisen));

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