'use strict';

/**
 * Integration tests for v6.12.0 Surgical Context (ask path):
 *   --mode index  — two-tier symbol-header output (anchors, no bodies)
 *   --since <ref> — delta context, restrict to files changed since a git ref
 */

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');

const GEN_CONTEXT = path.resolve(__dirname, '../../gen-context.js');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log(`  PASS  ${name}`);
    passed++;
  } catch (err) {
    console.log(`  FAIL  ${name}: ${err.message}`);
    failed++;
  }
}

function withTempProject(fn) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cf-surgical-'));
  try {
    fn(dir);
  } finally {
    fs.rmSync(dir, { recursive: true, force: true });
  }
}

function run(dir, args) {
  return execSync(`node "${GEN_CONTEXT}" ${args}`, {
    cwd: dir, encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
  });
}

// Seed a context file with anchored signatures + matching real source files.
function seed(dir, files) {
  const gh = path.join(dir, '.github');
  fs.mkdirSync(gh, { recursive: true });
  const lines = ['<!-- Generated by SigMap -->', '# Code signatures', '', '## src', ''];
  for (const f of files) {
    fs.mkdirSync(path.join(dir, path.dirname(f.rel)), { recursive: true });
    fs.writeFileSync(path.join(dir, f.rel), f.source);
    lines.push(`### ${f.rel}`, '```', ...f.sigs, '```', '');
  }
  fs.writeFileSync(path.join(gh, 'copilot-instructions.md'), lines.join('\n'));
  fs.writeFileSync(path.join(dir, 'gen-context.config.json'), JSON.stringify({ secretScan: false }));
}

const LOADER = {
  rel: 'src/config/loader.js',
  source: 'function loadConfig(cwd, options) {\n  return {};\n}\nmodule.exports = { loadConfig };\n',
  sigs: [
    'function loadConfig(cwd, options, overrides, environment, fallbackDefaults) → ResolvedConfigObject  :1-3',
    'function detectAutoSrcDirs(cwd, excludeList, maxDepth, ignorePatterns) → string[]  :5-40',
    'function deepCloneConfig(source, visitedRefs, cloneOptions) → ResolvedConfigObject  :42-70',
  ],
};
const RANKER = {
  rel: 'src/retrieval/ranker.js',
  source: 'function rankConfig(query, index) {\n  return [];\n}\nmodule.exports = { rankConfig };\n',
  sigs: [
    'function rankConfig(query, sigIndex, weights, graphBoost, intentWeights) → RankedResult[]  :1-3',
    'function scoreConfigFile(filePath, sigs, queryTokens, weights) → ScoredFileResult  :5-40',
    'function buildConfigSigIndex(cwd, options, cacheStrategy) → Map<string, string[]>  :42-70',
  ],
};

console.log('\nsurgical-context: --mode index + --since\n');

test('default ask emits full signatures (params + bodies kept)', () => {
  withTempProject((dir) => {
    seed(dir, [LOADER]);
    run(dir, 'ask "config loader" ');
    const out = fs.readFileSync(path.join(dir, '.context', 'query-context.md'), 'utf8');
    assert.ok(out.includes('fallbackDefaults'), 'default mode keeps the parameter list');
  });
});

test('--mode index emits symbol headers with anchors and no bodies', () => {
  withTempProject((dir) => {
    seed(dir, [LOADER]);
    run(dir, 'ask "config loader" --mode index');
    const out = fs.readFileSync(path.join(dir, '.context', 'query-context.md'), 'utf8');
    assert.ok(out.includes('(index mode)'), 'index header should be present');
    assert.ok(out.includes(':1-3'), 'line anchor must be preserved');
    assert.ok(out.includes('function loadConfig'), 'symbol name must be present');
    assert.ok(!out.includes('fallbackDefaults'), 'parameter list must be dropped in index mode');
  });
});

test('--mode index produces fewer tokens than default mode', () => {
  withTempProject((dir) => {
    seed(dir, [LOADER, RANKER]);
    const full = JSON.parse(run(dir, 'ask "config" --json').trim());
    const idx = JSON.parse(run(dir, 'ask "config" --mode index --json').trim());
    assert.ok(idx.contextTokens < full.contextTokens,
      `index (${idx.contextTokens}) should be < default (${full.contextTokens})`);
  });
});

test('--since restricts output to files changed since a git ref', () => {
  withTempProject((dir) => {
    seed(dir, [LOADER, RANKER]);
    execSync('git init -q && git add -A && git -c user.email=t@t -c user.name=t commit -q -m c1', {
      cwd: dir, stdio: ['pipe', 'pipe', 'pipe'],
    });
    const base = execSync('git rev-parse HEAD', { cwd: dir, encoding: 'utf8' }).trim();
    // Change only the loader, commit it.
    fs.appendFileSync(path.join(dir, LOADER.rel), '\n// touched\n');
    execSync('git -c user.email=t@t -c user.name=t commit -aqm c2', {
      cwd: dir, stdio: ['pipe', 'pipe', 'pipe'],
    });

    run(dir, `ask "config" --since ${base}`);
    const out = fs.readFileSync(path.join(dir, '.context', 'query-context.md'), 'utf8');
    assert.ok(out.includes('src/config/loader.js'), 'changed file should be present');
    assert.ok(!out.includes('src/retrieval/ranker.js'), 'unchanged file should be filtered out');
  });
});

test('--mode index materially collapses real anchored JavaScript (issue #223)', () => {
  withTempProject((dir) => {
    // Real extraction: verbose top-level JS functions now carry anchors, so
    // index mode can collapse the param lists (was a no-op before #223).
    fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
    const lines = [];
    for (let i = 0; i < 6; i++) {
      lines.push(`export function handleRequest${i}(requestContext, payloadEnvelope, authToken, traceId, retryPolicyConfig, idempotencyKey) {`);
      lines.push('  return null;');
      lines.push('}');
    }
    fs.writeFileSync(path.join(dir, 'src', 'handlers.js'), lines.join('\n'));
    fs.writeFileSync(path.join(dir, 'gen-context.config.json'), JSON.stringify({ secretScan: false }));
    run(dir, ''); // generate context (runs the JS extractor → anchored verbose sigs)

    const full = JSON.parse(run(dir, 'ask "handle request payload" --json').trim());
    const idx = JSON.parse(run(dir, 'ask "handle request payload" --mode index --json').trim());

    assert.ok(idx.contextTokens < full.contextTokens * 0.85,
      `index (${idx.contextTokens}) should cut >15% vs default (${full.contextTokens})`);

    // Default keeps the param list; index drops it but keeps the anchor.
    run(dir, 'ask "handle request payload" --mode index');
    const out = fs.readFileSync(path.join(dir, '.context', 'query-context.md'), 'utf8');
    assert.ok(!out.includes('retryPolicyConfig'), 'index mode must drop the parameter list');
    assert.ok(/handleRequest0\s+:\d+-\d+/.test(out), 'index mode must keep symbol + anchor');
  });
});

console.log(`\n${passed} passed, ${failed} failed\n`);
if (failed > 0) process.exit(1);
