import datetime
import os
import re
from nova_cli import config
from string import Template
from typing import List, Optional, Dict
from nova_cli.local.utils import generate_ast_map

# --- CONFIGURATION ---
EXCLUDED_DIRS = {
    ".git", "__pycache__", "venv", "env", "node_modules", 
    ".idea", ".vscode", "dist", "build", ".next", ".ds_store", 
    ".mypy_cache", ".nova"
}

_TREE_CACHE = None
MAX_CONTEXT_CHARS = 150000  # Threshold to trigger skeletal view

def clear_file_tree_cache():
    global _TREE_CACHE
    _TREE_CACHE = None

# --- TEMPLATES ---
SYSTEM_TEMPLATE = Template("""
/// SYSTEM BOOT SEQUENCE ///
IDENTITY: N.O.V.A. (Neural Orchestration & Virtual Assistant)
USER: $user_name (Authorization: Level 10)
TIME: $current_time
LOC:  $location
CWD:  $cwd

/// REPOSITORY MAP (AST) ///
The following is a high-level map of the codebase structure:
$dir_structure

/// PERSISTENT CONTEXT ///
$loaded_files_block

/// ACTIVE CONTEXT ///
$context_modules

/// PRIME DIRECTIVES ///
1. [PERSONA & BEHAVIOR]
   - You are a high-performance Code Engine.
   - **INPUT CLASSIFICATION**:
     - **CASUAL/VAGUE**: Respond conversationally.
     - **TECHNICAL**: Execute immediate file operations.
   - **STRICT**: No "Certainly sir". Get straight to the point.

2. [SCOPE ADHERENCE]
   - **DEFAULT LOCATION**: Create files in the **CURRENT DIRECTORY** ($cwd).
   - **NO HALLUCINATION**: You see only the files listed in PERSISTENT CONTEXT.

3. [WRITE PROTOCOLS - SURGICAL MODE]
   - **Do NOT rewrite the whole file** when editing.
   - Use SEARCH/REPLACE blocks.
   - **COMMAND SYNTAX**:
     - New Directory: `[MKDIR: path/to/dir]`
     - New File: `[CREATE: path/to/file.ext]` followed immediately by a **CODE BLOCK**.
     - Edit File: `[EDIT: path/to/file.ext]` followed by SEARCH/REPLACE blocks.
     - Delete File/Dir: `[DELETE: path/to/target]`
   
   **SEARCH/REPLACE BLOCK FORMAT**:
   <<<<<<< SEARCH
   (original code lines - must match EXACTLY)
   =======
   (new code lines)
   >>>>>>> REPLACE

/// INCOMING TRANSMISSION ///
$user_input
""")

def get_repo_map_cached(startpath: str) -> str:
    """
    Retrieves the AST-based repo map from utils.py, caching the result
    until clear_file_tree_cache() is called (usually after a file op).
    """
    global _TREE_CACHE
    if _TREE_CACHE is not None: return _TREE_CACHE
    
    # Call the new smart mapper in utils
    result = generate_ast_map(startpath)
    if not result:
        result = "[Empty or Non-Python Directory]"
        
    _TREE_CACHE = result
    return result

def generate_skeleton(content: str) -> str:
    """
    Minifies file content to save tokens. 
    Keeps imports, class definitions, and function signatures. 
    Hides bodies.
    """
    lines = content.splitlines()
    skeleton = []
    for line in lines:
        stripped = line.strip()
        # Keep imports, classes, defs, and decorators
        if (stripped.startswith("import ") or 
            stripped.startswith("from ") or 
            stripped.startswith("class ") or 
            stripped.startswith("def ") or 
            stripped.startswith("@")):
            skeleton.append(line)
        # Keep non-indented assignments (globals)
        elif line and not line[0].isspace() and "=" in line:
            skeleton.append(line)
            
    return "\n".join(skeleton) + "\n... [BODY HIDDEN] ..."

def get_loaded_files_block(loaded_files: Dict[str, str], active_file: Optional[str] = None) -> str:
    """
    Injects content. 
    Feature: SMART CONTEXT. 
    If total size > MAX_CONTEXT_CHARS, minifies files that are NOT the active file.
    """
    if not loaded_files:
        return "[NO FILES LOADED]"
    
    # Calculate total size
    total_size = sum(len(c) for c in loaded_files.values())
    use_minification = total_size > MAX_CONTEXT_CHARS

    block = []
    for fname, content in loaded_files.items():
        # Determine if this file needs to be fully visible
        is_active = active_file and (fname == active_file or fname == os.path.basename(active_file))
        
        if use_minification and not is_active:
            display_content = generate_skeleton(content)
            header = f"--- START FILE (OUTLINE): {fname} ---"
        else:
            # Full content for active file or small context
            display_content = content
            header = f"--- START FILE: {fname} ---"

        block.append(f"{header}\n{display_content}\n--- END FILE: {fname} ---")
    
    return "\n\n".join(block)

def get_system_prompt(
    current_task: Optional[str] = None,
    loaded_files: Dict[str, str] = {},
    active_file: Optional[str] = None
) -> str:
    """Constructs the dynamic system prompt."""
    return SYSTEM_TEMPLATE.substitute(
        user_name=config.USER_NAME,
        current_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
        location=config.LOCATION,
        cwd=os.getcwd(),
        dir_structure=get_repo_map_cached(os.getcwd()),
        loaded_files_block=get_loaded_files_block(loaded_files, active_file),
        context_modules=f"[TASK]: {current_task}" if current_task else "[STANDBY]",
        user_input="" 
    ).strip()