const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');

const root = path.resolve(__dirname, '..');
const sourceRoot = path.resolve(root, '..', '..', 'v3-src');
const contractsRoot = path.join(sourceRoot, 'contracts');
const outputRoot = path.join(root, 'tests', 'fixtures', 'reviewed-artifacts');
const solc = process.env.SOLC;
if (!solc) throw new Error('SOLC must name the reviewed Solidity 0.8.24 compiler');

const sources = {};
const collect = (directory, keyRoot = sourceRoot, keyPrefix = '') => {
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
    const absolute = path.join(directory, entry.name);
    if (entry.isDirectory()) {
      collect(absolute, keyRoot, keyPrefix);
    } else if (entry.name.endsWith('.sol')) {
      const relative = `${keyPrefix}${path.relative(keyRoot, absolute).split(path.sep).join('/')}`;
      sources[relative] = { content: fs.readFileSync(absolute, 'utf8') };
    }
  }
};
collect(contractsRoot);
collect(
  path.join(sourceRoot, 'lib', 'openzeppelin-contracts', 'contracts'),
  path.join(sourceRoot, 'lib', 'openzeppelin-contracts'),
  'openzeppelin-contracts/',
);

const supported = {
  AccountabilityBondV3Safe: 'contracts/bonds/AccountabilityBondV3Safe.sol',
  PartnershipBondV3Safe: 'contracts/bonds/PartnershipBondV3Safe.sol',
  MultisigGovernanceV3: 'contracts/governance/MultisigGovernanceV3.sol',
  MandateRegistryV3: 'contracts/mandate/MandateRegistryV3.sol',
};
fs.mkdirSync(outputRoot, { recursive: true });
const dependencyClosure = (start) => {
  const selected = {};
  const visit = (sourceName) => {
    if (selected[sourceName]) return;
    const source = sources[sourceName];
    if (!source) throw new Error(`unresolved Solidity source ${sourceName}`);
    selected[sourceName] = source;
    const expression = /import(?:\s+[^"']+\s+from)?\s*["']([^"']+)["'];/g;
    for (const match of source.content.matchAll(expression)) {
      const imported = match[1].startsWith('.')
        ? path.posix.normalize(path.posix.join(path.posix.dirname(sourceName), match[1]))
        : match[1];
      visit(imported);
    }
  };
  visit(start);
  return selected;
};
const failures = [];
for (const [contractName, sourceName] of Object.entries(supported)) {
  const input = {
    language: 'Solidity',
    sources: dependencyClosure(sourceName),
    settings: {
      viaIR: true,
      optimizer: { enabled: true, runs: 200 },
      evmVersion: 'paris',
      metadata: { bytecodeHash: 'none' },
      outputSelection: {
        '*': {
          '*': ['abi', 'metadata', 'evm.deployedBytecode.object', 'evm.deployedBytecode.immutableReferences'],
        },
      },
    },
  };
  const raw = execFileSync(solc, ['--standard-json'], {
    input: JSON.stringify(input),
    encoding: 'utf8',
    maxBuffer: 128 * 1024 * 1024,
  });
  const output = JSON.parse(raw);
  const errors = (output.errors ?? []).filter((entry) => entry.severity === 'error');
  if (errors.length > 0) {
    failures.push(`${contractName}:\n${errors.map((entry) => entry.formattedMessage).join('\n')}`);
    continue;
  }
  const contract = output.contracts?.[sourceName]?.[contractName];
  if (!contract) throw new Error(`solc output omitted ${contractName}`);
  fs.writeFileSync(path.join(outputRoot, `${contractName}.json`), `${JSON.stringify({
    contractName,
    sourceName,
    compilerVersion: '0.8.24',
    abi: contract.abi,
    metadata: contract.metadata,
    deployedBytecode: {
      object: contract.evm.deployedBytecode.object,
      immutableReferences: contract.evm.deployedBytecode.immutableReferences,
    },
  }, null, 2)}\n`);
}
if (failures.length > 0) throw new Error(failures.join('\n\n'));
