const { Rng } = require('./rng');

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function safeLayer(layerNo) {
  return Math.max(1, Math.trunc(Number(layerNo || 1)));
}

function stepProgress(stepNo, stepCount) {
  const total = Math.max(1, Math.trunc(Number(stepCount || 1)));
  if (total <= 1) return 1;
  return clamp((Math.max(1, Number(stepNo || 1)) - 1) / (total - 1), 0, 1);
}

function jitter(rng, size = 1) {
  if (!rng || size <= 0) return 0;
  return rng.pick([-size, 0, 0, size]);
}

function layerParameterBaseCount(layerNo) {
  const n = safeLayer(layerNo);
  const early = Math.floor(Math.log2(n + 1) / 2.6);
  const middle = n > 80 ? Math.floor(Math.log2(n / 80 + 1)) : 0;
  const late = n > 500 ? Math.floor(Math.sqrt(n - 500) / 28) : 0;
  return clamp(1 + early + middle + late, 1, 8);
}

function selectParameterCount({
  layerNo = 1,
  stepNo = 1,
  stepCount = 1,
  seed = 'parameter-count',
  minCount = 1,
  maxCount = 10,
} = {}) {
  const progress = stepProgress(stepNo, stepCount);
  const rng = new Rng(`${seed}:${layerNo}:${stepNo}:${stepCount}:count`);
  const base = layerParameterBaseCount(layerNo);
  const progressBonus = Math.floor(progress * 3);
  const raw = Math.max(base, base + progressBonus + jitter(rng, 1));
  const count = clamp(raw, Math.max(1, Math.trunc(minCount)), Math.max(1, Math.trunc(maxCount)));

  return {
    parameter_count: count,
    base_parameter_count: base,
    step_progress: Number(progress.toFixed(4)),
    min_parameter_count: Math.max(1, Math.trunc(minCount)),
    max_parameter_count: Math.max(1, Math.trunc(maxCount)),
  };
}

function singleParameterScoreTarget({ layerNo = 1, stepNo = 1, stepCount = 1, seed = 'parameter-score' } = {}) {
  const n = safeLayer(layerNo);
  const progress = stepProgress(stepNo, stepCount);
  const rng = new Rng(`${seed}:${layerNo}:${stepNo}:${stepCount}:score`);
  const layerPressure = 1 - Math.exp(-n / 520);
  const deepPressure = n > 800 ? Math.min(180, Math.log2(n / 800 + 1) * 75) : 0;
  const progressPressure = progress * 180;
  const raw = Math.round(80 + layerPressure * 700 + deepPressure + progressPressure + jitter(rng, 25));
  return clamp(raw, 40, 1000);
}

function scoreWindow(target, spread) {
  return {
    min: clamp(Math.round(target - spread), 1, 1000),
    max: clamp(Math.round(target + spread), 1, 1000),
  };
}

function selectParameterDifficulty({
  layerNo = 1,
  stepNo = 1,
  stepCount = 1,
  parameterCount = 1,
  seed = 'parameter-difficulty',
} = {}) {
  const count = clamp(Math.trunc(Number(parameterCount || 1)), 1, 10);
  const singleTarget = singleParameterScoreTarget({ layerNo, stepNo, stepCount, seed });
  const numberTarget = clamp(Math.round(singleTarget * 0.62), 1, 1000);
  const operatorTarget = clamp(Math.round(singleTarget * 0.78), 1, 1000);
  const numberSpread = clamp(Math.round(90 + singleTarget * 0.18), 40, 260);
  const operatorSpread = clamp(Math.round(120 + singleTarget * 0.2), 50, 300);
  const numberRange = scoreWindow(numberTarget, numberSpread);
  const operatorRange = scoreWindow(operatorTarget, operatorSpread);
  const estimatedSingleInjectionScore = numberTarget + operatorTarget;

  return {
    single_parameter_score_target: singleTarget,
    parameter_score_target: singleTarget * count,
    estimated_single_injection_score_target: estimatedSingleInjectionScore,
    estimated_injection_score_target: estimatedSingleInjectionScore * count,
    operator_score_min: operatorRange.min,
    operator_score_max: operatorRange.max,
    number_score_min: numberRange.min,
    number_score_max: numberRange.max,
    parameter_count: count,
  };
}

function buildParameterPlan({
  layerNo = 1,
  stepNo = 1,
  stepCount = 1,
  seed = 'parameter-plan',
} = {}) {
  const countDecision = selectParameterCount({
    layerNo,
    stepNo,
    stepCount,
    seed,
  });
  const difficultyDecision = selectParameterDifficulty({
    layerNo,
    stepNo,
    stepCount,
    parameterCount: countDecision.parameter_count,
    seed,
  });

  return {
    ...countDecision,
    ...difficultyDecision,
  };
}

module.exports = {
  buildParameterPlan,
  selectParameterCount,
  selectParameterDifficulty,
  singleParameterScoreTarget,
};
