const EventEmitter = require('events');
const {
  logger,
  configUtils,
  configManager,
  childRegistrationUtils,
  MeasurementContainer,
  coolprop,
  interpolation
} = require('generalFunctions');

class PumpingStation {
  constructor(config = {}) {
    this.emitter = new EventEmitter();
    this.configManager = new configManager();
    this.defaultConfig = this.configManager.getConfig('pumpingStation');
    this.configUtils = new configUtils(this.defaultConfig);
    this.config = this.configUtils.initConfig(config);
    this.interpolate = new interpolation();
    this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel,this.config.general.name);

    this.measurements = new MeasurementContainer({
      autoConvert: true,
      preferredUnits: { flow: 'm3/s', netFlowRate: 'm3/s', level: 'm', volume: 'm3' }
    });

    this.childRegistrationUtils = new childRegistrationUtils(this);
    this.machines = {};
    this.stations = {};
    this.machineGroups = {};
    this.predictedFlowChildren = new Map();

    this.flowVariants = ['measured', 'predicted'];
    this.levelVariants = ['measured', 'predicted'];
    this.volVariants = ['measured', 'predicted'];
    this.flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] };

    this.mode = this.config.control.mode;
    this._levelState = { crossed: new Set(), dwellUntil: null };
    this.state = { direction: 'steady', netFlow: 0, flowSource: null, seconds: null, remainingSource: null };

    const thresholdFromConfig = Number(this.config.general?.flowThreshold);
    this.flowThreshold = Number.isFinite(thresholdFromConfig) ? thresholdFromConfig : 1e-4;

    this.initBasinProperties();
    this.logger.debug('PumpingStation initialized');
  }

  /* --------------------------- Registration --------------------------- */

  registerChild(child, softwareType) {
    this.logger.debug(`Registering child (${softwareType}) "${child.config.general.name}"`);

    if (softwareType === 'measurement') {
      this._registerMeasurementChild(child);
      return;
    }

    if (softwareType === 'machine') {
      this.machines[child.config.general.id] = child;
    } else if (softwareType === 'pumpingstation') {
      this.stations[child.config.general.id] = child;
    } else if (softwareType === 'machinegroup') {
      this.machineGroups[child.config.general.id] = child;
      this._registerPredictedFlowChild(child);
    }

    if (softwareType === 'machine' || softwareType === 'pumpingstation' || softwareType === 'machinegroup') {
      this._registerPredictedFlowChild(child);
    }
  }

  _registerMeasurementChild(child) {
    const position = child.config.functionality.positionVsParent;
    const measurementType = child.config.asset.type;
    const eventName = `${measurementType}.measured.${position}`;

    child.measurements.emitter.on(eventName, (eventData = {}) => {
      this.logger.debug(
        `Measurement update ${eventName} <- ${eventData.childName || child.config.general.name}: ${eventData.value} ${eventData.unit}`
      );

      this.measurements
        .type(measurementType)
        .variant('measured')
        .position(position)
        .value(eventData.value, eventData.timestamp, eventData.unit);

      this._handleMeasurement(measurementType, eventData.value, position, eventData);
    });
  }

  _registerPredictedFlowChild(child) {
    const position = (child.config.functionality.positionVsParent || '').toLowerCase();
    const childName = child.config.general.name;
    const childId = child.config.general.id ?? childName;

    let posKey;
    let eventNames;
    switch (position) {
      case 'downstream':
      case 'out':
      case 'atequipment':
        posKey = 'out';
        eventNames = ['flow.predicted.downstream', 'flow.predicted.atequipment'];
        break;
      case 'upstream':
      case 'in':
        posKey = 'in';
        eventNames = ['flow.predicted.upstream', 'flow.predicted.atequipment'];
        break;
      default:
        this.logger.warn(`Unsupported predicted flow position "${position}" from ${childName}`);
        return;
    }

    if (!this.predictedFlowChildren.has(childId)) {
      this.predictedFlowChildren.set(childId, { in: 0, out: 0 });
    }

    const handler = (eventData = {}) => {
      const unit = eventData.unit || child.config?.general?.unit;
      const ts = eventData.timestamp || Date.now();

      this.logger.debug(`Emitting for child ${unit} `);
      this.measurements
        .type('flow')
        .variant('predicted')
        .position(posKey)
        .child(childId)
        .value(eventData.value, ts, unit);
    };

    eventNames.forEach((ev) => child.measurements.emitter.on(ev, handler));
  }

  /* --------------------------- Calibration --------------------------- */

  calibratePredictedVolume(calibratedVol, timestamp = Date.now()) {
    const volume = this.measurements.type('volume').variant('predicted').position('atequipment').get();
    const level = this.measurements.type('level').variant('predicted').position('atequipment').get();

    if (volume) {
      volume.values = [];
      volume.timestamps = [];
    }
    
    if (level) {
      level.values = [];
      level.timestamps = [];
    }

    this.measurements.type('volume').variant('predicted').position('atequipment').value(calibratedVol, timestamp, 'm3').unit('m3');
    this.measurements.type('level').variant('predicted').position('atequipment').value(this._calcLevelFromVolume(calibratedVol), timestamp, 'm');

    this._predictedFlowState = { inflow: 0, outflow: 0, lastTimestamp: timestamp };
  }

  calibratePredictedLevel(val, timestamp = Date.now(), unit = 'm') {
    const volumeChain = this.measurements.type('volume').variant('predicted').position('atequipment');
    const levelChain = this.measurements.type('level').variant('predicted').position('atequipment');

    const volumeMeasurement = volumeChain.exists() ? volumeChain.get() : null;
    if (volumeMeasurement) {
      volumeMeasurement.values = [];
      volumeMeasurement.timestamps = [];
    }

    const levelMeasurement = levelChain.exists() ? levelChain.get() : null;
    if (levelMeasurement) {
      levelMeasurement.values = [];
      levelMeasurement.timestamps = [];
    }

    levelChain.value(val, timestamp).unit(unit);
    volumeChain.value(this._calcVolumeFromLevel(val), timestamp, 'm3');

    this._predictedFlowState = { inflow: 0, outflow: 0, lastTimestamp: timestamp };
  }

  setManualInflow(value, timestamp = Date.now(), unit) {
    const num = Number(value);
    this.measurements.type('flow').variant('predicted').position('in').child('manual-qin').value(num, timestamp, unit);
  }

  /* --------------------------- Tick / Control --------------------------- */

  tick() {
    this._updatePredictedVolume();

    const netFlow = this._selectBestNetFlow();
    const remaining = this._computeRemainingTime(netFlow);

    this._safetyController(remaining.seconds, netFlow.direction);
    if (this.safetyControllerActive) return;

    this._controlLogic(netFlow.direction);

    this.state = {
      direction: netFlow.direction,
      netFlow: netFlow.value,
      flowSource: netFlow.source,
      seconds: remaining.seconds,
      remainingSource: remaining.source
    };

    this.logger.debug(`netflow = ${JSON.stringify(netFlow)}`);
    this.logger.debug(
      `Height : ${this.measurements.type('level').variant('predicted').position('atequipment').getCurrentValue('m')} m`
    );
  }

  changeMode(newMode){
    if ( this.config.control.allowedModes.has(newMode) ){
      const currentMode = this.mode;
      this.logger.info(`Control mode changing from ${currentMode} to ${newMode}`);
      this.mode = newMode;
    }
    else{
      this.logger.warn(`Attempted to change to unsupported control mode: ${newMode}`);
    }

  }


  _controlLogic(direction) {
    switch (this.mode) {
      case 'levelbased':
        this._controlLevelBased(direction);
        break;
      case 'flowbased':
        this._controlFlowBased?.();
        break;
      case 'manual':
        break;
      default:
        this.logger.warn(`Unsupported control mode: ${this.mode}`);
    }
  }

  async _controlLevelBased(direction) {
    const { startLevel, stopLevel } = this.config.control.levelbased;
    const flowUnit = this.measurements.getUnit('flow');
    const levelUnit = this.measurements.getUnit('level');

    const level = this._pickVariant('level', this.levelVariants, 'atequipment', levelUnit);
    if (level == null) {
      this.logger.warn('No valid level found');
      return;
    }

    if (level > startLevel && direction === 'filling') {
      const percControl = this._scaleLevelToFlowPercent(level);
      this.logger.debug(`Controllevel based => Level ${level} control applying to pump : ${percControl}`);
      await this._applyMachineLevelControl(percControl);
    }

    if (level < stopLevel && direction === 'draining') {
      Object.values(this.machines).forEach((machine) => {
        const pos = machine?.config?.functionality?.positionVsParent;
        if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) {
          machine.handleInput('parent', 'execSequence', 'shutdown');
        }
      });
      Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown'));
      Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
    }
  }

  _controlFlowBased() {
    // placeholder for flow-based logic
  }

  async _applyMachineGroupLevelControl(percentControl) {
    if (!this.machineGroups || Object.keys(this.machineGroups).length === 0) return;
    await Promise.all(
      Object.values(this.machineGroups).map((group) =>
        group.handleInput('parent', percentControl).catch((err) => {
          this.logger.error(`Failed to send level control to group "${group.config.general.name}": ${err.message}`);
        })
      )
    );
  }

  async _applyMachineLevelControl(percentControl) {
    const machines = Object.values(this.machines).filter((machine) => {
      const pos = machine?.config?.functionality?.positionVsParent;
      return (pos === 'downstream' || pos === 'atequipment');
    });

    if (!machines.length) return;

    const perMachine = percentControl / machines.length;
    for (const machine of machines) {
      try {
        await machine.handleInput('parent', 'execSequence', 'startup');
        await machine.handleInput('parent', 'execMovement', perMachine);
      } catch (err) {
        this.logger.error(`Failed to start machine "${machine.config.general.name}": ${err.message}`);
      }
    }
  }

  /* --------------------------- Measurements --------------------------- */

  _handleMeasurement(measurementType, value, position, context) {
    switch (measurementType) {
      case 'level':
        this._onLevelMeasurement(position, value, context);
        break;
      case 'pressure':
        this._onPressureMeasurement(position, value, context);
        break;
      default:
        break;
    }
  }

  _onLevelMeasurement(position, value, context = {}) {
    this.measurements.type('level').variant('measured').position(position).value(value).unit(context.unit);
    const levelSeries = this.measurements.type('level').variant('measured').position(position);
    const levelMeters = levelSeries.getCurrentValue('m');
    if (levelMeters == null) return;

    const volume = this._calcVolumeFromLevel(levelMeters);
    const percent = this.interpolate.interpolate_lin_single_point(
      volume,
      this.basin.minVol,
      this.basin.maxVolOverflow,
      0,
      100
    );

    this.measurements.type('volume').variant('measured').position('atequipment').value(volume, context.timestamp, 'm3');
    this.measurements
      .type('volumePercent')
      .variant('measured')
      .position('atequipment')
      .value(percent, context.timestamp, '%');
  }

  _onPressureMeasurement(position, value, context = {}) {
    let kelvinTemp =
      this.measurements.type('temperature').variant('measured').position('atequipment').getCurrentValue('K') ?? null;

    if (kelvinTemp === null) {
      this.logger.warn('No temperature measurement; assuming 15C for pressure to level conversion.');
      this.measurements.type('temperature').variant('assumed').position('atequipment').value(15, Date.now(), 'C');
      kelvinTemp = this.measurements.type('temperature').variant('assumed').position('atequipment').getCurrentValue('K');
    }

    if (kelvinTemp == null) return;

    const density = coolprop.PropsSI('D', 'T', kelvinTemp, 'P', 101325, 'Water');
    const pressurePa = this.measurements.type('pressure').variant('measured').position(position).getCurrentValue('Pa');
    if (!Number.isFinite(pressurePa) || !Number.isFinite(density)) return;

    const g = 9.80665;
    const level = pressurePa / (density * g);
    this.measurements.type('level').variant('predicted').position(position).value(level, context.timestamp, 'm');
  }

  /* --------------------------- Core Calculations --------------------------- */

  _pickVariant(type, variants, position, unit) {
    for (const variant of variants) {
      const val = this.measurements.type(type).variant(variant).position(position).getCurrentValue(unit);
      if (!Number.isFinite(val)) continue;
      return val;
    }
    return null;
  }

  //scaled for robin min 2039 - 2960 max 53.04
  _scaleLevelToFlowPercent(level) {
    const { minFlowLevel, maxFlowLevel } = this.config.control.levelbased;
    this.logger.debug(`Scaling minflow level : ${minFlowLevel} and maxflowLevel : ${maxFlowLevel}`);
    return this.interpolate.interpolate_lin_single_point(level, minFlowLevel, maxFlowLevel, 0, 100);
  }

  _levelRate(variant) {
    const chain = this.measurements.type('level').variant(variant).position('atequipment');
    if (!chain.exists({ requireValues: true })) return null;
    const m = chain.get();
    const current = m?.getLaggedSample?.(0);
    const previous = m?.getLaggedSample?.(1);
    if (!current || !previous || previous.timestamp == null) return null;
    const dt = (current.timestamp - previous.timestamp) / 1000;
    if (!Number.isFinite(dt) || dt <= 0) return null;
    return (current.value - previous.value) / dt;
  }

  _updatePredictedVolume() {
    const flowUnit = 'm3/s'; // this has to be in m3/s for the actions below
    const now = Date.now();

    const inflow = this.measurements.sum('flow', 'predicted', this.flowPositions.inflow, flowUnit) || 0;
    const outflow = this.measurements.sum('flow', 'predicted', this.flowPositions.outflow, flowUnit) || 0;

    if (!this._predictedFlowState) {
      this._predictedFlowState = { inflow, outflow, lastTimestamp: now };
    }

    const timestampPrev = this._predictedFlowState.lastTimestamp ?? now;
    const deltaSeconds = Math.max((now - timestampPrev) / 1000, 0);
    const netVolumeChange = deltaSeconds > 0 ? (inflow - outflow) * deltaSeconds : 0;

    const volumeSeries = this.measurements.type('volume').variant('predicted').position('atequipment');
    const currentVolume = volumeSeries.getCurrentValue('m3');  
  
    const nextVolume = currentVolume + netVolumeChange;
    const writeTimestamp = timestampPrev + deltaSeconds * 1000;

    volumeSeries.value(nextVolume, writeTimestamp, 'm3').unit('m3'); //olifant

    const nextLevel = this._calcLevelFromVolume(nextVolume);
    this.measurements
      .type('level')
      .variant('predicted')
      .position('atequipment')
      .value(nextLevel, writeTimestamp, 'm')
      .unit('m');

    const percent = this.interpolate.interpolate_lin_single_point(
      nextVolume,
      this.basin.minVol,
      this.basin.maxVolOverflow,
      0,
      100
    );

    this.measurements
      .type('volumePercent')
      .variant('predicted')
      .position('atequipment')
      .value(percent, writeTimestamp, '%');

    this._predictedFlowState = { inflow, outflow, lastTimestamp: writeTimestamp };
  }

  _selectBestNetFlow() {
    const type = 'flow';
    const unit = this.measurements.getUnit(type) || 'm3/s';

    for (const variant of this.flowVariants) {
      const bucket = this.measurements.measurements?.[type]?.[variant];
      if (!bucket || Object.keys(bucket).length === 0) continue;

      const inflow = this.measurements.sum(type, variant, this.flowPositions.inflow, unit) || 0;
      const outflow = this.measurements.sum(type, variant, this.flowPositions.outflow, unit) || 0;
      if (Math.abs(inflow) < this.flowThreshold && Math.abs(outflow) < this.flowThreshold) continue;

      const net = inflow - outflow;
      this.measurements.type('netFlowRate').variant(variant).position('atequipment').value(net, Date.now(), unit);
      return { value: net, source: variant, direction: this._deriveDirection(net) };
    }

    // Fallback: level trend
    for (const variant of this.levelVariants) {
      const rate = this._levelRate(variant);
      if (!Number.isFinite(rate)) continue;
      const netFlow = rate * this.basin.surfaceArea;
      return { value: netFlow, source: `level:${variant}`, direction: this._deriveDirection(netFlow) };
    }

    this.logger.warn('No usable measurements to compute net flow; assuming steady.');
    return { value: 0, source: null, direction: 'steady' };
  }

  _computeRemainingTime(netFlow) {
    if (!netFlow || Math.abs(netFlow.value) < this.flowThreshold) return { seconds: null, source: null };

    const { heightOverflow, heightOutlet, surfaceArea } = this.basin;
    if (!Number.isFinite(surfaceArea) || surfaceArea <= 0) return { seconds: null, source: null };

    for (const variant of this.levelVariants) {
      const lvl = this.measurements.type('level').variant(variant).position('atequipment').getCurrentValue('m');
      if (!Number.isFinite(lvl)) continue;

      const remainingHeight = netFlow.value > 0 ? Math.max(heightOverflow - lvl, 0) : Math.max(lvl - heightOutlet, 0);
      const seconds = (remainingHeight * surfaceArea) / Math.abs(netFlow.value);
      if (!Number.isFinite(seconds)) continue;

      return { seconds, source: `${netFlow.source}/${variant}` };
    }

    return { seconds: null, source: netFlow.source };
  }

  _deriveDirection(netFlow) {
    if (netFlow > this.flowThreshold) return 'filling';
    if (netFlow < -this.flowThreshold) return 'draining';
    return 'steady';
  }

  /* --------------------------- Safety --------------------------- */

  _safetyController(remainingTime, direction) {
    this.safetyControllerActive = false;

    const volUnit = this.measurements.getUnit('volume');
    const vol = this._pickVariant('volume', this.volVariants, 'atequipment', volUnit);
    if (vol == null) {
      Object.values(this.machines).forEach((machine) => machine.handleInput('parent', 'execSequence', 'shutdown'));
      this.logger.warn('No volume data available to safe guard system; shutting down all machines.');
      this.safetyControllerActive = true;
      return;
    }

    const {
      enableDryRunProtection,
      dryRunThresholdPercent,
      enableOverfillProtection,
      overfillThresholdPercent,
      timeleftToFullOrEmptyThresholdSeconds
    } = this.config.safety || {};

    const dryRunEnabled = Boolean(enableDryRunProtection);
    const overfillEnabled = Boolean(enableOverfillProtection);
    const timeProtectionEnabled = timeleftToFullOrEmptyThresholdSeconds > 0;
    const triggerHighVol = this.basin.maxVolOverflow * ((Number(overfillThresholdPercent) || 0) / 100);
    const triggerLowVol = this.basin.minVol * (1 + ((Number(dryRunThresholdPercent) || 0) / 100));

    if (direction === 'draining') {
      const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
      const dryRunTriggered = dryRunEnabled && vol < triggerLowVol;
      if (timeTriggered || dryRunTriggered) {
        Object.values(this.machines).forEach((machine) => {
          const pos = machine?.config?.functionality?.positionVsParent;
          if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) {
            machine.handleInput('parent', 'execSequence', 'shutdown');
          }
        });
        Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown'));
        Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
        this.logger.warn(
          `Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down downstream equipment`
        );
        this.safetyControllerActive = true;
      }
    }

    if (direction === 'filling') {
      const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
      const overfillTriggered = overfillEnabled && vol > triggerHighVol;
      if (timeTriggered || overfillTriggered) {
        Object.values(this.machines).forEach((machine) => {
          const pos = machine?.config?.functionality?.positionVsParent;
          if (pos === 'upstream' && machine._isOperationalState()) {
            machine.handleInput('parent', 'execSequence', 'shutdown');
          }
        });
        Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
        Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown'));
        this.logger.warn(
          `Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down upstream equipment`
        );
        this.safetyControllerActive = true;
      }
    }
  }

  /* --------------------------- Basin --------------------------- */

  initBasinProperties() {
    const minHeightBasedOn = this.config.hydraulics.minHeightBasedOn;
    const volEmptyBasin = this.config.basin.volume;
    const heightBasin = this.config.basin.height;
    const heightInlet = this.config.basin.heightInlet;
    const heightOutlet = this.config.basin.heightOutlet;
    const heightOverflow = this.config.basin.heightOverflow;

    const surfaceArea = volEmptyBasin / heightBasin;
    const maxVol = heightBasin * surfaceArea;
    const maxVolOverflow = heightOverflow * surfaceArea;
    const minVolOut = heightOutlet * surfaceArea;
    const minVolIn = heightInlet * surfaceArea;
    const minVol = minHeightBasedOn === 'inlet' ? minVolIn : minVolOut;

    this.basin = {
      volEmptyBasin,
      heightBasin,
      heightInlet,
      heightOutlet,
      heightOverflow,
      surfaceArea,
      maxVol,
      maxVolOverflow,
      minVolIn,
      minVolOut,
      minVol,
      minHeightBasedOn
    };

    this.measurements.type('volume').variant('predicted').position('atequipment').value(minVol).unit('m3');
  }

  _calcVolumeFromLevel(level) {
    return Math.max(level, 0) * this.basin.surfaceArea;
  }

  _calcLevelFromVolume(volume) {
    return Math.max(volume, 0) / this.basin.surfaceArea;
  }

  /* --------------------------- Output --------------------------- */

  getOutput() {
    const output = this.measurements.getFlattenedOutput();
    output.direction = this.state.direction;
    output.flowSource = this.state.flowSource;
    output.timeleft = this.state.seconds;
    output.volEmptyBasin = this.basin.volEmptyBasin;
    output.heightInlet = this.basin.heightInlet;
    output.heightOverflow = this.basin.heightOverflow;
    output.maxVol = this.basin.maxVol;
    output.minVol = this.basin.minVol;
    output.maxVolOverflow = this.basin.maxVolOverflow;
    output.minVolOut = this.basin.minVolOut;
    output.minVolIn = this.basin.minVolIn;
    output.minHeightBasedOn = this.basin.minHeightBasedOn;
    return output;
  }
}

module.exports = PumpingStation;
/* ------------------------------------------------------------------------- */
/* Example usage                                                             */
/* ------------------------------------------------------------------------- */

if (require.main === module) {
  const Measurement = require('../../measurement/src/specificClass');
  const RotatingMachine = require('../../rotatingMachine/src/specificClass');

  function createPumpingStationConfig(name) {
    return {
      general: {
        logging: { enabled: true, logLevel: 'debug' },
        name,
        id: `${name}-${Date.now()}`,
        flowThreshold: 1e-4
      },
      functionality: {
        softwareType: 'pumpingStation',
        role: 'stationcontroller'
      },
      basin: {
        volume: 43.75,
        height: 10,
        heightInlet: 3,
        heightOutlet: 0.2,
        heightOverflow: 3.2
      },
      hydraulics: {
        refHeight: 'NAP',
        basinBottomRef: 0
      },
      safety: {
        enableDryRunProtection:false,
        enableOverfillProtection:false
      }
    };
  }

  function createLevelMeasurementConfig(name) {
    return {
      general: {
        logging: { enabled: true, logLevel: 'debug' },
        name,
        id: `${name}-${Date.now()}`,
        unit: 'm'
      },
      functionality: {
        softwareType: 'measurement',
        role: 'sensor',
        positionVsParent: 'atequipment'
      },
      asset: {
        category: 'sensor',
        type: 'level',
        model: 'demo-level',
        supplier: 'demoCo',
        unit: 'm'
      },
      scaling: { enabled: false },
      smoothing: { smoothWindow: 5, smoothMethod: 'none' }
    };
  }

  function createFlowMeasurementConfig(name, position) {
    return {
      general: {
        logging: { enabled: true, logLevel: 'debug' },
        name,
        id: `${name}-${Date.now()}`,
        unit: 'm3/s'
      },
      functionality: {
        softwareType: 'measurement',
        role: 'sensor',
        positionVsParent: position
      },
      asset: {
        category: 'sensor',
        type: 'flow',
        model: 'demo-flow',
        supplier: 'demoCo',
        unit: 'm3/s'
      },
      scaling: { enabled: false },
      smoothing: { smoothWindow: 5, smoothMethod: 'none' }
    };
  }

  function createMachineConfig(name,position) {
    return {
      general: {
        name,
        logging: { enabled: false, logLevel: 'debug' }
      },
      functionality: {
        softwareType: "machine",
        positionVsParent: position
      },
      asset: {
        supplier: 'Hydrostal',
        type: 'pump',
        category: 'centrifugal',
        model: 'hidrostal-H05K-S03R'
      }
    };
  }

  function createMachineStateConfig() {
    return {
      general: {
        logging: {
          enabled: true,
          logLevel: 'debug'
        }
      },
      movement: { speed: 1 },
      time: {
        starting: 2,
        warmingup: 3,
        stopping: 2,
        coolingdown: 3
      }
    };
  }

  function seedSample(measurement, type, value, unit) {
    const pos = measurement.config.functionality.positionVsParent;
    measurement.measurements.type(type).variant('measured').position(pos).value(value, Date.now(), unit);
  }

  (async function demo() {
    const station = new PumpingStation(createPumpingStationConfig('PumpingStationDemo'));
    const pump1 = new RotatingMachine(createMachineConfig('Pump1','downstream'), createMachineStateConfig());
    //const pump2 = new RotatingMachine(createMachineConfig('Pump2','upstream'), createMachineStateConfig());

    //const levelSensor = new Measurement(createLevelMeasurementConfig('WetWellLevel'));
    //const inflowSensor = new Measurement(createFlowMeasurementConfig('InfluentFlow', 'in'));
    //const outflowSensor = new Measurement(createFlowMeasurementConfig('PumpDischargeFlow', 'out'));

    //station.childRegistrationUtils.registerChild(levelSensor, levelSensor.config.functionality.softwareType);
    //station.childRegistrationUtils.registerChild(inflowSensor, inflowSensor.config.functionality.softwareType);
    //station.childRegistrationUtils.registerChild(outflowSensor, outflowSensor.config.functionality.softwareType);
    
    station.childRegistrationUtils.registerChild(pump1, 'machine');
    //station.childRegistrationUtils.registerChild(pump2, 'machine');

    // Seed initial measurements
    
    //seedSample(levelSensor, 'level', 1.8, 'm');
    //seedSample(inflowSensor, 'flow', 0.35, 'm3/s');
    //seedSample(outflowSensor, 'flow', 0.20, 'm3/s');

    

    setInterval(
      () => station.tick(), 1000);

      await new Promise((resolve) => setTimeout(resolve, 10));

      console.log('Initial state:', station.state);
      station.setManualInflow(300,Date.now(),'l/s');
      station.calibratePredictedVolume(3.4);
      //await pump1.handleInput('parent', 'execSequence', 'startup');
      //await pump1.handleInput('parent', 'execMovement', 10);
//
      //await pump2.handleInput('parent', 'execSequence', 'startup');
      //await pump2.handleInput('parent', 'execMovement', 10);

      console.log('Station state:', station.state);
      console.log('Station output:', station.getOutput());
    })().catch((err) => {
      console.error('Demo failed:', err);
    });
}
  //*/