#!/usr/bin/env node
/*
  Generates an auditable map of external call sites in Solidity.

  Focus:
  - low-level calls: .call / .delegatecall / .staticcall
  - value transfers: call{value: ...}
  - try/catch external calls (heuristic)

  - Scans contracts/** (recursive) for .sol files (excluding contracts/deprecated)
  - Writes docs/security/EXTERNAL_CALLS_SURFACE_AUTOGEN.md

  This is documentation tooling, not a formal parser.
*/

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

const ROOT = process.cwd();
const CONTRACTS_DIR = path.join(ROOT, 'contracts');
const OUT_PATH = path.join(ROOT, 'docs', 'security', 'EXTERNAL_CALLS_SURFACE_AUTOGEN.md');

function walk(dir, out) {
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      if (full.includes(`${path.sep}contracts${path.sep}deprecated${path.sep}`)) continue;
      walk(full, out);
    } else if (entry.isFile() && entry.name.endsWith('.sol')) {
      if (full.includes(`${path.sep}contracts${path.sep}deprecated${path.sep}`)) continue;
      out.push(full);
    }
  }
}

function stripLineComments(line) {
  const idx = line.indexOf('//');
  return idx >= 0 ? line.slice(0, idx) : line;
}

function extractSites(filePath) {
  const content = fs.readFileSync(filePath, 'utf8');
  const lines = content.split(/\r?\n/);

  const sites = []; // { kind, fileRel, line, snippet }
  const fileRel = path.relative(ROOT, filePath).split(path.sep).join('/');

  for (let i = 0; i < lines.length; i++) {
    const raw = lines[i];
    const line = stripLineComments(raw);
    const trimmed = raw.trim();

    // Low-level calls
    if (/\.call\b/.test(line) || /\.delegatecall\b/.test(line) || /\.staticcall\b/.test(line)) {
      const kind = line.includes('call{value:') ? 'call{value:}' : (line.includes('.delegatecall') ? 'delegatecall' : (line.includes('.staticcall') ? 'staticcall' : 'call'));
      sites.push({ kind, fileRel, line: i + 1, snippet: trimmed });
      continue;
    }

    // try/catch external calls (heuristic: try Something.method(...))
    const tryMatch = line.match(/\btry\s+([A-Za-z0-9_]+\.[A-Za-z0-9_]+)\s*\(/);
    if (tryMatch) {
      sites.push({ kind: 'try-call', fileRel, line: i + 1, snippet: trimmed });
    }
  }

  return sites;
}

function render(sites) {
  const parts = [];
  parts.push('# External Call Surface (Autogenerated)');
  parts.push('');
  parts.push('This file is autogenerated from Solidity sources. Do not hand-edit.');
  parts.push('');
  parts.push('- Generated by: `node tools/generate_external_calls_surface.js`');
  parts.push('');
  parts.push('Vaultfire principle: external call sites are part of the attack surface and should stay visible.');
  parts.push('');

  if (!sites.length) {
    parts.push('_No external call sites detected in non-deprecated contracts._');
    parts.push('');
    return parts.join('\n');
  }

  parts.push('## Sites');
  parts.push('');
  for (const s of sites) {
    parts.push(`- ${s.fileRel}:${s.line} [${s.kind}] ${s.snippet}`);
  }
  parts.push('');

  parts.push('---');
  parts.push('');
  parts.push('If this output changes, review CEI ordering, reentrancy protections, and failure modes.');
  parts.push('');

  return parts.join('\n');
}

(function main() {
  const files = [];
  walk(CONTRACTS_DIR, files);

  const sites = [];
  for (const file of files) {
    sites.push(...extractSites(file));
  }

  const sorted = sites.sort((a, b) => {
    const fc = a.fileRel.localeCompare(b.fileRel);
    if (fc !== 0) return fc;
    if (a.line !== b.line) return a.line - b.line;
    return a.kind.localeCompare(b.kind);
  });

  fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
  fs.writeFileSync(OUT_PATH, render(sorted), 'utf8');
})();
