import asyncio
import json
import os
import subprocess
import sys
from pathlib import Path

import typer
import yaml
from rich.console import Console
from rich.prompt import Confirm, Prompt

from warden.analysis.application.project_structure_analyzer import ProjectStructureAnalyzer
from warden.cli.commands.init_helpers import (
    _is_ci_environment,
    configure_agent_tools,
    configure_ci_workflow,
    configure_llm,
    configure_vector_db,
    generate_ai_tool_files,
    select_ci_provider,
)
from warden.cli.commands.install import install as run_install

console = Console()


def _generate_ignore_file(root: Path, meta):
    """Generate .wardenignore based on project type."""
    ignore_path = root / ".wardenignore"
    if ignore_path.exists():
        console.print("[dim].wardenignore exists, skipping.[/dim]")
        return

    # Smart Deduplication: Read .gitignore to avoid redundancy
    gitignore_patterns = set()
    gitignore_path = root / ".gitignore"
    if gitignore_path.exists():
        try:
            with open(gitignore_path) as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith("#"):
                        gitignore_patterns.add(line)
        except Exception as e:
            console.print(f"[dim yellow]Warning: could not read .gitignore for deduplication: {e}[/dim yellow]")

    # Filter out patterns already in .gitignore
    final_content = []

    # Header
    final_content.extend(["# Warden Ignore File", "# Auto-generated by Smart Init", "# (Supplements .gitignore)", ""])

    default_patterns = [
        # Version Control
        ".git/",
        ".svn/",
        "",
        # Warden
        ".warden/",
        "",
        # Dependencies
        "node_modules/",
        "venv/",
        ".venv/",
        "env/",
        "target/",  # Rust
        "vendor/",  # Go/PHP
        "dist/",
        "build/",
        "",
        # IDEs
        ".idea/",
        ".vscode/",
        "",
        # Logs
        "*.log",
        "",
        # Mobile / Flutter
        "ios/Pods/",
        "Pods/",
        ".dart_tool/",
        ".pub-cache/",
        ".fvm/",
        "DerivedData/",
        "",
        # Language Specific
    ]

    # Add language specific defaults
    lang_defaults = []
    if meta.language == "python":
        lang_defaults = ["__pycache__/", "*.pyc", "*.pyo", ".pytest_cache/", ".mypy_cache/", "htmlcov/"]
    elif meta.language in ["javascript", "typescript"]:
        lang_defaults = [".next/", ".nuxt/", "coverage/", ".turbo/"]

    all_defaults = default_patterns + lang_defaults

    # write patterns if not in gitignore
    # We do a simple check: exact match or match without trailing slash
    for line in all_defaults:
        if not line.strip():  # keep empty lines for formatting
            final_content.append(line)
            continue

        # Check logic
        clean_line = line.strip()
        is_covered = clean_line in gitignore_patterns

        # Try variations commonly used in gitignore
        if not is_covered and clean_line.endswith("/"):
            # "build/" might be "build" in gitignore
            is_covered = clean_line.rstrip("/") in gitignore_patterns

        if not is_covered:
            final_content.append(line)

    with open(ignore_path, "w") as f:
        f.write("\n".join(final_content))
    console.print("[green]Created .wardenignore (Smart Deduplication Active)[/green]")


def _write_default_taint_catalog(catalog_path: Path) -> None:
    """Write a commented-out taint_catalog.yaml scaffold for user extension."""
    from warden.validation.frames.security._internal.taint_analyzer import (
        JS_SANITIZERS,
        JS_TAINT_SINKS,
        JS_TAINT_SOURCES,
        KNOWN_SANITIZERS,
        TAINT_SINKS,
        TAINT_SOURCES,
    )

    def _comment_list(items: list[str], indent: str = "    ") -> str:
        return "\n".join(f"{indent}# - {item}" for item in sorted(items))

    def _comment_dict_keys(d: dict[str, str], sink_type: str, indent: str = "    ") -> str:
        keys = sorted(k for k, v in d.items() if v == sink_type)
        return "\n".join(f"{indent}# - {k}" for k in keys)

    sink_types = ["SQL-value", "CMD-argument", "HTML-content", "CODE-execution", "FILE-path"]

    sinks_section = ""
    for st in sink_types:
        py_keys = _comment_dict_keys(TAINT_SINKS, st)
        js_keys = _comment_dict_keys(JS_TAINT_SINKS, st)
        builtin_comments = "\n".join(filter(None, [py_keys, js_keys]))
        sinks_section += f"  {st}:\n"
        if builtin_comments:
            sinks_section += f"    # Built-in (always active — shown for reference):\n"
            sinks_section += builtin_comments + "\n"
        sinks_section += "    # Add your custom sinks below:\n\n"

    sanitizers_section = ""
    for st in sink_types:
        py_sans = KNOWN_SANITIZERS.get(st, set())
        js_sans = JS_SANITIZERS.get(st, set())
        all_sans = sorted(py_sans | js_sans)
        sanitizers_section += f"  {st}:\n"
        if all_sans:
            sanitizers_section += "    # Built-in (always active — shown for reference):\n"
            sanitizers_section += "\n".join(f"    # - {s}" for s in all_sans) + "\n"
        sanitizers_section += "    # Add your custom sanitizers below:\n\n"

    content = f"""# Warden Taint Catalog
# Built-in defaults are always active. Add entries here to EXTEND them.
# Warden performs a UNION — your custom entries are added, never replacing built-ins.
#
# Format:
#   sources:
#     python:
#       - my.custom.source
#     javascript:
#       - ctx.request.body
#   sinks:
#     SQL-value:
#       - prisma.raw
#   sanitizers:
#     HTML-content:
#       - myCustomSanitizer

sources:
  python:
    # Built-in Python sources (always active — shown for reference):
{_comment_list(list(TAINT_SOURCES))}
    # Add your custom Python sources below:

  javascript:
    # Built-in JavaScript/TypeScript sources (always active — shown for reference):
{_comment_list(list(JS_TAINT_SOURCES))}
    # Add your custom JavaScript sources below:

sinks:
{sinks_section}
sanitizers:
{sanitizers_section}
"""
    catalog_path.write_text(content, encoding="utf-8")


def _setup_semantic_search(config_path: Path):
    """
    Initialize semantic search:
    1. Check availability / Auto-install dependencies
    2. Index codebase
    Handles failures gracefully (Soft Failure).
    """
    console.print("\n[bold cyan]📚 Initializing Semantic Index...[/bold cyan]")
    try:
        # Load config
        with open(config_path) as f:
            final_config = yaml.safe_load(f)

        ss_config = final_config.get("semantic_search", {})
        if not ss_config.get("enabled"):
            return

        from warden.shared.services.semantic_search_service import SemanticSearchService

        # Reset singleton
        SemanticSearchService._instance = None
        service = SemanticSearchService(ss_config)

        # Inner function to perform indexing
        async def run_indexing_if_files_exist_async():
            from warden.shared.infrastructure.ignore_matcher import IgnoreMatcher

            matcher = IgnoreMatcher(Path.cwd())
            code_extensions = {
                ".py",
                ".js",
                ".ts",
                ".jsx",
                ".tsx",
                ".java",
                ".go",
                ".rs",
                ".cpp",
                ".c",
                ".h",
                ".dart",
                ".swift",
                ".kt",
            }

            files = []
            for f in Path.cwd().rglob("*"):
                if f.is_file() and f.suffix in code_extensions and not matcher.should_ignore_file(f):
                    files.append(f)

            if not files:
                console.print("[yellow]No code files found to index.[/yellow]")
                return

            try:
                with console.status(
                    f"[bold green]Indexing {len(files)} files...[/bold green]", spinner="dots"
                ) as status:
                    processed = 0
                    total = len(files)

                    async def progress_cb(count):
                        nonlocal processed
                        processed += count
                        status.update(f"[bold green]Indexing {total} files... ({processed}/{total})[/bold green]")

                    await service.index_project(Path.cwd(), files, progress_callback=progress_cb)

                console.print(f"[green]✓ Semantic Index Ready ({len(files)} files)[/green]")
            except KeyboardInterrupt:
                console.print("\n[yellow]⚠️  Indexing skipped by user.[/yellow]")
                console.print("[dim]Run 'warden index' later to complete setup.[/dim]")

        # Check service availability and run indexing
        if service.is_available():
            asyncio.run(run_indexing_if_files_exist_async())
        else:
            console.print("[yellow]Semantic service dependencies missing.[/yellow]")

            # Use DependencyManager for robust installation
            from warden.services.dependencies import DependencyManager

            dep_manager = DependencyManager()

            required_pkgs = ["chromadb", "sentence-transformers"]
            success = asyncio.run(dep_manager.install_packages_async(required_pkgs))

            if success:
                console.print("[green]Dependencies installed successfully. Retrying setup...[/green]")
                # Re-instantiate service to pick up new modules
                service = SemanticSearchService(ss_config)
                if service.is_available():
                    asyncio.run(run_indexing_if_files_exist_async())
                else:
                    console.print("[red]Service unavailable even after install.[/red]")
            else:
                console.print("[red]Semantic features will be disabled.[/red]")

    except Exception as e:
        console.print("\n[red]❌ Semantic Indexing Failed[/red]")
        console.print(f"[red]Error: {e!s}[/red]")
        console.print("[yellow]💡 Suggestion: Run 'warden index' manually to see detailed errors.[/yellow]")
        console.print(
            "[dim]The project is initialized, but AI features (Orphan/Purpose) may be limited until fixed.[/dim]"
        )


async def _create_baseline_async(root: Path, config_path: Path):
    """Run initial scan and save as baseline."""
    console.print("\n[bold blue]📉 Creating Baseline...[/bold blue]")
    console.print("[dim]Running initial scan to identify existing technical debt...[/dim]")

    try:
        from warden.cli_bridge.bridge import WardenBridge

        bridge = WardenBridge(project_root=root, config_path=str(config_path))

        # Run scan on current directory with streaming progress
        # We use execute_pipeline_stream_async to get live updates
        all_issues = []
        last_summary = {}
        processed_count = 0
        total_files = 0

        with console.status("[bold blue]🚀 Starting Analysis...[/bold blue]", spinner="dots") as status:
            async for event in bridge.execute_pipeline_stream_async(str(root)):
                etype = event.get("type")

                if etype == "progress":
                    evt = event.get("event")
                    data = event.get("data", {})

                    if evt == "discovery_complete":
                        files_found = data.get("total_files", 0)
                        status.update(f"[bold blue]🔍 Analyzable Files: {files_found}[/bold blue]")

                    elif evt == "pipeline_started":
                        # Initial guess based on file count
                        files_found = data.get("file_count") or files_found
                        status.update(f"[bold blue]🛡️ Starting Scan on {files_found} files...[/bold blue]")

                    elif evt == "progress_init":
                        # More accurate total work units (files * frames)
                        total_files = data.get("total_units", 0)
                        processed_count = 0
                        status.update(f"[bold blue]🛡️ Scanning... (0/{total_files})[/bold blue]")

                    elif evt == "progress_update":
                        increment = data.get("increment", 0)

                        # Only increment if we have a valid increment value
                        if increment > 0:
                            processed_count += increment

                        # Clamp to never exceed total
                        if total_files > 0:
                            if processed_count > total_files:
                                processed_count = total_files
                            status.update(f"[bold blue]🛡️ Scanning... ({processed_count}/{total_files})[/bold blue]")

                elif etype == "result":
                    res = event.get("data")
                    if res:
                        last_summary = res.get("summary", {})

                        # Flatten issues for legacy baseline format
                        for fr in res.get("frame_results", []):
                            for f in fr.get("findings", []):
                                all_issues.append(
                                    {
                                        "filePath": f.get("file_path") or f.get("file", ""),
                                        "severity": f.get("severity", "medium"),
                                        "message": f.get("message", ""),
                                        "line": f.get("line_number") or f.get("line", 0),
                                        "frame": fr.get("frame_id"),
                                    }
                                )

        # Construct final result for baseline
        result = {"success": True, "issues": all_issues, "summary": last_summary}

        # Save baseline
        baseline_path = root / ".warden" / "baseline.json"

        # Extract issues (findings) from result
        # Result structure depends on bridge output. Usually {'success': ..., 'results': ...}
        # We simply save the whole result or a subset as baseline.
        # For now, save entire result.

        with open(baseline_path, "w") as f:
            json.dump(result, f, indent=2)

        issue_count = result.get("summary", {}).get("total_issues", 0)
        console.print(f"[green]Baseline created with {issue_count} existing issues.[/green]")
        console.print("[dim]Future scans will prioritize NEW issues.[/dim]")

    except Exception as e:
        console.print(f"[red]Failed to create baseline: {e}[/red]")


async def _generate_intelligence_async(root: Path, config_files: dict = None):
    """
    Generate project intelligence for CI optimization.

    This creates the .warden/intelligence/project.json file that CI scans
    can use for risk-based analysis without running LLM.
    """
    console.print("\n[bold blue]🧠 Generating Project Intelligence...[/bold blue]")
    console.print("[dim]Analyzing project structure for CI optimization...[/dim]")

    try:
        from warden.analysis.application.project_purpose_detector import ProjectPurposeDetector
        from warden.analysis.domain.intelligence import SecurityPosture
        from warden.analysis.services.intelligence_saver import IntelligenceSaver

        # Get all code files for analysis
        code_extensions = {".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rs", ".cpp", ".c", ".h", ".dart"}
        files = [
            f
            for f in root.rglob("*")
            if f.is_file()
            and f.suffix in code_extensions
            and "node_modules" not in str(f)
            and ".venv" not in str(f)
            and ".git" not in str(f)
            and ".warden" not in str(f)
            and "__pycache__" not in str(f)
        ]

        if not files:
            console.print("[yellow]No code files found for intelligence generation.[/yellow]")
            return

        console.print(f"[dim]Analyzing {len(files)} files...[/dim]")

        # Detect project purpose and modules
        detector = ProjectPurposeDetector(root)
        purpose, architecture, module_map = await detector.detect_async(files, config_files or {})

        # Determine security posture based on project type
        security_posture = SecurityPosture.STANDARD
        if purpose:
            purpose_lower = purpose.lower()
            if any(kw in purpose_lower for kw in ["payment", "banking", "crypto", "auth"]):
                security_posture = SecurityPosture.STRICT
            elif any(kw in purpose_lower for kw in ["healthcare", "medical", "pii", "gdpr"]):
                security_posture = SecurityPosture.PARANOID

        # Save intelligence
        saver = IntelligenceSaver(root)
        success = saver.save(
            purpose=purpose,
            architecture=architecture,
            security_posture=security_posture,
            module_map=module_map,
            project_name=root.name,
        )

        if success:
            # Count risk distribution
            risk_counts = {"P0": 0, "P1": 0, "P2": 0, "P3": 0}
            for info in module_map.values():
                risk_counts[info.risk_level.value] = risk_counts.get(info.risk_level.value, 0) + 1

            console.print("[green]✓ Intelligence generated successfully![/green]")
            console.print(f"[dim]   Modules: {len(module_map)} | Posture: {security_posture.value}[/dim]")
            console.print(
                f"[dim]   Risk Distribution: P0={risk_counts['P0']}, P1={risk_counts['P1']}, P2={risk_counts['P2']}, P3={risk_counts['P3']}[/dim]"
            )
            console.print("[dim]   CI scans will use this for optimized analysis.[/dim]")
        else:
            console.print("[yellow]⚠️  Intelligence generation failed. CI will use default settings.[/yellow]")

    except Exception as e:
        console.print(f"[yellow]⚠️  Intelligence generation skipped: {e}[/yellow]")
        console.print("[dim]CI scans will still work but may be slower.[/dim]")


def init_command(
    ctx: typer.Context,
    force: bool = typer.Option(False, "--force", "-f", help="Force initialization even if config exists"),
    mode: str = typer.Option("normal", "--mode", "-m", help="Initialization mode (vibe, normal, strict)"),
    ci: bool = typer.Option(False, "--ci", help="Generate GitHub Actions CI workflow"),
    skip_mcp: bool = typer.Option(False, "--skip-mcp", help="Skip MCP server registration"),
    agent: bool = typer.Option(
        True,
        "--agent/--no-agent",
        help="Configure agent files (Claude/Cursor) and register MCP server",
    ),
    baseline: bool = typer.Option(
        True,
        "--baseline/--no-baseline",
        help="Create baseline from current issues",
    ),
    intel: bool = typer.Option(
        True,
        "--intel/--no-intel",
        help="Generate project intelligence for CI optimization",
    ),
    grammars: bool = typer.Option(
        True,
        "--grammars/--no-grammars",
        help="Install missing tree-sitter grammars",
    ),
    provider: str | None = typer.Option(
        None,
        "--provider",
        "-p",
        help=(
            "LLM provider for non-interactive/CI use. "
            "Options: ollama, anthropic, openai, groq, azure, deepseek, gemini. "
            "Note: claude_code is excluded from CI usage."
        ),
    ),
) -> None:
    """
    Initialize Warden in the current directory with Smart Detection.
    """
    console.print("[bold blue]🛡️  Initializing Warden (Smart Mode)...[/bold blue]")

    warden_dir = Path(".warden")
    warden_dir.mkdir(parents=True, exist_ok=True)

    # --- Step 1: Detect Project ---
    console.print("[bold blue]🔍 Detecting project environment...[/bold blue]")

    # Use Core Analyzer
    analyzer = ProjectStructureAnalyzer(Path.cwd())
    # Run async analysis synchronously
    context = asyncio.run(analyzer.analyze_async())

    # Map ProjectContext to metadata expected by init
    # We create a simple object to hold the data compatible with previous logic
    class MetaAdapter:
        pass

    meta = MetaAdapter()
    meta.language = context.primary_language
    meta.frameworks = [context.framework.value] if context.framework.value != "none" else []
    meta.project_type = context.project_type.value
    meta.ci_providers = []  # Core analyzer detects this but stores in config_files/special_dirs differently?
    # PSA stores build tools in context.build_tools
    meta.build_tools = [bt.value for bt in context.build_tools]

    # Suggest frames logic (Dynamic Discovery)
    # 1. Base Core Frames (built-in frames that come with warden-core)
    # Available built-in frames: security, resilience, orphan, fuzz, property, spec, gitchanges
    meta.suggested_frames = ["security", "resilience", "orphan"]

    # 2. Dynamic Language-Specific Frames (Registry Search)
    try:
        from warden.services.package_manager.registry import RegistryClient

        registry = RegistryClient()  # Use default paths

        # Sync if cache is missing (first run)
        if not registry._catalog_cache:
            console.print("[dim]Syncing registry for suggestions...[/dim]")
            asyncio.run(registry.sync_async())

        # Get languages to check (fall back to primary if detected is empty)
        languages_to_check = context.detected_languages or [context.primary_language]

        # Collect suggestions from all languages
        all_suggestions = set()

        for lang in set(languages_to_check):
            if not lang or lang == "unknown":
                continue

            suggestions = registry.suggest_for_language(lang)
            if suggestions:
                for s in suggestions:
                    all_suggestions.add(s)

        if all_suggestions:
            meta.suggested_frames.extend(list(all_suggestions))
            console.print(
                f"[dim]Found {len(all_suggestions)} language-specific frames for {', '.join(set(languages_to_check))}[/dim]"
            )

    except Exception as e:
        console.print(f"[dim yellow]Warning: frame suggestion failed: {e}[/dim yellow]")

    if meta.project_type in ["api", "backend", "microservice"]:
        # Add fuzz and property testing for API/backend projects
        if "fuzz" not in meta.suggested_frames:
            meta.suggested_frames.append("fuzz")
        if "property" not in meta.suggested_frames:
            meta.suggested_frames.append("property")
    # Helper to check for CI config files from context.config_files
    ci_files = {".github/workflows": "github-actions", ".gitlab-ci.yml": "gitlab-ci", "Jenkinsfile": "jenkins"}
    for f, p in ci_files.items():
        if f in context.config_files:
            meta.ci_providers.append(p)

    console.print(f"   [green]✓[/green] Language: [cyan]{meta.language}[/cyan]")
    console.print(
        f"   [green]✓[/green] Framework: [cyan]{', '.join(meta.frameworks) if meta.frameworks else 'None detected'}[/cyan]"
    )
    console.print(f"   [green]✓[/green] Type: [cyan]{meta.project_type}[/cyan]")

    # Language Distribution Table
    from rich.table import Table

    if context.language_breakdown:
        console.print()
        table = Table(title="Detected Languages", box=None)
        table.add_column("Language", style="cyan")
        table.add_column("Distribution", justify="right", style="magenta")
        table.add_column("Status", style="dim")

        from warden.ast.domain.enums import CodeLanguage
        from warden.shared.languages.registry import LanguageRegistry

        sorted_stats = sorted(context.language_breakdown.items(), key=lambda x: x[1], reverse=True)
        for lang_id, percent in sorted_stats:
            is_included = lang_id in context.detected_languages
            status = "[green]●[/green] Included" if is_included else "[dim]○ Ignored (<2%)[/dim]"

            # Get pretty name from registry
            try:
                lang_enum = CodeLanguage(lang_id)
                defn = LanguageRegistry.get_definition(lang_enum)
                pretty_name = defn.name if defn else lang_id.capitalize()
            except ValueError:
                pretty_name = lang_id.capitalize()

            table.add_row(pretty_name, f"{percent:.1f}%", status)
        console.print(table)
        console.print()

    console.print(f"Suggested Frames: {', '.join(meta.suggested_frames)}")
    # The original code had an `except` block here, but with ProjectStructureAnalyzer,
    # we assume it either succeeds or raises an error that should propagate if not handled.
    # For now, we'll remove the try/except around detection as the new analyzer is more robust.
    # If specific errors need handling, they should be added here.

    # --- Step 2: Mode Selection ---
    mode_map = {"vibe": "1", "normal": "2", "strict": "3"}
    mode_choice = mode_map.get(mode.lower(), "2")

    is_interactive = sys.stdin.isatty() and os.environ.get("WARDEN_NON_INTERACTIVE") != "true"

    if is_interactive:
        console.print("\n[bold cyan]🎚️  Select Operation Mode[/bold cyan]")
        console.print("1. [bold green]Vibe Mode[/bold green] (Silent, Critical Only) - Best for active dev")
        console.print("2. [bold yellow]Normal Mode[/bold yellow] (High+Critical) - Standard PR checks")
        console.print("3. [bold red]Strict Mode[/bold red] (All Issues) - Zero tolerance / Security audit")
        mode_choice = Prompt.ask("Select Mode", choices=["1", "2", "3"], default=mode_choice)

    mode_config = {}
    if mode_choice == "1":  # Vibe
        mode_config = {"fail_fast": False, "min_severity": "critical", "quiet": True}
        meta.suggested_frames = [
            f for f in meta.suggested_frames if f in ["security", "env-security"]
        ]  # Vibe uses minimal frames
    elif mode_choice == "3":  # Strict
        mode_config = {"fail_fast": True, "min_severity": "low", "strict": True}
    else:  # Normal
        mode_config = {"fail_fast": False, "min_severity": "high"}

    # Load existing config for defaults
    existing_config = {}
    config_path = warden_dir / "config.yaml"
    if config_path.exists():
        try:
            with open(config_path) as f:
                existing_config = yaml.safe_load(f) or {}
        except (FileNotFoundError, PermissionError, yaml.YAMLError):
            pass  # Use empty config as default

    # --- Step 3: LLM Config ---
    # Set WARDEN_INIT_PROVIDER so select_llm_provider() picks the forced provider
    if provider:
        os.environ["WARDEN_INIT_PROVIDER"] = provider.strip().lower()
    llm_config, new_env_vars = configure_llm(existing_config)
    provider = llm_config["provider"]
    model = llm_config["model"]

    # Update .env
    env_path = Path(".env")
    if new_env_vars:
        write_mode = "a" if env_path.exists() else "w"
        current_env = env_path.read_text() if env_path.exists() else ""
        with open(env_path, write_mode) as f:
            if write_mode == "a":
                f.write("\n")
            for k, v in new_env_vars.items():
                if k not in current_env:
                    f.write(f"{k}={v}\n")
        console.print("[green]Updated .env[/green]")

        # Create or update .env.example with placeholders
        env_example_path = Path(".env.example")
        example_lines = []
        if env_example_path.exists():
            example_lines = env_example_path.read_text().splitlines()
        else:
            example_lines = ["# Warden Environment Variables"]

        def ensure_line(key: str):
            nonlocal example_lines
            if not any(l.split("=")[0] == key for l in example_lines if "=" in l):
                example_lines.append(f"{key}=")

        # Common
        ensure_line("WARDEN_LLM_PROVIDER")
        if provider == "azure":
            ensure_line("AZURE_OPENAI_API_KEY")
            ensure_line("AZURE_OPENAI_ENDPOINT")
            ensure_line("AZURE_OPENAI_DEPLOYMENT_NAME")
        elif provider not in ("none", "ollama", "claude_code"):
            ensure_line(f"{provider.upper()}_API_KEY")

        with open(env_example_path, "w") as f:
            f.write("\n".join(example_lines) + "\n")
        console.print("[green]Updated .env.example[/green]")

    # --- Step 4: Vector Database ---
    vector_config = configure_vector_db()

    # --- Step 5: Generate Config ---
    if not config_path.exists():
        # Build frames_config based on detection
        frames_config = {}
        for frame in meta.suggested_frames:
            frames_config[frame] = {"enabled": True}

        # Add taint thresholds for security frame (import central defaults)
        if "security" in frames_config:
            from warden.validation.frames.security._internal.taint_analyzer import TAINT_DEFAULTS

            taint_init = {
                "confidence_threshold": TAINT_DEFAULTS["confidence_threshold"],
                "sanitizer_penalty": TAINT_DEFAULTS["sanitizer_penalty"],
            }
            # Mode-based adjustment
            if mode_choice == "3":  # Strict — catch more taint flows
                taint_init["confidence_threshold"] = 0.7
            elif mode_choice == "1":  # Vibe — only high-confidence
                taint_init["confidence_threshold"] = 0.9
            frames_config["security"]["taint"] = taint_init

        # Apply mode settings
        if mode_choice == "1":  # Vibe
            for f in frames_config:
                frames_config[f]["severity_threshold"] = "critical"

        config_data = {
            "version": "1.0.0",
            "project": {
                "name": Path.cwd().name,
                "language": meta.language,
                "type": meta.project_type,
                "frameworks": meta.frameworks,
            },
            "dependencies": {"architectural": "latest", "security": "latest"},
            "llm": {
                "provider": provider,
                "model": model,
                "smart_model": model,
                "fast_model": llm_config.get("fast_model", "qwen2.5-coder:0.5b"),
                "timeout": 300,
            },
            "frames": meta.suggested_frames,
            "frames_config": frames_config,
            "settings": {
                "fail_fast": mode_config["fail_fast"],
                "enable_classification": True,
                "mode": ["vibe", "normal", "strict"][int(mode_choice) - 1],
                "use_llm": provider != "none",
                "use_local_llm": llm_config.get("use_local_llm", False),
            },
            "semantic_search": vector_config,
            "routing": {
                "fast_tier": "ollama" if llm_config.get("use_local_llm", False) else provider,
                "smart_tier": provider,
            },
            "baseline": {
                "enabled": True,
                "path": ".warden/baseline.json",
                "auto_fetch": False,
                "fetch_command": "gh run download -n warden-baseline",
            },
        }
        # Preserve Azure details if selected
        if provider == "azure":
            config_data["llm"]["azure"] = {
                "endpoint": "${AZURE_OPENAI_ENDPOINT}",
                "api_key": "${AZURE_OPENAI_API_KEY}",
                "deployment_name": "${AZURE_OPENAI_DEPLOYMENT_NAME}",
                "api_version": "2024-02-15-preview",
            }

        # New root manifest (Standardized to .warden/config.yaml)
        root_config_path = warden_dir / "config.yaml"
        with open(root_config_path, "w") as f:
            yaml.dump(config_data, f, default_flow_style=False)
        console.print(f"[green]Created project configuration: [bold]{root_config_path}[/bold][/green]")

        config_path = root_config_path

    else:
        # Config exists - Offer Update/Merge
        console.print(f"[yellow]Config file exists: {config_path}[/yellow]")
        if force or (
            is_interactive
            and Confirm.ask("Update configuration with detected settings? (Merges frames & mode)", default=False)
        ):
            # Update Frames - merge new suggestion into existing
            existing_frames = set(existing_config.get("frames", []))
            new_frames = set(meta.suggested_frames)
            merged_frames = list(existing_frames.union(new_frames))
            existing_config["frames"] = merged_frames

            # Update Frames Config - enable new frames
            if "frames_config" not in existing_config:
                existing_config["frames_config"] = {}

            for frame in meta.suggested_frames:
                if frame not in existing_config["frames_config"]:
                    existing_config["frames_config"][frame] = {"enabled": True}
                    if mode_choice == "1":
                        existing_config["frames_config"][frame]["severity_threshold"] = "critical"

            # Update Project Metadata
            existing_config["project"]["language"] = meta.language
            existing_config["project"]["type"] = meta.project_type
            existing_config["project"]["frameworks"] = meta.frameworks

            # Update Settings based on Mode
            if "settings" not in existing_config:
                existing_config["settings"] = {}
            existing_config["settings"]["fail_fast"] = mode_config["fail_fast"]
            existing_config["settings"]["mode"] = ["vibe", "normal", "strict"][int(mode_choice) - 1]
            if "min_severity" in mode_config:
                existing_config["settings"]["min_severity"] = mode_config["min_severity"]

            # Ensure Semantic Search is present
            existing_config["semantic_search"] = vector_config

            # Ensure routing present/updated
            existing_config["routing"] = existing_config.get(
                "routing",
                {
                    "fast_tier": "ollama" if llm_config.get("use_local_llm", False) else provider,
                    "smart_tier": provider,
                },
            )

            # Ensure baseline defaults
            if "baseline" not in existing_config:
                existing_config["baseline"] = {
                    "enabled": True,
                    "path": ".warden/baseline.json",
                    "auto_fetch": False,
                    "fetch_command": "gh run download -n warden-baseline",
                }

            # Save
            with open(config_path, "w") as f:
                yaml.dump(existing_config, f, default_flow_style=False)
            console.print("[green]Merged configuration successfully.[/green]")

    # --- Step 5.5: Taint Catalog Scaffold ---
    catalog_path = warden_dir / "taint_catalog.yaml"
    if not catalog_path.exists():
        try:
            _write_default_taint_catalog(catalog_path)
            console.print(f"[green]Created taint catalog: [bold]{catalog_path}[/bold][/green]")
        except Exception as e:
            console.print(f"[yellow]Warning: Could not create taint_catalog.yaml: {e}[/yellow]")

    # --- Step 6: Ignore Files ---
    _generate_ignore_file(Path.cwd(), meta)

    # Create .warden/ignore.yaml from template
    ignore_yaml_path = warden_dir / "ignore.yaml"
    if not ignore_yaml_path.exists():
        try:
            import importlib.resources

            ignore_template = importlib.resources.read_text("warden.templates", "ignore.yaml")
            with open(ignore_yaml_path, "w") as f:
                f.write(ignore_template)
            console.print(f"[green]Created ignore configuration: {ignore_yaml_path}[/green]")
        except Exception as e:
            console.print(f"[yellow]Warning: Could not create ignore.yaml: {e}[/yellow]")

    # --- Step 7: Example Rules ---
    rules_dir = warden_dir / "rules"
    rules_dir.mkdir(exist_ok=True)
    example_rule_path = rules_dir / "my_custom_rules.yaml"
    if not example_rule_path.exists():
        example_content = """# Example Custom Rule Definition
# Check https://github.com/warden-ai/docs for full syntax

rules:
  - id: "company-no-print"
    name: "Company No Print"
    description: "Do not use print statements in production code"
    category: "convention"
    severity: "warning"
    isBlocker: false
    enabled: true
    type: "pattern"
    conditions:
      forbiddenPatterns:
        - "print("
      exclude:
        - "tests/**"
        - "scripts/**"
"""
        with open(example_rule_path, "w") as f:
            f.write(example_content)
        console.print(f"[green]Created example rules: {example_rule_path}[/green]")

    # --- Step 8: Update Config with Comments ---
    # We re-read the just created/merged config to append comments if needed
    # Or ensures generation includes it.

    # If we created a new config in Step 5, we likely used yaml.dump.
    # yaml.dump removes comments. We need to append the example usage at the end manually
    # or handle it better.

    # Let's read the file as text and check if the comment exists, if not, append it.
    if config_path.exists():
        current_content = config_path.read_text()
        comment_block = """
# --- Custom Rules Configuration ---
# To enable your custom rules, uncomment the following lines:
#
custom_rules:
  - .warden/rules/my_custom_rules.yaml
"""
        if "custom_rules:" not in current_content:
            with open(config_path, "a") as f:
                f.write(comment_block)
            console.print("[green]Added custom rules configuration[/green]")

        # Semantic Search is now AUTO-ADDED, so no hint needed.

        # HINT: CI Output Formats
        if "ci:" not in current_content and "output:" not in current_content:
            ci_hint = """
# --- CI/CD Output Configuration ---
# Generate reports in multiple formats (Markdown, JSON, SARIF)
#
# ci:
#   enabled: true
#   output:
#     - format: markdown
#       path: .warden/reports/warden-report.md
#     - format: sarif
#       path: .warden/reports/warden-report.sarif
"""
            with open(config_path, "a") as f:
                f.write(ci_hint)

        console.print("[green]Added configuration examples (Rules, Semantic Search, CI)[/green]")

    # --- Step 9: Semantic Indexing ---
    _setup_semantic_search(config_path)

    # --- Step 10: Agent & MCP Configuration ---
    console.print("\n[bold blue]🤖 Configuring AI Agent Integration...[/bold blue]")
    try:
        is_ci = _is_ci_environment()
        if not agent:
            console.print("[dim]Agent configuration disabled (--no-agent).[/dim]")
        elif is_ci:
            console.print("[dim]CI environment detected — skipping agent/MCP configuration.[/dim]")
        else:
            # Generate AI tool files from templates (CLAUDE.md, .cursorrules, etc.)
            generate_ai_tool_files(Path.cwd(), llm_config)
            if not skip_mcp:
                configure_agent_tools(Path.cwd())
            else:
                console.print("[dim]MCP registration skipped (--skip-mcp flag used)[/dim]")
    except Exception as e:
        console.print(f"[red]Failed to configure agent tools: {e}[/red]")

    # --- Step 11: Baseline ---
    should_create_baseline = force  # default non-interactive behavior unchanged
    if is_interactive and baseline:
        should_create_baseline = Confirm.ask(
            "\nCreate Baseline from current issues? (Recommended for existing projects)", default=True
        )

    if should_create_baseline:
        try:
            asyncio.run(_create_baseline_async(Path.cwd(), config_path))
        except KeyboardInterrupt:
            console.print("\n[yellow]⚠️  Baseline creation skipped by user.[/yellow]")
        except Exception as e:
            console.print(f"[red]Warning: Failed to create baseline: {e}[/red]")

    # --- Step 12: Intelligence Generation (CI Optimization) ---
    should_gen_intel = force
    if is_interactive and intel:
        should_gen_intel = Confirm.ask(
            "\nGenerate Project Intelligence for CI? (Recommended for faster CI scans)", default=True
        )

    if should_gen_intel:
        try:
            asyncio.run(_generate_intelligence_async(Path.cwd(), context.config_files))
        except KeyboardInterrupt:
            console.print("\n[yellow]⚠️  Intelligence generation skipped by user.[/yellow]")
        except Exception as e:
            console.print(f"[yellow]Warning: Intelligence generation failed: {e}[/yellow]")

    # --- Step 13: CI/CD (Template-Based) ---
    should_gen_ci = ci
    if not should_gen_ci and is_interactive:
        should_gen_ci = Confirm.ask("\nGenerate CI/CD Workflow?", default=False)

    if should_gen_ci:
        # Use detected branch or default
        branch = "main"
        try:
            branch = subprocess.check_output(["git", "branch", "--show-current"], text=True).strip()
        except (OSError, PermissionError):  # Cleanup is best-effort
            pass

        # Select CI provider
        ci_provider = select_ci_provider()

        # Generate workflow from template
        if configure_ci_workflow(ci_provider, llm_config, Path.cwd(), branch):
            console.print("[green]✓ CI/CD workflow configured successfully![/green]")

    # --- Step 14: Verify Built-in Frames ---
    console.print("\n[bold blue]📦 Verifying Built-in Frames...[/bold blue]")
    try:
        from warden.validation.infrastructure.frame_registry import FrameRegistry

        registry = FrameRegistry()
        frames = registry.discover_all(project_root=Path.cwd())
        frame_names = [f().name for f in frames]
        console.print(
            f"[green]✓ Found {len(frames)} built-in frames: {', '.join(frame_names[:5])}{'...' if len(frames) > 5 else ''}[/green]"
        )
    except Exception as e:
        console.print(f"[yellow]Warning: Could not verify frames: {e}[/yellow]")

    # --- Step 15: Install Tree-Sitter Grammars ---
    if not grammars:
        console.print("[dim]Skipping tree-sitter grammar installation (--no-grammars).[/dim]")
        console.print("\n[bold green]✨ Warden Initialization Complete![/bold green]")
        console.print("Run [bold cyan]warden scan[/bold cyan] to start.")
        console.print("[dim]Available frames: security, resilience, orphan, fuzz, property[/dim]")
        return
    console.print("\n[bold blue]🌳 Installing Tree-Sitter Grammars...[/bold blue]")
    console.print("[dim]Auto-installing parsers for detected languages...[/dim]")

    try:
        from warden.ast.domain.enums import CodeLanguage
        from warden.ast.providers.tree_sitter_provider import TreeSitterProvider

        provider = TreeSitterProvider()

        # Convert detected languages to CodeLanguage enums
        detected_code_langs = []
        for lang_id in context.detected_languages:
            try:
                detected_code_langs.append(CodeLanguage(lang_id))
            except ValueError:
                console.print(f"[dim yellow]Warning: Unknown language '{lang_id}', skipping[/dim yellow]")

        if not detected_code_langs:
            console.print("[dim]No languages detected, skipping tree-sitter setup.[/dim]")
        else:
            # Filter to only missing grammars
            missing_grammars = []
            for lang in detected_code_langs:
                if lang in provider._missing_modules:
                    missing_grammars.append(lang)

            if not missing_grammars:
                console.print(
                    f"[green]✓ All grammars already installed for: {', '.join([l.value for l in detected_code_langs])}[/green]"
                )
            else:
                console.print(f"[dim]Installing {len(missing_grammars)} missing grammar(s)...[/dim]")

                installed_count = 0
                failed_langs = []

                for lang in missing_grammars:
                    package_name = provider._missing_modules[lang].replace("_", "-")

                    with console.status(f"[bold cyan]Installing {package_name}...[/bold cyan]", spinner="dots"):
                        success = asyncio.run(provider.auto_install_grammar(lang))

                    if success:
                        installed_count += 1
                        console.print(f"[green]✓ Installed {package_name}[/green]")
                    else:
                        failed_langs.append(lang.value)
                        console.print(f"[yellow]⚠️  Failed to install {package_name}[/yellow]")

                if installed_count > 0:
                    console.print(
                        f"\n[green]✓ Installed {installed_count}/{len(missing_grammars)} tree-sitter grammar(s)[/green]"
                    )

                if failed_langs:
                    console.print(
                        f"[dim]Failed: {', '.join(failed_langs)}. Install manually with 'pip install tree-sitter-<lang>'[/dim]"
                    )

    except Exception as e:
        console.print(f"[yellow]Warning: Tree-sitter setup failed: {e}[/yellow]")
        console.print("[dim]Parsers can be installed manually later.[/dim]")

    # Optional: Try to install additional frames from Hub (non-blocking)
    if is_interactive and Confirm.ask("\nInstall additional frames from Warden Hub? (requires network)", default=False):
        try:
            from warden.cli.commands.update import update_command

            try:
                update_command()
            except Exception:
                console.print("[yellow]Warning: Could not update registry.[/yellow]")

            run_install(frame_id=None)
        except Exception as e:
            console.print(f"[yellow]Warning: Hub install failed: {e}[/yellow]")
            console.print("[dim]Built-in frames are still available.[/dim]")

    # --- Step 16: Generate Context (Structure/Style/Testing/Commands) ---
    try:
        from warden.cli.commands.context import (
            _detect_commands,
            _detect_commit_convention,
            _detect_structure,
            _detect_style,
            _detect_testing,
            _load_yaml,
            _merge,
            _read_pyproject,
            _safe_yaml_dump,
        )

        root = Path.cwd()
        pyproj = _read_pyproject(root)
        context_data = {
            "structure": _detect_structure(root),
            "style": _detect_style(pyproj),
            "testing": _detect_testing(pyproj),
            "commands": _detect_commands(),
            "repo": {"commit_convention": _detect_commit_convention(root)},
        }
        ctx_path = root / ".warden" / "context.yaml"
        existing_ctx = _load_yaml(ctx_path)
        merged = _merge(existing_ctx, context_data)
        if merged != existing_ctx:
            ctx_path.parent.mkdir(parents=True, exist_ok=True)
            ctx_path.write_text(_safe_yaml_dump(merged), encoding="utf-8")
            console.print(f"[green]Created context:[/green] {ctx_path}")
        else:
            console.print("[dim]Context unchanged (.warden/context.yaml)[/dim]")
    except Exception as e:
        console.print(f"[yellow]Warning: Context generation skipped: {e}[/yellow]")

    console.print("\n[bold green]✨ Warden Initialization Complete![/bold green]")
    console.print("Run [bold cyan]warden scan[/bold cyan] to start.")
    console.print("[dim]Available frames: security, resilience, orphan, fuzz, property[/dim]")
