const fs = require('fs');
const path = require('path');

const ROOT = path.resolve(__dirname, '..');
const OUT_FILE = path.join(ROOT, 'MazeX_formula_assets.json');

const CATEGORY_CODES = {
  mathematics: 'MATH',
  physics: 'PHYS',
  chemistry: 'CHEM',
  biology: 'BIO',
  computer_science: 'CS',
  graph_networks: 'GRAPH',
  board_games: 'GAME',
  information_encoding: 'INFO',
  language_symbols: 'LANG',
  music_sound_signal: 'MUSIC',
  image_vision: 'VISION',
  geography_navigation_maps: 'MAP',
  engineering_mechanics_circuits: 'ENG',
  economics_game_systems: 'ECON',
  topology_spatial_relations: 'TOPO',
  probability_statistics: 'PROB',
  state_memory_dynamics: 'STATE',
  composite_systems: 'BOSS',
  domain: 'DOM',
};

const STRUCTURE_TEMPLATES = {
  S01_IDENTITY_SHIFT: {
    u: '(x + c) mod N',
    v: '(y + f) mod N',
  },
  S02_LINEAR_AFFINE: {
    u: '(a*x + b*y + c) mod N',
    v: '(d*x + e*y + f) mod N',
  },
  S03_MATRIX_TRANSFORM: {
    u: '(a*x + b*y + c) mod N',
    v: '(d*x + e*y + f) mod N',
  },
  S04_POLYNOMIAL: {
    u: '(a*x*x + b*y + c*x*y + seed) mod N',
    v: '(d*y*y + e*x + f*x*y + seed2) mod N',
  },
  S05_MOD_RESIDUE: {
    u: '(x + a*(y mod p) + c) mod N',
    v: '(y + b*(x mod q) + f) mod N',
  },
  S06_PIECEWISE_GATE: {
    u: 'if((x + y + gate_seed) mod 2 == 0, (a*x + b*y + c) mod N, (a2*x + b2*y + c2) mod N)',
    v: 'if((x + y + gate_seed) mod 2 == 0, (d*x + e*y + f) mod N, (d2*x + e2*y + f2) mod N)',
  },
  S07_DISTANCE_METRIC: {
    u: '(x + a*(abs(x - target_x) + abs(y - target_y)) + c) mod N',
    v: '(y + b*(abs(x - target_x) + abs(y - target_y)) + f) mod N',
  },
  S08_BUCKET_FLOOR: {
    u: '(x + a*floor(y / height) + c) mod N',
    v: '(y + b*floor(x / width) + f) mod N',
  },
  S09_LOOKUP_PERMUTATION: {
    u: '(x + table((a*x + b*y + seed) mod period, lookup_table) + c) mod N',
    v: '(y + table((a*x + b*y + seed + offset) mod period, lookup_table) + f) mod N',
  },
  S10_PERIODIC_WAVE: {
    u: '(x + table((kx*x + ky*y + phase) mod period, wave_table) + c) mod N',
    v: '(y + table((ky*x - kx*y + phase + offset) mod period, wave_table) + f) mod N',
  },
  S11_DECAY_TIME: {
    u: '(x + floor(amplitude / (2^min(floor((x + y + t_seed) / half_life), 12))) + c) mod N',
    v: '(y + k*floor((x + y + t_seed) / half_life) + f) mod N',
  },
  S12_GRAPH_RULE: {
    u: '(x + a*(abs(x - target_x) + abs(y - target_y) + graph_seed) + c) mod N',
    v: '(y + b*((x + 2*y + graph_seed) mod degree) + f) mod N',
  },
  S13_BOARD_RULE: {
    u: 'if((abs(x - target_x) == 1 && abs(y - target_y) == 2) || (abs(x - target_x) == 2 && abs(y - target_y) == 1), (a*x + b*y + c) mod N, (N - 1 - x + c2) mod N)',
    v: 'if((abs(x - target_x) == 1 && abs(y - target_y) == 2) || (abs(x - target_x) == 2 && abs(y - target_y) == 1), (d*x + e*y + f) mod N, (N - 1 - y + f2) mod N)',
  },
  S14_ENCODING_HASH: {
    u: '(x + ((a*x + b*y + seed) mod p) + c) mod N',
    v: '(y + (((a*x + b*y + seed) mod p) + x + 2*y) mod q + f) mod N',
  },
  S15_PROBABILITY_SEEDED: {
    u: '(x + a*floor(((seed + a*x*17 + b*y*31 + x*y) mod 997) / width) + c) mod N',
    v: '(y + b*(floor(((seed + a*x*17 + b*y*31 + x*y) mod 997) / width) mod q) + f) mod N',
  },
  S16_STATE_MEMORY: {
    u: '(x + a*(abs(x - last_x) + abs(y - last_y) + ((x + y + memory_seed) mod 17)) + c) mod N',
    v: '(y + b*(abs(x - last_x) + abs(y - last_y) + ((x + y + memory_seed) mod 17)) + f) mod N',
  },
  S17_FSM_DYNAMIC: {
    u: 'if((x + 2*y + state_seed) mod 3 == 0, (p0a*x + p0b*y + p0c) mod N, if((x + 2*y + state_seed) mod 3 == 1, (p1a*x + p1b*y + p1c) mod N, (p2a*x + p2b*y + p2c) mod N))',
    v: 'if((x + 2*y + state_seed) mod 3 == 0, (p0d*x + p0e*y + p0f) mod N, if((x + 2*y + state_seed) mod 3 == 1, (p1d*x + p1e*y + p1f) mod N, (p2d*x + p2e*y + p2f) mod N))',
  },
  S18_RECURRENCE_SEQUENCE: {
    u: '(x + a*table((x + y + t_seed) mod period, sequence_table) + c) mod N',
    v: '(y + b*table((x + y + t_seed + offset) mod period, sequence_table) + f) mod N',
  },
  S19_CONVOLUTION_LOCAL: {
    u: '(x + a*((kx*(x - y) + ky*(x + y) + edge_seed) mod N) + c) mod N',
    v: '(y + b*abs((kx*(x - y) + ky*(x + y) + edge_seed) mod N) + f) mod N',
  },
  S20_OPTIMIZATION_OBJECTIVE: {
    u: 'if(abs(x - ax) + abs(y - ay) <= abs(x - bx) + abs(y - by), (x + p*(abs(x - ax) + abs(y - ay)) + c) mod N, (x + q*(abs(x - bx) + abs(y - by)) + c2) mod N)',
    v: 'if(abs(x - ax) + abs(y - ay) <= abs(x - bx) + abs(y - by), (y + r*(abs(x - ax) + abs(y - ay)) + f) mod N, (y + s*(abs(x - bx) + abs(y - by)) + f2) mod N)',
  },
  S21_LANGUAGE_SYMBOL: {
    u: '(x + table((a*x + b*y + seed) mod period, symbol_table) + c) mod N',
    v: '(y + (table((a*x + b*y + seed) mod period, symbol_table)*table((a*x + b*y + seed) mod period, symbol_table) mod N) + f) mod N',
  },
  S22_BIO_REACTION_GROWTH: {
    u: '(x + a*floor(rate*((x + y + t_seed) mod capacity)*(capacity - ((x + y + t_seed) mod capacity))/scale) + c) mod N',
    v: '(y + b*(floor(rate*((x + y + t_seed) mod capacity)*(capacity - ((x + y + t_seed) mod capacity))/scale) mod q) + f) mod N',
  },
  S23_ECON_GAME_SYSTEM: {
    u: 'if(p*x - q*y >= threshold, (x + a*y + c) mod N, (x + b*y + c2) mod N)',
    v: 'if(p*x - q*y >= threshold, (y + d*x + f) mod N, (y + e*x + f2) mod N)',
  },
  S24_COMPOSITE_BOSS: {
    u: 'if(((a*x + b*y + c) + (d*x + e*y + f)) mod p == 0, (a*x + b*y + c + q*((x + y) mod p) + r) mod N, (N - 1 - ((a*x + b*y + c) mod N) + r) mod N)',
    v: 'if(((a*x + b*y + c) + (d*x + e*y + f)) mod p == 0, (d*x + e*y + f + q*((x + y) mod p) + s) mod N, (N - 1 - ((d*x + e*y + f) mod N) + s) mod N)',
  },
  S25_CHAIN_STATE_DEPENDENT: {
    u: '(a*x + b*y + c + ((history_mix + layer_no*31 + step_no*17) mod N)) mod N',
    v: '(y + k*((history_mix + layer_no*31 + step_no*17) mod N) + f) mod N',
  },
  S26_SPECIAL_CONSTANT_FIELD: {
    u: '(x + floor(pi_scale*((a*x + b*y + seed) mod scale)/scale) + floor(phi_scale*((x + y + seed2) mod scale)/scale) + r) mod N',
    v: '(y + floor(e_scale*((c*x + d*y + seed3) mod scale)/scale) + floor(phi_scale*((x + y + seed2) mod scale)/scale) + s) mod N',
  },
  S27_PRIME_ARITHMETIC: {
    u: '(((a*x + b*y + c) mod prime_a)*prime_b*inv_a + ((d*x + e*y + f) mod prime_b)*prime_a*inv_b + r) mod N',
    v: '(((a*x + b*y + c) mod prime_a)*prime_b*inv_a + ((d*x + e*y + f) mod prime_b)*prime_a*inv_b + s) mod N',
  },
  S28_SPECTRAL_TRANSFORM: {
    u: '(x + a*table((kx*x + ky*y + phase) mod period, spectrum_table) + b*table((h*(kx*x + ky*y + phase) + offset) mod period, spectrum_table) + c) mod N',
    v: '(y + d*table((kx*x + ky*y + phase) mod period, spectrum_table) - e*table((h*(kx*x + ky*y + phase) + offset) mod period, spectrum_table) + f) mod N',
  },
  S29_GROUP_SYMMETRY: {
    u: '(if((a*x + b*y + seed) mod 4 == 0, x, if((a*x + b*y + seed) mod 4 == 1, y, if((a*x + b*y + seed) mod 4 == 2, N - 1 - x, N - 1 - y))) + p*((x + y) mod 4) + r) mod N',
    v: '(if((a*x + b*y + seed) mod 4 == 0, y, if((a*x + b*y + seed) mod 4 == 1, N - 1 - x, if((a*x + b*y + seed) mod 4 == 2, N - 1 - y, x))) + q*((x + y) mod 4) + s) mod N',
  },
  S30_COMPLEX_ANALYTIC: {
    u: '(floor((a*x - b*y + p)/(abs(cx*x - cy*y + d0) + 1)) + r) mod N',
    v: '(floor((b*x + a*y + q)/(abs(cx*x - cy*y + d0) + 1)) + s) mod N',
  },
  S31_TENSOR_GEOMETRY: {
    u: '(x + alpha*(abs(((a*x + b*y + c) mod M)*((p*x + q*y + r) mod M) - ((d*x + e*y + f) mod M)*((d*x + e*y + f) mod M)) mod K) + s) mod N',
    v: '(y + beta*(((a*x + b*y + c) mod M) + ((p*x + q*y + r) mod M)) + t) mod N',
  },
  S32_VARIATIONAL_HAMILTONIAN: {
    u: '(x + ((2*b*y + c*x) mod N) + r) mod N',
    v: '(y - ((2*a*x + c*y) mod N) + s) mod N',
  },
  S33_FIELD_EQUATION: {
    u: '(x + alpha*((a*x + b*y + seedE) mod M) + beta*((p*x + q*y + seedB) mod M) + c0) mod N',
    v: '(y + gamma*((p*x + q*y + seedB) mod M) - delta*((a*x + b*y + seedE) mod M) + f0) mod N',
  },
  S34_QUANTUM_OPERATOR: {
    u: '(x + alpha*((((a*x + b*y + seedR) mod M)^2 + ((c*x - d*y + seedI) mod M)^2 + potential) mod N) + r) mod N',
    v: '(y + beta*((((a*x + b*y + seedR) mod M)^2 + ((c*x - d*y + seedI) mod M)^2 + potential) mod N) + s) mod N',
  },
  S35_FLUID_NAVIER_STOKES: {
    u: '(x + ((a*y + b*(x mod p) + seedX) mod M) - pressure + viscosity*((x + y) mod M) + r) mod N',
    v: '(y + ((c*x + d*(y mod q) + seedY) mod M) - pressure + viscosity*((x + y) mod M) + s) mod N',
  },
  S36_STATISTICAL_MECHANICS: {
    u: '(x + alpha*table(((a*x + b*y + seed) mod period), boltzmann_table) + r) mod N',
    v: '(y + beta*abs((J*((x + y) mod 3 - 1) + field)) + s) mod N',
  },
  S37_GAUGE_LATTICE_FIELD: {
    u: '(x + a*((table(x + seedX, link_table) + table(y + seedYX, link_table) - table(x + y + seedXY, link_table) - table(y + seedY, link_table) + p*1000) mod p) + r) mod N',
    v: '(y + b*((charge*((table(x + seedX, link_table) + table(y + seedYX, link_table) - table(x + y + seedXY, link_table) - table(y + seedY, link_table) + p*1000) mod p) + seed) mod p) + s) mod N',
  },
};

function slug(value) {
  return String(value || '')
    .normalize('NFKD')
    .replace(/[^\x00-\x7F]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '');
}

function walk(dir, out = []) {
  if (!fs.existsSync(dir)) return out;
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (['.git', 'node_modules', 'data', 'public'].includes(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) walk(full, out);
    else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) out.push(full);
  }
  return out;
}

function discoverMarkdownFiles() {
  const roots = [
    path.join(ROOT, 'docs', 'generated-formula-libraries'),
    ...fs
      .readdirSync(ROOT, { withFileTypes: true })
      .filter((entry) => entry.isDirectory() && entry.name.startsWith('MazeX_'))
      .map((entry) => path.join(ROOT, entry.name)),
  ];
  return Array.from(new Set(roots.flatMap((root) => walk(root))));
}

function parseDifficulty(value) {
  const match = String(value || '').match(/(\d+)\s*-\s*(\d+)/);
  if (match) return { min: Number(match[1]), max: Number(match[2]) };
  const single = String(value || '').match(/\d+/);
  const n = single ? Number(single[0]) : 5;
  return { min: Math.max(1, n - 1), max: Math.min(10, n + 1) };
}

function parseRows(file) {
  const text = fs.readFileSync(file, 'utf8');
  const rows = [];
  for (const line of text.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed.startsWith('|') || /^\|\s*:?-{3}/.test(trimmed)) continue;
    const cells = trimmed
      .split('|')
      .slice(1, -1)
      .map((cell) => cell.trim().replace(/^`|`$/g, ''));
    if (cells.length < 5) continue;
    const first = cells[0];
    if (!/[a-zA-Z0-9_.-]{6,}/.test(first) || /family id/i.test(first)) continue;
    rows.push({
      file,
      family_id: slug(first).replace(/_/g, '.'),
      subclass: cells[1] || 'formula family',
      difficulty: parseDifficulty(cells[2]),
      theme: cells[3] || '',
      boss: /yes|boss|true/i.test(cells[4] || ''),
      prototype: cells[5] || '',
    });
  }
  return rows;
}

function inferCategory(row) {
  const text = `${row.family_id} ${row.file}`.toLowerCase();
  if (text.includes('math')) return 'mathematics';
  if (text.includes('physics')) 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('geography') || 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('probability') || text.includes('stochastic') || text.includes('statistics')) 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 'domain';
}

function inferStructure(row) {
  const text = `${row.family_id} ${row.subclass} ${row.prototype}`.toLowerCase();
  const checks = [
    ['S37_GAUGE_LATTICE_FIELD', ['gauge', 'plaquette']],
    ['S36_STATISTICAL_MECHANICS', ['boltzmann', 'statistical', 'entropy']],
    ['S35_FLUID_NAVIER_STOKES', ['navier', 'fluid', 'turbulence']],
    ['S34_QUANTUM_OPERATOR', ['quantum', 'operator', 'commutator']],
    ['S33_FIELD_EQUATION', ['maxwell', 'field', 'flux']],
    ['S32_VARIATIONAL_HAMILTONIAN', ['hamilton', 'lagrangian', 'symplectic', 'variational']],
    ['S31_TENSOR_GEOMETRY', ['tensor', 'curvature', 'metric']],
    ['S30_COMPLEX_ANALYTIC', ['complex', 'mobius', 'conformal']],
    ['S29_GROUP_SYMMETRY', ['group', 'symmetry', 'orbit']],
    ['S28_SPECTRAL_TRANSFORM', ['fourier', 'spectral', 'wavelet', 'harmonic']],
    ['S27_PRIME_ARITHMETIC', ['prime', 'crt', 'residue']],
    ['S26_SPECIAL_CONSTANT_FIELD', ['constant', 'pi', 'phi']],
    ['S25_CHAIN_STATE_DEPENDENT', ['chain', 'history', 'commitment']],
    ['S24_COMPOSITE_BOSS', ['boss', 'composite', 'combine', 'hybrid']],
    ['S23_ECON_GAME_SYSTEM', ['payoff', 'market', 'auction', 'equilibrium']],
    ['S22_BIO_REACTION_GROWTH', ['bio', 'growth', 'reaction', 'enzyme']],
    ['S21_LANGUAGE_SYMBOL', ['language', 'symbol', 'grammar', 'token']],
    ['S20_OPTIMIZATION_OBJECTIVE', ['optimization', 'argmin', 'cost']],
    ['S19_CONVOLUTION_LOCAL', ['convolution', 'kernel', 'edge']],
    ['S18_RECURRENCE_SEQUENCE', ['recurrence', 'sequence', 'fibonacci']],
    ['S17_FSM_DYNAMIC', ['state machine', 'fsm', 'automata']],
    ['S16_STATE_MEMORY', ['memory', 'history', 'window']],
    ['S15_PROBABILITY_SEEDED', ['probability', 'random', 'stochastic']],
    ['S14_ENCODING_HASH', ['encoding', 'hash', 'checksum', 'cipher']],
    ['S13_BOARD_RULE', ['board', 'chess', 'knight', 'sudoku']],
    ['S12_GRAPH_RULE', ['graph', 'path', 'network', 'node']],
    ['S11_DECAY_TIME', ['decay', 'half-life', 'time']],
    ['S10_PERIODIC_WAVE', ['wave', 'periodic', 'phase', 'signal']],
    ['S09_LOOKUP_PERMUTATION', ['lookup', 'permutation', 'table']],
    ['S08_BUCKET_FLOOR', ['bucket', 'floor', 'grid']],
    ['S07_DISTANCE_METRIC', ['distance', 'metric', 'route']],
    ['S06_PIECEWISE_GATE', ['piecewise', 'threshold', 'gate', 'branch']],
    ['S05_MOD_RESIDUE', ['mod', 'residue', 'ring']],
    ['S04_POLYNOMIAL', ['polynomial', 'quadratic', 'surface']],
    ['S03_MATRIX_TRANSFORM', ['matrix', 'projection', 'rotation']],
    ['S02_LINEAR_AFFINE', ['linear', 'affine', 'calibration']],
  ];
  for (const [structure, tokens] of checks) {
    if (tokens.some((token) => text.includes(token))) return structure;
  }
  return 'S02_LINEAR_AFFINE';
}

function secondaryStructures(primary) {
  const defaults = ['S05_MOD_RESIDUE'];
  if (primary === 'S06_PIECEWISE_GATE') return ['S02_LINEAR_AFFINE'];
  if (primary.startsWith('S2') || primary.startsWith('S3')) return ['S06_PIECEWISE_GATE', 'S05_MOD_RESIDUE'];
  if (primary === 'S02_LINEAR_AFFINE') return ['S05_MOD_RESIDUE'];
  return defaults;
}

function variableNames(template) {
  const builtIns = new Set(['x', 'y', 'N', 'if', 'abs', 'floor', 'ceil', 'round', 'sqrt', 'min', 'max', 'sin', 'cos', 'tan', 'log', 'mod', 'table']);
  const names = new Set();
  for (const expr of [template.u, template.v]) {
    for (const name of String(expr).match(/[A-Za-z_][A-Za-z0-9_]*/g) || []) {
      if (!builtIns.has(name)) names.add(name);
    }
  }
  return ['x', 'y', 'N', ...Array.from(names).sort()];
}

function unlockLayerFor(row, structure) {
  const numeric = Number(String(structure).match(/^S(\d+)/)?.[1] || 1);
  return Math.max(1, Math.round(row.difficulty.max * 6 + numeric * 3));
}

function makeAsset(row, index) {
  const category = inferCategory(row);
  const primary = inferStructure(row);
  const code = CATEGORY_CODES[category] || CATEGORY_CODES.domain;
  const structureNumber = String(primary.match(/^S(\d+)/)?.[1] || '00').padStart(2, '0');
  const template = STRUCTURE_TEMPLATES[primary] || STRUCTURE_TEMPLATES.S02_LINEAR_AFFINE;
  const assetId = `MXF-S${structureNumber}-${code}-${String(index + 1).padStart(5, '0')}`;
  return {
    asset_id: assetId,
    pool_id: `POOL-${String(index + 1).padStart(5, '0')}`,
    source_file: path.relative(ROOT, row.file).replace(/\\/g, '/'),
    source_category: category,
    primary_structure: primary,
    secondary_structure: secondaryStructures(primary),
    family_id: row.family_id,
    formula_template: template,
    variables: variableNames(template),
    state_dependency: primary === 'S16_STATE_MEMORY' || primary === 'S17_FSM_DYNAMIC' || primary === 'S25_CHAIN_STATE_DEPENDENT' ? 'contextual' : 'none',
    difficulty_min: row.difficulty.min,
    difficulty_max: row.difficulty.max,
    boss_enabled: row.boss || row.difficulty.max >= 9 || primary === 'S24_COMPOSITE_BOSS',
    duplicate_policy: 'behavior_hash',
    theme_candidates: [row.theme, row.subclass].filter(Boolean).slice(0, 4),
    verification_mode: 'exact_integer',
    risk_notes: 'Parameters must keep the formula non-degenerate and bounded by mod N.',
    enabled: true,
    asset_status: 'official_refined_formula',
    official_formula: true,
    compiled_from_formula_library: true,
    runtime_mode: 'direct_formula_template_integer_grid',
    parameter_rules: Object.fromEntries(variableNames(template).filter((name) => !['x', 'y', 'N'].includes(name)).map((name) => [name, 'deterministic_seeded_parameter'])),
    degeneration_checks: [
      'output_must_not_be_constant',
      'parameters_must_not_remove_primary_structure',
      'integer_grid_output_must_be_bounded_by_mod_N',
    ],
    reveal_fields: ['asset_id', 'family_id', 'formula_template', 'parameters', 'parameter_seed', 'verification_seed'],
    unlock_layer: unlockLayerFor(row, primary),
  };
}

function main() {
  const files = discoverMarkdownFiles();
  const rows = files.flatMap(parseRows);
  const seen = new Set();
  const uniqueRows = rows.filter((row) => {
    const key = `${row.family_id}:${row.file}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
  const assets = uniqueRows.map(makeAsset);
  const doc = {
    version: 'mazex-formula-assets-english-generator-1',
    asset_count: assets.length,
    grid_default: 40,
    selection_rules: {
      status: 'official_refined_formula',
      requirement: 'Each asset must be directly allocatable, executable by the blackbox, and verifiable by the verifier.',
    },
    assets,
    official_formula_count: assets.length,
    formula_source_rows: uniqueRows.length,
    all_assets_are_official_formulas: true,
  };
  fs.writeFileSync(OUT_FILE, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
  console.log(JSON.stringify({ files: files.length, rows: rows.length, assets: assets.length, output: OUT_FILE }, null, 2));
}

main();
