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

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

import json
import time
from datetime import datetime
from pathlib import Path

from . import __version__, 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}

## Project Analysis Tools

gdep is a **static project structure analyser** — think of it as a map and
reconnaissance drone for your codebase. It scans source files and engine assets
to tell you *what exists* and *how things connect*, so the AI agent doesn't
have to read every file.

- **What gdep knows:** classes, inheritance hierarchies, dependencies, call graphs,
  Blueprint↔C++ bridges, GAS flows, Animator state machines, engine asset references
  (prefabs, Blueprints, .uasset), design patterns, unused assets, API search
- **What gdep does NOT know:** runtime behavior, Blueprint visual-script internals,
  GameplayEffect numerical values, or "why does this crash"

### Available capabilities

1. **gdep tools provide cached, fast structural analysis** — they understand
   game-engine semantics (Blueprint↔C++ bridges, GAS flows, Animator state machines).
   Consider calling `get_project_context` for a cached structural overview at the start of a new session.

2. **Check the Confidence level in every gdep response before acting on it:**
   - **HIGH** (C++ / Roslyn source parsing) — trust without additional verification
   - **MEDIUM** (binary pattern match + cross-reference) — reliable for most queries;
     cross-check source files before architecture-level decisions
   - **LOW** (filename heuristics, LFS stubs > 50%) — treat as an index only;
     always read the relevant source files before making changes

3. **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.

4. **For a complete list of all tools and parameters,
   read `.gdep/HELP.md`** (generated alongside this file).

5. **Output volume control:** Some tools offer summary vs full output modes.
   Use summary mode first to save tokens, then request full output only when needed.
   - `explore_class_semantics(compact=True)` — default. Limits fields/methods/refs
     shown per section (15/25/10). Use `compact=False` only when the full listing is needed.
   - `explore_class_semantics(include_source=True)` — appends source code after structure.
   - `explain_method_logic(include_source=True)` — appends method body after control flow.
   - `read_class_source(method_name="Foo")` — returns only that method's body (token-efficient).
   - `trace_gameplay_flow(summary=True)` — compact 2-level tree + stats (< 500 tokens).
     Full mode can produce 2000+ tokens at depth 5.
   - `analyze_impact_and_risk(detail_level="summary")` — quick count vs full blast-radius.
   - `analyze_ue5_gas(detail_level="summary")` — overview vs full tag/ability listing.
   - `find_method_callers` — always returns concise, structured output.
   - `find_call_path` — concise path output (**C#/Unity only**).

6. **Cache & freshness — you can trust gdep results.**
   gdep uses a multi-layer cache invalidation system:

   - **Tool results** (trace, callers, scan, GAS, BP, etc.):
     Auto-invalidated by MD5 fingerprint of source file mtimes.
     If any `.cs`, `.h`, `.cpp`, `.uasset`, or `.umap` file is modified,
     the next tool call automatically rescans — no manual action needed.

   - **This AGENTS.md file**:
     Auto-refreshed when `get_project_context` detects source file changes
     or a gdep version update. No manual `--force` required.
     Freshness is checked via `.gdep/.agents_meta.json` (fingerprint + version).

   - **gdep version check**:
     All caches embed the gdep version (`_gdep_ver`) that created them.
     When gdep is updated, caches from the old version are automatically
     invalidated on next use — ensuring parser/format changes are picked up.

   - **LLM summaries** (`explore_class_semantics`):
     Per-class file cache. Use `refresh=True` to force regeneration.

   **Manual refresh:** `gdep init <project_path> --force`
   (only needed if auto-detection misses edge cases).

"""


# ─────────────────────────────────────────────────────────────
# Quick Reference + 엔진별 세션 플로우
# ─────────────────────────────────────────────────────────────

_QUICK_REF_COMMON = """\
## Quick Reference
Common: `explore_class_semantics` · `trace_gameplay_flow` · `analyze_impact_and_risk` · `inspect_architectural_health` · `get_architecture_advice` · `explain_method_logic` · `suggest_lint_fixes` · `summarize_project_diff`
Source code: `read_class_source(path, cls)` — full class source · `read_class_source(path, cls, method_name=m)` — single method body · `explore_class_semantics(path, cls, include_source=True)` — structure + source
Method-level: `find_method_callers(path, cls, method)` — who calls this method · `find_call_path(path, fromCls, fromMethod, toCls, toMethod)` — shortest A→B path · `analyze_impact_and_risk(path, cls, method_name=m)` — callers + impact tree
Structure: `find_class_hierarchy(path, cls)` — inheritance tree (ancestors + descendants) · `query_project_api(path, "keyword")` — search project API by name · `detect_patterns(path)` — detect architectural patterns
Assets: `find_unused_assets(path)` — find unreferenced assets (Unity/UE5)

"""

_SESSION_FLOW_UE5 = """\
## UE5 Tools & Session Flow
UE5: `analyze_ue5_gas` · `analyze_ue5_blueprint_mapping` · `analyze_ue5_behavior_tree` · `analyze_ue5_state_tree` · `analyze_ue5_animation`
Discovery: `find_class_hierarchy` · `query_project_api` · `detect_patterns` · `find_unused_assets`

```
# New UE5 session
1. get_project_context(path)
2. explore_class_semantics(path, "AMyChar")
3. find_class_hierarchy(path, "AMyChar")
4. analyze_ue5_gas(path)
5. analyze_ue5_blueprint_mapping(path, "AMyChar")
6. trace_gameplay_flow(path, "AMyChar", "BeginPlay")
```

"""

_SESSION_FLOW_UNITY = """\
## Unity Tools & Session Flow
Unity: `find_unity_event_bindings` · `analyze_unity_animator`
Discovery: `find_class_hierarchy` · `query_project_api` · `detect_patterns` · `find_unused_assets`

```
# New Unity session
1. get_project_context(path)
2. explore_class_semantics(path, "GameManager")
3. find_class_hierarchy(path, "GameManager")
4. find_unity_event_bindings(path)
5. analyze_unity_animator(path)
6. analyze_impact_and_risk(path, "ClassName")
```

"""

_SESSION_FLOW_AXMOL = """\
## Axmol Tools & Session Flow
Axmol: `analyze_axmol_events`

```
# New Axmol session
1. get_project_context(path)
2. explore_class_semantics(path, "AppDelegate")
3. analyze_axmol_events(path)
4. analyze_impact_and_risk(path, "ClassName")
```

"""


# ─────────────────────────────────────────────────────────────
# HELP.md 템플릿 — 도구 레퍼런스 (AI가 필요할 때 참조)
# ─────────────────────────────────────────────────────────────

_HELP_MD_HEADER = """\
<!--
  This file is auto-generated by `gdep init`.
  Complete tool reference for AI agents. Read when you need to know
  what tools are available or what parameters they accept.
  Regenerate with `gdep init --force`.
-->

# gdep Tool Reference
> Engine: {engine}

## Freshness & Cache
- **AGENTS.md**: Static snapshot. Regenerate with `gdep init <path> --force` after structural changes.
- **Tool results**: Auto-invalidate on source file mtime change. No manual action needed.
- **LLM summaries**: Per-class cache. Use `explore_class_semantics(path, cls, refresh=True)` to force refresh.
- **When results feel wrong**: Run `gdep init <path> --force` first.

## Reading Guide
- `*` = required parameter
- Default values shown after `=`
- `project_path`: use the path from AGENTS.md "Project Info" section

"""

_HELP_MD_COMMON = """\
## Common Tools

### get_project_context
Cached project overview — recommended first call in a new session.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | Any path within the game project |

### explore_class_semantics
Full class structure: fields, methods, dependencies, asset usages.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | e.g. "BattleManager", "AHSAttributeSet" |
| summarize | bool | True | AI 3-line role summary (requires LLM config) |
| refresh | bool | False | Bypass summary cache |
| include_source | bool | False | Append class source code after structure |
| max_source_chars | int | 6000 | Max chars for appended source |
| compact | bool | True | Limit items per section (15 fields, 25 methods, 10 refs) |
> Tip: Default `compact=True` keeps output AI-friendly (~4-8KB). Use `compact=False` only when you need the complete field/method listing.

### analyze_impact_and_risk
Blast radius analysis before modifying a class or method.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | |
| method_name | str | None | If provided, also traces method-level callers of class_name::method_name |
| detail_level | str | "summary" | "summary" (fast) or "full" (expensive) |
| query | str | None | Filter results by class name/pattern |
| max_results | int | 0 | Limit shown items (0=auto: 5 for summary, 20 for full) |
> Tip: Use `method_name=` to find exactly who calls a specific method (C++ and C# supported).
> Always use `detail_level="summary"` first to gauge scope before requesting "full".
> Now includes Source↔Asset cross-reference (Blueprint/Prefab assets referencing the target class).

### trace_gameplay_flow
Trace a method's full call chain with source code.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | Entry-point class |
| method_name* | str | — | Entry-point method |
| depth | int | 5 | Max 6 recommended |
| include_source | bool | True | Append source of entry class |
| summary | bool | False | Compact 2-level tree + stats (saves tokens) |
> Tip: Use `summary=True` for agent workflows to reduce context window usage.
> Full mode can produce 2000+ tokens at depth 5; summary mode stays under 500.

### find_method_callers
Find all methods that call a specific method (reverse call graph).
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | Class containing the target method |
| method_name* | str | — | Method to find callers of |
| max_results | int | 30 | Limit shown callers (reports omitted count) |
> Use this to understand blast radius before modifying a method.

### read_class_source
Return actual source code of a class or a specific method within it.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | e.g. "BattleManager", "AHSCharacterBase" |
| max_chars | int | 8000 | Max characters to return (max recommended 15000) |
| method_name | str | None | If provided, returns only that method's body |
> Use `method_name=` for token-efficient method-level reads.
> Use after `explore_class_semantics` or `find_method_callers` to read actual implementation.

### find_call_path
Find the shortest call path between two methods (A → B connection).
**C#/Unity only** — C++ and UE5 projects are not supported yet.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| from_class* | str | — | Source class |
| from_method* | str | — | Source method |
| to_class* | str | — | Target class |
| to_method* | str | — | Target method |
| depth | int | 10 | Max BFS search depth |
> Example: `find_call_path(path, "UIBattle", "OnClick", "ManagerBattle", "PlayHand")`

### inspect_architectural_health
Full health check: coupling, cycles, dead code, anti-patterns.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| include_dead_code | bool | True | |
| include_refs | bool | True | Factor in asset refs for dead-code |
| top | int | 15 | High-coupling classes to show |
> Note: May take 10-60s on large projects.

### explain_method_logic
Internal control flow of a specific method (Guard/Branch/Loop/Always).
Supports C++ `ClassName::method` and namespace-style functions (`namespace Foo { void bar() {} }`).
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | Use namespace name for namespace-style C++ functions |
| method_name* | str | — | |
| include_source | bool | False | Append the method body source code after control flow |
| max_source_chars | int | 4000 | Max chars for appended source |
> If method not found in the given class, the tool suggests classes that DO contain it.
> Use `include_source=True` to get both the control flow summary and actual code in one call.

### get_architecture_advice
Architecture diagnosis with refactoring suggestions.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| focus_class | str | None | Auto-selects highest-coupling if None |

### suggest_test_scope
Which test files to run when a class is modified.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | |
| depth | int | 3 | Reverse-dep tracing depth |

### suggest_lint_fixes
Anti-pattern detection with actionable code fix suggestions.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| rule_ids | list[str] | None | e.g. ["UNI-PERF-001"]. See Lint Rule IDs below |

### summarize_project_diff
Git diff architectural impact analysis.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| commit_ref | str | None | Default: HEAD~1. e.g. "main", commit SHA |

### find_class_hierarchy
Full inheritance hierarchy — ancestor chain and/or descendant tree.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name* | str | — | e.g. "APlayerCharacter", "ManagerBattle" |
| direction | str | "both" | "up" (ancestors), "down" (descendants), or "both" |
| max_depth | int | 10 | Maximum traversal depth |
> Use "up" before refactoring a class to see what it inherits.
> Use "down" before changing a base class to see all affected subclasses.

### find_unused_assets
Detect unreferenced assets for project cleanup (Unity/UE5 only).
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | Project root or Source/Scripts path |
| scan_dir | str | None | Optional subdirectory to limit scan |
| max_results | int | 50 | Pass 0 for unlimited |
> ⚠ Assets loaded dynamically (Resources.Load, soft references, Addressables) may be falsely reported. Verify before deleting.
> Reports wasted disk space and groups by file type.

### query_project_api
Search project code as an API reference — find classes, methods, properties by keyword.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| query* | str | — | e.g. "Health", "Attack", "Save" |
| scope | str | "all" | "all", "classes", "methods", or "properties" |
| max_results | int | 20 | Pass 0 for unlimited |
> Searches project code only (not engine source). Results ranked by relevance.
> UE5: auto-categorizes by prefix (A=Actor, U=Object, F=Struct, E=Enum, I=Interface).

### detect_patterns
Detect design patterns and architectural patterns used in the project.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| max_results | int | 30 | Pass 0 for unlimited |
> UE5 patterns: Subsystem, Component Composition, GAS Ability/AttributeSet, Event Delegate, Replication, Interface, BehaviorTree
> Unity patterns: Singleton, Coroutine Workflow, ScriptableObject, Event/Observer, Object Pooling, State Machine

### execute_gdep_cli
Raw CLI access — run any gdep command directly.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| args* | list[str] | — | CLI args excluding 'gdep'. See CLI Commands below |

"""

_HELP_MD_UE5 = """\
---

## UE5-Specific Tools

### analyze_ue5_gas
Gameplay Ability System analysis: tags, abilities, effects, attribute sets.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name | str | None | Filter to specific class |
| detail_level | str | "summary" | "summary" or "full" |
| category | str | None | Tag prefix filter (e.g. "Event") |
| query | str | None | Keyword search (case-insensitive) |
> Tip: Always call with "summary" first. Use category/query to narrow before "full".

### analyze_ue5_blueprint_mapping
Blueprint ↔ C++ detailed mapping.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| cpp_class | str | None | None → project-wide index; with class → full detail |

### analyze_ue5_behavior_tree
BehaviorTree .uasset structure extraction.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| asset_name | str | None | .uasset name (no extension); None → all BTs |

### analyze_ue5_state_tree
StateTree .uasset structure (UE 5.2+).
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| asset_name | str | None | None → all StateTrees |

### analyze_ue5_animation
AnimBlueprint (ABP) and Animation Montage analysis.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| asset_name | str | None | Filter to specific asset |
| asset_type | str | "all" | "abp", "montage", or "all" |
| detail_level | str | "summary" | "summary" (gameplay-relevant) or "full" (all notifies + refs) |

"""

_HELP_MD_UNITY = """\
---

## Unity-Specific Tools

### find_unity_event_bindings
Inspector event wiring (UnityEvent, Button.onClick) in prefabs/scenes/assets.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| method_name | str | None | Filter by method name; None → all bindings |

### analyze_unity_animator
Animator Controller structure: layers, states, transitions, blend trees.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| controller_name | str | None | Specific .controller file; None → all |
| detail_level | str | "summary" | "summary" → names + counts; "full" → deep analysis |

"""

_HELP_MD_AXMOL = """\
---

## Axmol-Specific Tools

### analyze_axmol_events
EventDispatcher, CC_CALLBACK, schedule/scheduleUpdate bindings.
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| project_path* | str | — | |
| class_name | str | None | Filter by class; None → all bindings |

"""

_HELP_MD_CLI = """\
---

## CLI Commands (via execute_gdep_cli)

### detect
Auto-detect project type (Unity, UE5, Axmol, C++, .NET, etc.).
| Option | Description |
|--------|-------------|
| --json | Output as JSON |
Example: `["detect", path]`

### scan
Analyze class coupling and detect structural issues.
| Option | Description |
|--------|-------------|
| --circular | Detect circular dependencies |
| --dead-code | Detect unreferenced classes (in-degree 0) |
| --deep | Analyze method bodies for deeper coupling |
| --include-refs | Factor in engine asset refs (prefabs/blueprints) for dead-code filtering |
| --top N | Show top N high-coupling classes (default: 20) |
| --namespace NS | Filter to specific namespace |
| --ignore PATTERN | Exclude file patterns (repeatable) |
| --format | console, json, mermaid, or dot (default: console) |
| --kind | Force project type: unity, dotnet, cpp, unreal |
Example: `["scan", path, "--circular", "--dead-code"]`

### describe
Show class structure: fields, methods, dependencies, asset usages.
| Option | Description |
|--------|-------------|
| --summarize | Prepend AI-generated 3-line role summary (cached) |
| --refresh | Bypass summary cache and regenerate |
| --format | console, mermaid, or dot (default: console) |
| --output FILE | Save output to file |
Example: `["describe", path, "ClassName"]`

### flow
Trace a method's call chain with async/lock/dispatch annotations.
| Option | Description |
|--------|-------------|
| --class CLASS | Entry-point class (required) |
| --method METHOD | Entry-point method (required) |
| --depth N | Tracing depth 1-10 (default: 4) |
| --focus-class CLASSES | Comma-separated classes to trace deeply |
| --format | console, json, mermaid, or dot |
| --output FILE | Save output to file |
| --kind | Force project type |
Example: `["flow", path, "--class", "Foo", "--method", "Bar"]`

### impact
Show change blast radius — who depends on this class, transitively.
| Option | Description |
|--------|-------------|
| --depth N | Reverse-dependency tracing depth (default: 3) |
| --kind | Force project type |
Example: `["impact", path, "ClassName"]`

### method-impact
Reverse-trace all callers of a specific method across the project.
C++ projects use regex-based call graph; C#/Unity projects use Roslyn.
Example: `["method-impact", path, "ClassName", "methodName"]`

### path
Find the shortest call path between two methods (BFS on source-level call graph).
**C#/Unity only** — C++ and UE5 projects are not supported yet.
| Option | Description |
|--------|-------------|
| --from | Source as Class.Method (required) |
| --to | Target as Class.Method (required) |
| --depth N | Max BFS search depth (default: 10) |
Example: `["path", path, "--from", "UIBattle.OnClick", "--to", "ManagerBattle.PlayHand"]`

### test-scope
Find test files that should run when a class is modified.
| Option | Description |
|--------|-------------|
| --depth N | Reverse-dependency depth (default: 3) |
| --format | console or json |
| --kind | Force project type |
Example: `["test-scope", path, "ClassName"]`

### lint
Scan for engine-specific anti-patterns (see Lint Rule IDs below).
| Option | Description |
|--------|-------------|
| --fix | Show actionable code fix suggestions (dry-run) |
| --format | console or json |
| --kind | Force project type |
Example: `["lint", path]`

### advise
Architecture diagnosis with data-driven or LLM-powered suggestions.
| Option | Description |
|--------|-------------|
| --focus CLASS | Center advice around this class |
| --refresh | Bypass cache and regenerate |
| --format | console or json |
| --kind | Force project type |
Example: `["advise", path]`

### graph
Export dependency graph in Mermaid or DOT format.
| Option | Description |
|--------|-------------|
| --format | mermaid or dot (default: mermaid) |
| --output FILE | Save to file |
| --cycles-only | Only include cycle-involved nodes |
| --no-isolated | Exclude zero-edge nodes |
Example: `["graph", path, "--cycles-only"]`

### diff
Compare architectural state against a git ref — detect new cycles, coupling changes.
| Option | Description |
|--------|-------------|
| --commit REF | Git ref to compare (default: HEAD~1) |
| --fail-on-cycles | Exit code 1 if new cycles introduced (for CI) |
Example: `["diff", path, "--commit", "HEAD~3"]`

### hints generate / hints show
Manage `.gdep-hints.json` — auto-detect static accessor patterns for flow tracer.
Example: `["hints", "generate", path]` / `["hints", "show", path]`

### config llm
View or configure LLM settings for AI summaries (describe --summarize, advise).
| Option | Description |
|--------|-------------|
| --provider | ollama, openai, gemini, or claude |
| --model | Model name |
| --api-key | API key (stored in ~/.gdep/llm_config.json) |
| --base-url | API base URL (Ollama only) |
Example: `["config", "llm"]`

### init
Generate `.gdep/AGENTS.md` and `.gdep/HELP.md` for AI agent context.
| Option | Description |
|--------|-------------|
| --force | Overwrite existing files |
Example: `["init", path, "--force"]`

### context
Print project context to stdout (reads AGENTS.md if exists, generates otherwise).
Example: `["context", path]`

### info
Show gdep environment info (binary path, DLL mode, LLM config).
Example: `["info"]`

---

## Lint Rule IDs

| ID | Engine | Severity | Fix Template | Description |
|----|--------|----------|-------------|-------------|
| UNI-PERF-001 | Unity | Warning | ✅ | GetComponent/Find inside Update |
| UNI-PERF-002 | Unity | Warning | ✅ | Object allocation inside Update |
| UNI-ASYNC-001 | Unity | Error | ✅ | IEnumerator while(true) with no yield |
| UNI-ASYNC-002 | Unity | Warning | — | Heavy Unity API inside Coroutine |
| UE5-PERF-001 | UE5 | Error | — | SpawnActor/LoadObject inside Tick |
| UE5-PERF-002 | UE5 | Warning | — | Sync LoadObject inside BeginPlay |
| UE5-BASE-001 | UE5 | Warning | ✅ | Missing Super:: in overridden lifecycle |
| UE5-GAS-001 | UE5 | Warning | — | ActivateAbility without CommitAbility |
| UE5-GAS-002 | UE5 | Warning | — | World query inside Ability |
| UE5-GAS-003 | UE5 | Info | — | Excessive BlueprintCallable (>10) |
| UE5-GAS-004 | UE5 | Info | — | BlueprintPure missing const |
| UE5-NET-001 | UE5 | Info | — | Replicated property without callback |
| UE5-BP-001 | UE5 | Warning | — | Blueprint references C++ class not found in source (orphan) |
| UE5-BP-002 | UE5 | Warning | — | Blueprint overrides K2_ method from deleted/changed function |
| UNI-ASSET-001 | Unity | Warning | — | Script GUID in prefab/scene with no matching .cs.meta file |
| AXM-PERF-001 | Axmol | Warning | ✅ | getChildByName/getChildByTag inside update() |
| AXM-MEM-001 | Axmol | Warning | ✅ | retain() called without matching release() |
| AXM-EVENT-001 | Axmol | Info | ✅ | addEventListenerWith* without removeEventListener |
| GEN-ARCH-001 | All | Warning | — | Circular dependency |

## Confidence Levels

| Level | Source | Guidance |
|-------|--------|----------|
| HIGH | C++/Roslyn source parsing | Trust without verification |
| MEDIUM | Binary pattern match + cross-ref | Reliable; cross-check for arch decisions |
| LOW | Filename heuristics, LFS stubs >50% | Index only; always read source first |
"""


def _build_help_md(project_path: str) -> str:
    """엔진에 맞는 HELP.md 내용을 조립하여 반환."""
    profile = detect(project_path)
    parts = [_HELP_MD_HEADER.format(engine=profile.display)]
    parts.append(_HELP_MD_COMMON)
    if profile.kind == ProjectKind.UNREAL:
        parts.append(_HELP_MD_UE5)
    elif profile.kind == ProjectKind.UNITY:
        parts.append(_HELP_MD_UNITY)
    elif profile.kind == ProjectKind.CPP:
        parts.append(_HELP_MD_AXMOL)
    parts.append(_HELP_MD_CLI)
    return "\n".join(parts)


# ─────────────────────────────────────────────────────────────
# 프로젝트 스냅샷 + 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. Quick Reference + 엔진별 세션 플로우
    lines.append(_QUICK_REF_COMMON)
    if profile.kind == ProjectKind.UNREAL:
        lines.append(_SESSION_FLOW_UE5)
    elif profile.kind == ProjectKind.UNITY:
        lines.append(_SESSION_FLOW_UNITY)
    elif profile.kind == ProjectKind.CPP:
        lines.append(_SESSION_FLOW_AXMOL)

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

    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, include_refs=True, 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 | **{len(d.get('cycles', []))}** |",
            f"| Dead code candidates | **{summary.get('deadCount', 0)}** |",
            "",
            "> **Dead code** = classes with in-degree 0 (no other class references them in code).",
            "> Unity: prefab/scene asset references are also checked — a class used only from a prefab is NOT dead code.",
            "> UE5: Blueprint, ABP, Montage, and GAS .uasset references are also checked — a class used only from a .uasset is NOT dead code.",
            "> Generic C++: only source-code references are checked.",
            "",
        ]
        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 요약 (confidence 정보 포함)."""
    try:
        from .ue5_gas_analyzer import _cached_gas_report
        report = _cached_gas_report(src_path)
        # Asset role counts
        ga_count  = sum(1 for r in report.asset_refs if r.asset_role == "GA")
        ge_count  = sum(1 for r in report.asset_refs if r.asset_role == "GE")
        as_count  = sum(1 for r in report.asset_refs if r.asset_role == "AS")
        abp_count = sum(1 for r in report.asset_refs if r.asset_role == "ABP")
        ref_count = sum(1 for r in report.asset_refs if r.asset_role == "ref")

        count_lines = [
            f"- Abilities (C++): {len(report.abilities)}",
            f"- Effects (C++): {len(report.effects)}",
            f"- AttributeSets: {len(report.attr_sets)}",
            f"- GameplayTags (in assets): {len(report.all_tags)} high-confidence"
            + (f" / {len(report.all_tags_low)} low" if report.all_tags_low else ""),
            f"- GAS .uassets — IS-A: GA {ga_count} / GE {ge_count} / AS {as_count} / ABP {abp_count}"
            f"  |  Referencers: {ref_count}",
        ]
        if count_lines:
            lines += ["## GAS Summary", ""]
            lines += count_lines
            if report.meta:
                lines.append(f"- Analysis confidence: **{report.meta.confidence.value.upper()}**"
                             f" ({report.meta.source_method})")
                lines.append(f"- Coverage: {report.meta.parsed}/{report.meta.scanned} assets"
                             f" ({report.meta.coverage_pct}%)")
                if report.meta.skipped_lfs:
                    lines.append(f"- ⚠ Git LFS stubs skipped: {report.meta.skipped_lfs}"
                                 f" — results may be incomplete")
            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



# ─────────────────────────────────────────────────────────────
# AGENTS.md freshness — fingerprint + metadata
# ─────────────────────────────────────────────────────────────

_AGENTS_META_FILE = ".agents_meta.json"


def _agents_fingerprint(profile) -> str:
    """프로젝트 소스 파일의 mtime 기반 MD5 fingerprint 계산."""
    if profile.kind in (ProjectKind.UNITY, ProjectKind.DOTNET):
        from .runner import _cs_fingerprint, _src
        return _cs_fingerprint(_src(profile))
    elif profile.kind == ProjectKind.UNREAL:
        from .uasset_cache import fingerprint_combined
        content_roots = []
        source_roots = list(profile.source_dirs)
        # Content 폴더 찾기
        for candidate in [Path(profile.root), *profile.source_dirs]:
            content = candidate.parent / "Content" if candidate.name != "Content" else candidate
            if content.is_dir():
                content_roots.append(content)
                break
        if not content_roots:
            content_dir = Path(profile.root) / "Content"
            if content_dir.is_dir():
                content_roots.append(content_dir)
        return fingerprint_combined(content_roots, [Path(d) for d in source_roots])
    elif profile.kind == ProjectKind.CPP:
        from .uasset_cache import fingerprint_source
        return fingerprint_source([Path(d) for d in profile.source_dirs])
    else:
        # Fallback: hash of source dir mtimes
        import hashlib
        import os
        h = hashlib.md5()
        for sd in profile.source_dirs:
            try:
                for entry in os.scandir(str(sd)):
                    if entry.is_file():
                        h.update(f"{entry.path}:{entry.stat().st_mtime_ns}\n".encode())
            except Exception:
                pass
        return h.hexdigest()


def _load_agents_meta(gdep_dir: Path) -> dict | None:
    """Load .gdep/.agents_meta.json. Returns None if missing or corrupt."""
    meta_path = gdep_dir / _AGENTS_META_FILE
    try:
        return json.loads(meta_path.read_text(encoding="utf-8"))
    except Exception:
        return None


def _save_agents_meta(gdep_dir: Path, fingerprint: str) -> None:
    """Save fingerprint + version metadata alongside AGENTS.md."""
    meta = {
        "fingerprint": fingerprint,
        "gdep_version": __version__,
        "generated_at": time.time(),
    }
    try:
        (gdep_dir / _AGENTS_META_FILE).write_text(
            json.dumps(meta, ensure_ascii=False), encoding="utf-8"
        )
    except Exception:
        pass


def _is_agents_md_fresh(profile) -> bool:
    """Check whether AGENTS.md matches current source fingerprint + gdep version."""
    gdep_dir = Path(profile.root) / ".gdep"
    meta = _load_agents_meta(gdep_dir)
    if meta is None:
        return False
    if meta.get("gdep_version") != __version__:
        return False
    current_fp = _agents_fingerprint(profile)
    return meta.get("fingerprint") == current_fp


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

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

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

    # stale 또는 missing → 자동 재생성
    try:
        agents_md = write_agents_md(project_path, force=True)
        return agents_md.read_text(encoding="utf-8")
    except Exception:
        return _build_agents_md(project_path)


def _ensure_gitignore(project_root: Path) -> None:
    """
    프로젝트 루트의 .gitignore에 .gdep/ 항목이 없으면 자동 추가.
    Git 저장소가 아닌 경우에도 조용히 스킵한다.
    """
    gitignore = project_root / ".gitignore"
    entry = ".gdep/"
    try:
        if gitignore.exists():
            content = gitignore.read_text(encoding="utf-8")
            # 이미 있으면 추가 안 함 (.gdep/ 또는 .gdep 로 시작하는 라인)
            if any(line.strip() in (entry, ".gdep") or line.strip().startswith(".gdep/")
                   for line in content.splitlines()):
                return
            # 끝에 개행 보장 후 추가
            sep = "" if content.endswith("\n") else "\n"
            gitignore.write_text(content + sep + entry + "\n", encoding="utf-8")
        else:
            # .gitignore 파일 자체가 없으면 생성 (Git 저장소 루트에만)
            if (project_root / ".git").is_dir():
                gitignore.write_text(entry + "\n", encoding="utf-8")
    except Exception:
        pass  # 권한 오류 등은 조용히 무시


def write_agents_md(project_path: str, force: bool = False) -> Path:
    """
    `gdep init` CLI에서 호출.
    .gdep/AGENTS.md + .gdep/HELP.md를 생성하고 AGENTS.md 경로를 반환한다.
    force=True 이면 무조건 재생성. False 이면 fresh할 때만 스킵.
    """
    profile = detect(project_path)
    gdep_dir = Path(profile.root) / ".gdep"
    gdep_dir.mkdir(exist_ok=True)
    _ensure_gitignore(Path(profile.root))   # .gitignore 자동 추가
    agents_md = gdep_dir / "AGENTS.md"

    if agents_md.exists() and not force:
        if _is_agents_md_fresh(profile):
            return agents_md  # fresh → 스킵
        # stale → fall through to regenerate

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

    # 메타데이터 저장 (fingerprint + version)
    fp = _agents_fingerprint(profile)
    _save_agents_meta(gdep_dir, fp)

    # HELP.md: 버전 불일치 또는 missing 또는 force 시 재생성
    help_md = gdep_dir / "HELP.md"
    meta = _load_agents_meta(gdep_dir)
    help_needs_regen = (
        not help_md.exists()
        or force
        or (meta and meta.get("gdep_version") != __version__)
    )
    if help_needs_regen:
        help_md.write_text(_build_help_md(project_path), encoding="utf-8")

    return agents_md

