// Safety controller for the pumping-station basin.
//
// Two hard rules, applied independently every tick:
//
//   1. DRY-RUN (volume below minVol while draining): pumps must stop.
//      Shuts down all DOWNSTREAM machines + machine groups + child
//      stations. Sets blocked=true so the orchestrator skips control
//      logic — only a manual override or estop can restart pumps.
//
//   2. OVERFILL (volume above overflow level while filling): pumps must
//      keep running. Shuts down UPSTREAM equipment only (stop more water
//      coming in) and child stations. Does NOT touch machine groups or
//      downstream pumps — they must keep draining. blocked stays false
//      so level-based control keeps demanding maximum throughput.
//
// A third path: if no volume reading is available, panic — shut down
// every machine and block control.

function pickVariant(measurements, type, variants, position, unit) {
  for (const variant of variants) {
    const v = measurements.type(type).variant(variant).position(position).getCurrentValue(unit);
    if (Number.isFinite(v)) return v;
  }
  return null;
}

class SafetyController {
  /**
   * @param {object} ctx
   * @param {object} ctx.measurements   MeasurementContainer-like instance
   * @param {object} ctx.basin          BasinGeometry snapshot ({maxVolAtOverflow, minVol, ...})
   * @param {object} ctx.config         pumpingStation config (uses .safety subtree)
   * @param {object} ctx.logger         generalFunctions logger
   * @param {object} ctx.machines       map of childId → rotatingMachine
   * @param {object} ctx.stations       map of childId → child pumpingStation
   * @param {object} ctx.machineGroups  map of childId → machineGroupControl
   * @param {string[]} [ctx.volVariants] order of volume variants to try
   */
  constructor(ctx) {
    this.ctx = ctx;
    this.volVariants = ctx.volVariants || ['measured', 'predicted'];
  }

  /**
   * Run the dry-run + overfill rules against the current measurement state.
   *
   * @param {object} flowSnapshot  { direction: 'filling'|'draining'|'steady',
   *                                 secondsRemaining: number|null }
   * @returns {{blocked:boolean, reason:string|null, triggered:string[]}}
   */
  evaluate(flowSnapshot) {
    const { measurements, basin, config, logger, machines } = this.ctx;
    const direction = flowSnapshot?.direction ?? 'steady';
    const secondsRemaining = flowSnapshot?.secondsRemaining ?? null;

    const volUnit = measurements.getUnit('volume');
    const vol = pickVariant(measurements, 'volume', this.volVariants, 'atequipment', volUnit);

    if (vol == null) {
      Object.values(machines).forEach((m) => m.handleInput('parent', 'execSequence', 'shutdown'));
      logger.warn('No volume data available to safe guard system; shutting down all machines.');
      return { blocked: true, reason: 'no-volume-data', triggered: ['no-volume-data'] };
    }

    const triggered = [];
    let blocked = false;
    let reason = null;

    const dry = this._dryRunRule(vol, direction, secondsRemaining);
    if (dry.triggered) {
      this._shutdownDownstream(vol, secondsRemaining);
      blocked = true;
      reason = 'dry-run';
      triggered.push(...dry.flags);
    }

    const over = this._overfillRule(vol, direction, secondsRemaining);
    if (over.triggered) {
      this._shutdownUpstream(vol, secondsRemaining);
      // Overfill never sets blocked — control keeps running.
      if (reason == null) reason = 'overfill';
      triggered.push(...over.flags);
    }

    return { blocked, reason, triggered };
  }

  _safetyConfig() {
    return this.ctx.config.safety || {};
  }

  _dryRunRule(vol, direction, secondsRemaining) {
    if (direction !== 'draining') return { triggered: false, flags: [] };
    const s = this._safetyConfig();
    const dryRunEnabled = Boolean(s.enableDryRunProtection);
    const timeProtectionEnabled = s.timeleftToFullOrEmptyThresholdSeconds > 0;
    const triggerLowVol = this.ctx.basin.minVol * (1 + ((Number(s.dryRunThresholdPercent) || 0) / 100));

    const flags = [];
    if (dryRunEnabled && vol < triggerLowVol) flags.push('dry-run-volume');
    if (timeProtectionEnabled && secondsRemaining != null && secondsRemaining < s.timeleftToFullOrEmptyThresholdSeconds) {
      flags.push('time-remaining');
    }
    return { triggered: flags.length > 0, flags };
  }

  _overfillRule(vol, direction, secondsRemaining) {
    if (direction !== 'filling') return { triggered: false, flags: [] };
    const s = this._safetyConfig();
    // basin-docs renamed enableOverfillProtection → enableHighVolumeSafety;
    // both work as aliases (HEAD already maps in buildDomainConfig).
    const enabled = Boolean(s.enableHighVolumeSafety ?? s.enableOverfillProtection);
    const timeProtectionEnabled = s.timeleftToFullOrEmptyThresholdSeconds > 0;
    const pct = Number(s.highVolumeSafetyThresholdPercent ?? s.overfillThresholdPercent) || 0;
    const triggerHighVol = this.ctx.basin.maxVolAtOverflow * (pct / 100);

    const flags = [];
    if (enabled && vol > triggerHighVol) flags.push('overfill-volume');
    if (timeProtectionEnabled && secondsRemaining != null && secondsRemaining < s.timeleftToFullOrEmptyThresholdSeconds) {
      flags.push('time-remaining');
    }
    return { triggered: flags.length > 0, flags };
  }

  _shutdownDownstream(vol, secondsRemaining) {
    const { machines, machineGroups, stations, logger } = this.ctx;
    Object.values(machines).forEach((machine) => {
      const pos = machine?.config?.functionality?.positionVsParent;
      if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) {
        machine.handleInput('parent', 'execSequence', 'shutdown');
      }
    });
    Object.values(stations).forEach((st) => st.handleInput('parent', 'execSequence', 'shutdown'));
    Object.values(machineGroups).forEach((g) => g.turnOffAllMachines());
    logger.warn(
      `Dry-run safety: vol=${vol.toFixed(2)} m3, remainingTime=${secondsRemaining ? secondsRemaining.toFixed(1) : 'N/A'} s; shutting down downstream equipment`
    );
  }

  _shutdownUpstream(vol, secondsRemaining) {
    const { machines, stations, logger } = this.ctx;
    Object.values(machines).forEach((machine) => {
      const pos = machine?.config?.functionality?.positionVsParent;
      if (pos === 'upstream' && machine._isOperationalState()) {
        machine.handleInput('parent', 'execSequence', 'shutdown');
      }
    });
    Object.values(stations).forEach((st) => st.handleInput('parent', 'execSequence', 'shutdown'));
    // Machine groups intentionally NOT shut down — they must keep draining.
    logger.warn(
      `Overfill safety: vol=${vol.toFixed(2)} m3, remainingTime=${secondsRemaining ? secondsRemaining.toFixed(1) : 'N/A'} s; shutting down upstream equipment only — pumps keep running`
    );
  }
}

module.exports = SafetyController;
