"""
gdep.init_context
gdep init / gdep context 커맨드 구현.

- gdep init  <path>    : .gdep/AGENTS.md 생성 (AI Agent용 프로젝트 컨텍스트)
- gdep context <path>  : 현재 프로젝트 컨텍스트를 stdout으로 출력
"""
from __future__ import annotations

from datetime import datetime
from pathlib import Path

from . import runner
from .detector import ProjectKind, detect

# ─────────────────────────────────────────────────────────────
# AGENTS.md 최상단 — 모든 AI 클라이언트에 공통으로 적용되는 지시문
# ─────────────────────────────────────────────────────────────

_PREAMBLE = """\
<!--
  This file is auto-generated by `gdep init`.
  It is read automatically by Claude Desktop, Cursor, Claude Code,
  Gemini CLI, and any AI agent that respects AGENTS.md / .cursorrules
  conventions. Do not delete — regenerate with `gdep init --force`.
-->

# gdep Agent Context
> Generated: {date}  |  Engine: {engine}  |  Project: {name}

## ⚡ CRITICAL: Read This First

This project uses **gdep MCP** for codebase analysis.
gdep is a static analyser that understands game engine internals:
prefab bindings, Blueprint↔C++ bridges, GAS flows, Animator state machines, etc.

### Non-negotiable rules for every AI assistant working on this project

1. **NEVER read raw source files to answer structural questions.**
   gdep already parsed everything. Use its tools — they are faster and more accurate.

2. **ALWAYS call `get_project_context` first** when starting a new session
   or when you are unsure what the project structure looks like.

3. **Use the trigger→tool mapping below** to pick the right tool automatically.
   Do not ask the user which tool to use — infer it from their words.

4. **All tools accept `project_path` as first argument.**
   The correct path for this project is in the "Project Info" section below.
   Use it verbatim — do not guess or construct paths.

5. **Chain tools when needed.** A question like "is it safe to refactor X?"
   warrants `analyze_impact_and_risk` followed by `inspect_architectural_health`.

"""


# ─────────────────────────────────────────────────────────────
# 트리거 → 도구 매핑 (공통 + 엔진별)
# ─────────────────────────────────────────────────────────────

_TRIGGER_TABLE_COMMON = """\
## Trigger → Tool Mapping

When you detect **any** of the following patterns in the user's message,
call the corresponding tool **immediately** without asking for confirmation.

### Universal triggers (always available)

| If the user says / asks … | Call this tool |
|---------------------------|---------------|
| "what does class X do?" / "show me X" / "explain X" | `explore_class_semantics(path, "X")` |
| "modify / refactor / rename / delete class X" | `analyze_impact_and_risk(path, "X")` |
| "what breaks if I change X?" / "is it safe to touch X?" | `analyze_impact_and_risk(path, "X")` |
| "how does [feature] work?" / "trace [method]" | `trace_gameplay_flow(path, "Class", "Method")` |
| "what calls X?" / "where is X called from?" | `trace_gameplay_flow(path, "X", ...)` |
| "technical debt" / "codebase health" / "circular deps" / "dead code" | `inspect_architectural_health(path)` |
| "scan" / "overview" / "coupling" / "dependencies" | `execute_gdep_cli(["scan", path, "--circular", "--dead-code"])` |
| "lint" / "anti-patterns" / "code smell" / "PR check" | `execute_gdep_cli(["lint", path])` |
| "impact of [commit/PR]" / "what changed?" | `execute_gdep_cli(["diff", path, "--commit", "HEAD~1"])` |
| anything about a class you've never seen before | `explore_class_semantics(path, "ClassName")` **first** |

### Rule: before writing ANY code that touches an existing class

Always call `analyze_impact_and_risk` first.
Only skip this if the user explicitly says "I know the impact, just write it."

"""

_TRIGGER_TABLE_UE5 = """\
### UE5-specific triggers

| If the user says / asks … | Call this tool |
|---------------------------|---------------|
| "GAS" / "Ability" / "GameplayEffect" / "AttributeSet" / "GameplayTag" | `analyze_ue5_gas(path)` |
| "which Blueprint inherits X?" / "BP implementation of X" | `analyze_ue5_blueprint_mapping(path, "X")` |
| "Blueprint event" / "K2 override" / "what does BP_X do?" | `analyze_ue5_blueprint_mapping(path, "X")` |
| "BehaviorTree" / "BT_" / "AIController" / "Blackboard" | `analyze_ue5_behavior_tree(path)` |
| "StateTree" / "ST_" / "AI state" | `analyze_ue5_state_tree(path)` |
| "animation" / "ABP" / "Montage" / "slot" / "GAS notify" | `analyze_ue5_animation(path)` |
| "C++ to Blueprint" / "native to BP" / "K2_Activate" | `analyze_ue5_blueprint_mapping(path)` |

### UE5 recommended session flow

```
# New session on a UE5 project
1. get_project_context(path)                    # mandatory first call
2. explore_class_semantics(path, "AMyChar")     # understand entry point
3. analyze_ue5_gas(path)                        # if GAS is used
4. analyze_ue5_blueprint_mapping(path, "AMyChar") # find BP implementations
5. trace_gameplay_flow(path, "AMyChar", "BeginPlay") # trace a specific flow
```

"""

_TRIGGER_TABLE_UNITY = """\
### Unity-specific triggers

| If the user says / asks … | Call this tool |
|---------------------------|---------------|
| "Inspector binding" / "Button.onClick" / "UnityEvent" / "who calls method X?" | `find_unity_event_bindings(path)` |
| "Animator" / "animation state" / "state machine" / ".controller" | `analyze_unity_animator(path)` |
| "Prefab ref" / "which prefab uses X?" | `execute_gdep_cli(["scan", path, "--include-refs"])` |

### Unity recommended session flow

```
# New session on a Unity project
1. get_project_context(path)                         # mandatory first call
2. explore_class_semantics(path, "GameManager")      # understand entry point
3. find_unity_event_bindings(path)                   # reveal Inspector wiring
4. analyze_unity_animator(path)                      # if animation is relevant
5. analyze_impact_and_risk(path, "ClassName")        # before any refactor
```

"""


# ─────────────────────────────────────────────────────────────
# 프로젝트 스냅샷 + MCP 가이드 조립
# ─────────────────────────────────────────────────────────────

def _build_agents_md(project_path: str) -> str:
    profile = detect(project_path)
    src_path = str(profile.source_dirs[0]) if profile.source_dirs else str(profile.root)
    lines: list[str] = []

    # 1. 공통 전문(preamble)
    lines.append(_PREAMBLE.format(
        date=datetime.now().strftime("%Y-%m-%d"),
        engine=profile.display,
        name=profile.name,
    ))

    # 2. 프로젝트 기본 정보
    lines += [
        "## Project Info",
        "",
        "| Field | Value |",
        "|-------|-------|",
        f"| Engine | {profile.display} |",
        f"| Project Name | {profile.name} |",
        f"| Root | `{profile.root}` |",
        f"| Source Path | `{src_path}` |",
        "",
        f"> **Use `{src_path}` as `project_path` in every gdep tool call.**",
        "",
    ]

    # 3. 빠른 스캔 스냅샷
    _append_scan_snapshot(lines, profile, src_path)

    # 4. 엔진 전용 컨텍스트
    if profile.kind == ProjectKind.UNREAL:
        _append_ue5_context(lines, src_path)
    elif profile.kind == ProjectKind.UNITY:
        _append_unity_context(lines, src_path)

    # 5. 트리거 → 도구 매핑
    lines.append(_TRIGGER_TABLE_COMMON)
    if profile.kind == ProjectKind.UNREAL:
        lines.append(_TRIGGER_TABLE_UE5)
    elif profile.kind == ProjectKind.UNITY:
        lines.append(_TRIGGER_TABLE_UNITY)

    # 6. 도구 빠른 참조
    lines += _build_tool_reference(src_path, profile.kind)

    return "\n".join(lines)


def _append_scan_snapshot(lines: list[str], profile, src_path: str) -> None:
    """스캔 결과 스냅샷 (클래스 수, 결합도 Top5, 순환참조)."""
    try:
        scan_result = runner.scan(profile, circular=True, dead_code=True,
                                  deep=False, top=10, fmt="json")
        if not (scan_result.ok and scan_result.data):
            return
        d = scan_result.data
        summary = d.get("summary", {})
        coupling = d.get("coupling", [])[:5]
        cycles   = d.get("cycles", [])[:3]
        dead     = d.get("deadNodes", [])[:3]

        lines += [
            "## Codebase Snapshot",
            "",
            f"| Metric | Value |",
            f"|--------|-------|",
            f"| Files | **{summary.get('fileCount', '?')}** |",
            f"| Classes | **{summary.get('classCount', '?')}** |",
            f"| Circular deps | **{summary.get('cycleCount', 0)}** |",
            f"| Dead code candidates | **{summary.get('deadCount', 0)}** |",
            "",
        ]
        if coupling:
            lines += ["### High-coupling classes (likely God Objects — modify with care)", ""]
            for item in coupling:
                lines.append(f"- `{item['name']}` — {item['score']} dependents")
            lines.append("")
        if cycles:
            lines += ["### Known circular dependencies", ""]
            for c in cycles:
                lines.append(f"- {c}")
            lines.append("")
    except Exception:
        pass


def _append_ue5_context(lines: list[str], src_path: str) -> None:
    """UE5 전용: GAS / BT / StateTree 요약."""
    try:
        from .ue5_gas_analyzer import analyze_gas
        gas = analyze_gas(src_path)
        gas_lines = [l for l in gas.splitlines()
                     if any(l.startswith(p) for p in
                            ("- Abilities", "- Effects", "- AttributeSets", "- GameplayTags"))]
        if gas_lines:
            lines += ["## GAS Summary", ""]
            lines += gas_lines
            lines.append("")
    except Exception:
        pass

    try:
        from .ue5_ai_analyzer import analyze_behavior_tree, analyze_state_tree
        for label, fn in [("BehaviorTrees", analyze_behavior_tree),
                          ("StateTrees",    analyze_state_tree)]:
            result = fn(src_path)
            names = [l.strip("# ").strip()
                     for l in result.splitlines() if l.startswith("## ")]
            if names and "No " not in result[:60]:
                lines += [f"## {label} found", ""]
                for n in names:
                    lines.append(f"- `{n}`")
                lines.append("")
    except Exception:
        pass


def _append_unity_context(lines: list[str], src_path: str) -> None:
    """Unity 전용: Animator Controller 목록."""
    try:
        from .unity_animator import _find_controllers
        controllers = _find_controllers(src_path)
        if controllers:
            lines += ["## Animator Controllers found", ""]
            for c in controllers[:10]:
                lines.append(f"- `{c.stem}`")
            lines.append("")
    except Exception:
        pass


def _build_tool_reference(src_path: str, kind: ProjectKind) -> list[str]:
    """모든 도구의 빠른 복사-붙여넣기 참조 섹션."""
    p = f'"{src_path}"'
    lines = [
        "## Tool Quick Reference",
        "",
        "Copy-paste ready. Replace `<X>` with actual class / method names.",
        "",
        "```python",
        "# ── Always call first ─────────────────────────────────",
        f"get_project_context({p})",
        "",
        "# ── Structure & health ────────────────────────────────",
        f'explore_class_semantics({p}, "<ClassName>")',
        f'analyze_impact_and_risk({p}, "<ClassName>")',
        f'inspect_architectural_health({p})',
        f'execute_gdep_cli(["scan", {p}, "--circular", "--dead-code", "--top", "20"])',
        f'execute_gdep_cli(["lint", {p}])',
        "",
        "# ── Flow tracing ──────────────────────────────────────",
        f'trace_gameplay_flow({p}, "<Class>", "<Method>")',
        f'trace_gameplay_flow({p}, "<Class>", "<Method>", depth=6)',
        "",
        "# ── Impact & diff ─────────────────────────────────────",
        f'execute_gdep_cli(["impact", {p}, "<ClassName>", "--depth", "5"])',
        f'execute_gdep_cli(["diff",   {p}, "--commit", "HEAD~1", "--fail-on-cycles"])',
    ]

    if kind == ProjectKind.UNREAL:
        lines += [
            "",
            "# ── UE5 specific ──────────────────────────────────────",
            f'analyze_ue5_gas({p})',
            f'analyze_ue5_blueprint_mapping({p})',
            f'analyze_ue5_blueprint_mapping({p}, "<CppClass>")',
            f'analyze_ue5_behavior_tree({p})',
            f'analyze_ue5_state_tree({p})',
            f'analyze_ue5_animation({p})',
            f'analyze_ue5_animation({p}, detail_level="full")',
        ]
    elif kind == ProjectKind.UNITY:
        lines += [
            "",
            "# ── Unity specific ────────────────────────────────────",
            f'find_unity_event_bindings({p})',
            f'find_unity_event_bindings({p}, method_name="<MethodName>")',
            f'analyze_unity_animator({p})',
            f'execute_gdep_cli(["scan", {p}, "--include-refs"])',
        ]

    lines += [
        "```",
        "",
        "---",
        "*Regenerate this file: `gdep init <project_path> --force`*",
        "",
    ]
    return lines


# ─────────────────────────────────────────────────────────────
# Public API
# ─────────────────────────────────────────────────────────────

def build_context_output(project_path: str) -> str:
    """
    get_project_context MCP 도구 및 `gdep context` CLI에서 호출.
    .gdep/AGENTS.md가 있으면 그걸 읽고, 없으면 자동 생성 후 반환.
    """
    profile = detect(project_path)
    agents_md = Path(profile.root) / ".gdep" / "AGENTS.md"

    if agents_md.exists():
        return agents_md.read_text(encoding="utf-8")

    # AGENTS.md 없으면 자동 생성 (저장 포함)
    try:
        agents_md = write_agents_md(project_path, force=False)
        return agents_md.read_text(encoding="utf-8")
    except Exception:
        # 저장 실패 시(권한 없는 경로 등) 즉석 생성만
        return _build_agents_md(project_path)


def write_agents_md(project_path: str, force: bool = False) -> Path:
    """
    `gdep init` CLI에서 호출.
    .gdep/AGENTS.md를 생성하고 경로를 반환한다.
    force=True 이면 기존 파일을 덮어쓴다.
    """
    profile = detect(project_path)
    gdep_dir = Path(profile.root) / ".gdep"
    gdep_dir.mkdir(exist_ok=True)
    agents_md = gdep_dir / "AGENTS.md"

    if agents_md.exists() and not force:
        return agents_md   # 이미 있으면 그냥 반환 (CLI에서 메시지 출력)

    content = _build_agents_md(project_path)
    agents_md.write_text(content, encoding="utf-8")
    return agents_md

