import { test } from 'node:test';
import assert from 'node:assert/strict';
import { ASCIILexicalTable, orderKeys } from './ASCIILexicalTable';

const a = 'a'.charCodeAt(0);
const z = 'z'.charCodeAt(0);

const makeLex = (maxWidth = 4) => new ASCIILexicalTable(a, z, maxWidth);

const isStrictlyIncreasing = (keys: string[]): boolean =>
  keys.every((key, i) => i === 0 || keys[i - 1] < key);

test('orderKeys returns an empty array for empty input', () => {
  const lex = makeLex();
  assert.deepEqual(orderKeys(lex, []), []);
});

test('orderKeys preserves all existing keys when none are missing', () => {
  const lex = makeLex();
  assert.deepEqual(orderKeys(lex, ['a', 'b', 'c']), ['a', 'b', 'c']);
});

test('orderKeys keeps existing keys and fills a single interior gap', () => {
  const lex = makeLex();
  const result = orderKeys(lex, ['b', undefined, 'd']);
  assert.deepEqual(result, ['b', 'c', 'd']);
});

test('orderKeys output length always matches input length', () => {
  const lex = makeLex();
  const inputs: Array<Array<string | undefined>> = [
    [undefined],
    [undefined, undefined],
    [undefined, undefined, undefined],
    ['b', undefined, undefined, 'y'],
  ];
  inputs.forEach((input) => {
    const result = orderKeys(lex, input);
    assert.ok(result, 'expected a defined result');
    assert.equal(result?.length, input.length);
  });
});

test('orderKeys produces strictly increasing, valid keys', () => {
  const lex = makeLex();
  const result = orderKeys(lex, [undefined, undefined, undefined, undefined]);
  assert.ok(result);
  assert.equal(result?.length, 4);
  assert.ok(isStrictlyIncreasing(result as string[]));
  assert.ok((result as string[]).every((key) => lex.has(key)));
});

test('orderKeys keeps fixed keys at their positions when filling gaps', () => {
  const lex = makeLex();
  const result = orderKeys(lex, ['b', undefined, undefined, 'y']) as string[];
  assert.equal(result[0], 'b');
  assert.equal(result[3], 'y');
  assert.ok(isStrictlyIncreasing(result));
});

test('orderKeys is deterministic for the same input', () => {
  const lex = makeLex();
  const first = orderKeys(lex, [undefined, undefined, undefined]);
  const second = orderKeys(lex, [undefined, undefined, undefined]);
  assert.deepEqual(first, second);
});

test('orderKeys handles a leading gap before an existing key', () => {
  const lex = makeLex();
  const result = orderKeys(lex, [undefined, 'm']) as string[];
  assert.equal(result.length, 2);
  assert.equal(result[1], 'm');
  assert.ok(result[0] < 'm');
});

test('orderKeys handles a trailing gap after an existing key', () => {
  const lex = makeLex();
  const result = orderKeys(lex, ['m', undefined]) as string[];
  assert.equal(result.length, 2);
  assert.equal(result[0], 'm');
  assert.ok(result[1] > 'm');
});

test('orderKeys works with a tiny table', () => {
  const tiny = new ASCIILexicalTable('a'.charCodeAt(0), 'b'.charCodeAt(0), 2);
  const result = orderKeys(tiny, [undefined, undefined]) as string[];
  assert.equal(result.length, 2);
  assert.ok(isStrictlyIncreasing(result));
  assert.ok(result.every((key) => tiny.has(key)));
});
