#!/usr/bin/env node
/*
  Generates an auditable map of Solidity events + emit sites.

  - Scans contracts/** (recursive) for .sol files (excluding contracts/deprecated)
  - Extracts `event ...;` declarations (grouped by contract)
  - Extracts `emit EventName(...)` sites with file:line (global)
  - Writes docs/security/EVENTS_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', 'EVENTS_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 extractEventsAndEmits(filePath) {
  const content = fs.readFileSync(filePath, 'utf8');
  const lines = content.split(/\r?\n/);

  let contractName = null;
  const events = []; // { contractName, signatureLine }
  const emits = [];  // { eventName, fileRel, line, lineText }

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

    const contractMatch = line.match(/^\s*contract\s+([A-Za-z0-9_]+)\b/);
    if (contractMatch) contractName = contractMatch[1];

    const eventMatch = line.match(/^\s*event\s+(.+);\s*$/);
    if (eventMatch) {
      const cleaned = ('event ' + eventMatch[1] + ';').trim();
      events.push({ contractName: contractName || '(unknown-contract)', signatureLine: cleaned });
    }

    // emit site: `emit X(`
    const emitMatch = line.match(/\bemit\s+([A-Za-z0-9_]+)\s*\(/);
    if (emitMatch) {
      const fileRel = path
        .relative(ROOT, filePath)
        .split(path.sep)
        .join('/');
      emits.push({
        eventName: emitMatch[1],
        fileRel,
        line: i + 1,
        lineText: raw.trim(),
      });
    }
  }

  return { events, emits };
}

function render(byContract, emits) {
  const parts = [];
  parts.push('# Events 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_events_surface.js`');
  parts.push('');
  parts.push('Vaultfire principle: monitoring should be **privacy-preserving** (protocol integrity), and event design should be explicit.');
  parts.push('');

  const contracts = Object.keys(byContract).sort((a, b) => a.localeCompare(b));
  if (!contracts.length) {
    parts.push('_No events detected in non-deprecated contracts._');
    parts.push('');
  } else {
    parts.push('## Event declarations');
    parts.push('');
    for (const name of contracts) {
      parts.push(`### ${name}`);
      for (const sig of byContract[name]) {
        parts.push('- `' + sig + '`');
      }
      parts.push('');
    }
  }

  parts.push('## Emit sites (best-effort)');
  parts.push('');
  if (!emits.length) {
    parts.push('_No emit sites detected._');
    parts.push('');
  } else {
    for (const e of emits) {
      parts.push(`- ${e.fileRel}:${e.line} → ${e.eventName}`);
    }
    parts.push('');
  }

  parts.push('---');
  parts.push('');
  parts.push('If this output changes, review whether monitoring guidance and dashboards need updates.');
  parts.push('');

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

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

  const byContract = {};
  const allEmits = [];

  for (const file of files) {
    const { events, emits } = extractEventsAndEmits(file);
    for (const e of events) {
      byContract[e.contractName] ||= [];
      byContract[e.contractName].push(e.signatureLine);
    }
    allEmits.push(...emits);
  }

  for (const key of Object.keys(byContract)) {
    byContract[key] = [...new Set(byContract[key])].sort((a, b) => a.localeCompare(b));
  }

  const sortedEmits = allEmits
    .map((e) => ({ ...e }))
    .sort((a, b) => {
      const fc = a.fileRel.localeCompare(b.fileRel);
      if (fc !== 0) return fc;
      return a.line - b.line;
    });

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