/**
 * 双盘／三盘合参 — Web 子集
 * 口径：multi_chart_hecan_model.dart / dual_chart_engine / triple_chart_engine
 * © 2026 Jason Li
 */
(function (global) {
  "use strict";

  const TG = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"];
  const DZ = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"];
  const STEM_WX = [0,0,1,1,2,2,3,3,4,4];
  const BRANCH_WX = [4,2,0,0,2,1,1,2,3,3,2,4];
  const WX = ["木","火","土","金","水"];
  const LIU_CHONG = {子:"午",午:"子",丑:"未",未:"丑",寅:"申",申:"寅",卯:"酉",酉:"卯",辰:"戌",戌:"辰",巳:"亥",亥:"巳"};
  const LIU_HE = {子:"丑",丑:"子",寅:"亥",亥:"寅",卯:"戌",戌:"卯",辰:"酉",酉:"辰",巳:"申",申:"巳",午:"未",未:"午"};
  const MANIFEST = 2.0;

  const CANON_LOCK =
    "两盘三盘合参：以年长日主的流年→流时定格到二、三盘命局来作用。其他事情的产生日同理。正从不改写。";
  const CANON_DUAL = CANON_LOCK;
  const CANON_TRIPLE = CANON_LOCK;

  function parseGz(text) {
    const parts = (text || "").match(/[甲乙丙丁戊己庚辛壬癸][子丑寅卯辰巳午未申酉戌亥]/g);
    if (!parts || parts.length < 4) throw new Error("请输入完整四柱，如：戊子 癸亥 丁丑 辛丑");
    return { year: parts[0], month: parts[1], day: parts[2], time: parts[3] };
  }

  function distE(i, j) {
    const a = Math.abs(j - i) - 1;
    if (a < 0) return 2;
    if (a === 0) return 1;
    if (a === 1) return 0.5;
    if (a === 2) return 0.25;
    return 1 / (1 << Math.min(a, 8));
  }

  function typeMult(type) {
    if (type === "冲") return 1.0;
    if (type === "合") return 0.85;
    if (type === "刑") return 0.75;
    if (type === "害") return 0.7;
    if (type === "同行") return 0.55;
    return 0.5;
  }

  function pillarList(p) {
    return [
      { role: "年", gz: p.year, seat: 0 },
      { role: "月", gz: p.month, seat: 1 },
      { role: "日", gz: p.day, seat: 2 },
      { role: "时", gz: p.time, seat: 3 },
    ];
  }

  /** 扫描跨盘键：己盘四柱 × 对照叠层四柱 */
  function scanCrossKeys(self, other, otherLabel) {
    const A = pillarList(self);
    const B = pillarList(other).map((x) => ({
      role: otherLabel + x.role,
      gz: x.gz,
      seat: x.seat,
    }));
    const links = [];
    A.forEach((a) => {
      B.forEach((b) => {
        const az = a.gz[1], bz = b.gz[1];
        const as = a.gz[0], bs = b.gz[0];
        const E = distE(a.seat, b.seat);
        if (LIU_CHONG[az] === bz) {
          links.push({
            type: "冲",
            label: `${a.role}${a.gz}冲${b.role}${b.gz}`,
            E,
            mult: typeMult("冲"),
            intensity: typeMult("冲") * E,
          });
        }
        if (LIU_HE[az] === bz) {
          links.push({
            type: "合",
            label: `${a.role}${a.gz}合${b.role}${b.gz}`,
            E,
            mult: typeMult("合"),
            intensity: typeMult("合") * E,
          });
        }
        // 同行：天干同五行或地支同五行
        if (STEM_WX[TG.indexOf(as)] === STEM_WX[TG.indexOf(bs)]) {
          links.push({
            type: "同行",
            label: `${a.role}${as}同行${b.role}${bs}（${WX[STEM_WX[TG.indexOf(as)]]}）`,
            E,
            mult: typeMult("同行"),
            intensity: typeMult("同行") * E,
          });
        } else if (BRANCH_WX[DZ.indexOf(az)] === BRANCH_WX[DZ.indexOf(bz)]) {
          links.push({
            type: "同行",
            label: `${a.role}${az}同行${b.role}${bz}（${WX[BRANCH_WX[DZ.indexOf(az)]]}）`,
            E,
            mult: typeMult("同行") * 0.85,
            intensity: typeMult("同行") * 0.85 * E,
          });
        }
      });
    });
    // 去重近似：同 label 只留一次
    const seen = new Set();
    const uniq = [];
    links.forEach((l) => {
      if (seen.has(l.label)) return;
      seen.add(l.label);
      uniq.push(l);
    });
    uniq.sort((x, y) => y.intensity - x.intensity);
    const sharedIntensity = uniq.reduce((s, l) => s + l.intensity, 0);
    return { links: uniq, sharedIntensity };
  }

  function ensureSolar() {
    if (global.Solar && global.Solar.fromYmdHms) return global.Solar;
    try {
      const L = require("./lunar.js");
      Object.keys(L).forEach(function (k) { if (global[k] == null) global[k] = L[k]; });
      return global.Solar;
    } catch (e) { return null; }
  }

  function normalizeDt(dt) {
    if (!dt) {
      const n = new Date();
      return { y: n.getUTCFullYear(), mo: n.getUTCMonth() + 1, d: n.getUTCDate(), h: 12, mi: 0 };
    }
    return {
      y: +dt.y,
      mo: +(dt.mo != null ? dt.mo : dt.m),
      d: +dt.d,
      h: dt.h != null ? +dt.h : 12,
      mi: dt.mi != null ? +dt.mi : 0,
    };
  }

  function clockOf(base, opts) {
    opts = opts || {};
    const dt = normalizeDt(opts.eventDt || opts.dt);
    return {
      dt: dt,
      lat: opts.lat != null ? +opts.lat : (base.lat != null ? +base.lat : 23.13),
      lon: opts.lon != null ? +opts.lon : (base.lon != null ? +base.lon : 113.26),
      zone: opts.zone != null ? +opts.zone : (base.zone != null ? +base.zone : 8),
      source: opts.eventDt ? "产生日" : "年长日主流时",
    };
  }

  /** 年长日主（或产生日）的流年→流时。一钟定格，不按二、三盘各自重排。 */
  function flowPillarsAt(dt, lon, lat, zone) {
    const Solar = ensureSolar();
    const E = global.KREngine;
    dt = normalizeDt(dt);
    lon = lon != null ? +lon : 113.26;
    lat = lat != null ? +lat : 23.13;
    zone = zone != null ? +zone : 8;
    let h = dt.h, mi = dt.mi;
    if (E && E.correctTime) {
      const ti = E.correctTime({ y: dt.y, m: dt.mo, d: dt.d, h: dt.h, mi: dt.mi }, lon, zone, true, Math.abs(zone - 8) < 1e-9);
      if (ti && ti.trueSolar && !ti.dayAhead) {
        h = ti.trueSolar.getHours();
        mi = ti.trueSolar.getMinutes();
      }
    }
    if (!Solar) throw new Error("需要 lunar.js 才能排出流年→流时");
    const solar = Solar.fromYmdHms(dt.y, dt.mo, dt.d, h, mi, 0);
    const ec = solar.getLunar().getEightChar();
    const raw = { year: ec.getYear(), month: ec.getMonth(), day: ec.getDay(), time: ec.getTime() };
    if (E && E.adjustPillarsForGeo) {
      return E.adjustPillarsForGeo(raw, {
        dayAhead: E.inAmericasDayAhead(lon),
        southern: lat < 0,
        hour: h,
        minute: mi,
      }).pillars;
    }
    return raw;
  }

  function flowExtras(flow) {
    return [
      { gz: flow.year, label: "流年", kind: "flow", scale: 1, flowIdx: 0 },
      { gz: flow.month, label: "流月", kind: "flow", scale: 1, flowIdx: 1 },
      { gz: flow.day, label: "流日", kind: "flow", scale: 1, flowIdx: 2 },
      { gz: flow.time, label: "流时", kind: "flow", scale: 1, flowIdx: 3 },
    ];
  }

  function packAnalysis(r, extras) {
    return {
      pillars: r.pillars,
      zheng: r.zhengDiHou,
      zhuan: r.zhengZhuan,
      vitality: r.vitality,
      geoRemedy: r.geoRemedy,
      bi: r.bi,
      works: r.works,
      geo: r.geo,
      verdicts: r.verdicts,
      analysis: r,
      extras: extras || [],
    };
  }

  function analyzePerson(person, stamp) {
    const E = global.KREngine;
    if (!E) {
      return { pillars: person.pillars, zheng: null, bi: null, works: [], geo: null };
    }
    const birthLat = person.lat != null ? person.lat : 39.9;
    const birthLon = person.lon != null ? person.lon : 116.4;
    const clock = stamp && stamp.clock;
    const extras = (stamp && stamp.extras) || null;
    const flow = (stamp && stamp.flow) || null;
    const r = E.analyze({
      pillars: person.pillars,
      birthLat: birthLat,
      birthLon: birthLon,
      standLat: clock ? clock.lat : birthLat,
      standLon: clock ? clock.lon : birthLon,
      localHour: person.hour != null ? person.hour : 12,
      localMinute: person.minute != null ? person.minute : 0,
      flowPillars: flow,
      extras: extras,
      sex: person.sex || (person.gender === 0 ? "F" : "M"),
      params: global.KRParams,
    });
    return packAnalysis(r, extras);
  }

  /** 合象做功示意：用跨盘键类型点名 */
  function heXiangWork(links, zheng) {
    const hits = [];
    const hasChong = links.some((l) => l.type === "冲");
    const hasHe = links.some((l) => l.type === "合");
    const hasTong = links.some((l) => l.type === "同行");
    if (hasChong) {
      hits.push({
        name: "冲激通道",
        intensity: links.filter((l) => l.type === "冲").reduce((s, l) => s + l.intensity, 0),
        note: zheng && zheng.useGuest ? "冲可泄耗／开局，相对用客宜察受方" : "冲动摇根气，护身优先",
      });
    }
    if (hasHe) {
      hits.push({
        name: "合化／合绊通道",
        intensity: links.filter((l) => l.type === "合").reduce((s, l) => s + l.intensity, 0),
        note: "合象可成事亦可绊；须回扣基底用忌",
      });
    }
    if (hasTong) {
      hits.push({
        name: "同行共振通道",
        intensity: links.filter((l) => l.type === "同行").reduce((s, l) => s + l.intensity, 0),
        note: "同行叠能：生扶或比劫争合视基底取向",
      });
    }
    if (!hits.length) {
      hits.push({ name: "合象待引动", intensity: 0, note: "暂无强跨盘键" });
    }
    hits.sort((a, b) => b.intensity - a.intensity);
    return hits;
  }

  function sortByAge(persons) {
    return (persons || []).slice().sort((a, b) => (a.birthMs || 0) - (b.birthMs || 0));
  }

  function juniorStampNote(j) {
    const z0 = j.natal.zheng;
    const z1 = j.stamped.zhuan;
    const v = j.stamped.vitality || {};
    const bits = [];
    if (z0) bits.push(`正从冻结${z0.pattern}·${z0.strength}（不改写）`);
    if (z1) bits.push(`定格后做功我${(z1.wo || 0).toFixed(2)}∶客${(z1.ta || 0).toFixed(2)}`);
    if (v.neng && v.neng.zh) bits.push(v.neng.zh);
    if (j.stamped.geoRemedy) bits.push("我党严重减少，地理可补总量");
    return bits.join(" · ");
  }

  /**
   * 合参主入口。年长者为钟；流年→流时定格到其余各盘再作用。
   * opts.eventDt = {y,mo,d,h,mi} 产生日；空则用年长日主流时／传入 dt。
   */
  function hecanAll(persons, opts) {
    if (!persons || persons.length < 2) throw new Error("合参至少两盘");
    const sorted = sortByAge(persons);
    const baseP = sorted[0];
    const juniors = sorted.slice(1);
    const clock = clockOf(baseP, opts || {});
    const flow = flowPillarsAt(clock.dt, clock.lon, clock.lat, clock.zone);
    const extras = flowExtras(flow);
    const stamp = { extras: extras, flow: flow, clock: clock };
    const baseNatal = analyzePerson(baseP);
    const baseLive = analyzePerson(baseP, stamp);
    const juniorRows = juniors.map(function (p) {
      const natal = analyzePerson(p);
      const stamped = analyzePerson(p, stamp);
      const vsFlow = scanCrossKeys(stamped.pillars, flow, "定格流");
      return { person: p, natal: natal, stamped: stamped, vsFlow: vsFlow };
    });

    const natalCross = [];
    juniorRows.forEach(function (j) {
      natalCross.push({
        name: j.person.name,
        cross: scanCrossKeys(baseNatal.pillars, j.natal.pillars, j.person.name || "对照"),
      });
    });
    const stackedLinks = juniorRows.reduce(function (acc, j) {
      return acc.concat(j.vsFlow.links);
    }, []);
    stackedLinks.sort((a, b) => b.intensity - a.intensity);
    const stackedIntensity = stackedLinks.reduce((s, l) => s + l.intensity, 0);
    const work = heXiangWork(stackedLinks, baseNatal.zheng);
    const core = work[0];
    const z = baseNatal.zheng;
    const modeZh = persons.length >= 3 ? "三盘合参" : "双盘合参";
    const juniorNames = juniorRows.map(j => j.person.name).join("、");
    const flowZh = `流年${flow.year} 流月${flow.month} 流日${flow.day} 流时${flow.time}`;
    const dt = clock.dt;
    const dtZh = `${dt.y}-${String(dt.mo).padStart(2, "0")}-${String(dt.d).padStart(2, "0")} ${String(dt.h).padStart(2, "0")}:${String(dt.mi).padStart(2, "0")}`;
    const headline =
      `${modeZh} · 年长${baseP.name || "日主"} · ${clock.source}${dtZh} 定格 · ${flowZh} · ` +
      (z ? `${z.pattern}·${z.strength}（身份不改）` : "") +
      (core && core.intensity > 0
        ? ` · 定格作用「${core.name}」烈${core.intensity.toFixed(1)}${core.intensity >= MANIFEST ? "事显" : ""}`
        : " · 定格作用待显");

    const hecanNotes = [
      CANON_LOCK,
      `${clock.source}${dtZh}（年长${baseP.name}之钟）→ ${flowZh}，定格到${juniorNames}来作用。`,
    ];
    juniorRows.forEach(function (j) {
      hecanNotes.push(`【定格·${j.person.name}】${juniorStampNote(j)}`);
      if (j.vsFlow.links.length) {
        hecanNotes.push(
          `【定格键·${j.person.name}】` +
            j.vsFlow.links.slice(0, 4).map(k => `${k.label}×${k.intensity.toFixed(2)}`).join("；")
        );
      }
    });
    natalCross.forEach(function (c) {
      if (c.cross.links.length) {
        hecanNotes.push(
          `【合象·${c.name}】` +
            c.cross.links.slice(0, 3).map(k => `${k.label}×${k.intensity.toFixed(2)}`).join("；")
        );
      }
    });

    const firstJunior = juniorRows[0];
    const lastJunior = juniorRows[juniorRows.length - 1];
    return {
      modeZh: modeZh,
      methodLineZh: `${CANON_LOCK}｜年长${baseP.name} · ${clock.source}${dtZh} · 定格${juniorNames}`,
      headlineZh: headline,
      clock: clock,
      flow: flow,
      extras: extras,
      roles: {
        base: { person: baseP, analysis: baseNatal, live: baseLive },
        second: firstJunior
          ? { person: firstJunior.person, analysis: firstJunior.stamped, natal: firstJunior.natal, flowAs: flow }
          : null,
        third: juniorRows[1]
          ? { person: juniorRows[1].person, analysis: juniorRows[1].stamped, natal: juniorRows[1].natal }
          : null,
      },
      juniors: juniorRows,
      natalCross: natalCross,
      flowKeys: firstJunior ? firstJunior.vsFlow : { links: [], sharedIntensity: 0 },
      stackedLinks: stackedLinks,
      stackedIntensity: stackedIntensity,
      sharedIntensityVsThird: lastJunior && juniorRows.length > 1 ? lastJunior.vsFlow.sharedIntensity : 0,
      work: work,
      hecanNotes: hecanNotes,
      synthesisNotes: [
        `年长${baseP.name}为钟 · ${clock.source}${dtZh}`,
        flowZh + " 定格到 " + juniorNames,
        "各盘正从身份不改；定格只改二、三盘的作用能态与烈度",
        z ? `年长定体 ${z.pattern}·${z.strength} 我${z.wo.toFixed(2)}∶客${z.ta.toFixed(2)}` : "",
      ].filter(Boolean),
      canonZh: CANON_LOCK,
      selfLabel: baseP.name,
      otherLabel: firstJunior ? firstJunior.person.name : "",
      self: baseNatal,
      other: firstJunior ? firstJunior.stamped : baseNatal,
      cross: { links: stackedLinks, sharedIntensity: stackedIntensity },
    };
  }

  function dualHecan(selfPerson, otherPerson, opts) {
    return hecanAll([selfPerson, otherPerson], opts || {});
  }

  function tripleHecan(persons, opts) {
    if (!persons || persons.length !== 3) throw new Error("三盘合参需要恰好三盘");
    return hecanAll(persons, opts || {});
  }

  global.KRHecan = {
    CANON_DUAL,
    CANON_TRIPLE,
    CANON_LOCK,
    MANIFEST,
    parseGz,
    scanCrossKeys,
    flowPillarsAt,
    flowExtras,
    clockOf,
    hecanAll,
    dualHecan,
    tripleHecan,
    analyzePerson,
  };
})(typeof window !== "undefined" ? window : globalThis);
