/**
 * D 类 null 防御检查脚本
 *
 * 静态扫描前端 performance 模块的页面代码，找出 API 返回数据直接调用
 * .map(), .length, .filter(), .toFixed() 等方法但没有 ?. 防御的位置。
 *
 * 运行方式: npx ts-node testing/scripts/null-safety-check.ts
 */

import * as fs from 'fs';
import * as path from 'path';

// ==================== 配置 ====================

const SCAN_DIRS = [
  'frontend/src/app/(modules)/performance',
];

const PROJECT_ROOT = path.resolve(__dirname, '../..');

// 需要检测的方法（数组方法 + 可能在 undefined 上调用的 Number/String 方法）
const ARRAY_METHODS = [
  // 数组方法
  'map', 'filter', 'find', 'forEach', 'some', 'every', 'reduce', 'sort', 'flat', 'flatMap',
  // 数组属性
  'length',
  // 数字方法
  'toFixed', 'toPrecision', 'toLocaleString',
  // 字符串方法
  'charAt', 'charCodeAt', 'slice', 'split', 'trim', 'toLowerCase', 'toUpperCase',
  'replace', 'replaceAll', 'match', 'startsWith', 'endsWith', 'padStart', 'padEnd',
  'substring', 'indexOf', 'lastIndexOf',
  // 通用方法
  'toString', 'valueOf', 'includes', 'concat', 'join',
  // 对象方法（Object.keys 等无法用此模式检测，单独处理）
];

// ==================== 文件扫描 ====================

function collectTsxFiles(dir: string): string[] {
  const results: string[] = [];
  const absDir = path.resolve(PROJECT_ROOT, dir);

  function walk(currentDir: string) {
    if (!fs.existsSync(currentDir)) return;
    const entries = fs.readdirSync(currentDir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(currentDir, entry.name);
      if (entry.isDirectory()) {
        walk(fullPath);
      } else if (entry.isFile() && entry.name.endsWith('.tsx')) {
        results.push(fullPath);
      }
    }
  }

  walk(absDir);
  return results;
}

// ==================== 安全模式识别 ====================

interface Finding {
  file: string;
  line: number;
  code: string;
  method: string;
  suggestion: string;
}

/**
 * 从文件内容中提取 useState 初始值为 [] 的变量名
 */
function extractSafeStateVars(content: string): Set<string> {
  const safeVars = new Set<string>();
  // const [items, setItems] = useState([])
  // const [items, setItems] = useState<Type[]>([])
  const pattern = /const\s+\[(\w+),\s*\w+\]\s*=\s*useState(?:<[^>]*>)?\(\s*\[\s*\]\s*\)/g;
  let match;
  while ((match = pattern.exec(content)) !== null) {
    safeVars.add(match[1]);
  }
  return safeVars;
}

/**
 * 检查该行是否是常量数组的调用（如 ['A','B'].map(...)）
 */
function isConstantArrayCall(line: string, matchIndex: number): boolean {
  // 向前查找，看 .method( 前面是否是 ] 结尾（即数组字面量）
  const before = line.substring(0, matchIndex).trimEnd();
  return before.endsWith(']');
}

/**
 * 检查变量是否在 if/guard 中被判断过（简化版：同一文件中有 if (var) 或 if (var?.xxx)）
 * 这里只检查同一行前面的行是否有守卫
 */
function isGuardedVariable(lines: string[], lineIndex: number, varName: string): boolean {
  // 向上搜索最近的 20 行，查看是否有守卫条件
  const startLine = Math.max(0, lineIndex - 20);
  for (let i = startLine; i < lineIndex; i++) {
    const line = lines[i].trim();
    // if (varName) { ... } 或 if (varName && ...) 或 varName && varName.xxx
    if (
      line.includes(`if (${varName})`) ||
      line.includes(`if (${varName} &&`) ||
      line.includes(`if (${varName} !==`) ||
      line.includes(`if (${varName} !=`) ||
      line.includes(`${varName} &&`) ||
      line.includes(`${varName} ?`) ||  // 三元中的守卫
      line.includes(`!${varName}`)       // early return 模式
    ) {
      // 检查守卫是否还在作用域内（简化：检查中间没有 } 结束块）
      let braceDepth = 0;
      for (let j = i; j < lineIndex; j++) {
        for (const ch of lines[j]) {
          if (ch === '{') braceDepth++;
          if (ch === '}') braceDepth--;
        }
      }
      // 如果守卫块还没关闭，认为变量是安全的
      if (braceDepth > 0) {
        return true;
      }
    }
  }
  return false;
}

/**
 * 检查该匹配是否在函数参数的链式调用中（如 .map((item) => item.xxx.map(...)）
 * 即 .method( 后面跟着回调参数的操作
 */
function isInCallbackChain(line: string, matchIndex: number): boolean {
  // 检查前面是否有 => 开头的箭头函数上下文中的嵌套调用
  // 这种情况下 item 是函数参数，不需要 null 防御
  return false; // 这个检查过于复杂，暂不做排除
}

/**
 * 检查是否是链式调用中间的方法（如 .filter(...).map(...)）
 * 前面已经有 .filter() 或 .map() 等，结果一定是数组
 */
function isChainedAfterArrayMethod(line: string, matchIndex: number): boolean {
  const before = line.substring(0, matchIndex);
  // 检查前面是否有 ) 紧跟着（即前一个方法调用的结果）
  const trimmed = before.trimEnd();
  if (trimmed.endsWith(')')) {
    // 可能是 .filter(...).map(...) 这样的链式调用
    // 再检查前面是否有 .filter( 或 .map( 等
    for (const method of ARRAY_METHODS) {
      if (method === 'length') continue; // length 不是方法调用
      if (trimmed.includes(`.${method}(`)) return true;
    }
  }
  return false;
}

// ==================== 核心检测 ====================

function checkFile(filePath: string): Finding[] {
  const content = fs.readFileSync(filePath, 'utf-8');
  const lines = content.split('\n');
  const findings: Finding[] = [];

  const safeStateVars = extractSafeStateVars(content);
  const relPath = path.relative(PROJECT_ROOT, filePath);

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

    // 跳过注释行
    const trimmedLine = line.trim();
    if (trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.startsWith('/*')) {
      continue;
    }

    for (const method of ARRAY_METHODS) {
      // 构建匹配模式
      // 对于 .length 不需要 ( 后缀
      const dotMethod = method === 'length' ? `.${method}` : `.${method}(`;

      // 找到所有出现位置
      let searchStart = 0;
      while (true) {
        const idx = line.indexOf(dotMethod, searchStart);
        if (idx === -1) break;
        searchStart = idx + dotMethod.length;

        // 检查是否已有安全防御
        // 1. ?.method( 模式
        if (idx >= 1 && line[idx - 1] === '?') continue;

        // 2. || []).method( 模式 - 向前找
        const beforeDot = line.substring(Math.max(0, idx - 10), idx);
        if (beforeDot.includes('|| [])') || beforeDot.includes('?? [])')) continue;

        // 3. 常量数组（如 ['a', 'b'].map(...)）
        if (isConstantArrayCall(line, idx)) continue;

        // 4. 链式调用（如 .filter(...).map(...)）
        if (isChainedAfterArrayMethod(line, idx)) continue;

        // 提取变量名（. 前面的标识符）
        const beforeMatch = line.substring(0, idx);
        const varMatch = beforeMatch.match(/(\w+(?:\.\w+)*)\s*$/);
        if (!varMatch) continue;
        const varName = varMatch[1];

        // 排除内置的安全值
        // 5. Object.keys(...).map 等
        if (['Object.keys', 'Object.values', 'Object.entries', 'Array.from', 'Array'].some(
          (prefix) => beforeMatch.trimEnd().endsWith(`)`) && beforeMatch.includes(prefix),
        )) continue;

        // 6. 函数参数标识（简单排除 .map((x) => x.yyy.map(...)）
        // 如果变量名是单个单词且看起来像回调参数
        const rootVar = varName.split('.')[0];

        // 7. useState 初始值为 [] 的变量
        if (safeStateVars.has(rootVar)) continue;

        // 8. 变量在 if 守卫中
        if (isGuardedVariable(lines, i, rootVar)) continue;

        // 9. 字符串字面量方法（如 'xxx'.split(...).map(...)）
        if (beforeMatch.trimEnd().endsWith("'") || beforeMatch.trimEnd().endsWith('"') || beforeMatch.trimEnd().endsWith('`')) continue;

        // 10. 解构后的数组（如 const items = data?.items || []; items.map(...)）
        // 检查是否在文件中有 const rootVar = ... || [] 的赋值
        const safeAssignPattern = new RegExp(`(?:const|let)\\s+${rootVar}\\s*=.*\\|\\|\\s*\\[\\s*\\]`);
        const nullishAssignPattern = new RegExp(`(?:const|let)\\s+${rootVar}\\s*=.*\\?\\?\\s*\\[\\s*\\]`);
        if (safeAssignPattern.test(content) || nullishAssignPattern.test(content)) continue;

        // 11. 类型断言或 as 表达式后的方法调用
        if (beforeMatch.trimEnd().endsWith(')') && beforeMatch.includes(' as ')) continue;

        // 生成建议
        const fullExpr = `${varName}${dotMethod}`;
        let suggestion: string;
        if (method === 'length') {
          suggestion = `(${varName} || []).${method}`;
        } else {
          suggestion = `(${varName} || []).${method}(`;
        }

        findings.push({
          file: relPath,
          line: i + 1,
          code: trimmedLine.length > 100 ? trimmedLine.substring(0, 100) + '...' : trimmedLine,
          method,
          suggestion,
        });
      }
    }
  }

  return findings;
}

// ==================== 主流程 ====================

function main() {
  console.log('=== D 类 null 防御检查 ===\n');

  const allFiles: string[] = [];
  for (const dir of SCAN_DIRS) {
    allFiles.push(...collectTsxFiles(dir));
  }

  console.log(`扫描目录: ${SCAN_DIRS.join(', ')}`);
  console.log(`找到 ${allFiles.length} 个 .tsx 文件\n`);

  const allFindings: Finding[] = [];
  const fileFindings = new Map<string, Finding[]>();

  for (const file of allFiles) {
    const findings = checkFile(file);
    if (findings.length > 0) {
      const relPath = path.relative(PROJECT_ROOT, file);
      fileFindings.set(relPath, findings);
      allFindings.push(...findings);
    }
  }

  // 输出结果
  if (allFindings.length === 0) {
    console.log('未发现缺少 null 防御的位置。\n');
    console.log(`汇总: 扫描 ${allFiles.length} 个文件，发现 0 处缺少 null 防御`);
    process.exit(0);
  }

  for (const [file, findings] of fileFindings) {
    const fileName = path.basename(file);
    console.log(`文件: ${fileName}`);
    console.log(`  路径: ${file}`);
    for (const f of findings) {
      const lineStr = `第 ${f.line} 行`;
      const codeSnippet = f.code.length > 60
        ? f.code.substring(0, 60) + '...'
        : f.code;
      // 找到关键的调用片段
      const methodCall = f.method === 'length'
        ? `.${f.method}`
        : `.${f.method}(`;
      console.log(`  ${lineStr}: ...${methodCall}`);
      console.log(`    → 建议: ${f.suggestion}`);
    }
    console.log('');
  }

  console.log(`汇总: 扫描 ${allFiles.length} 个文件，发现 ${allFindings.length} 处缺少 null 防御`);
  process.exit(1);
}

main();
