/**
 * Regression: no stale `gstack-brain-init`, `gbrain_sync_mode`, or
 * `~/.gstack-brain-remote.txt` references survive the v1.27.0.0 rename.
 *
 * Per codex Findings #1 + #8 + #9: the rename's blast radius is wider than
 * the obvious bin/ + scripts/ surface. This test grep-scans the broader
 * tree (bin, scripts, *.tmpl, generated *.md, test/, docs/) for the
 * deprecated identifiers and fails CI if any callers were missed.
 *
 * Allowlist: the migration script (`gstack-upgrade/migrations/v1.27.0.0.sh`)
 * legitimately references the old names — it's the rename actor itself.
 * Old migration scripts (v1.17.0.0.sh and similar) reference the old names
 * for their own historical context and are also allowlisted.
 *
 * The test is mechanical: if you find yourself adding a non-historical
 * file to the allowlist, you probably need to actually fix the rename
 * instead.
 */

import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import { spawnSync } from 'child_process';

const ROOT = path.resolve(import.meta.dir, '..');

const ALLOWLIST = [
  // The migration script that performs the rename. Self-references are expected.
  'gstack-upgrade/migrations/v1.27.0.0.sh',
  // Older migration scripts — historical references; these document past state.
  'gstack-upgrade/migrations/v1.17.0.0.sh',
  // The migration test itself — it asserts on the migration's behavior.
  'test/migrations-v1.27.0.0.test.ts',
  // The test for the v1.17.0.0 historical migration.
  'test/gstack-upgrade-migration-v1_17_0_0.test.ts',
  // CHANGELOG entries describe historical state by their nature.
  'CHANGELOG.md',
  // TODOS may reference past or future states by name.
  'TODOS.md',
  // The plan file for v1.27.0.0 documents why we're renaming.
  '.context/plans/setup-gbrain-remote-mcp-rename-brain-artifacts.md',
  // The bin/gstack-config comment explicitly preserves the rename note.
  'bin/gstack-config',
  // Detect script's "renamed in v1.27.0.0" comment + brain-remote-fallback path.
  'bin/gstack-gbrain-detect',
  // brain-restore + source-wireup keep the old file as a migration-window fallback
  // (read both, prefer artifacts). brain-uninstall has the same fallback.
  'bin/gstack-brain-restore',
  'bin/gstack-gbrain-source-wireup',
  'bin/gstack-brain-uninstall',
  // The preamble resolver reads the legacy file as a fallback during the
  // migration window — same pattern.
  'scripts/resolvers/preamble/generate-brain-sync-block.ts',
  // gstack-upgrade.test.ts may exercise old migration behavior.
  'test/gstack-upgrade.test.ts',
  // This test itself references the patterns to grep for.
  'test/no-stale-gstack-brain-refs.test.ts',
  // The v1.36.0.0 doc-config drift guard intentionally defends the rename
  // by listing the deprecated keys in its DEPRECATED_KEYS denylist.
  'test/docs-config-keys.test.ts',
  // memory.md documents the rename context.
  'setup-gbrain/memory.md',
  // The new init script's header comment intentionally cites the rename.
  'bin/gstack-artifacts-init',
  // The replacement test mirrors the pattern of the old test (lineage note).
  'test/gstack-artifacts-init.test.ts',
  // The post-rename-doc-regen test references the patterns it greps for.
  'test/post-rename-doc-regen.test.ts',
  // The Path 4 structural lint references some legacy names in comments.
  'test/setup-gbrain-path4-structure.test.ts',
  // Generated docs that include the preamble bash (which has the fallback).
  // We grep template sources, not generated output, by limiting scan paths.
];

const FORBIDDEN_PATTERNS = [
  'gstack-brain-init',
  'gbrain_sync_mode',
];

const SCAN_PATHS = [
  'bin/',
  'scripts/',
  'setup-gbrain/SKILL.md.tmpl',
  'setup-gbrain/sections',
  'sync-gbrain/SKILL.md.tmpl',
  'health/SKILL.md.tmpl',
  'plan-eng-review/SKILL.md.tmpl',
  'plan-ceo-review/SKILL.md.tmpl',
  'review/SKILL.md.tmpl',
  'ship/SKILL.md.tmpl',
  'test/',
  // Fork port wave 2: docs were deliberately excluded and that exact gap let
  // a dead command (gstack-brain-init) survive ~36 releases as a
  // command-not-found instruction. Docs are user-facing surface; scan them.
  'docs/',
  'README.md',
  'USING_GBRAIN_WITH_GSTACK.md',
];

function grepRefs(pattern: string): string[] {
  const args = ['-rn', '--', pattern, ...SCAN_PATHS.map((p) => path.join(ROOT, p))];
  const r = spawnSync('grep', args, { encoding: 'utf-8' });
  // grep exits 1 when no matches — that's fine for our purposes.
  const lines = (r.stdout || '').split('\n').filter((l) => l.trim().length > 0);
  return lines
    .map((line) => {
      // Strip ROOT prefix to get repo-relative path.
      const colon = line.indexOf(':');
      const file = line.slice(0, colon);
      return path.relative(ROOT, file);
    })
    .filter((file) => !ALLOWLIST.includes(file))
    // Filter out any file that's inside a directory we don't actually scan.
    .filter((file) => !file.startsWith('node_modules/') && !file.startsWith('.git/'));
}

describe('no stale gstack-brain refs (v1.27.0.0 rename)', () => {
  for (const pattern of FORBIDDEN_PATTERNS) {
    test(`no non-allowlisted references to "${pattern}"`, () => {
      const offenders = [...new Set(grepRefs(pattern))];
      if (offenders.length > 0) {
        console.error(`Found stale "${pattern}" references in:\n${offenders.map((f) => `  - ${f}`).join('\n')}`);
        console.error(
          `If a file is intentionally referencing the old name (migration, historical doc, fallback path), add it to ALLOWLIST in this test.`
        );
      }
      expect(offenders).toEqual([]);
    });
  }
});
