/**
 * Self-Reflector — *빈 깡통(Hollow Code)* 검출 휴리스틱.
 *
 * Phase C(syntax/lint)는 문법 오류만 잡는다. 작은 LLM이 가장 자주 만드는
 * 실패 패턴은 *문법은 맞지만 본문이 비어 있는* 코드 — `def foo(): pass`,
 * `# TODO: implement`, import만 있고 로직 0줄인 모듈 등. 사용자가 "완료
 * 됐다"라는 응답을 받고 파일을 열면 빈 깡통만 들어 있는 사고가 여기서
 * 나온다.
 *
 * 이 모듈은 *정규식 + 라인 카운팅* 만으로 빈 깡통 패턴을 잡는다. LLM 콜
 * 0회, 추가 비용 0. 한계는 있지만(일부 위양성/위음성) 작은 모델 실패의
 * 80% 이상은 이 단순 휴리스틱으로 잡힌다.
 *
 * 호출자(dispatcher / agent.ts)가 syntax 체크 직후에 같이 부르면:
 *   ✅ 검증 통과 → 그대로 응답
 *   ❌ 빈 깡통 → action-report에 한 줄 경고 추가 + (회사 모드면 verifier
 *      retry 트리거 조건에 합류)
 */
import * as fs from 'fs';
import * as path from 'path';

/** 한 파일의 검사 결과. ok=false면 reasons에 잡힌 패턴들. */
export interface HollowCheckResult {
    relPath: string;
    ok: boolean;
    /** ok=false일 때 잡힌 사유 1~3줄. */
    reasons: string[];
    /** 진단 메타 — 디버그/로깅용. */
    meta?: {
        totalLines: number;
        codeLines: number;
        stubFnRatio: number;
        todoRatio: number;
    };
}

/** 어떤 파일 확장자를 hollow 검사 대상으로 삼을지. */
function _isSupportedExt(ext: string): boolean {
    return ['.py', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'].includes(ext.toLowerCase());
}

/**
 * 라인이 *의미 있는 코드*인지 판정. 주석/빈줄/혼자 떠 있는 닫는 괄호 등은
 * 의미 라인이 아니다. 작은 파일도 너무 가혹하게 평가하지 않도록 import는
 * 의미 라인으로 인정 (재export 모듈 등 유효한 패턴 보호).
 */
function _isMeaningfulCodeLine(line: string, ext: string): boolean {
    const t = line.trim();
    if (!t) return false;
    // 한줄/블록 주석 단독 라인
    if (ext === '.py') {
        if (t.startsWith('#')) return false;
        if (t.startsWith('"""') || t.startsWith("'''")) return false;
    } else {
        if (t.startsWith('//')) return false;
        if (t.startsWith('/*') || t.startsWith('*') || t === '*/') return false;
    }
    // 혼자 떠 있는 brace / 괄호
    if (/^[\}\)\]\s]+;?$/.test(t)) return false;
    return true;
}

/** 라인이 stub 의심 표현인지. */
function _isStubLine(line: string): boolean {
    const t = line.trim();
    if (!t) return false;
    if (t === 'pass') return true;
    if (t === '...') return true;
    if (/^return\s*(None|null|undefined)?\s*;?$/.test(t)) return true;
    if (/^(?:#|\/\/)\s*(TODO|FIXME|XXX|HACK|implement|구현|placeholder|여기에)/i.test(t)) return true;
    if (/^["']?(TODO|FIXME|TBD|placeholder|구현 필요|여기에 구현)["']?\s*$/i.test(t)) return true;
    return false;
}

/**
 * Python 함수/메서드의 본문이 stub뿐인지 판정.
 * 본문은 def 시그니처 다음 들여쓰기 된 라인들. 닫히는 시점은 들여쓰기 감소.
 */
function _countPyHollowFunctions(src: string): { total: number; hollow: number } {
    const lines = src.split(/\r?\n/);
    let total = 0, hollow = 0;
    for (let i = 0; i < lines.length; i++) {
        const m = lines[i].match(/^(\s*)def\s+\w+\s*\(/);
        if (!m) continue;
        total++;
        const baseIndent = m[1].length;
        const bodyLines: string[] = [];
        for (let j = i + 1; j < lines.length; j++) {
            const ln = lines[j];
            if (!ln.trim()) continue;
            const indent = (ln.match(/^\s*/)?.[0].length) ?? 0;
            if (indent <= baseIndent) break;
            bodyLines.push(ln);
        }
        // docstring 한 줄 무시.
        const cleaned = bodyLines.filter((l) => {
            const t = l.trim();
            return !!t && !t.startsWith('"""') && !t.startsWith("'''") && !t.startsWith('#');
        });
        if (cleaned.length === 0 || cleaned.every((l) => _isStubLine(l))) {
            hollow++;
        }
    }
    return { total, hollow };
}

/**
 * JS/TS의 *간단한* hollow 함수 카운트. 정확한 AST 파싱은 비용 큼 →
 * 정규식만으로 충분히 잡히는 패턴 위주.
 * 패턴: `function X(...) { ...body... }` / `X(...) { ...body... }` (메서드)
 *       / `() => { ... }` 화살표
 * body 분석은 첫 \`{\` ~ 매칭되는 \`}\` 까지 brace 카운팅.
 */
function _countJsHollowFunctions(src: string): { total: number; hollow: number } {
    let total = 0, hollow = 0;
    // 시그니처 시작점 후보들.
    const sigRe = /(?:function\s*\w*\s*\([^)]*\)\s*\{|=>\s*\{|\b\w+\s*\([^)]*\)\s*\{)/g;
    let m: RegExpExecArray | null;
    while ((m = sigRe.exec(src)) !== null) {
        const openIdx = src.indexOf('{', m.index);
        if (openIdx === -1) continue;
        total++;
        // matching close
        let depth = 1, j = openIdx + 1;
        let inStr: string | null = null;
        for (; j < src.length; j++) {
            const ch = src[j];
            if (inStr) {
                if (ch === '\\') { j++; continue; }
                if (ch === inStr) inStr = null;
                continue;
            }
            if (ch === '"' || ch === "'" || ch === '`') { inStr = ch; continue; }
            if (ch === '{') depth++;
            else if (ch === '}') { depth--; if (depth === 0) break; }
        }
        if (depth !== 0) continue; // 짝 안 맞으면 skip
        const body = src.slice(openIdx + 1, j);
        // body 내용 의미 라인만.
        const meaningful = body.split(/\r?\n/).filter((l) => {
            const t = l.trim();
            if (!t) return false;
            if (t.startsWith('//') || t.startsWith('/*') || t.startsWith('*')) return false;
            if (_isStubLine(t)) return false;
            return true;
        });
        if (meaningful.length === 0) hollow++;
    }
    return { total, hollow };
}

/** 단일 파일 검사. 지원 안 하는 확장자면 ok 반환 (skip). */
export function checkHollow(absPath: string, projectRoot: string): HollowCheckResult {
    const relPath = path.relative(projectRoot, absPath);
    const ext = path.extname(absPath).toLowerCase();
    const result: HollowCheckResult = { relPath, ok: true, reasons: [] };
    if (!_isSupportedExt(ext)) return result;

    let src = '';
    try {
        src = fs.readFileSync(absPath, 'utf8');
    } catch {
        return result; // 못 읽으면 검사 skip (다른 검증 layer가 잡음)
    }

    const lines = src.split(/\r?\n/);
    const totalLines = lines.length;
    const codeLines = lines.filter((l) => _isMeaningfulCodeLine(l, ext)).length;
    const stubLines = lines.filter(_isStubLine).length;
    const todoMatches = (src.match(/\b(?:TODO|FIXME|XXX|HACK)\b/gi) ?? []).length;

    const reasons: string[] = [];

    // 패턴 1 — 코드 라인이 너무 적음 (단순 모듈 보호: 6줄 미만은 의심).
    if (codeLines < 4) {
        reasons.push(`의미 있는 코드가 너무 적음 (${codeLines}줄)`);
    }

    // 패턴 2 — stub 비율이 높음.
    const stubFnRes = (ext === '.py')
        ? _countPyHollowFunctions(src)
        : _countJsHollowFunctions(src);
    const stubFnRatio = stubFnRes.total > 0 ? stubFnRes.hollow / stubFnRes.total : 0;
    if (stubFnRes.total > 0 && stubFnRes.hollow === stubFnRes.total) {
        reasons.push(`모든 함수(${stubFnRes.total}개)가 stub만 있음`);
    } else if (stubFnRes.total >= 2 && stubFnRatio >= 0.5) {
        reasons.push(`함수 ${stubFnRes.hollow}/${stubFnRes.total}개가 stub`);
    }

    // 패턴 3 — TODO/FIXME 텍스트 라벨이 코드 라인 수를 압도.
    const todoRatio = codeLines > 0 ? todoMatches / codeLines : 0;
    if (todoMatches >= 2 && todoRatio >= 0.5) {
        reasons.push(`TODO/FIXME가 너무 많음 (${todoMatches}개)`);
    }

    // 패턴 4 — 파일 전체가 import만.
    const allImports = lines.every((l) => {
        const t = l.trim();
        if (!t) return true;
        if (t.startsWith('#') || t.startsWith('//')) return true;
        if (ext === '.py') {
            return /^(?:from\s+\S+\s+import\s|import\s)/.test(t);
        }
        return /^(?:import\s|export\s+\{|export\s+\*|export\s+default\s+from\s|const\s+\w+\s*=\s*require\()/.test(t);
    });
    if (allImports && codeLines > 0) {
        reasons.push('파일에 import 외 실제 로직이 없음');
    }

    if (reasons.length > 0) {
        result.ok = false;
        result.reasons = reasons;
    }
    result.meta = { totalLines, codeLines, stubFnRatio, todoRatio };
    return result;
}

/**
 * report 한 줄에서 `✅ Created: foo.py` / `✅ Edited: foo.py` 형태 경로 추출.
 * Phase C와 동일 로직.
 */
function _extractPathFromReportLine(line: string): string | null {
    const m = line.match(/^\s*[✅⚠️]\s*(?:Created|Edited|Updated)\s*:\s*(.+)$/);
    return m ? m[1].trim() : null;
}

/**
 * report 내 모든 생성/편집 파일에 대해 hollow 검사 실행. 추가 report 라인을
 * 반환 — 호출자가 actionReport에 그대로 append.
 *
 * @returns 빈 깡통 경고 라인들 + ok 통과 라인. 검사 대상 없으면 빈 배열.
 */
export function verifyHollow(report: string[], projectRoot: string): {
    extraLines: string[];
    hasHollow: boolean;
    hollowReasons: string[];
} {
    const extraLines: string[] = [];
    const hollowReasons: string[] = [];
    for (const line of report) {
        const rel = _extractPathFromReportLine(line);
        if (!rel) continue;
        const abs = path.isAbsolute(rel) ? rel : path.join(projectRoot, rel);
        if (!abs.startsWith(projectRoot)) continue;
        if (!fs.existsSync(abs)) continue;
        const check = checkHollow(abs, projectRoot);
        const ext = path.extname(abs).toLowerCase();
        if (!_isSupportedExt(ext)) continue;
        if (check.ok) {
            // 통과는 표시 안 함 (노이즈) — 실패만 보고.
        } else {
            const reasonStr = check.reasons.join(' / ');
            extraLines.push(`❌ Hollow code: ${check.relPath} — ${reasonStr}`);
            for (const r of check.reasons) hollowReasons.push(`${check.relPath}: ${r}`);
        }
    }
    return { extraLines, hasHollow: extraLines.length > 0, hollowReasons };
}
