const { sha256 } = require('./hash');
const { Rng } = require('./rng');
const { assertSeedSourceForStep } = require('./chain_seed');

const BASE1_ALGORITHM_VERSION = 'mazex-coordinate-expression-tree-2.0';
const INTERNAL_MOD = 1_000_000_007;

const DEFAULT_BASE1_CONFIG = {
  base_grid: 20,
  grid_unit: 5,
  base_steps: 10,
  step_unit: 2,
  first_growth_layer: 10,
};

const DYNAMIC_PARAMETER_POOL = [
  { id: 'click_count', unlock_tier: 3, score: 80, range: '0..127' },
  { id: 'user_click_count', unlock_tier: 3, score: 90, range: '0..127' },
  { id: 'global_click_count', unlock_tier: 3, score: 100, range: '0..255' },
  { id: 'last_x', unlock_tier: 3, score: 90, range: '0..N-1' },
  { id: 'last_y', unlock_tier: 3, score: 90, range: '0..N-1' },
  { id: 'last_u', unlock_tier: 4, score: 110, range: '0..N-1' },
  { id: 'last_v', unlock_tier: 4, score: 110, range: '0..N-1' },
  { id: 'last_dx', unlock_tier: 4, score: 120, range: '-N..N' },
  { id: 'last_dy', unlock_tier: 4, score: 120, range: '-N..N' },
  { id: 'last_distance', unlock_tier: 4, score: 130, range: '0..2N' },
  { id: 'elapsed_seconds', unlock_tier: 4, score: 130, range: '0..3599' },
  { id: 'time_bucket', unlock_tier: 4, score: 150, range: '0..255' },
  { id: 'time_mod', unlock_tier: 5, score: 160, range: '0..N-1' },

  { id: 'history_hash', unlock_tier: 6, score: 260, range: '0..N-1' },
  { id: 'history_sum_x', unlock_tier: 6, score: 220, range: '0..N-1' },
  { id: 'history_sum_y', unlock_tier: 6, score: 220, range: '0..N-1' },
  { id: 'history_sum_u', unlock_tier: 6, score: 230, range: '0..N-1' },
  { id: 'history_sum_v', unlock_tier: 6, score: 230, range: '0..N-1' },
  { id: 'history_unique_count', unlock_tier: 6, score: 210, range: '0..K' },
  { id: 'history_repeat_count', unlock_tier: 6, score: 210, range: '0..K' },
  { id: 'history_path_length', unlock_tier: 6, score: 240, range: '0..N-1' },
  { id: 'previous_step_hash', unlock_tier: 6, score: 280, range: '0..N-1' },
  { id: 'previous_solution_hash', unlock_tier: 6, score: 300, range: '0..N-1' },
  { id: 'previous_block_hash', unlock_tier: 6, score: 300, range: '0..N-1' },
  { id: 'block_height', unlock_tier: 6, score: 220, range: '0..999999' },
  { id: 'chain_seed', unlock_tier: 6, score: 320, range: '0..N-1' },
  { id: 'difficulty_epoch', unlock_tier: 7, score: 260, range: '0..1023' },
  { id: 'hash_click_state', unlock_tier: 7, score: 340, range: '0..N-1' },
  { id: 'hash_history_state', unlock_tier: 7, score: 360, range: '0..N-1' },
  { id: 'hash_chain_state', unlock_tier: 7, score: 380, range: '0..N-1' },
  { id: 'nonlinear_jump', unlock_tier: 8, score: 520, range: '0..N-1' },

  { id: 'half_life_bucket', unlock_tier: 9, score: 440, range: '0..1023' },
  { id: 'decay_value', unlock_tier: 9, score: 460, range: '0..N-1' },
  { id: 'cooldown_value', unlock_tier: 9, score: 420, range: '0..N-1' },
  { id: 'age_factor', unlock_tier: 9, score: 430, range: '0..N-1' },
  { id: 'freshness_factor', unlock_tier: 9, score: 430, range: '0..N-1' },
  { id: 'cycle_phase', unlock_tier: 9, score: 460, range: '0..cycle-1' },
  { id: 'cycle_index', unlock_tier: 9, score: 420, range: '0..1023' },
  { id: 'pulse_state', unlock_tier: 9, score: 390, range: '0..1' },
  { id: 'wave_bucket', unlock_tier: 9, score: 480, range: '0..N-1' },
  { id: 'global_sample_count', unlock_tier: 9, score: 440, range: '0..999999' },
  { id: 'global_submit_count', unlock_tier: 9, score: 450, range: '0..999999' },
  { id: 'unique_sampler_count', unlock_tier: 9, score: 420, range: '0..99999' },
  { id: 'solver_count', unlock_tier: 9, score: 420, range: '0..99999' },
  { id: 'network_activity_bucket', unlock_tier: 9, score: 500, range: '0..N-1' },
  { id: 'multi_state_hash', unlock_tier: 9, score: 620, range: '0..N-1' },
];

const OPERATOR_POOL = [
  { id: 'add', unlock_tier: 0, score: 10, slot: 'offset' },
  { id: 'sub', unlock_tier: 0, score: 12, slot: 'offset' },
  { id: 'mul', unlock_tier: 0, score: 25, slot: 'scale' },
  { id: 'safe_div', unlock_tier: 0, score: 35, slot: 'divisor' },
  { id: 'mod', unlock_tier: 0, score: 40, slot: 'modulus' },
  { id: 'abs_shift', unlock_tier: 0, score: 45, slot: 'offset' },
  { id: 'neg_shift', unlock_tier: 0, score: 45, slot: 'offset' },
  { id: 'min_with_param', unlock_tier: 1, score: 55, slot: 'threshold' },
  { id: 'max_with_param', unlock_tier: 1, score: 55, slot: 'threshold' },
  { id: 'mix_x', unlock_tier: 1, score: 80, slot: 'scale' },
  { id: 'mix_y', unlock_tier: 1, score: 80, slot: 'scale' },
  { id: 'mix_N', unlock_tier: 1, score: 95, slot: 'scale' },

  { id: 'square', unlock_tier: 3, score: 150, slot: 'modulus' },
  { id: 'cube', unlock_tier: 3, score: 180, slot: 'modulus' },
  { id: 'pow_small', unlock_tier: 3, score: 240, slot: 'exponent' },
  { id: 'sqrt_abs', unlock_tier: 3, score: 140, slot: 'offset' },
  { id: 'log_safe', unlock_tier: 3, score: 160, slot: 'offset' },
  { id: 'sin_phase', unlock_tier: 3, score: 210, slot: 'phase', dynamic_params: ['time_bucket'] },
  { id: 'cos_phase', unlock_tier: 3, score: 210, slot: 'phase', dynamic_params: ['time_bucket'] },
  { id: 'xor_mask', unlock_tier: 3, score: 220, slot: 'mask', dynamic_params: ['click_count'] },
  { id: 'and_mask', unlock_tier: 3, score: 210, slot: 'mask', dynamic_params: ['click_count'] },
  { id: 'or_mask', unlock_tier: 3, score: 210, slot: 'mask', dynamic_params: ['click_count'] },
  { id: 'shift_left_safe', unlock_tier: 3, score: 230, slot: 'shift' },
  { id: 'shift_right_safe', unlock_tier: 3, score: 230, slot: 'shift' },
  { id: 'parity_gate', unlock_tier: 4, score: 260, slot: 'threshold', dynamic_params: ['click_count', 'last_x', 'last_y'] },
  { id: 'threshold_gate', unlock_tier: 4, score: 290, slot: 'threshold', dynamic_params: ['last_x', 'last_y', 'time_bucket'] },
  { id: 'coord_sum', unlock_tier: 4, score: 240, slot: 'scale' },
  { id: 'coord_diff', unlock_tier: 4, score: 250, slot: 'scale' },
  { id: 'coord_product', unlock_tier: 4, score: 310, slot: 'scale' },
  { id: 'square_x_mix', unlock_tier: 4, score: 340, slot: 'scale' },
  { id: 'square_y_mix', unlock_tier: 4, score: 340, slot: 'scale' },
  { id: 'sum_square_mix', unlock_tier: 4, score: 370, slot: 'scale' },
  { id: 'diff_square_mix', unlock_tier: 4, score: 380, slot: 'scale' },
  { id: 'cube_x_mix', unlock_tier: 5, score: 460, slot: 'scale' },
  { id: 'cube_y_mix', unlock_tier: 5, score: 460, slot: 'scale' },
  { id: 'xy_square_mix', unlock_tier: 5, score: 520, slot: 'scale' },
  { id: 'distance_origin', unlock_tier: 4, score: 300, slot: 'scale' },
  { id: 'distance_center', unlock_tier: 4, score: 320, slot: 'scale' },
  { id: 'time_shift', unlock_tier: 4, score: 330, slot: 'scale', requires_dynamic: true, dynamic_params: ['elapsed_seconds', 'time_bucket', 'time_mod'] },
  { id: 'click_shift', unlock_tier: 4, score: 320, slot: 'scale', requires_dynamic: true, dynamic_params: ['click_count', 'user_click_count', 'global_click_count'] },
  { id: 'last_point_shift', unlock_tier: 5, score: 360, slot: 'scale', requires_dynamic: true, dynamic_params: ['last_x', 'last_y', 'last_u', 'last_v', 'last_dx', 'last_dy'] },

  { id: 'hash_mix', unlock_tier: 6, score: 520, slot: 'hash_salt', dynamic_params: ['hash_click_state', 'hash_history_state', 'hash_chain_state'] },
  { id: 'hash_jump', unlock_tier: 6, score: 620, slot: 'hash_salt', dynamic_params: ['nonlinear_jump', 'chain_seed', 'history_hash'] },
  { id: 'chain_seed_mix', unlock_tier: 6, score: 560, slot: 'hash_salt', dynamic_params: ['chain_seed', 'previous_block_hash', 'block_height'] },
  { id: 'previous_hash_gate', unlock_tier: 6, score: 580, slot: 'branch_seed', dynamic_params: ['previous_step_hash', 'previous_solution_hash', 'previous_block_hash'] },
  { id: 'history_hash_mix', unlock_tier: 6, score: 560, slot: 'hash_salt', dynamic_params: ['history_hash', 'hash_history_state'] },
  { id: 'history_sum_mix', unlock_tier: 6, score: 480, slot: 'scale', dynamic_params: ['history_sum_x', 'history_sum_y', 'history_sum_u', 'history_sum_v'] },
  { id: 'path_length_mix', unlock_tier: 6, score: 500, slot: 'scale', dynamic_params: ['history_path_length'] },
  { id: 'piecewise_3way', unlock_tier: 6, score: 680, slot: 'branch_seed', dynamic_params: ['history_hash', 'chain_seed', 'click_count'] },
  { id: 'prime_mod', unlock_tier: 6, score: 520, slot: 'prime_seed', dynamic_params: ['chain_seed'] },
  { id: 'next_prime_shift', unlock_tier: 6, score: 540, slot: 'offset', dynamic_params: ['chain_seed'] },
  { id: 'matrix_2x2_mix', unlock_tier: 7, score: 760, slot: 'scale', dynamic_params: ['chain_seed'] },
  { id: 'pow_x_small', unlock_tier: 7, score: 780, slot: 'exponent', dynamic_params: ['chain_seed'] },
  { id: 'pow_y_small', unlock_tier: 7, score: 780, slot: 'exponent', dynamic_params: ['chain_seed'] },
  { id: 'poly2_mix', unlock_tier: 7, score: 860, slot: 'scale', dynamic_params: ['chain_seed'] },
  { id: 'poly3_mix', unlock_tier: 8, score: 1080, slot: 'scale', dynamic_params: ['chain_seed', 'history_hash'] },
  { id: 'quadratic_gate', unlock_tier: 8, score: 1120, slot: 'threshold', dynamic_params: ['history_hash', 'chain_seed', 'click_count'] },
  { id: 'rotate_coord', unlock_tier: 7, score: 720, slot: 'phase', dynamic_params: ['time_bucket'] },
  { id: 'mirror_coord', unlock_tier: 7, score: 640, slot: 'threshold', dynamic_params: ['click_count', 'last_x', 'last_y'] },
  { id: 'fold_coord', unlock_tier: 7, score: 700, slot: 'threshold', dynamic_params: ['history_sum_x', 'history_sum_y'] },
  { id: 'permutation_gate', unlock_tier: 7, score: 760, slot: 'branch_seed', dynamic_params: ['history_hash', 'chain_seed'] },
  { id: 'state_machine_step', unlock_tier: 7, score: 860, slot: 'branch_seed', dynamic_params: ['history_hash', 'previous_step_hash', 'chain_seed'] },
  { id: 'iterated_mix_safe', unlock_tier: 8, score: 920, slot: 'iteration', dynamic_params: ['chain_seed', 'history_hash'] },
  { id: 'chaos_logistic_safe', unlock_tier: 8, score: 980, slot: 'scale', dynamic_params: ['chain_seed', 'nonlinear_jump'] },

  { id: 'half_life_decay', unlock_tier: 9, score: 1120, slot: 'scale', requires_dynamic: true, dynamic_params: ['half_life_bucket', 'decay_value', 'age_factor'] },
  { id: 'cooldown_gate', unlock_tier: 9, score: 1060, slot: 'threshold', dynamic_params: ['cooldown_value', 'freshness_factor'] },
  { id: 'cycle_phase_mix', unlock_tier: 9, score: 1100, slot: 'phase', requires_dynamic: true, dynamic_params: ['cycle_phase', 'cycle_index'] },
  { id: 'wave_interference', unlock_tier: 9, score: 1240, slot: 'phase', dynamic_params: ['wave_bucket', 'cycle_phase', 'time_bucket'] },
  { id: 'pulse_gate', unlock_tier: 9, score: 1160, slot: 'branch_seed', dynamic_params: ['pulse_state', 'cycle_phase'] },
  { id: 'network_activity_mix', unlock_tier: 9, score: 1180, slot: 'scale', dynamic_params: ['network_activity_bucket', 'unique_sampler_count', 'solver_count'] },
  { id: 'global_count_gate', unlock_tier: 9, score: 1200, slot: 'branch_seed', dynamic_params: ['global_sample_count', 'global_submit_count'] },
  { id: 'multi_state_hash', unlock_tier: 9, score: 1500, slot: 'hash_salt', dynamic_params: ['multi_state_hash', 'history_hash', 'chain_seed', 'network_activity_bucket'] },
  { id: 'delayed_state_mix', unlock_tier: 9, score: 1280, slot: 'scale', dynamic_params: ['history_hash', 'previous_solution_hash'] },
  { id: 'memory_window_gate', unlock_tier: 9, score: 1320, slot: 'branch_seed', dynamic_params: ['history_unique_count', 'history_repeat_count', 'history_path_length'] },
  { id: 'adaptive_epoch_gate', unlock_tier: 9, score: 1260, slot: 'branch_seed', dynamic_params: ['difficulty_epoch', 'block_height'] },
  { id: 'nested_piecewise', unlock_tier: 10, score: 1660, slot: 'branch_seed', dynamic_params: ['multi_state_hash', 'cycle_phase', 'chain_seed'] },
  { id: 'cubic_gate', unlock_tier: 10, score: 1840, slot: 'threshold', dynamic_params: ['multi_state_hash', 'history_hash', 'chain_seed'] },
  { id: 'fractal_fold', unlock_tier: 10, score: 1720, slot: 'iteration', dynamic_params: ['cycle_phase', 'chain_seed'] },
  { id: 'chaotic_jump', unlock_tier: 10, score: 1880, slot: 'hash_salt', dynamic_params: ['nonlinear_jump', 'multi_state_hash'] },
  { id: 'proof_state_mix', unlock_tier: 10, score: 1520, slot: 'hash_salt', dynamic_params: ['previous_solution_hash', 'previous_step_hash'] },
  { id: 'solver_race_mix', unlock_tier: 10, score: 1480, slot: 'scale', dynamic_params: ['solver_count', 'global_submit_count'] },
  { id: 'epoch_seed_jump', unlock_tier: 10, score: 1700, slot: 'hash_salt', dynamic_params: ['difficulty_epoch', 'chain_seed'] },
  { id: 'bounded_recursive_tree', unlock_tier: 10, score: 2100, slot: 'iteration', dynamic_params: ['multi_state_hash', 'history_hash'] },
];

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

function mod(value, n) {
  const divisor = Math.max(1, Math.trunc(Number(n) || 1));
  return ((Math.trunc(Number(value) || 0) % divisor) + divisor) % divisor;
}

function safeNumber(value) {
  if (!Number.isFinite(Number(value))) return 0;
  return mod(value, INTERNAL_MOD);
}

function triangular(n) {
  const x = Math.max(0, Math.trunc(Number(n) || 0));
  return Math.trunc((x * (x + 1)) / 2);
}

function growthTier(layerNo, config = DEFAULT_BASE1_CONFIG) {
  const layer = Math.max(1, Math.trunc(Number(layerNo || 1)));
  const first = Math.max(1, Math.trunc(Number(config.first_growth_layer || 10)));
  if (layer < first) return 0;
  return Math.floor(Math.log2(layer / first)) + 1;
}

function layerSchedule(layerNo, config = DEFAULT_BASE1_CONFIG) {
  const tier = growthTier(layerNo, config);
  const growth = triangular(tier);
  const gridSize = Number(config.base_grid) + Number(config.grid_unit) * growth;
  const stepCount = Number(config.base_steps) + Number(config.step_unit) * growth;
  return {
    algorithm: BASE1_ALGORITHM_VERSION,
    layer_no: Math.max(1, Math.trunc(Number(layerNo || 1))),
    growth_tier: tier,
    growth_units: growth,
    grid_size: Math.max(2, Math.trunc(gridSize)),
    step_count: Math.max(1, Math.trunc(stepCount)),
  };
}

function stepRatio(stepNo, stepCount) {
  if (stepCount <= 1) return 1;
  return clamp((Math.max(1, stepNo) - 1) / (stepCount - 1), 0, 1);
}

function difficultyRange({ layerNo, stepNo, schedule }) {
  const ratio = stepRatio(stepNo, schedule.step_count);
  const growth = triangular(schedule.growth_tier);
  const base = schedule.grid_size * schedule.grid_size + growth * 500 + schedule.layer_no * (schedule.growth_tier + 1);
  const pressure = Math.round(ratio * (schedule.growth_tier + 1) * 900);
  return {
    min: base + pressure,
    max: base + pressure + (schedule.growth_tier + 1) * 1500,
    ratio,
    layer_no: layerNo,
  };
}

function operatorCountRange({ tier, ratio }) {
  const min = Math.max(2, 2 + tier + Math.floor(ratio * (tier + 2)));
  const max = min + 2 + Math.floor(tier / 2);
  return { min, max };
}

function normalizeFragmentGenerationProfile(profile = {}) {
  const safeProfile = profile && typeof profile === 'object' ? profile : {};
  const kindCounts =
    safeProfile.kind_counts && typeof safeProfile.kind_counts === 'object' ? safeProfile.kind_counts : {};
  const scoreSum = Math.max(0, Math.trunc(Number(safeProfile.score_sum || 0)));
  const selectedCount = Math.max(0, Math.trunc(Number(safeProfile.selected_count || 0)));
  const operatorBias = Math.max(0, Math.trunc(Number(kindCounts.operator || 0)));
  const staticBias = Math.max(0, Math.trunc(Number(kindCounts.static_parameter || 0)));
  const dynamicBias = Math.max(0, Math.trunc(Number(kindCounts.dynamic_parameter || 0)));
  const compositionBias = Math.max(0, Math.trunc(Number(kindCounts.composition || 0)));
  const domainBias = Math.max(0, Math.trunc(Number(kindCounts.domain_rule || 0)));
  const behaviorBias = Math.max(0, Math.trunc(Number(kindCounts.behavior_modifier || 0)));
  const verifierBias = Math.max(0, Math.trunc(Number(kindCounts.verifier_constraint || 0)));
  return {
    profile_hash: safeProfile.profile_hash || null,
    selected_count: selectedCount,
    kind_counts: kindCounts,
    score_sum: scoreSum,
    operator_count_bonus: clamp(Math.floor((scoreSum + operatorBias * 5 + compositionBias * 4) / 18), 0, 5),
    static_range_multiplier: clamp(1 + staticBias * 0.04 + domainBias * 0.03, 1, 1.5),
    dynamic_probability_bonus: clamp(dynamicBias * 0.035 + behaviorBias * 0.03, 0, 0.25),
    branch_weight_bonus: compositionBias * 40 + behaviorBias * 35,
    hash_weight_bonus: behaviorBias * 45 + domainBias * 25,
    verifier_pressure_score: verifierBias * 120,
  };
}

function staticRangeForSlot(slot, { N, tier, fragmentProfile = null }) {
  const scale = Math.max(1, tier + 1);
  const profile = normalizeFragmentGenerationProfile(fragmentProfile);
  const multiplier = profile.static_range_multiplier;
  const range = (() => {
    switch (slot) {
    case 'offset':
      return { min: -100 * scale, max: 100 * scale, non_zero: true };
    case 'scale':
      return { min: -64 * scale, max: 64 * scale, non_zero: true };
    case 'divisor':
      return { min: 1, max: 64 * scale, non_zero: true };
    case 'modulus':
      return { min: 2, max: Math.max(3, 2 * N + 16 * scale), non_zero: true };
    case 'exponent':
      return { min: 1, max: Math.min(12, 4 + tier), non_zero: true };
    case 'threshold':
      return { min: 1, max: Math.max(1, N - 1), non_zero: true };
    case 'mask':
      return { min: 1, max: 65535, non_zero: true };
    case 'shift':
      return { min: 1, max: 8, non_zero: true };
    case 'iteration':
      return { min: 1, max: Math.min(32, 4 + tier * 2), non_zero: true };
    case 'phase':
      return { min: 1, max: 360, non_zero: true };
    case 'branch_seed':
      return { min: 1, max: 9999 * scale, non_zero: true };
    case 'hash_salt':
      return { min: 1, max: 1_000_000_000, non_zero: true };
    case 'prime_seed':
      return { min: 2, max: 997, non_zero: true };
    default:
      return { min: -100 * scale, max: 100 * scale, non_zero: true };
    }
  })();
  if (['offset', 'scale', 'threshold', 'branch_seed', 'hash_salt', 'prime_seed'].includes(slot)) {
    return {
      ...range,
      min: Math.trunc(range.min * multiplier),
      max: Math.trunc(range.max * multiplier),
    };
  }
  return range;
}

function drawNonZeroInt(rng, min, max) {
  const safeMin = Math.trunc(min);
  const safeMax = Math.max(safeMin, Math.trunc(max));
  for (let attempt = 0; attempt < 32; attempt += 1) {
    const value = rng.int(safeMin, safeMax);
    if (value !== 0) return value;
  }
  if (safeMin > 0) return safeMin;
  if (safeMax < 0) return safeMax;
  return 1;
}

function hashNumber(value, modulo = INTERNAL_MOD) {
  return Number(BigInt(sha256(value)) % BigInt(Math.max(1, Math.trunc(modulo))));
}

function dynamicDefinition(id) {
  return DYNAMIC_PARAMETER_POOL.find((item) => item.id === id) || null;
}

function dynamicCandidates(operator, tier) {
  return (operator.dynamic_params || [])
    .map((id) => dynamicDefinition(id))
    .filter((item) => item && item.unlock_tier <= tier);
}

function operatorCandidates(tier) {
  return OPERATOR_POOL.filter((operator) => {
    if (operator.unlock_tier > tier) return false;
    if (!operator.requires_dynamic) return true;
    return dynamicCandidates(operator, tier).length > 0;
  });
}

function operatorFragmentWeightBonus(operator, fragmentProfile = null) {
  const profile = normalizeFragmentGenerationProfile(fragmentProfile);
  let bonus = 0;
  if ((operator.dynamic_params || []).length > 0 || operator.requires_dynamic) {
    bonus += Math.round(profile.dynamic_probability_bonus * 500);
  }
  if (String(operator.slot || '').includes('branch')) bonus += profile.branch_weight_bonus;
  if (String(operator.slot || '').includes('hash')) bonus += profile.hash_weight_bonus;
  if (operator.unlock_tier >= 6) bonus += Math.min(160, profile.selected_count * 12 + profile.score_sum * 3);
  return bonus;
}

function pickWeighted(items, rng, weightFn = null) {
  const total = items.reduce((sum, item) => sum + Math.max(1, weightFn ? weightFn(item) : item.score), 0);
  let cursor = rng.next() * total;
  for (const item of items) {
    cursor -= Math.max(1, weightFn ? weightFn(item) : item.score);
    if (cursor <= 0) return item;
  }
  return items[items.length - 1];
}

function shuffle(items, rng) {
  const output = [...items];
  for (let index = output.length - 1; index > 0; index -= 1) {
    const swapIndex = rng.int(0, index);
    [output[index], output[swapIndex]] = [output[swapIndex], output[index]];
  }
  return output;
}

function makeOperatorUnit({ index, operator, schedule, rng, fragmentProfile = null }) {
  const profile = normalizeFragmentGenerationProfile(fragmentProfile);
  const range = staticRangeForSlot(operator.slot, {
    N: schedule.grid_size,
    tier: schedule.growth_tier,
    fragmentProfile: profile,
  });
  const raw = drawNonZeroInt(rng, range.min, range.max);
  const dynamicOptions = dynamicCandidates(operator, schedule.growth_tier);
  const shouldUseDynamic =
    operator.requires_dynamic ||
    (dynamicOptions.length > 0 &&
      rng.next() < clamp(0.16 + schedule.growth_tier * 0.04 + profile.dynamic_probability_bonus, 0, 0.82));
  const dynamic = shouldUseDynamic && dynamicOptions.length ? rng.pick(dynamicOptions) : null;
  const staticScore = Math.ceil(Math.log2(Math.abs(raw) + 2)) * 8;
  const fragmentScore = operatorFragmentWeightBonus(operator, profile) + Math.round(profile.verifier_pressure_score / 10);
  const score = operator.score + staticScore + (dynamic ? dynamic.score : 0) + fragmentScore;
  return {
    id: `unit_${index}`,
    operator_id: operator.id,
    operator_score: operator.score,
    slot: operator.slot,
    static_param: {
      id: `p${index}`,
      raw,
      value: raw,
      range: [range.min, range.max],
      non_zero: true,
    },
    dynamic_param: dynamic
      ? {
          id: dynamic.id,
          range: dynamic.range,
          score: dynamic.score,
        }
      : null,
    fragment_score: fragmentScore,
    fragment_profile_hash: profile.profile_hash,
    score,
  };
}

function generateOperatorUnits({ schedule, stepNo, rng, fragmentProfile = null }) {
  const profile = normalizeFragmentGenerationProfile(fragmentProfile);
  const ratio = stepRatio(stepNo, schedule.step_count);
  const countRange = operatorCountRange({ tier: schedule.growth_tier, ratio });
  const adjustedCountRange = {
    min: countRange.min + profile.operator_count_bonus,
    max: countRange.max + profile.operator_count_bonus,
  };
  const targetCount = rng.int(adjustedCountRange.min, adjustedCountRange.max);
  const candidates = operatorCandidates(schedule.growth_tier);
  const units = [];
  for (let index = 1; index <= targetCount; index += 1) {
    const operator = pickWeighted(candidates, rng, (item) => item.score + operatorFragmentWeightBonus(item, profile));
    units.push(makeOperatorUnit({ index, operator, schedule, rng, fragmentProfile: profile }));
  }
  return {
    units: shuffle(units, rng),
    count_range: adjustedCountRange,
    base_count_range: countRange,
    target_count: targetCount,
  };
}

function varNode(name) {
  return { type: 'var', name };
}

function constNode(value) {
  return { type: 'const', value };
}

function opNode(unit, target) {
  return {
    type: 'op',
    unit,
    children: [target, constNode(unit.static_param.value)],
  };
}

function cloneNode(node) {
  if (!node.children) return { ...node };
  return {
    ...node,
    children: node.children.map(cloneNode),
  };
}

function collectPaths(node, current = [], paths = []) {
  paths.push(current);
  if (node.children) {
    node.children.forEach((child, index) => collectPaths(child, [...current, index], paths));
  }
  return paths;
}

function getNode(node, path) {
  let cursor = node;
  for (const index of path) {
    cursor = cursor.children[index];
  }
  return cursor;
}

function replaceNode(node, path, replacement) {
  if (path.length === 0) return replacement;
  const root = cloneNode(node);
  let cursor = root;
  for (let index = 0; index < path.length - 1; index += 1) {
    cursor = cursor.children[path[index]];
  }
  cursor.children[path[path.length - 1]] = replacement;
  return root;
}

function pathLabel(path) {
  return path.length ? path.join('.') : 'root';
}

function insertUnit(root, unit, rng) {
  const paths = collectPaths(root);
  const path = rng.pick(paths);
  const target = getNode(root, path);
  const nextRoot = replaceNode(root, path, opNode(unit, cloneNode(target)));
  return {
    root: nextRoot,
    insert_path: pathLabel(path),
    target_type: target.type,
  };
}

function buildExpressionTrees({ units, rng }) {
  const trees = {
    u: varNode('x'),
    v: varNode('y'),
  };
  const placedUnits = [];
  for (const original of units) {
    const unit = { ...original };
    const axis = rng.pick(['u', 'v']);
    const result = insertUnit(trees[axis], unit, rng);
    unit.axis = axis;
    unit.insert_path = result.insert_path;
    unit.target_type = result.target_type;
    trees[axis] = result.root;
    placedUnits.push(unit);
  }
  return { trees, placedUnits };
}

function safeScale(value) {
  const number = Math.trunc(Number(value) || 0);
  const sign = number < 0 ? -1 : 1;
  return sign * (1 + (Math.abs(number) % 64));
}

function safeDivisor(value) {
  return 1 + (Math.abs(Math.trunc(Number(value) || 0)) % 64);
}

function safeModulus(value, N) {
  return 2 + (Math.abs(Math.trunc(Number(value) || 0)) % Math.max(3, 2 * N));
}

function safeExponent(value) {
  return 1 + (Math.abs(Math.trunc(Number(value) || 0)) % 8);
}

function safeThreshold(value, N) {
  return Math.abs(Math.trunc(Number(value) || 0)) % Math.max(1, N);
}

function safeMask(value) {
  return Math.abs(Math.trunc(Number(value) || 0)) % 65536;
}

function safeShift(value) {
  return Math.abs(Math.trunc(Number(value) || 0)) % 8;
}

function safeIteration(value) {
  return 1 + (Math.abs(Math.trunc(Number(value) || 0)) % 16);
}

function safePhase(value) {
  return Math.abs(Math.trunc(Number(value) || 0)) % 360;
}

function toUint32(value) {
  return mod(value, 0x100000000);
}

function powMod(base, exponent, modulus = INTERNAL_MOD) {
  let result = 1n;
  let b = BigInt(mod(base, modulus));
  let e = BigInt(Math.max(0, Math.trunc(exponent)));
  const m = BigInt(modulus);
  while (e > 0n) {
    if (e & 1n) result = (result * b) % m;
    b = (b * b) % m;
    e >>= 1n;
  }
  return Number(result);
}

function isPrime(value) {
  const n = Math.max(2, Math.trunc(value));
  if (n === 2) return true;
  if (n % 2 === 0) return false;
  for (let factor = 3; factor * factor <= n; factor += 2) {
    if (n % factor === 0) return false;
  }
  return true;
}

function nextPrime(value) {
  let candidate = Math.max(2, Math.trunc(value));
  while (!isPrime(candidate)) candidate += 1;
  return candidate;
}

function resolveDynamicValue(id, context = {}, N = 20, step = {}) {
  if (!id) return 0;
  const value = (key, fallback = 0) => context[key] ?? fallback;
  const elapsed = Math.max(0, Math.trunc(Number(value('elapsed_seconds', 0))));
  switch (id) {
    case 'click_count':
      return mod(value('click_count', 0), 128);
    case 'user_click_count':
      return mod(value('user_click_count', value('click_count', 0)), 128);
    case 'global_click_count':
      return mod(value('global_click_count', value('click_count', 0)), 256);
    case 'last_x':
      return mod(value('last_x', 0), N);
    case 'last_y':
      return mod(value('last_y', 0), N);
    case 'last_u':
      return mod(value('last_u', 0), N);
    case 'last_v':
      return mod(value('last_v', 0), N);
    case 'last_dx':
      return clamp(Math.trunc(Number(value('last_dx', 0))), -N, N);
    case 'last_dy':
      return clamp(Math.trunc(Number(value('last_dy', 0))), -N, N);
    case 'last_distance':
      return mod(value('last_distance', 0), Math.max(1, 2 * N));
    case 'elapsed_seconds':
      return elapsed % 3600;
    case 'time_bucket':
      return Math.floor(elapsed / 30) % 256;
    case 'time_mod':
      return elapsed % Math.max(1, N);
    case 'history_sum_x':
    case 'history_sum_y':
    case 'history_sum_u':
    case 'history_sum_v':
    case 'history_path_length':
    case 'network_activity_bucket':
    case 'wave_bucket':
    case 'decay_value':
    case 'cooldown_value':
    case 'age_factor':
    case 'freshness_factor':
      return mod(value(id, 0), N);
    case 'history_unique_count':
    case 'history_repeat_count':
      return mod(value(id, 0), 1024);
    case 'block_height':
    case 'global_sample_count':
    case 'global_submit_count':
      return mod(value(id, 0), 1_000_000);
    case 'unique_sampler_count':
    case 'solver_count':
      return mod(value(id, 0), 100_000);
    case 'difficulty_epoch':
    case 'half_life_bucket':
    case 'cycle_index':
      return mod(value(id, 0), 1024);
    case 'cycle_phase':
      return mod(value(id, Math.floor(elapsed / 10)), 360);
    case 'pulse_state':
      return mod(value(id, Math.floor(elapsed / 15)), 2);
    case 'history_hash':
    case 'previous_step_hash':
    case 'previous_solution_hash':
    case 'previous_block_hash':
    case 'chain_seed':
    case 'hash_click_state':
    case 'hash_history_state':
    case 'hash_chain_state':
    case 'nonlinear_jump':
    case 'multi_state_hash':
      return hashNumber({
        id,
        context_value: value(id, null),
        previous_hash: step.previous_hash,
        seed: step.seed,
        click_count: value('click_count', 0),
        last_x: value('last_x', 0),
        last_y: value('last_y', 0),
        elapsed,
      }, N);
    default:
      return mod(value(id, 0), N);
  }
}

function applyOperator(operatorId, z, p, dynamic, vars, N, step) {
  const x = vars.x;
  const y = vars.y;
  const d = Number(dynamic || 0);
  const threshold = safeThreshold(p + d, N);
  switch (operatorId) {
    case 'add':
      return z + p;
    case 'sub':
      return z - p;
    case 'mul':
      return z * safeScale(p);
    case 'safe_div':
      return Math.floor(z / safeDivisor(p));
    case 'mod':
      return mod(z, safeModulus(p, N));
    case 'abs_shift':
      return Math.abs(z + p);
    case 'neg_shift':
      return -z + p;
    case 'min_with_param':
      return Math.min(z, threshold);
    case 'max_with_param':
      return Math.max(z, threshold);
    case 'mix_x':
      return z + safeScale(p) * x;
    case 'mix_y':
      return z + safeScale(p) * y;
    case 'mix_N':
      return z + safeScale(p) * N;
    case 'square':
      return powMod(z + p, 2);
    case 'cube':
      return powMod(z + p, 3);
    case 'pow_small':
      return powMod(z + 1, safeExponent(p));
    case 'sqrt_abs':
      return Math.floor(Math.sqrt(Math.abs(z + p)));
    case 'log_safe':
      return Math.floor(Math.log1p(Math.abs(z + p)) * 1000);
    case 'sin_phase':
      return Math.round(Math.sin(((z + safePhase(p) + d) * Math.PI) / 180) * 10000);
    case 'cos_phase':
      return Math.round(Math.cos(((z + safePhase(p) + d) * Math.PI) / 180) * 10000);
    case 'xor_mask':
      return toUint32(z) ^ (safeMask(p) ^ toUint32(d));
    case 'and_mask':
      return toUint32(z) & (safeMask(p) | toUint32(d));
    case 'or_mask':
      return toUint32(z) | (safeMask(p) ^ toUint32(d));
    case 'shift_left_safe':
      return toUint32(z) << safeShift(p);
    case 'shift_right_safe':
      return toUint32(z) >>> safeShift(p);
    case 'parity_gate':
      return mod(z + x + y + threshold + d, 2) === 0 ? z + p + d : z - p - d;
    case 'threshold_gate':
      return mod(x + y + d, N) > threshold ? z + p : z - p;
    case 'coord_sum':
      return z + safeScale(p) * (x + y);
    case 'coord_diff':
      return z + safeScale(p) * (x - y);
    case 'coord_product':
      return z + safeScale(p) * x * y;
    case 'square_x_mix':
      return z + safeScale(p) * x * x;
    case 'square_y_mix':
      return z + safeScale(p) * y * y;
    case 'sum_square_mix':
      return z + safeScale(p) * (x + y) * (x + y);
    case 'diff_square_mix':
      return z + safeScale(p) * (x - y) * (x - y);
    case 'cube_x_mix':
      return z + safeScale(p) * x * x * x;
    case 'cube_y_mix':
      return z + safeScale(p) * y * y * y;
    case 'xy_square_mix':
      return z + safeScale(p) * x * x * y * y;
    case 'distance_origin':
      return z + safeScale(p) * Math.floor(Math.sqrt(x * x + y * y));
    case 'distance_center': {
      const c = Math.floor(N / 2);
      return z + safeScale(p) * Math.floor(Math.sqrt((x - c) ** 2 + (y - c) ** 2));
    }
    case 'time_shift':
    case 'click_shift':
    case 'last_point_shift':
      return z + safeScale(p) * d;
    case 'hash_mix':
    case 'hash_jump':
    case 'chain_seed_mix':
    case 'history_hash_mix':
    case 'multi_state_hash':
    case 'chaotic_jump':
    case 'proof_state_mix':
    case 'epoch_seed_jump':
      return hashNumber({ operatorId, z, p, d, x, y, N, seed: step.seed }, INTERNAL_MOD);
    case 'previous_hash_gate':
      return mod(d + p, 3) === 0 ? z + hashNumber({ z, p, d }, N) : z - hashNumber({ d, p, z }, N);
    case 'history_sum_mix':
    case 'path_length_mix':
    case 'network_activity_mix':
    case 'solver_race_mix':
    case 'delayed_state_mix':
      return z + safeScale(p) * d;
    case 'piecewise_3way': {
      const branch = mod(z + d + p, 3);
      if (branch === 0) return z + p + d;
      if (branch === 1) return z * safeScale(p) + d;
      return z - p - d;
    }
    case 'piecewise_5way':
    case 'nested_piecewise': {
      const branch = mod(z + d + p, 5);
      return [z + p, z - p, z * safeScale(p), z + x * y + d, hashNumber({ z, p, d }, INTERNAL_MOD)][branch];
    }
    case 'cubic_gate': {
      const c = mod(x * x * x + y * y * y + d, N);
      return c > threshold ? z + safeScale(p) * c : z - safeScale(p) * c;
    }
    case 'prime_mod':
      return mod(z + d, nextPrime(Math.abs(p) + N));
    case 'next_prime_shift':
      return z + nextPrime(Math.abs(p) + d + 2);
    case 'matrix_2x2_mix':
      return z + safeScale(p) * x + (safeScale(p + d) + 1) * y;
    case 'pow_x_small':
      return z + safeScale(p + d) * powMod(x + 1, safeExponent(p), INTERNAL_MOD);
    case 'pow_y_small':
      return z + safeScale(p + d) * powMod(y + 1, safeExponent(p), INTERNAL_MOD);
    case 'poly2_mix':
      return z + safeScale(p) * x * x + safeScale(p + d) * x * y + safeScale(p - d) * y * y;
    case 'poly3_mix':
      return z + safeScale(p) * x * x * x + safeScale(p + d) * x * x * y + safeScale(p - d) * x * y * y + safeScale(p + 2 * d) * y * y * y;
    case 'quadratic_gate': {
      const q = mod(x * x + y * y + d, N);
      return q > threshold ? z + safeScale(p) * q : z - safeScale(p) * q;
    }
    case 'rotate_coord': {
      const phase = (safePhase(p) * Math.PI) / 180;
      return z + Math.round(x * Math.cos(phase) - y * Math.sin(phase)) + d;
    }
    case 'mirror_coord':
      return mod(d + p, 2) === 0 ? z + (N - 1 - x) : z + (N - 1 - y);
    case 'fold_coord':
      return z + Math.abs(x - threshold) + Math.abs(y - threshold) + d;
    case 'permutation_gate':
      return mod(d + p, 2) === 0 ? z + y * N + x : z + x * N + y;
    case 'state_machine_step':
      return mod(z + p + d, 4) === 0 ? z + x : mod(z + p + d, 4) === 1 ? z + y : mod(z + p + d, 4) === 2 ? z - x : z - y;
    case 'iterated_mix_safe': {
      let next = z;
      const count = safeIteration(p);
      for (let i = 0; i < count; i += 1) next = safeNumber(next + safeScale(p + i) * (d + i + 1));
      return next;
    }
    case 'chaos_logistic_safe': {
      const r = 3 + (Math.abs(safeScale(p)) % 1000) / 1000;
      const normalized = (mod(z + d, N) + 1) / (N + 1);
      return Math.floor(r * normalized * (1 - normalized) * INTERNAL_MOD);
    }
    case 'half_life_decay':
      return z + Math.floor((safeScale(p) * 1024) / (1 + d));
    case 'cooldown_gate':
      return d > threshold ? z + p : z - p;
    case 'cycle_phase_mix':
      return z + Math.round(Math.sin(((d + safePhase(p)) * Math.PI) / 180) * 10000);
    case 'wave_interference':
      return z + Math.round((Math.sin(((d + safePhase(p)) * Math.PI) / 180) + Math.cos(((x + y + safePhase(p)) * Math.PI) / 180)) * 10000);
    case 'pulse_gate':
      return mod(d, 2) === 0 ? z + p : z - p;
    case 'global_count_gate':
      return mod(d + p, 3) === 0 ? z + x * p : z + y * p;
    case 'memory_window_gate':
      return mod(d + p, 2) === 0 ? z + d + p : z - d - p;
    case 'adaptive_epoch_gate':
      return mod(d + p, 2) === 0 ? z + safeScale(p) * N : z - safeScale(p) * N;
    case 'fractal_fold': {
      let folded = z + d;
      const count = safeIteration(p);
      for (let i = 0; i < count; i += 1) folded = Math.abs(mod(folded + x - y + i, 2 * N) - N);
      return folded;
    }
    case 'bounded_recursive_tree': {
      let acc = z;
      const count = safeIteration(p);
      for (let i = 0; i < count; i += 1) acc = safeNumber(acc + hashNumber({ acc, d, p, i }, N) + x + y);
      return acc;
    }
    default:
      return z + p;
  }
}

function evaluateNode(node, vars, context, step) {
  if (node.type === 'var') {
    if (node.name === 'x') return vars.x;
    if (node.name === 'y') return vars.y;
    if (node.name === 'N') return vars.N;
    return 0;
  }
  if (node.type === 'const') return Number(node.value || 0);
  const z = evaluateNode(node.children[0], vars, context, step);
  const p = evaluateNode(node.children[1], vars, context, step);
  const dynamic = resolveDynamicValue(node.unit.dynamic_param?.id, context, vars.N, step);
  return safeNumber(applyOperator(node.unit.operator_id, z, p, dynamic, vars, vars.N, step));
}

function dynamicExpression(unit) {
  return unit.dynamic_param ? unit.dynamic_param.id : '0';
}

function expressionForOp(unit, z, p) {
  const d = dynamicExpression(unit);
  switch (unit.operator_id) {
    case 'add':
      return `(${z} + ${p})`;
    case 'sub':
      return `(${z} - ${p})`;
    case 'mul':
      return `(${z} * safe_scale(${p}))`;
    case 'safe_div':
      return `floor(${z} / (1 + abs(${p}) mod 64))`;
    case 'mod':
      return `(${z} mod safe_modulus(${p}, N))`;
    case 'abs_shift':
      return `abs(${z} + ${p})`;
    case 'neg_shift':
      return `(-${z} + ${p})`;
    case 'min_with_param':
      return `min(${z}, threshold(${p}, N))`;
    case 'max_with_param':
      return `max(${z}, threshold(${p}, N))`;
    case 'mix_x':
      return `(${z} + safe_scale(${p}) * x)`;
    case 'mix_y':
      return `(${z} + safe_scale(${p}) * y)`;
    case 'mix_N':
      return `(${z} + safe_scale(${p}) * N)`;
    case 'square':
      return `pow_mod(${z} + ${p}, 2)`;
    case 'cube':
      return `pow_mod(${z} + ${p}, 3)`;
    case 'pow_small':
      return `pow_mod(${z} + 1, exponent(${p}))`;
    case 'sqrt_abs':
      return `floor(sqrt(abs(${z} + ${p})))`;
    case 'log_safe':
      return `floor(log(1 + abs(${z} + ${p})) * 1000)`;
    case 'sin_phase':
      return `round(sin(${z} + phase(${p}) + ${d}) * 10000)`;
    case 'cos_phase':
      return `round(cos(${z} + phase(${p}) + ${d}) * 10000)`;
    case 'xor_mask':
      return `xor(${z}, mask(${p}) xor ${d})`;
    case 'and_mask':
      return `and(${z}, mask(${p}) or ${d})`;
    case 'or_mask':
      return `or(${z}, mask(${p}) xor ${d})`;
    case 'shift_left_safe':
      return `shift_left(${z}, shift(${p}))`;
    case 'shift_right_safe':
      return `shift_right(${z}, shift(${p}))`;
    case 'parity_gate':
      return `if((${z} + x + y + threshold(${p}, N) + ${d}) mod 2 == 0, ${z} + ${p} + ${d}, ${z} - ${p} - ${d})`;
    case 'threshold_gate':
      return `if((x + y + ${d}) mod N > threshold(${p}, N), ${z} + ${p}, ${z} - ${p})`;
    case 'coord_sum':
      return `(${z} + safe_scale(${p}) * (x + y))`;
    case 'coord_diff':
      return `(${z} + safe_scale(${p}) * (x - y))`;
    case 'coord_product':
      return `(${z} + safe_scale(${p}) * x * y)`;
    case 'square_x_mix':
      return `(${z} + safe_scale(${p}) * x^2)`;
    case 'square_y_mix':
      return `(${z} + safe_scale(${p}) * y^2)`;
    case 'sum_square_mix':
      return `(${z} + safe_scale(${p}) * (x + y)^2)`;
    case 'diff_square_mix':
      return `(${z} + safe_scale(${p}) * (x - y)^2)`;
    case 'cube_x_mix':
      return `(${z} + safe_scale(${p}) * x^3)`;
    case 'cube_y_mix':
      return `(${z} + safe_scale(${p}) * y^3)`;
    case 'xy_square_mix':
      return `(${z} + safe_scale(${p}) * x^2 * y^2)`;
    case 'distance_origin':
      return `(${z} + safe_scale(${p}) * distance(x, y, 0, 0))`;
    case 'distance_center':
      return `(${z} + safe_scale(${p}) * distance(x, y, N/2, N/2))`;
    case 'time_shift':
    case 'click_shift':
    case 'last_point_shift':
    case 'history_sum_mix':
    case 'path_length_mix':
    case 'network_activity_mix':
    case 'solver_race_mix':
    case 'delayed_state_mix':
      return `(${z} + safe_scale(${p}) * ${d})`;
    case 'prime_mod':
      return `((${z} + ${d}) mod next_prime(abs(${p}) + N))`;
    case 'next_prime_shift':
      return `(${z} + next_prime(abs(${p}) + ${d} + 2))`;
    case 'matrix_2x2_mix':
      return `(${z} + safe_scale(${p}) * x + safe_scale(${p} + ${d}) * y)`;
    case 'pow_x_small':
      return `(${z} + safe_scale(${p} + ${d}) * pow_mod(x + 1, exponent(${p})))`;
    case 'pow_y_small':
      return `(${z} + safe_scale(${p} + ${d}) * pow_mod(y + 1, exponent(${p})))`;
    case 'poly2_mix':
      return `(${z} + safe_scale(${p})*x^2 + safe_scale(${p}+${d})*x*y + safe_scale(${p}-${d})*y^2)`;
    case 'poly3_mix':
      return `(${z} + safe_scale(${p})*x^3 + safe_scale(${p}+${d})*x^2*y + safe_scale(${p}-${d})*x*y^2 + safe_scale(${p}+2*${d})*y^3)`;
    case 'quadratic_gate':
      return `quadratic_gate(${z}, ${p}, ${d}, x, y, N)`;
    case 'rotate_coord':
      return `(${z} + rotate_x(x, y, phase(${p})) + ${d})`;
    case 'mirror_coord':
      return `if((${d} + ${p}) mod 2 == 0, ${z} + (N - 1 - x), ${z} + (N - 1 - y))`;
    case 'fold_coord':
      return `(${z} + abs(x - threshold(${p}, N)) + abs(y - threshold(${p}, N)) + ${d})`;
    case 'piecewise_3way':
    case 'previous_hash_gate':
    case 'permutation_gate':
    case 'state_machine_step':
    case 'cooldown_gate':
    case 'pulse_gate':
    case 'global_count_gate':
    case 'memory_window_gate':
    case 'adaptive_epoch_gate':
    case 'nested_piecewise':
    case 'cubic_gate':
      return `${unit.operator_id}(${z}, ${p}, ${d}, x, y, N)`;
    case 'half_life_decay':
    case 'cycle_phase_mix':
    case 'wave_interference':
    case 'iterated_mix_safe':
    case 'chaos_logistic_safe':
    case 'fractal_fold':
    case 'bounded_recursive_tree':
      return `${unit.operator_id}(${z}, ${p}, ${d}, x, y, N)`;
    default:
      return `hash_op(${unit.operator_id}, ${z}, ${p}, ${d}, x, y, N)`;
  }
}

function nodeToExpression(node) {
  if (node.type === 'var') return node.name;
  if (node.type === 'const') return String(node.value);
  const z = nodeToExpression(node.children[0]);
  const p = nodeToExpression(node.children[1]);
  return expressionForOp(node.unit, z, p);
}

function expressionForAxis(step, axis) {
  return `normalize(${nodeToExpression(step.trees[axis])}, N)`;
}

function evaluateAxis(step, axis, x, y, context = {}) {
  const N = step.grid_size;
  const value = evaluateNode(step.trees[axis], { x: mod(x, N), y: mod(y, N), N }, context, step);
  return mod(value, N);
}

function evaluateBase1Step(step, x, y, context = {}) {
  return {
    u: evaluateAxis(step, 'u', x, y, context),
    v: evaluateAxis(step, 'v', x, y, context),
  };
}

const HISTORY_DYNAMIC_IDS = new Set([
  'history_hash',
  'history_sum_x',
  'history_sum_y',
  'history_sum_u',
  'history_sum_v',
  'history_unique_count',
  'history_repeat_count',
  'history_path_length',
  'hash_history_state',
]);

const CHAIN_DYNAMIC_IDS = new Set([
  'previous_step_hash',
  'previous_solution_hash',
  'previous_block_hash',
  'block_height',
  'chain_seed',
  'difficulty_epoch',
  'hash_chain_state',
]);

const JUMP_DYNAMIC_IDS = new Set([
  'hash_click_state',
  'nonlinear_jump',
  'multi_state_hash',
]);

const TOP_DYNAMIC_IDS = new Set([
  'half_life_bucket',
  'decay_value',
  'cooldown_value',
  'age_factor',
  'freshness_factor',
  'cycle_phase',
  'cycle_index',
  'pulse_state',
  'wave_bucket',
  'global_sample_count',
  'global_submit_count',
  'unique_sampler_count',
  'solver_count',
  'network_activity_bucket',
]);

const HASH_OPERATOR_IDS = new Set([
  'hash_mix',
  'hash_jump',
  'chain_seed_mix',
  'history_hash_mix',
  'multi_state_hash',
  'chaotic_jump',
  'proof_state_mix',
  'epoch_seed_jump',
]);

const STATE_OPERATOR_IDS = new Set([
  'previous_hash_gate',
  'state_machine_step',
  'adaptive_epoch_gate',
  'delayed_state_mix',
  'memory_window_gate',
  'solver_race_mix',
]);

const POLYNOMIAL_OPERATOR_IDS = new Set([
  'square_x_mix',
  'square_y_mix',
  'sum_square_mix',
  'diff_square_mix',
  'cube_x_mix',
  'cube_y_mix',
  'xy_square_mix',
  'pow_x_small',
  'pow_y_small',
  'poly2_mix',
  'poly3_mix',
  'quadratic_gate',
  'cubic_gate',
]);

const BASIC_OPERATOR_IDS = new Set([
  'add',
  'sub',
  'mul',
  'safe_div',
  'mod',
  'abs_shift',
  'neg_shift',
  'min_with_param',
  'max_with_param',
]);

const COORDINATE_OPERATOR_IDS = new Set([
  'mix_x',
  'mix_y',
  'mix_N',
  'coord_sum',
  'coord_diff',
  'coord_product',
  'distance_origin',
  'distance_center',
  'matrix_2x2_mix',
  'rotate_coord',
  'mirror_coord',
  'fold_coord',
  'permutation_gate',
]);

const NONLINEAR_OPERATOR_IDS = new Set([
  'square',
  'cube',
  'pow_small',
  'sqrt_abs',
  'log_safe',
  'sin_phase',
  'cos_phase',
  'xor_mask',
  'and_mask',
  'or_mask',
  'shift_left_safe',
  'shift_right_safe',
  'prime_mod',
  'next_prime_shift',
  'iterated_mix_safe',
  'chaos_logistic_safe',
  'fractal_fold',
  'bounded_recursive_tree',
]);

const CONDITION_OPERATOR_IDS = new Set([
  'parity_gate',
  'threshold_gate',
  'piecewise_3way',
  'piecewise_5way',
  'cooldown_gate',
  'pulse_gate',
  'global_count_gate',
  'nested_piecewise',
]);

const TOP_OPERATOR_IDS = new Set([
  'half_life_decay',
  'cycle_phase_mix',
  'wave_interference',
  'network_activity_mix',
  'multi_state_hash',
  'chaotic_jump',
  'proof_state_mix',
  'epoch_seed_jump',
  'bounded_recursive_tree',
]);

const SLOT_SCORE = {
  offset: 1000,
  scale: 2500,
  divisor: 2200,
  modulus: 3500,
  exponent: 8000,
  threshold: 4000,
  mask: 5500,
  shift: 4500,
  iteration: 9000,
  phase: 4500,
  branch_seed: 10000,
  hash_salt: 14000,
  prime_seed: 7000,
};

function dynamicDifficultyScore(id) {
  if (!id) return 0;
  if (TOP_DYNAMIC_IDS.has(id)) return 80000;
  if (JUMP_DYNAMIC_IDS.has(id)) return 60000;
  if (CHAIN_DYNAMIC_IDS.has(id)) return 40000;
  if (HISTORY_DYNAMIC_IDS.has(id)) return 30000;
  return 10000;
}

function operatorDifficultyScore(id) {
  if (TOP_OPERATOR_IDS.has(id)) return 180000;
  if (HASH_OPERATOR_IDS.has(id)) return 90000;
  if (STATE_OPERATOR_IDS.has(id)) return 65000;
  if (CONDITION_OPERATOR_IDS.has(id)) return 30000;
  if (POLYNOMIAL_OPERATOR_IDS.has(id)) return 22000;
  if (NONLINEAR_OPERATOR_IDS.has(id)) return 14000;
  if (COORDINATE_OPERATOR_IDS.has(id)) return 9000;
  if (BASIC_OPERATOR_IDS.has(id)) return 4000;
  return 6000;
}

function parameterSlotScore(slot) {
  return SLOT_SCORE[slot] || 1500;
}

function parameterScoreForUnit(unit) {
  const [min, max] = unit.static_param.range;
  const rangeSize = Math.max(1, Math.abs(max - min) + 1);
  const rawAbs = Math.abs(unit.static_param.value);
  const rangeScore = Math.ceil(Math.log10(rangeSize + 1) * 4000);
  const valueScore = Math.ceil(Math.log2(rawAbs + 2) * 400);
  const slotScore = parameterSlotScore(unit.slot);
  return {
    range_score: rangeScore,
    value_score: valueScore,
    slot_score: slotScore,
    total: rangeScore + valueScore + slotScore,
  };
}

function positionDifficultyScore(unit) {
  if (unit.insert_path === 'root') return 500;
  const depth = unit.insert_path.split('.').length;
  return 1000 + depth * 2000;
}

function treeStats(node, depth = 0) {
  if (!node.children || node.children.length === 0) {
    return { nodes: 1, max_depth: depth };
  }
  return node.children.reduce(
    (acc, child) => {
      const childStats = treeStats(child, depth + 1);
      return {
        nodes: acc.nodes + childStats.nodes,
        max_depth: Math.max(acc.max_depth, childStats.max_depth),
      };
    },
    { nodes: 1, max_depth: depth },
  );
}

function scoreStep(step) {
  const operatorCount = step.operator_units.length;
  const parameterDetails = step.operator_units.map(parameterScoreForUnit);
  const parameterValueScore = parameterDetails.reduce((sum, item) => sum + item.value_score, 0);
  const parameterRangeScore = parameterDetails.reduce((sum, item) => sum + item.range_score, 0);
  const parameterSlotScoreTotal = parameterDetails.reduce((sum, item) => sum + item.slot_score, 0);
  const parameterQuantityScore = operatorCount * 2000 + operatorCount * operatorCount * 100;
  const dynamicParameterScore = step.operator_units.reduce((sum, unit) => sum + dynamicDifficultyScore(unit.dynamic_param?.id), 0);
  const parameterScore =
    parameterValueScore +
    parameterRangeScore +
    parameterSlotScoreTotal +
    parameterQuantityScore +
    dynamicParameterScore;

  const operatorBaseScore = step.operator_units.reduce((sum, unit) => sum + operatorDifficultyScore(unit.operator_id), 0);
  const operatorQuantityScore = operatorCount * 5000 + operatorCount * operatorCount * 250;
  const operatorPositionScore = step.operator_units.reduce((sum, unit) => sum + positionDifficultyScore(unit), 0);
  const uTree = treeStats(step.trees.u);
  const vTree = treeStats(step.trees.v);
  const operatorTreeScore = (uTree.nodes + vTree.nodes) * 250 + (uTree.max_depth + vTree.max_depth) * 2500;
  const operatorScore = operatorBaseScore + operatorQuantityScore + operatorPositionScore + operatorTreeScore;

  const gridScore = step.grid_size * step.grid_size * 100;
  const layerBaseScore = step.layer_no * (1 + step.growth_tier * step.growth_tier) * 100;
  const stepProgressScore = Math.round(layerBaseScore * stepRatio(step.step_no, step.step_count) * 0.05);
  const layerScore = layerBaseScore + stepProgressScore;
  const finalScore = gridScore + layerScore + parameterScore + operatorScore;

  return {
    grid_score: gridScore,
    layer_score: layerScore,
    parameter_score: parameterScore,
    operator_score: operatorScore,
    final_score: finalScore,

    parameter_value_score: parameterValueScore,
    parameter_range_score: parameterRangeScore,
    parameter_slot_score: parameterSlotScoreTotal,
    parameter_quantity_score: parameterQuantityScore,
    dynamic_parameter_score: dynamicParameterScore,

    operator_base_score: operatorBaseScore,
    operator_quantity_score: operatorQuantityScore,
    operator_position_score: operatorPositionScore,
    operator_tree_score: operatorTreeScore,

    layer_base_score: layerBaseScore,
    step_progress_score: stepProgressScore,

    // Compatibility fields for older audit scripts.
    operator_unit_score: operatorScore,
    dynamic_complexity_score: dynamicParameterScore,
    advanced_operator_score: operatorBaseScore,
    layer_growth_score: layerScore,
    insertion_position_score: operatorPositionScore,
    tree_structure_score: operatorTreeScore,
    base_score: finalScore,
    layer_multiplier: 1,
    step_multiplier: 1,
  };
}

function generateBase1Step({
  layerNo = 1,
  stepNo = 1,
  previousHash = 'genesis',
  seasonSeed = 'mazex-expression-tree-season',
  seedSource = null,
  fragmentProfile = null,
  config = DEFAULT_BASE1_CONFIG,
} = {}) {
  const schedule = layerSchedule(layerNo, config);
  const safeStep = clamp(Math.trunc(Number(stepNo || 1)), 1, schedule.step_count);
  const range = difficultyRange({ layerNo: schedule.layer_no, stepNo: safeStep, schedule });
  const finalizedSeedSource = seedSource
    ? assertSeedSourceForStep(seedSource, {
        layerNo: schedule.layer_no,
        stepNo: safeStep,
        algorithm: BASE1_ALGORITHM_VERSION,
      })
    : null;
  const effectivePreviousHash = finalizedSeedSource?.source_hash || previousHash;
  const seed =
    finalizedSeedSource?.step_seed ||
    sha256({
      algorithm: BASE1_ALGORITHM_VERSION,
      layer_no: schedule.layer_no,
      step_no: safeStep,
      previous_hash: previousHash,
      season_seed: seasonSeed,
    });
  const rng = new Rng(seed);
  const normalizedFragmentProfile = normalizeFragmentGenerationProfile(fragmentProfile);
  const generated = generateOperatorUnits({
    schedule,
    stepNo: safeStep,
    rng,
    fragmentProfile: normalizedFragmentProfile,
  });
  const { trees, placedUnits } = buildExpressionTrees({ units: generated.units, rng });
  const dynamicParameters = placedUnits
    .filter((unit) => unit.dynamic_param)
    .map((unit) => unit.dynamic_param)
    .filter((item, index, list) => list.findIndex((other) => other.id === item.id) === index);

  const step = {
    algorithm: BASE1_ALGORITHM_VERSION,
    layer_no: schedule.layer_no,
    step_no: safeStep,
    step_count: schedule.step_count,
    growth_tier: schedule.growth_tier,
    growth_units: schedule.growth_units,
    grid_size: schedule.grid_size,
    previous_hash: effectivePreviousHash,
    randomness_mode: finalizedSeedSource ? 'chain_seed' : 'fallback_previous_hash',
    seed_source: finalizedSeedSource,
    seed_source_hash: finalizedSeedSource?.source_hash || null,
    seed,
    fragment_generation_profile: normalizedFragmentProfile.profile_hash ? normalizedFragmentProfile : null,
    fragment_generation_profile_hash: normalizedFragmentProfile.profile_hash,
    difficulty_range: range,
    operator_count_range: generated.count_range,
    target_operator_count: generated.target_count,
    operator_units: placedUnits,
    static_parameters: placedUnits.map((unit) => ({
      id: unit.static_param.id,
      value: unit.static_param.value,
      range: unit.static_param.range,
      operator_id: unit.operator_id,
      unit_id: unit.id,
    })),
    dynamic_parameters: dynamicParameters,
    operations: {
      u: placedUnits.filter((unit) => unit.axis === 'u'),
      v: placedUnits.filter((unit) => unit.axis === 'v'),
    },
    counts: {
      operator_unit_count: placedUnits.length,
      static_parameter_count: placedUnits.length,
      dynamic_parameter_count: dynamicParameters.length,
    },
    trees,
  };
  const expressions = {
    u: expressionForAxis(step, 'u'),
    v: expressionForAxis(step, 'v'),
  };
  const scores = scoreStep(step);
  return {
    ...step,
    expressions,
    scores,
    formula_hash: sha256({
      algorithm: BASE1_ALGORITHM_VERSION,
      layer_no: schedule.layer_no,
      step_no: safeStep,
      seed,
      expressions,
      scores,
      operator_units: placedUnits,
      fragment_generation_profile_hash: normalizedFragmentProfile.profile_hash,
    }),
  };
}

module.exports = {
  BASE1_ALGORITHM_VERSION,
  DEFAULT_BASE1_CONFIG,
  DYNAMIC_PARAMETER_POOL,
  OPERATOR_POOL,
  evaluateBase1Step,
  expressionForAxis,
  generateBase1Step,
  growthTier,
  layerSchedule,
  normalizeFragmentGenerationProfile,
  triangular,
};
