// Threshold-ordering validator for the pumpingStation basin + control +
// safety config. Pure: returns the issues array, never logs or throws.
// The caller decides what to do (warn, surface to status badge, fail tests).
//
// Invariants enforced (level-space, bottom → top):
//   0 < outflowLevel < inflowLevel < overflowLevel ≤ basinHeight
//   dryRunLevel ≤ minLevel ≤ startLevel ≤ holdLevel < maxLevel ≤ highVolumeSafetyLevel < overflowLevel
//
// startLevel is INTENTIONALLY not constrained against inflowLevel: setting
// startLevel above the gravity-feed inlet is the "buffer in the sewer"
// configuration where the upstream pipe network is used as overflow storage
// before pumping engages. holdLevel (optional, defaults to startLevel when
// omitted) is the 0 % ramp foot — pumps engage at startLevel but hold at
// min flow until level rises through holdLevel.
//
// dryRunLevel and highVolumeSafetyLevel are DERIVED from safety percentages.
// The validator recomputes them so a config that places minLevel below the
// effective dry-run trigger (a no-op control band) is caught here.

/**
 * Derived safety thresholds + reference levels. Exposed so the editor /
 * status badge / FlowAggregator can read the same values without
 * recomputing them.
 */
function computeSafetyPoints(basin, safety = {}) {
  const dryRunPct = Number(safety.dryRunThresholdPercent) || 0;
  const rawHighPct = safety.highVolumeSafetyThresholdPercent ?? safety.overfillThresholdPercent;
  // When neither high-volume nor overfill pct is supplied, use 100 % so
  // the validator's `maxLevel <= highVolumeSafetyLevel` check is a no-op
  // (the basin can't physically exceed overflow anyway). Tests pin this.
  const highPct = Number(rawHighPct);
  const effectiveHighPct = Number.isFinite(highPct) ? highPct : 100;
  const minVol = Number(basin?.minVol) || 0;
  const maxVolAtOverflow = Number(basin?.maxVolAtOverflow) || 0;
  const dryRunSafetyVol = minVol * (1 + dryRunPct / 100);
  const highVolumeSafetyVol = maxVolAtOverflow * (effectiveHighPct / 100);
  const refLowLevel = basin?.minHeightBasedOn === 'inlet'
    ? Number(basin?.inflowLevel)
    : Number(basin?.outflowLevel);
  const dryRunLevel = Number.isFinite(refLowLevel)
    ? refLowLevel * (1 + dryRunPct / 100)
    : Number.NaN;
  const overflowLevel = Number(basin?.overflowLevel) || 0;
  const highVolumeSafetyLevel = overflowLevel * (effectiveHighPct / 100);
  return {
    dryRunSafetyVol,
    dryRunLevel,
    highVolumeSafetyVol,
    highVolumeSafetyLevel,
    // Back-compat alias — pre-basin-docs name.
    overfillVol: highVolumeSafetyVol,
  };
}

/**
 * @param {object} basin - BasinGeometry instance OR plain {inflowLevel, outflowLevel, overflowLevel, heightBasin, minHeightBasedOn}
 * @param {object} levelbased - config.control.levelbased ({ minLevel, startLevel, maxLevel })
 * @param {object} safety - config.safety ({ dryRunThresholdPercent, highVolumeSafetyThresholdPercent | overfillThresholdPercent })
 * @returns {Array<{aName, a, op, bName, b, msg}>}
 */
function validateThresholdOrdering(basin, levelbased, safety) {
  const lvl = levelbased || {};
  const points = computeSafetyPoints(basin, safety);
  const { dryRunLevel, highVolumeSafetyLevel } = points;

  // holdLevel is optional — when omitted (null/undefined/NaN) it equals
  // startLevel at runtime, so skip both holdLevel-related checks in that
  // case (the canonical engine semantics still hold). Explicit null/undefined
  // check first so `Number(null) === 0` doesn't accidentally flag a default
  // schema value as a real operator-provided one.
  const rawHold = lvl.holdLevel;
  const holdLevelProvided = rawHold != null && Number.isFinite(Number(rawHold));
  const holdLevel = holdLevelProvided ? Number(rawHold) : null;

  const checks = [
    ['outflowLevel',  basin.outflowLevel,  '<',  'inflowLevel',           basin.inflowLevel],
    ['inflowLevel',   basin.inflowLevel,   '<',  'overflowLevel',         basin.overflowLevel],
    ['overflowLevel', basin.overflowLevel, '<=', 'basinHeight',           basin.heightBasin],
    ['dryRunLevel',   dryRunLevel,         '<=', 'minLevel',              lvl.minLevel],
    ['minLevel',      lvl.minLevel,        '<=', 'startLevel',            lvl.startLevel],
    ['startLevel',    lvl.startLevel,      '<',  'maxLevel',              lvl.maxLevel],
    ...(holdLevelProvided ? [
      ['startLevel',  lvl.startLevel,      '<=', 'holdLevel',             holdLevel],
      ['holdLevel',   holdLevel,           '<',  'maxLevel',              lvl.maxLevel],
    ] : []),
    ['maxLevel',      lvl.maxLevel,        '<=', 'highVolumeSafetyLevel', highVolumeSafetyLevel],
  ];

  const issues = [];
  for (const [aName, a, op, bName, b] of checks) {
    if (!Number.isFinite(a) || !Number.isFinite(b)) continue;
    const ok = op === '<' ? a < b : a <= b;
    if (!ok) {
      issues.push({
        aName,
        a,
        op,
        bName,
        b,
        msg: `Threshold invariant violated: ${aName} (${a}) must be ${op} ${bName} (${b})`,
      });
    }
  }
  return issues;
}

module.exports = { validateThresholdOrdering, computeSafetyPoints };
