// Unit tests for the level-based control strategy.
// Run with: node --test test/basic/control-levelBased.basic.test.js

const test = require('node:test');
const assert = require('node:assert/strict');

const levelBased = require('../../src/control/levelBased');

function makeMeasurements(levelMeters) {
  // Minimal MeasurementContainer stand-in. The strategy only calls
  // getUnit('level') and a chain ending in getCurrentValue(unit).
  const chain = {
    type() { return chain; },
    variant() { return chain; },
    position() { return chain; },
    getCurrentValue() {
      return Number.isFinite(levelMeters) ? levelMeters : null;
    },
  };
  return {
    getUnit: () => 'm',
    type: () => chain,
  };
}

function makeGroup(name) {
  const calls = { handleInput: [], turnOff: 0 };
  return {
    config: { general: { name } },
    handleInput: async (...args) => { calls.handleInput.push(args); },
    turnOffAllMachines: () => { calls.turnOff += 1; },
    _calls: calls,
  };
}

function makeCtx(levelMeters, opts = {}) {
  const groups = {
    a: makeGroup('A'),
    b: makeGroup('B'),
    c: makeGroup('C'),
  };
  return {
    measurements: makeMeasurements(levelMeters),
    config: {
      control: { levelbased: { minLevel: 1, startLevel: 2, maxLevel: 4, ...(opts.levelbased || {}) } },
    },
    logger: { warn: () => {}, debug: () => {}, info: () => {}, error: () => {} },
    machineGroups: groups,
    machines: {},
    levelVariants: ['measured', 'predicted'],
  };
}

test('level < minLevel → STOP: turnOffAllMachines on every group, percControl = 0', async () => {
  const ctx = makeCtx(0.5);
  const state = { percControl: 42 };
  await levelBased.run(ctx, state);

  assert.equal(state.percControl, 0);
  for (const g of Object.values(ctx.machineGroups)) {
    assert.equal(g._calls.turnOff, 1, 'turnOffAllMachines called once per group');
    assert.equal(g._calls.handleInput.length, 0, 'no demand sent in stop zone');
  }
});

// basin-docs behavior: between minLevel and the active ramp foot, demand
// is commanded to 0 % (not "unchanged"). MGC still receives the command;
// only the explicit minLevel hard-stop path skips handleInput.
test('minLevel ≤ level < ramp foot → commands 0 % without shutdown', async () => {
  const ctx = makeCtx(1.5);
  const state = { percControl: 17 };
  await levelBased.run(ctx, state);

  assert.equal(state.percControl, 0, 'percControl driven to 0 in the hold zone');
  for (const g of Object.values(ctx.machineGroups)) {
    assert.equal(g._calls.turnOff, 0);
    assert.equal(g._calls.handleInput.length, 1, 'one demand=0 forward per group');
    assert.deepEqual(g._calls.handleInput[0], ['parent', 0]);
  }
});

test('level == startLevel → percControl == 0 (lower edge of ramp)', async () => {
  const ctx = makeCtx(2);
  const state = { percControl: null };
  await levelBased.run(ctx, state);
  assert.equal(state.percControl, 0);
});

test('level == maxLevel → percControl == 100 (upper edge of ramp)', async () => {
  const ctx = makeCtx(4);
  const state = { percControl: null };
  await levelBased.run(ctx, state);
  assert.equal(state.percControl, 100);
});

test('level above maxLevel → percControl clamped at 100 (interpolation limit_input behaviour)', async () => {
  const ctx = makeCtx(10);
  const state = { percControl: null };
  await levelBased.run(ctx, state);
  // interpolate_lin_single_point clamps via limit_input(o_min, o_max).
  assert.equal(state.percControl, 100);
});

test('percControl forwarded to every group via handleInput("parent", percControl)', async () => {
  const ctx = makeCtx(3); // halfway between startLevel=2 and maxLevel=4 → 50%
  const state = { percControl: null };
  await levelBased.run(ctx, state);

  assert.equal(state.percControl, 50);
  for (const g of Object.values(ctx.machineGroups)) {
    assert.equal(g._calls.handleInput.length, 1, 'one forward per group');
    assert.deepEqual(g._calls.handleInput[0], ['parent', 50]);
    assert.equal(g._calls.turnOff, 0);
  }
});

test('no valid level → warns and returns without mutating percControl or calling groups', async () => {
  const ctx = makeCtx(NaN);
  let warned = false;
  ctx.logger.warn = () => { warned = true; };
  const state = { percControl: 7 };
  await levelBased.run(ctx, state);

  assert.equal(warned, true);
  assert.equal(state.percControl, 7);
  for (const g of Object.values(ctx.machineGroups)) {
    assert.equal(g._calls.turnOff, 0);
    assert.equal(g._calls.handleInput.length, 0);
  }
});
