#!/usr/bin/env python3
import argparse
import json
import re
from pathlib import Path


DEFAULT_EXTS = {
    ".php", ".java", ".go", ".py", ".js", ".ts", ".cs", ".rb", ".kt", ".swift"
}

EXCLUDE_PARTS = {
    "vendor", "node_modules", "runtime", "cache", "logs", "log", "dist", "build",
    ".git", "public", "static", "extend", "ai_app", "ai_store", "android",
    "unpackage", "coverage", "tests", "test"
}

LOW_VALUE_PATTERNS = [
    "login", "passport", "auth", "password", "register", "middleware",
    "basecontroller", "basemodel", "helper", "util", "utils", "config"
]

HIGH_VALUE_PATH_HINTS = [
    "service", "logic", "model", "domain", "algorithm", "stats", "rank",
    "score", "weight", "activity", "course", "meal"
]


def excluded(path: Path, root: Path) -> bool:
    try:
        parts = {part.lower() for part in path.relative_to(root).parts}
    except ValueError:
        parts = {part.lower() for part in path.parts}
    return any(part in parts for part in EXCLUDE_PARTS)


def score_file(path: Path, keywords):
    text = path.read_text(encoding="utf-8", errors="ignore")
    lower_path = str(path).lower()
    lower_text = text.lower()
    keyword_hits = 0
    matched = []
    for keyword in keywords:
        key = keyword.strip()
        if not key:
            continue
        count = lower_text.count(key.lower()) + lower_path.count(key.lower()) * 3
        if count:
            keyword_hits += count
            matched.append(key)
    if not matched:
        return None
    path_bonus = sum(8 for hint in HIGH_VALUE_PATH_HINTS if hint in lower_path)
    low_penalty = sum(10 for bad in LOW_VALUE_PATTERNS if bad in lower_path)
    method_count = len(re.findall(r"function\s+[A-Za-z_][A-Za-z0-9_]*\s*\(", text))
    class_count = len(re.findall(r"class\s+[A-Za-z_][A-Za-z0-9_]*", text))
    nonblank = sum(1 for line in text.splitlines() if line.strip())
    score = keyword_hits + path_bonus + method_count * 2 + class_count * 2 - low_penalty
    return {
        "path": str(path),
        "score": score,
        "matched_keywords": matched,
        "nonblank_lines": nonblank,
        "method_count": method_count,
        "class_count": class_count,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--root", required=True)
    parser.add_argument("--keywords", required=True, help="comma-separated keywords")
    parser.add_argument("--out", required=True)
    parser.add_argument("--limit", type=int, default=80)
    parser.add_argument("--exts", default=",".join(sorted(DEFAULT_EXTS)))
    args = parser.parse_args()

    root = Path(args.root).resolve()
    keywords = [item.strip() for item in args.keywords.split(",") if item.strip()]
    exts = {item.strip() for item in args.exts.split(",") if item.strip()}
    results = []
    for path in root.rglob("*"):
        if not path.is_file() or path.suffix not in exts or excluded(path, root):
            continue
        try:
            item = score_file(path, keywords)
        except OSError:
            continue
        if item:
            results.append(item)
    results.sort(key=lambda item: item["score"], reverse=True)
    payload = {
        "root": str(root),
        "keywords": keywords,
        "count": len(results),
        "candidates": results[: args.limit],
    }
    out = Path(args.out)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    print(out)


if __name__ == "__main__":
    main()
