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

function findGridRule(layerNo, model) {
  const rules = model.grid_schedule || [];
  return (
    rules.find((rule) => {
      const max = rule.layer_max === null || rule.layer_max === undefined ? Infinity : Number(rule.layer_max);
      return layerNo >= Number(rule.layer_min) && layerNo <= max;
    }) || rules[rules.length - 1]
  );
}

function gridSizeForLayer(layerNo, model) {
  const rule = findGridRule(Number(layerNo || 1), model);
  if (!rule) return 40;
  let size = Number(rule.grid_size || 40);
  if (rule.layer_max === null && Number(rule.increase_every_layers) > 0) {
    const steps = Math.floor((Number(layerNo || 1) - Number(rule.layer_min)) / Number(rule.increase_every_layers));
    size += Math.max(0, steps) * Number(rule.increase_by || 0);
  }
  if (Number.isFinite(Number(rule.grid_size_max))) size = Math.min(size, Number(rule.grid_size_max));
  return Math.max(2, Math.trunc(size));
}

function baseScoreForTarget(targetDifficulty, model) {
  const table = model.target_difficulty_to_base_score || {};
  return Number(table[String(targetDifficulty)] || table['1'] || 80);
}

function scoreForStructure(structureId, model) {
  return Number(model.structure_base_score?.[structureId] || 0);
}

function unique(values) {
  return Array.from(new Set(values.filter(Boolean)));
}

const DIMENSION_KEYS = [
  'layer_growth',
  'step_progress',
  'input_space',
  'algebraic_complexity',
  'discrete_logic',
  'number_theory',
  'geometry_projection',
  'pseudo_3d_projection',
  'parameter_difficulty',
  'composition_depth',
  'branching_piecewise',
  'state_dependency',
  'time_dependency',
  'verification_strength',
  'answer_budget_pressure',
  'semantic_abstraction',
];

const DEFAULT_DIMENSION_WEIGHTS = {
  layer_growth: 10,
  step_progress: 6,
  input_space: 6,
  algebraic_complexity: 8,
  discrete_logic: 9,
  number_theory: 10,
  geometry_projection: 7,
  pseudo_3d_projection: 12,
  parameter_difficulty: 10,
  composition_depth: 16,
  branching_piecewise: 12,
  state_dependency: 18,
  time_dependency: 20,
  verification_strength: 14,
  answer_budget_pressure: 15,
  semantic_abstraction: 11,
};

const STRUCTURE_DIMENSIONS = {
  S01_IDENTITY_SHIFT: {
    algebraic_complexity: 1,
    parameter_difficulty: 1,
  },
  S02_LINEAR_AFFINE: {
    algebraic_complexity: 3,
    geometry_projection: 2,
    parameter_difficulty: 3,
  },
  S03_GEOMETRIC_TRANSFORM: {
    algebraic_complexity: 3,
    geometry_projection: 5,
    parameter_difficulty: 3,
  },
  S03_MATRIX_TRANSFORM: {
    algebraic_complexity: 3,
    geometry_projection: 5,
    parameter_difficulty: 3,
  },
  S04_POLYNOMIAL: {
    algebraic_complexity: 6,
    geometry_projection: 2,
    parameter_difficulty: 5,
  },
  S04_POLYNOMIAL_SURFACE: {
    algebraic_complexity: 6,
    pseudo_3d_projection: 4,
    parameter_difficulty: 5,
  },
  S05_MOD_RESIDUE: {
    discrete_logic: 4,
    number_theory: 4,
    parameter_difficulty: 3,
  },
  S06_PIECEWISE_GATE: {
    discrete_logic: 6,
    branching_piecewise: 7,
    parameter_difficulty: 5,
    semantic_abstraction: 2,
  },
  S07_DISTANCE_METRIC: {
    algebraic_complexity: 4,
    geometry_projection: 6,
    parameter_difficulty: 4,
  },
  S08_BUCKET_FLOOR: {
    discrete_logic: 6,
    geometry_projection: 4,
    branching_piecewise: 4,
    parameter_difficulty: 4,
  },
  S09_GRAPH_ADJACENCY: {
    discrete_logic: 7,
    geometry_projection: 4,
    semantic_abstraction: 4,
  },
  S10_PERIODIC_WAVE: {
    algebraic_complexity: 5,
    discrete_logic: 4,
    time_dependency: 3,
    parameter_difficulty: 4,
  },
  S11_RECURSION_SEQUENCE: {
    algebraic_complexity: 5,
    state_dependency: 5,
    time_dependency: 4,
    semantic_abstraction: 3,
  },
  S12_PROBABILITY_FIELD: {
    algebraic_complexity: 5,
    parameter_difficulty: 6,
    semantic_abstraction: 4,
  },
  S13_STATE_MACHINE: {
    discrete_logic: 7,
    state_dependency: 8,
    branching_piecewise: 6,
  },
  S14_GAME_RULE: {
    discrete_logic: 8,
    branching_piecewise: 6,
    semantic_abstraction: 6,
  },
  S15_ENCODING_PERMUTATION: {
    discrete_logic: 8,
    number_theory: 5,
    parameter_difficulty: 6,
  },
  S16_SIGNAL_FILTER: {
    algebraic_complexity: 6,
    time_dependency: 6,
    parameter_difficulty: 6,
  },
  S17_IMAGE_KERNEL: {
    geometry_projection: 7,
    discrete_logic: 5,
    pseudo_3d_projection: 3,
  },
  S18_LANGUAGE_SYMBOL: {
    discrete_logic: 5,
    semantic_abstraction: 9,
    branching_piecewise: 5,
  },
  S19_CHEMICAL_BALANCE: {
    algebraic_complexity: 6,
    parameter_difficulty: 7,
    semantic_abstraction: 5,
  },
  S20_BIOLOGICAL_GROWTH: {
    algebraic_complexity: 6,
    state_dependency: 5,
    time_dependency: 5,
  },
  S21_ECONOMIC_DYNAMIC: {
    branching_piecewise: 7,
    state_dependency: 6,
    time_dependency: 4,
    semantic_abstraction: 5,
  },
  S22_CONTROL_SYSTEM: {
    algebraic_complexity: 7,
    state_dependency: 7,
    time_dependency: 6,
  },
  S23_OPTIMIZATION_LANDSCAPE: {
    algebraic_complexity: 8,
    pseudo_3d_projection: 5,
    parameter_difficulty: 7,
  },
  S24_STOCHASTIC_PROCESS: {
    algebraic_complexity: 7,
    state_dependency: 6,
    time_dependency: 7,
    parameter_difficulty: 7,
  },
  S25_MULTI_AGENT_GAME: {
    discrete_logic: 8,
    state_dependency: 7,
    semantic_abstraction: 8,
  },
  S26_SPECIAL_CONSTANT: {
    number_theory: 5,
    algebraic_complexity: 7,
    parameter_difficulty: 7,
  },
  S27_PRIME_ARITHMETIC: {
    number_theory: 10,
    discrete_logic: 7,
    parameter_difficulty: 8,
  },
  S28_SPECTRAL_TRANSFORM: {
    algebraic_complexity: 8,
    time_dependency: 6,
    pseudo_3d_projection: 4,
  },
  S29_GROUP_SYMMETRY: {
    algebraic_complexity: 8,
    geometry_projection: 8,
    semantic_abstraction: 6,
  },
  S30_COMPLEX_ANALYTIC: {
    algebraic_complexity: 9,
    geometry_projection: 7,
    pseudo_3d_projection: 6,
  },
  S31_TENSOR_GEOMETRY: {
    algebraic_complexity: 9,
    geometry_projection: 9,
    pseudo_3d_projection: 8,
  },
  S32_VARIATIONAL_HAMILTONIAN: {
    algebraic_complexity: 10,
    time_dependency: 8,
    state_dependency: 6,
    semantic_abstraction: 7,
  },
  S33_FIELD_EQUATION: {
    algebraic_complexity: 10,
    pseudo_3d_projection: 8,
    time_dependency: 7,
  },
  S34_QUANTUM_OPERATOR: {
    algebraic_complexity: 10,
    discrete_logic: 8,
    semantic_abstraction: 9,
  },
  S35_NAVIER_STOKES: {
    algebraic_complexity: 10,
    pseudo_3d_projection: 9,
    state_dependency: 8,
    time_dependency: 9,
  },
  S36_STATISTICAL_MECHANICS: {
    algebraic_complexity: 9,
    state_dependency: 8,
    time_dependency: 8,
    parameter_difficulty: 8,
  },
  S37_GAUGE_LATTICE_FIELD: {
    algebraic_complexity: 10,
    discrete_logic: 9,
    number_theory: 8,
    pseudo_3d_projection: 9,
    semantic_abstraction: 10,
  },
  S09_LOOKUP_PERMUTATION: {
    discrete_logic: 8,
    parameter_difficulty: 7,
    semantic_abstraction: 4,
  },
  S11_DECAY_TIME: {
    algebraic_complexity: 5,
    time_dependency: 8,
    parameter_difficulty: 6,
  },
  S12_GRAPH_RULE: {
    discrete_logic: 8,
    geometry_projection: 6,
    semantic_abstraction: 5,
  },
  S13_BOARD_RULE: {
    discrete_logic: 8,
    branching_piecewise: 7,
    semantic_abstraction: 6,
  },
  S14_ENCODING_HASH: {
    discrete_logic: 8,
    number_theory: 5,
    parameter_difficulty: 6,
  },
  S15_PROBABILITY_SEEDED: {
    discrete_logic: 7,
    parameter_difficulty: 8,
    semantic_abstraction: 5,
  },
  S16_STATE_MEMORY: {
    state_dependency: 8,
    discrete_logic: 6,
    parameter_difficulty: 6,
  },
  S17_FSM_DYNAMIC: {
    state_dependency: 9,
    branching_piecewise: 8,
    discrete_logic: 8,
  },
  S18_RECURRENCE_SEQUENCE: {
    time_dependency: 7,
    state_dependency: 6,
    algebraic_complexity: 6,
  },
  S19_CONVOLUTION_LOCAL: {
    geometry_projection: 8,
    discrete_logic: 6,
    parameter_difficulty: 6,
  },
  S20_OPTIMIZATION_OBJECTIVE: {
    branching_piecewise: 8,
    geometry_projection: 6,
    semantic_abstraction: 6,
  },
  S21_LANGUAGE_SYMBOL: {
    semantic_abstraction: 9,
    discrete_logic: 7,
    parameter_difficulty: 6,
  },
  S22_BIO_REACTION_GROWTH: {
    time_dependency: 7,
    state_dependency: 5,
    algebraic_complexity: 6,
  },
  S23_ECON_GAME_SYSTEM: {
    branching_piecewise: 8,
    discrete_logic: 6,
    semantic_abstraction: 6,
  },
  S24_COMPOSITE_BOSS: {
    composition_depth: 10,
    branching_piecewise: 8,
    semantic_abstraction: 7,
  },
  S25_CHAIN_STATE_DEPENDENT: {
    state_dependency: 9,
    number_theory: 6,
    semantic_abstraction: 7,
  },
  S26_SPECIAL_CONSTANT_FIELD: {
    number_theory: 6,
    algebraic_complexity: 7,
    parameter_difficulty: 7,
  },
  S35_FLUID_NAVIER_STOKES: {
    algebraic_complexity: 10,
    pseudo_3d_projection: 9,
    state_dependency: 8,
    time_dependency: 9,
  },
};

const SOURCE_DIMENSIONS = {
  mathematics: { algebraic_complexity: 1, number_theory: 1 },
  physics: { geometry_projection: 1, time_dependency: 1, semantic_abstraction: 1 },
  geography_navigation_maps: { geometry_projection: 2, pseudo_3d_projection: 1 },
  geo_navigation_maps: { geometry_projection: 2, pseudo_3d_projection: 1 },
  image_vision: { geometry_projection: 2, discrete_logic: 1 },
  information_encoding: { discrete_logic: 2, number_theory: 1 },
  music_sound_signal: { discrete_logic: 1, time_dependency: 2 },
  board_games: { discrete_logic: 2, branching_piecewise: 2, semantic_abstraction: 1 },
  economics_game_systems: { branching_piecewise: 2, state_dependency: 1, semantic_abstraction: 1 },
  economics_game_theory: { branching_piecewise: 2, state_dependency: 1, semantic_abstraction: 1 },
  engineering_mechanics_circuits: { algebraic_complexity: 1, state_dependency: 1 },
  state_time: { state_dependency: 2, time_dependency: 2 },
  state_memory_dynamics: { state_dependency: 2, time_dependency: 2 },
  state_memory_dynamic: { state_dependency: 2, time_dependency: 2 },
  graph_networks: { discrete_logic: 2, geometry_projection: 2, semantic_abstraction: 1 },
  graph_structures: { discrete_logic: 2, geometry_projection: 2, semantic_abstraction: 1 },
  game_rule_systems: { discrete_logic: 2, branching_piecewise: 2, semantic_abstraction: 1 },
  probability_statistics: { parameter_difficulty: 2, semantic_abstraction: 1 },
  computer_science: { discrete_logic: 2, state_dependency: 2 },
  language_symbols: { semantic_abstraction: 3, discrete_logic: 1 },
  biology: { time_dependency: 2, state_dependency: 1 },
  chemistry: { algebraic_complexity: 2, semantic_abstraction: 1 },
  topology_spatial_relations: { geometry_projection: 2, semantic_abstraction: 2 },
  composite_systems: { composition_depth: 3, branching_piecewise: 1 },
  composite_structures: { composition_depth: 3, branching_piecewise: 1 },
};

function sourceDimensionKey(asset = {}) {
  const text = `${asset.source_category || ''} ${asset.family_id || ''} ${asset.source_file || ''}`
    .normalize('NFKD')
    .replace(/[^\x00-\x7F]/g, '')
    .toLowerCase();
  if (text.includes('math')) return 'mathematics';
  if (text.includes('phys')) return 'physics';
  if (text.includes('chem')) return 'chemistry';
  if (text.includes('bio')) return 'biology';
  if (text.includes('computer') || text.includes('.cs.') || text.includes('algorithm')) return 'computer_science';
  if (text.includes('graph') || text.includes('network')) return 'graph_networks';
  if (text.includes('game') || text.includes('board') || text.includes('chess') || text.includes('go')) return 'board_games';
  if (text.includes('encoding') || text.includes('cipher') || text.includes('hash') || text.includes('info')) return 'information_encoding';
  if (text.includes('language') || text.includes('symbol')) return 'language_symbols';
  if (text.includes('music') || text.includes('sound') || text.includes('signal')) return 'music_sound_signal';
  if (text.includes('vision') || text.includes('image')) return 'image_vision';
  if (text.includes('map') || text.includes('geo') || text.includes('navigation')) return 'geography_navigation_maps';
  if (text.includes('engineering') || text.includes('mechanics') || text.includes('circuit')) return 'engineering_mechanics_circuits';
  if (text.includes('econ') || text.includes('market') || text.includes('payoff')) return 'economics_game_systems';
  if (text.includes('topology') || text.includes('spatial')) return 'topology_spatial_relations';
  if (text.includes('prob') || text.includes('stochastic') || text.includes('statistic')) return 'probability_statistics';
  if (text.includes('state') || text.includes('memory') || text.includes('dynamic') || text.includes('time') || text.includes('chain')) return 'state_memory_dynamics';
  if (text.includes('boss') || text.includes('composite')) return 'composite_systems';
  return String(asset.source_category || '')
    .normalize('NFKD')
    .replace(/[^\x00-\x7F]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '');
}

function dimensionWeights(model) {
  return {
    ...DEFAULT_DIMENSION_WEIGHTS,
    ...(model.difficulty_dimensions?.weights || {}),
  };
}

function normalizeDimensionProfile(profile = {}) {
  return Object.fromEntries(
    DIMENSION_KEYS.map((key) => [key, clamp(Math.round(Number(profile[key] || 0)), 0, 10)]),
  );
}

function addDimensionMaps(...maps) {
  const result = Object.fromEntries(DIMENSION_KEYS.map((key) => [key, 0]));
  for (const map of maps) {
    for (const key of DIMENSION_KEYS) {
      result[key] = clamp(Math.max(result[key], Number(map?.[key] || 0)), 0, 10);
    }
  }
  return result;
}

function rampByLayer(layerNo, startLayer, fullLayer, minValue = 0, maxValue = 10) {
  const n = Math.max(1, Number(layerNo || 1));
  if (n <= startLayer) return minValue;
  const span = Math.max(1, fullLayer - startLayer);
  const progress = clamp((n - startLayer) / span, 0, 1);
  const eased = Math.pow(progress, 0.58);
  return clamp(Math.round(minValue + (maxValue - minValue) * eased), minValue, maxValue);
}

function inputSpaceDimension(gridSize) {
  const n = Number(gridSize || 40);
  if (n <= 40) return 2;
  if (n <= 60) return 3;
  if (n <= 80) return 4;
  if (n <= 100) return 5;
  if (n <= 150) return 6;
  if (n <= 240) return 7;
  if (n <= 400) return 8;
  if (n <= 700) return 9;
  return 10;
}

function buildLayerDimensionProfile({ layerNo, gridSize }) {
  return normalizeDimensionProfile({
    layer_growth: rampByLayer(layerNo, 1, 1200, 1, 10),
    step_progress: 0,
    input_space: inputSpaceDimension(gridSize),
    algebraic_complexity: rampByLayer(layerNo, 1, 900, 1, 10),
    discrete_logic: rampByLayer(layerNo, 10, 1000, 1, 10),
    number_theory: rampByLayer(layerNo, 20, 1400, 0, 10),
    geometry_projection: rampByLayer(layerNo, 1, 800, 1, 10),
    pseudo_3d_projection: rampByLayer(layerNo, 80, 2200, 0, 10),
    parameter_difficulty: rampByLayer(layerNo, 1, 1100, 1, 10),
    composition_depth: rampByLayer(layerNo, 30, 1500, 0, 10),
    branching_piecewise: rampByLayer(layerNo, 50, 1600, 0, 10),
    state_dependency: rampByLayer(layerNo, 400, 3000, 0, 10),
    time_dependency: rampByLayer(layerNo, 700, 4200, 0, 10),
    verification_strength: rampByLayer(layerNo, 1, 1200, inputSpaceDimension(gridSize), 10),
    answer_budget_pressure: rampByLayer(layerNo, 40, 1600, 1, 10),
    semantic_abstraction: rampByLayer(layerNo, 120, 2500, 0, 10),
  });
}

function buildStepDimensionProfile({ layerDimensions, stepNo, stepCount, band, targetDifficulty, rng }) {
  const progress = stepCount <= 1 ? 1 : (Number(stepNo || 1) - 1) / Math.max(1, Number(stepCount || 1) - 1);
  const profile = { ...normalizeDimensionProfile(layerDimensions) };
  const jitter = () => (rng ? rng.pick([-1, 0, 0, 1]) : 0);

  profile.step_progress = clamp(Math.round(progress * 10), 0, 10);
  profile.algebraic_complexity = clamp(Math.max(profile.algebraic_complexity, Number(targetDifficulty || 1) - 1) + jitter(), 0, 10);
  profile.parameter_difficulty = clamp(profile.parameter_difficulty + Math.floor(Number(targetDifficulty || 1) / 4) + jitter(), 0, 10);

  if (band === 'warmup') {
    profile.composition_depth = clamp(profile.composition_depth - 2, 0, 10);
    profile.branching_piecewise = clamp(profile.branching_piecewise - 1, 0, 10);
    profile.verification_strength = clamp(profile.verification_strength - 1, 0, 10);
    profile.answer_budget_pressure = clamp(profile.answer_budget_pressure - 1, 0, 10);
  } else if (band === 'advanced') {
    profile.composition_depth = clamp(profile.composition_depth + 1, 0, 10);
    profile.branching_piecewise = clamp(profile.branching_piecewise + 1, 0, 10);
    profile.verification_strength = clamp(profile.verification_strength + 1, 0, 10);
  } else if (band === 'boss') {
    profile.composition_depth = clamp(profile.composition_depth + 2, 0, 10);
    profile.branching_piecewise = clamp(profile.branching_piecewise + 2, 0, 10);
    profile.parameter_difficulty = clamp(profile.parameter_difficulty + 1, 0, 10);
    profile.verification_strength = clamp(profile.verification_strength + 2, 0, 10);
    profile.answer_budget_pressure = clamp(profile.answer_budget_pressure + 2, 0, 10);
    profile.semantic_abstraction = clamp(profile.semantic_abstraction + 1, 0, 10);
  }

  return normalizeDimensionProfile(profile);
}

function weightedDimensionScore(profile = {}, model = {}) {
  const normalized = normalizeDimensionProfile(profile);
  const weights = dimensionWeights(model);
  return DIMENSION_KEYS.reduce((sum, key) => sum + normalized[key] * Number(weights[key] || 1), 0);
}

function topDimensions(profile = {}, limit = 5) {
  const normalized = normalizeDimensionProfile(profile);
  return DIMENSION_KEYS.map((key) => ({ key, score: normalized[key] }))
    .filter((item) => item.score > 0)
    .sort((a, b) => b.score - a.score || a.key.localeCompare(b.key))
    .slice(0, limit);
}

function assetDimensionProfile(asset, gridSize = 40) {
  if (Array.isArray(asset.components) && asset.components.length) {
    const componentProfile = addDimensionMaps(...asset.components.map((component) => assetDimensionProfile(component, gridSize)));
    return normalizeDimensionProfile({
      ...componentProfile,
      input_space: inputSpaceDimension(gridSize),
      composition_depth: clamp(Math.max(componentProfile.composition_depth, asset.components.length + 1), 0, 10),
      parameter_difficulty: clamp(componentProfile.parameter_difficulty + Math.ceil(asset.components.length / 2), 0, 10),
    });
  }

  const structures = structuresForAsset(asset);
  const structureProfile = addDimensionMaps(...structures.map((structure) => STRUCTURE_DIMENSIONS[structure] || {}));
  const sourceProfile = SOURCE_DIMENSIONS[sourceDimensionKey(asset)] || {};
  const variables = unique((asset.variables || []).filter((name) => !['x', 'y', 'N'].includes(name))).length;
  return normalizeDimensionProfile({
    ...addDimensionMaps(structureProfile, sourceProfile),
    input_space: inputSpaceDimension(gridSize),
    parameter_difficulty: clamp(Math.max(structureProfile.parameter_difficulty || 0, variables), 0, 10),
  });
}

function dimensionMatchScore(assetProfile = {}, targetProfile = {}, model = {}) {
  const assetDimensions = normalizeDimensionProfile(assetProfile);
  const targetDimensions = normalizeDimensionProfile(targetProfile);
  const weights = dimensionWeights(model);
  let score = 0;
  for (const key of DIMENSION_KEYS) {
    const target = targetDimensions[key];
    if (target <= 0) continue;
    const coverage = Math.min(assetDimensions[key], target) / target;
    score += coverage * Number(weights[key] || 1) * target;
  }
  return Math.round(score);
}

function componentAssets(asset) {
  return Array.isArray(asset.components) && asset.components.length ? asset.components : [asset];
}

function structuresForAsset(asset) {
  const components = componentAssets(asset);
  return unique(
    components.flatMap((component) => [
      component.primary_structure,
      ...(component.secondary_structure || component.secondary_structures || []),
    ]),
  );
}

function structureComplexity(asset, model) {
  if (Array.isArray(asset.components) && asset.components.length) {
    const componentScore = asset.components.reduce((sum, component) => {
      const primary = scoreForStructure(component.primary_structure, model);
      const secondary = (component.secondary_structure || component.secondary_structures || []).reduce(
        (subtotal, structureId) => subtotal + scoreForStructure(structureId, model) * 0.22,
        0,
      );
      return sum + primary + secondary;
    }, 0);
    return Math.round(componentScore + structuresForAsset(asset).length * 120);
  }
  const primary = scoreForStructure(asset.primary_structure, model);
  const secondary = (asset.secondary_structure || asset.secondary_structures || []).reduce(
    (sum, structureId) => sum + scoreForStructure(structureId, model) * 0.28,
    0,
  );
  return Math.round(primary + secondary);
}

function compositionComplexity(asset) {
  if (Array.isArray(asset.components) && asset.components.length) {
    const componentTemplates = asset.components.map((component) => JSON.stringify(component.formula_template || {})).join(' ');
    const branchSignals = (componentTemplates.match(/\bif\b|threshold|piecewise|gate|bucket|table|floor/gi) || []).length;
    const uniqueStructures = structuresForAsset(asset).length;
    return Math.round(180 + asset.components.length * 260 + uniqueStructures * 135 + branchSignals * 95);
  }
  const template = JSON.stringify(asset.formula_template || {});
  const secondaryCount = (asset.secondary_structure || asset.secondary_structures || []).length;
  const helperCount = Array.isArray(asset.formula_template?.helpers) ? asset.formula_template.helpers.length : 0;
  const branchSignals = (template.match(/\bif\b|threshold|piecewise|gate|bucket|table/gi) || []).length;
  return Math.round(35 + secondaryCount * 42 + helperCount * 28 + branchSignals * 55 + Math.max(0, template.length - 80) * 0.45);
}

function parameterEntropy(asset, targetDifficulty, gridSize) {
  const variables = Math.max(
    1,
    unique(componentAssets(asset).flatMap((component) => component.variables || [])).filter((name) => !['x', 'y', 'N'].includes(name)).length +
      Math.max(0, componentAssets(asset).length - 1) * 2,
  );
  const gridBits = Math.log2(Math.max(2, gridSize));
  return Math.round(variables * (gridBits * 12 + targetDifficulty * 18));
}

function stateSpaceScore(asset, targetDifficulty) {
  if (!asset.state_dependency || asset.state_dependency === 'none') return 0;
  return Math.round(180 + Number(targetDifficulty || 1) * 70);
}

function verificationProfile({ gridSize, scoreHint, model }) {
  const policy = model.verification_policy || {};
  const domain = gridSize * gridSize;
  const fullDomainLimit = Number(policy.full_domain_grid_size_lte || 40);
  const logStrength = Math.log2(Math.max(2, domain)) * Number(policy.strength_log_multiplier || 28);

  if (gridSize <= fullDomainLimit) {
    return {
      verification_mode: 'full_domain',
      verification_point_count: domain,
      verification_strength: Math.round(logStrength + Number(policy.full_domain_bonus || 0)),
    };
  }

  const generated = Math.round(
    Number(policy.generated_points_base || 80) +
      Number(scoreHint || 0) * Number(policy.generated_points_per_score || 0.06) +
      gridSize * Number(policy.generated_points_per_grid || 0.35),
  );
  const pointCount = clamp(
    generated,
    Number(policy.generated_points_min || 80),
    Number(policy.generated_points_max || 20000),
  );
  return {
    verification_mode: 'deterministic_generated',
    verification_point_count: pointCount,
    verification_strength: Math.round(logStrength + Number(policy.generated_mode_bonus || 0) + pointCount * 0.18),
  };
}

function answerBudgetPressure(targetDifficulty, model) {
  const policy = model.answer_budget_pressure || {};
  const baseline = Number(policy.baseline_cost_by_target_difficulty?.[String(targetDifficulty)] || 140);
  return Math.round(baseline * Number(policy.pressure_multiplier || 0.35));
}

function solverComplexityScore(asset, targetDifficulty, gridSize, model) {
  const components = componentAssets(asset);
  const structures = structuresForAsset(asset);
  const hardStructures = structures.filter((structureId) =>
    ['S04_POLYNOMIAL', 'S05_MOD_RESIDUE', 'S06_PIECEWISE_GATE', 'S07_DISTANCE_METRIC', 'S08_BUCKET_FLOOR', 'S10_PERIODIC_WAVE'].includes(
      structureId,
    ),
  ).length;
  const nonlinearStructures = structures.filter((structureId) =>
    ['S04_POLYNOMIAL', 'S06_PIECEWISE_GATE', 'S08_BUCKET_FLOOR', 'S10_PERIODIC_WAVE'].includes(structureId),
  ).length;
  const baseStructure = structureComplexity(asset, model);
  const composition = Math.max(0, components.length - 1);
  return Math.round(
    baseStructure * 0.65 +
      components.length * 420 +
      composition * composition * 350 +
      structures.length * 160 +
      hardStructures * 500 +
      nonlinearStructures * 700 +
      Math.log2(Math.max(2, gridSize)) * 40 +
      Number(targetDifficulty || 1) * 45,
  );
}

function layerPressure(layerNo, gridSize, model) {
  const policy = model.layer_pressure || {};
  const n = Math.max(1, Number(layerNo || 1));
  const baseline =
    Math.log2(n + 1) * Number(policy.log_multiplier || 120) +
    Math.sqrt(n) * Number(policy.sqrt_multiplier || 18) +
    gridSize * Number(policy.grid_multiplier || 0.8);
  if (policy.mode !== 'hybrid_exponential') return Math.round(baseline);

  const start = Number(policy.exponential_start_layer || 100);
  const scale = Number(policy.exponential_scale || 0);
  const distance = Math.max(0, n - start);
  const doublingInterval = Number(policy.doubling_interval_layers || 0);
  const exponential =
    doublingInterval > 0
      ? scale * (Math.pow(2, distance / doublingInterval) - 1)
      : scale * (Math.pow(1 + Number(policy.exponential_rate_per_layer || 0), distance) - 1);
  const cap = Number(policy.exponential_cap || Number.MAX_SAFE_INTEGER);
  return Math.round(Math.min(cap, baseline + exponential));
}

function readableDifficultyForScore(score, model) {
  const bands = model.readable_bands || [];
  const match = bands.find((band) => {
    const max = band.max_score === null || band.max_score === undefined ? Infinity : Number(band.max_score);
    return score >= Number(band.min_score || 0) && score <= max;
  });
  return match?.label || 'D?';
}

function rewardWeightForScore(score, band, model) {
  const policy = model.reward_weight_policy || {};
  const base = Math.max(Number(policy.min_weight || 1), Math.round(score / Number(policy.score_divisor || 100)));
  return band === 'boss' ? Math.round(base * Number(policy.boss_multiplier || 1.5)) : base;
}

function computeDifficultyProfile({ layerNo, stepNo, stepCount, targetDifficulty, band, asset, gridSize, model }) {
  const base = baseScoreForTarget(targetDifficulty, model);
  const structure = structureComplexity(asset, model);
  const composition = compositionComplexity(asset);
  const entropy = parameterEntropy(asset, targetDifficulty, gridSize);
  const solver = solverComplexityScore(asset, targetDifficulty, gridSize, model);
  const state = stateSpaceScore(asset, targetDifficulty);
  const budget = answerBudgetPressure(targetDifficulty, model);
  const layer = layerPressure(layerNo, gridSize, model);
  const dimensionProfile = normalizeDimensionProfile(asset.dimension_profile || asset.step_dimension_profile || {});
  const dimensionScore = weightedDimensionScore(dimensionProfile, model);
  const progress = Math.round(((Number(stepNo || 1) - 1) / Math.max(1, Number(stepCount || 1) - 1)) * 120);
  const scoreHint = base + structure + composition + entropy + solver + state + budget + layer + progress;
  const verification = verificationProfile({ gridSize, scoreHint, model });
  const multiplier = Number(model.progress_band_multiplier?.[band] || 1);
  const rawScore =
    base +
    structure +
    composition +
    entropy +
    solver +
    state +
    verification.verification_strength +
    budget +
    layer +
    progress +
    dimensionScore;
  const difficultyScore = Math.max(1, Math.round(rawScore * multiplier));

  return {
    difficulty_score: difficultyScore,
    work_score: difficultyScore,
    readable_difficulty: readableDifficultyForScore(difficultyScore, model),
    grid_size: gridSize,
    verification_mode: verification.verification_mode,
    verification_point_count: verification.verification_point_count,
    verification_strength: verification.verification_strength,
    answer_budget_pressure: budget,
    layer_pressure: layer,
    dimension_profile: dimensionProfile,
    dimension_score: dimensionScore,
    dominant_dimensions: topDimensions(dimensionProfile),
    solver_complexity_score: solver,
    reward_weight: rewardWeightForScore(difficultyScore, band, model),
    difficulty_components: {
      base_difficulty: base,
      structure_complexity: structure,
      composition_complexity: composition,
      parameter_entropy: entropy,
      solver_complexity_score: solver,
      state_space: state,
      verification_strength: verification.verification_strength,
      answer_budget_pressure: budget,
      layer_pressure: layer,
      dimension_score: dimensionScore,
      progress_pressure: progress,
      progress_multiplier: multiplier,
    },
  };
}

module.exports = {
  DIMENSION_KEYS,
  assetDimensionProfile,
  buildLayerDimensionProfile,
  buildStepDimensionProfile,
  dimensionMatchScore,
  gridSizeForLayer,
  computeDifficultyProfile,
  readableDifficultyForScore,
  topDimensions,
  verificationProfile,
  weightedDimensionScore,
};
