const assert = require('assert');
const {
  BASE1_ALGORITHM_VERSION,
  evaluateBase1Step,
  generateBase1Step,
  layerSchedule,
} = require('../src/lib/base1_algorithm');
const { rewardForStep, submitFeeForStep } = require('../src/lib/base1_state');

function short(hash) {
  return `${hash.slice(0, 10)}...${hash.slice(-6)}`;
}

function printSchedule() {
  const layers = [1, 9, 10, 20, 40, 80, 160, 320, 640, 1000, 5999, 10000];
  console.log('\nCoordinate expression-tree layer/grid/step schedule');
  console.table(
    layers.map((layerNo) => {
      const schedule = layerSchedule(layerNo);
      return {
        layer: layerNo,
        tier: schedule.growth_tier,
        growth_units: schedule.growth_units,
        grid: `${schedule.grid_size}x${schedule.grid_size}`,
        cells: schedule.grid_size * schedule.grid_size,
        steps: schedule.step_count,
      };
    }),
  );
}

function sampleStep(layerNo, stepNo, previousHash) {
  const step = generateBase1Step({ layerNo, stepNo, previousHash });
  const mid = Math.floor(step.grid_size / 2);
  const output = evaluateBase1Step(step, mid, mid, {
    click_count: 7,
    last_x: 3,
    last_y: 5,
    elapsed_seconds: 91,
    chain_state: previousHash,
  });
  assert(Number.isInteger(output.u));
  assert(Number.isInteger(output.v));
  assert(output.u >= 0 && output.u < step.grid_size);
  assert(output.v >= 0 && output.v < step.grid_size);
  return {
    layer: layerNo,
    step: step.step_no,
    tier: step.growth_tier,
    grid: step.grid_size,
    static_params: step.static_parameters.length,
    dynamic_params: step.dynamic_parameters.length,
    operators_u: step.operations.u.length,
    operators_v: step.operations.v.length,
    score: step.scores.final_score,
    reward: rewardForStep(step),
    submit_fee: submitFeeForStep(step),
    grid_score: step.scores.grid_score,
    layer_score: step.scores.layer_score,
    param_score: step.scores.parameter_score,
    op_score: step.scores.operator_score,
    hash: short(step.formula_hash),
    sample: `A(${mid},${mid})->B(${output.u},${output.v})`,
    expr_u_len: step.expressions.u.length,
    expr_v_len: step.expressions.v.length,
  };
}

function printSamples() {
  const requests = [
    [1, 1],
    [10, 1],
    [10, 6],
    [20, 1],
    [40, 12],
    [80, 18],
    [160, 30],
    [5999, 1],
    [5999, 40],
    [10000, 55],
  ];
  console.log('\nCoordinate expression-tree generated step samples');
  console.table(requests.map(([layerNo, stepNo]) => sampleStep(layerNo, stepNo, `prev-${layerNo}-${stepNo}`)));
}

function verifyRandomSourceBehavior() {
  const sameA = generateBase1Step({ layerNo: 200, stepNo: 20, previousHash: 'same-source' });
  const sameB = generateBase1Step({ layerNo: 200, stepNo: 20, previousHash: 'same-source' });
  const different = generateBase1Step({ layerNo: 200, stepNo: 20, previousHash: 'different-source' });
  assert.strictEqual(sameA.formula_hash, sameB.formula_hash);
  assert.notStrictEqual(sameA.formula_hash, different.formula_hash);

  const variants = Array.from({ length: 10 }, (_, index) =>
    generateBase1Step({ layerNo: 200, stepNo: 20, previousHash: `random-source-${index}` }),
  );
  const hashes = new Set(variants.map((item) => item.formula_hash));
  const expressions = new Set(variants.map((item) => `${item.expressions.u}|${item.expressions.v}`));
  assert.strictEqual(hashes.size, variants.length);
  assert.strictEqual(expressions.size, variants.length);
  console.log('\nRandom-source check passed: same source is reproducible, different sources generate different formulas.');
}

function main() {
  console.log(`Algorithm: ${BASE1_ALGORITHM_VERSION}`);
  printSchedule();
  printSamples();
  verifyRandomSourceBehavior();
}

main();
