// Teaching example for yenpo.app. Run with Node.js 22+: node --test configuration-flow.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import { parseArgs } from 'node:util';

export function resolveLimit(cli, env, config) {
  const raw = cli.limit ?? env.REPORT_LIMIT ?? config.limit ?? 10;
  const limit = Number(raw);
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
    throw new Error('limit must be an integer from 1 to 100');
  }
  return limit;
}

export async function runReport({ args, env, loadConfig, loadRows }) {
  const { values } = parseArgs({
    args,
    options: { limit: { type: 'string' } },
    strict: true,
  });
  const config = await loadConfig();
  const limit = resolveLimit(values, env, config);
  const rows = await loadRows();
  return rows
    .slice(0, limit)
    .map((row) => row.name)
    .join('\n');
}

test('precedence: command line, environment, config, default', () => {
  assert.equal(
    resolveLimit({ limit: '2' }, { REPORT_LIMIT: '4' }, { limit: 6 }),
    2,
  );
  assert.equal(resolveLimit({}, { REPORT_LIMIT: '4' }, { limit: 6 }), 4);
  assert.equal(resolveLimit({}, {}, { limit: 6 }), 6);
  assert.equal(resolveLimit({}, {}, {}), 10);
});

test('an empty environment value is present and fails validation', () => {
  assert.throws(
    () => resolveLimit({}, { REPORT_LIMIT: '' }, { limit: 6 }),
    /integer/,
  );
});

test('invalid limits fail rather than silently falling back', () => {
  for (const value of ['0', '-1', '1.5', '101', 'many']) {
    assert.throws(() => resolveLimit({ limit: value }, {}, {}), /integer/);
  }
});

test('the selected limit reaches the output', async () => {
  const calls = [];
  const output = await runReport({
    args: ['--limit', '2'],
    env: { REPORT_LIMIT: '4' },
    loadConfig: async () => {
      calls.push('config');
      return { limit: 6 };
    },
    loadRows: async () => {
      calls.push('rows');
      return ['Ada', 'Lin', 'Sam'].map((name) => ({ name }));
    },
  });
  assert.equal(output, 'Ada\nLin');
  assert.deepEqual(calls, ['config', 'rows']);
});

test('invalid input prevents the row read', async () => {
  let reads = 0;
  await assert.rejects(
    runReport({
      args: [],
      env: { REPORT_LIMIT: '' },
      loadConfig: async () => ({ limit: 6 }),
      loadRows: async () => {
        reads++;
        return [];
      },
    }),
    /integer/,
  );
  assert.equal(reads, 0);
});

test('configuration failure happens even with an explicit command-line limit', async () => {
  let reads = 0;
  await assert.rejects(
    runReport({
      args: ['--limit', '2'],
      env: {},
      loadConfig: async () => {
        throw new Error('invalid config JSON');
      },
      loadRows: async () => {
        reads++;
        return [];
      },
    }),
    /invalid config JSON/,
  );
  assert.equal(reads, 0);
});
