// Browser-safe. THE ONLY implementation of the worksheet's time-ledger math.
// Mirrors manual-roast-planner.html's worksheet box 6 (lines 1-9, totals A/C/D)
// and box 7 (the four sanity checks) exactly. Imported by both server and browser.
//
// Two changes from the original worksheet, made 2026-07-31 after a methodology review (see
// shared/learn.js's header): first crack is now scaled by a multiplicative machine-pace factor —
// the operator's own 14-roast data showed a ~13% batch-size increase stretching first crack ~40%,
// a scaling relationship a flat added-seconds guess can't represent. Pace is applied to the WHOLE
// pre-pace sum, INCLUDING line 4 (± batch-size correction), not just lines 1-3: shared/learn.js
// fits pace from each historical roast's l1+l2+l3+l4 against what actually happened, so applying
// it only to l1+l2+l3 here and adding l4 back afterwards would double-count whatever that
// historical l4 was already compensating for. And the drying/Maillard sanity checks are now
// marked `informational`: because yellow is DERIVED as YELLOW_RATIO × A rather than entered
// independently, drying% and Maillard% are algebraic consequences of the DTR check, not
// independent validations — whenever DTR passes, both are mathematically guaranteed to also pass.
// Only DTR and the development ceiling test anything a plan could actually fail.

import { parseDuration, parseRangeMidpoint } from "./time.js?v=__ASSET_VERSION__";
import { YELLOW_RATIO, SANITY_BANDS } from "./reference-data.js?v=__ASSET_VERSION__";

/**
 * @param {import("./fields.js").Plan} plan
 * @param {import("./learn.js").computeMachineProfile extends (...a:any)=>infer R ? R : never} [machineProfile]
 *   optional learned per-user profile from shared/learn.js; omitted or a profile with no learned
 *   data behaves exactly as before (pace 1.0 — the reference/original worksheet behavior).
 * @returns ledger result — every time value in seconds, null when not computable.
 */
export function computeLedger(plan, machineProfile = null) {
  const f = plan.fields ?? {};
  const isBlend = f["2.1"] === "blend";
  const warnings = [];
  const pace = machineProfile?.pace?.value ?? 1;

  const l1 = parseRangeMidpoint(isBlend ? f["2.4"] : f["1.4"]);
  const l2 = parseDuration(f["1.6"]) ?? 0;
  const l3 = parseDuration(f["5.6"]) ?? 0;
  const l4 = parseDuration(f["6.4"]) ?? 0;
  const baseA = l1 === null ? null : l1 + l2 + l3 + l4;
  const A = baseA === null ? null : Math.round(baseA * pace);

  const yellow = A === null ? null : Math.round(A * YELLOW_RATIO);
  const maillard = A === null ? null : A - yellow;

  const l7 = parseRangeMidpoint(f["4.3"]);
  const l8 = parseDuration(isBlend ? f["2.5"] : f["3.2"]) ?? 0;
  const l9 = parseDuration(f["1.7"]) ?? 0;
  const C = l7 === null ? null : l7 + l8 + l9; // rows 5 and 6 (yellow/Maillard) are NEVER summed here

  const D = A === null || C === null ? null : A + C;

  if (l1 === null) warnings.push("No cultivar/blend anchor (field 1.4 or 2.4) — first crack cannot be computed.");
  if (l7 === null) warnings.push("No development base (field 4.3) — development cannot be computed.");

  const pct = (part) => (D && D > 0 && part !== null ? (part / D) * 100 : null);

  const checks = {
    drying: { ...passCheck(pct(yellow), SANITY_BANDS.drying), informational: true },
    maillard: { ...passCheck(pct(maillard), SANITY_BANDS.maillard), informational: true },
    dtr: passCheck(pct(C), SANITY_BANDS.dtr),
    ceiling: {
      valueS: C,
      maxS: SANITY_BANDS.ceilingS,
      pass: C === null ? null : C < SANITY_BANDS.ceilingS,
    },
  };

  return {
    isBlend,
    lines: { l1, l2, l3, l4, l7, l8, l9 },
    pace,
    subtotalA: baseA, // lines 1-4 summed, before pace — exposed so the UI/print sheet can show
    // the multiplication step explicitly instead of jumping straight to A with no visible way to
    // reconstruct it from the printed lines (see the header comment above).
    A,
    yellow,
    maillard,
    C,
    D,
    checks,
    warnings,
  };
}

function passCheck(pct, band) {
  return {
    pct,
    lo: band.lo,
    hi: band.hi,
    pass: pct === null ? null : pct >= band.lo && pct <= band.hi,
  };
}
