# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
"""Checklist Executor for LLM-Driven Code Generation.

This module executes checklist items generated by ChecklistGenerator.
For code-generating templates, the LLM is invoked per item to produce
contextual, high-quality code. CLI commands remain deterministic.

The executor:
1. Receives a GeneratedChecklist from ChecklistGenerator
2. For each item, routes to LLM generation or deterministic execution
3. Tracks results and handles errors with recovery
4. Returns a complete ExecutionResult

Error handling follows the three-tier strategy:
1. RETRY: Simple retries for transient errors
2. FIX_AND_RETRY: Auto-fix and retry for known issues
3. ABORT: Stop execution for unrecoverable errors
"""

import inspect
import json
import logging
import os
import sys
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Protocol

from gaia.agents.base.console import AgentConsole
from gaia.agents.base.tools import _TOOL_REGISTRY

from .checklist_generator import ChecklistItem, GeneratedChecklist
from .steps.base import StepResult, ToolExecutor, UserContext
from .steps.error_handler import ErrorHandler, RecoveryAction
from .template_catalog import get_template

logger = logging.getLogger(__name__)


class ChatSDK(Protocol):
    """Protocol for chat SDK interface used by LLM code generation."""

    def send(self, message: str, timeout: int = 600, no_history: bool = False) -> Any:
        """Send a message and get response."""
        ...

    def send_stream(self, message: str, **kwargs) -> Any:
        """Send a message and get streaming response."""
        ...


# ============================================================================
# Template Classification
# ============================================================================

# Templates that execute CLI commands - remain deterministic (no LLM)
DETERMINISTIC_TEMPLATES = {
    "create_next_app",
    "setup_prisma",
    "prisma_db_sync",
    "setup_testing",
    "run_typescript_check",
    "validate_styles",
    "generate_style_tests",
    "run_tests",
    "fix_code",
}

# Templates that generate code files - use LLM for contextual generation
# NOTE: generate_prisma_model is NOT here because it must APPEND to schema.prisma,
# not overwrite it. The manage_data_model tool handles this correctly.
LLM_GENERATED_TEMPLATES = {
    "generate_react_component",
    "generate_api_route",
    "setup_app_styling",
    "update_landing_page",
}

# Template metadata for validation of LLM-generated code
TEMPLATE_METADATA: Dict[str, Dict[str, Any]] = {
    "generate_react_component": {
        "list": {
            "requires_client": False,
            "expected_classes": ["page-title", "btn-primary"],
            "file_pattern": "src/app/{resource}s/page.tsx",
        },
        "form": {
            "requires_client": True,
            "expected_classes": [
                "input-field",
                "btn-primary",
                "btn-secondary",
            ],
            "file_pattern": "src/components/{Resource}Form.tsx",
        },
        "new": {
            "requires_client": True,
            "expected_classes": ["page-title", "link-back"],
            "file_pattern": "src/app/{resource}s/new/page.tsx",
        },
        "detail": {
            "requires_client": True,
            "expected_classes": [
                "page-title",
                "btn-primary",
                "btn-danger",
            ],
            "file_pattern": "src/app/{resource}s/[id]/page.tsx",
        },
    },
    "generate_api_route": {
        "collection": {
            "requires_client": False,
            "expected_classes": [],
            "file_pattern": "src/app/api/{resource}s/route.ts",
        },
        "item": {
            "requires_client": False,
            "expected_classes": [],
            "file_pattern": "src/app/api/{resource}s/[id]/route.ts",
        },
    },
    # NOTE: generate_prisma_model removed - uses tool-based execution, not LLM
    "setup_app_styling": {
        "default": {
            "requires_client": False,
            "expected_classes": ["page-title", "btn-primary"],
            "file_pattern": "src/app/globals.css",
        },
    },
    "update_landing_page": {
        "default": {
            "requires_client": False,
            "expected_classes": ["page-title"],
            "file_pattern": "src/app/page.tsx",
        },
    },
}

# Templates whose results should be logged for downstream validation/QA prompts
VALIDATION_TEMPLATES = {
    "run_typescript_check",
    "validate_styles",
    "run_tests",
}


@dataclass
class ItemExecutionResult:
    """Result of executing a single checklist item."""

    template: str
    params: Dict[str, Any]
    description: str
    success: bool
    files: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    error: Optional[str] = None
    error_recoverable: bool = True
    output: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "template": self.template,
            "params": self.params,
            "description": self.description,
            "success": self.success,
            "files": self.files,
            "warnings": self.warnings,
            "error": self.error,
        }


@dataclass
class ValidationLogEntry:
    """Structured log entry for validation/test steps."""

    template: str
    description: str
    success: bool
    error: Optional[str]
    output: Dict[str, Any] = field(default_factory=dict)
    files: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to plain dictionary for serialization."""
        return {
            "template": self.template,
            "description": self.description,
            "success": self.success,
            "error": self.error,
            "files": self.files,
            "output": self.output,
        }


@dataclass
class ChecklistExecutionResult:
    """Result of executing a complete checklist."""

    checklist: GeneratedChecklist
    item_results: List[ItemExecutionResult] = field(default_factory=list)
    success: bool = True
    total_files: List[str] = field(default_factory=list)
    errors: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    validation_logs: List[ValidationLogEntry] = field(default_factory=list)

    @property
    def items_succeeded(self) -> int:
        """Count of successfully executed items."""
        return sum(1 for r in self.item_results if r.success)

    @property
    def items_failed(self) -> int:
        """Count of failed items."""
        return sum(1 for r in self.item_results if not r.success)

    @property
    def summary(self) -> str:
        """Human-readable summary of execution."""
        status = "SUCCESS" if self.success else "FAILED"
        return (
            f"{status}: {self.items_succeeded}/{len(self.item_results)} items completed, "
            f"{len(self.total_files)} files created"
        )

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "success": self.success,
            "summary": self.summary,
            "reasoning": self.checklist.reasoning,
            "items": [r.to_dict() for r in self.item_results],
            "files": self.total_files,
            "errors": self.errors,
            "warnings": self.warnings,
            "validation_logs": [log.to_dict() for log in self.validation_logs],
        }


# Template to tool mapping
TEMPLATE_TO_TOOL: Dict[str, str] = {
    "create_next_app": "run_cli_command",  # Uses npx create-next-app
    "setup_app_styling": "setup_app_styling",
    "setup_prisma": "run_cli_command",  # Uses npx prisma init + creates singleton
    "prisma_db_sync": "run_cli_command",  # Uses npx prisma generate && npx prisma db push
    "setup_testing": "setup_nextjs_testing",
    "generate_prisma_model": "manage_data_model",
    "generate_api_route": "manage_api_endpoint",
    "generate_react_component": "manage_react_component",
    "update_landing_page": "update_landing_page",
    "run_typescript_check": "validate_typescript",
    "validate_styles": "validate_styles",  # CSS/design system validation (Issue #1002)
    "generate_style_tests": "generate_style_tests",  # Generate CSS tests
    "run_tests": "run_cli_command",  # Uses npm test
    "fix_code": "fix_code",
}

# Next.js version for create-next-app
NEXTJS_VERSION = "14.2.33"


class ChecklistExecutor:
    """Execute checklist items with LLM-driven code generation.

    For code-generating templates, the LLM is invoked per item to produce
    contextual, high-quality code. CLI commands remain deterministic.

    Template routing:
    - DETERMINISTIC_TEMPLATES: Execute CLI commands directly (no LLM)
    - LLM_GENERATED_TEMPLATES: Use LLM to generate code with template as guidance
    - Fallback: Use tool executor for unknown templates

    Error recovery uses the three-tier strategy via ErrorHandler:
    1. RETRY: Simple retry for transient errors
    2. FIX_AND_RETRY: LLM fixes code then retry
    3. ESCALATE: LLM rewrites from scratch
    4. ABORT: Give up after max attempts
    """

    def __init__(
        self,
        tool_executor: ToolExecutor,
        llm_client: Optional[ChatSDK] = None,
        error_handler: Optional[ErrorHandler] = None,
        progress_callback: Optional[Callable[[str, int, int], None]] = None,
        console: Optional[AgentConsole] = None,
    ):
        """Initialize the checklist executor.

        Args:
            tool_executor: Function to execute tools (name, args) -> result
            llm_client: Optional LLM client for code generation (enables per-item LLM)
            error_handler: Optional error handler for recovery (enables retries)
            progress_callback: Optional callback(item_desc, current, total)
            console: Optional console for displaying output
        """
        self.tool_executor = tool_executor
        self.llm_client = llm_client
        self.error_handler = error_handler
        self.progress_callback = progress_callback
        self.console = console or AgentConsole()
        self._tool_signature_cache: Dict[str, inspect.Signature] = {}

    def _tool_accepts_parameter(self, tool_name: str, parameter: str) -> bool:
        """Return True if the tool accepts the provided parameter."""
        tool_entry = _TOOL_REGISTRY.get(tool_name)
        if not tool_entry:
            return False

        if tool_name in self._tool_signature_cache:
            signature = self._tool_signature_cache[tool_name]
        else:
            tool_func = tool_entry.get("function")
            if not tool_func:
                return False
            try:
                signature = inspect.signature(tool_func)
            except (TypeError, ValueError):
                return False
            self._tool_signature_cache[tool_name] = signature

        if parameter in signature.parameters:
            return True

        return any(
            param.kind == inspect.Parameter.VAR_KEYWORD
            for param in signature.parameters.values()
        )

    def execute(
        self,
        checklist: GeneratedChecklist,
        context: UserContext,
        stop_on_error: bool = True,
        step_through: bool = False,
    ) -> ChecklistExecutionResult:
        """Execute all checklist items in order.

        Args:
            checklist: Checklist to execute
            context: User context with project info
            stop_on_error: Whether to stop on first critical error
            step_through: Enable step-through debugging

        Returns:
            ChecklistExecutionResult with all item results
        """
        logger.debug(
            f"Executing checklist with {len(checklist.items)} items: "
            f"{checklist.reasoning}"
        )

        self.console.print_checklist_reasoning(checklist.reasoning)

        result = ChecklistExecutionResult(checklist=checklist)

        # Check for validation errors first
        if not checklist.is_valid:
            logger.error(
                f"Checklist has validation errors: {checklist.validation_errors}"
            )
            result.success = False
            result.errors.extend(checklist.validation_errors)
            return result

        # Use items in the order generated by LLM
        ordered_items = checklist.items
        total = len(ordered_items)

        # Execute each item with error recovery
        for idx, item in enumerate(ordered_items, 1):
            self.console.print_checklist(ordered_items, idx - 1)
            self._report_progress(item.description, idx, total)

            # Use recovery wrapper if error_handler is available
            item_result = self._execute_item_with_recovery(item, context)
            result.item_results.append(item_result)

            # Capture validation/test output for downstream prompts
            if item.template in VALIDATION_TEMPLATES:
                result.validation_logs.append(
                    ValidationLogEntry(
                        template=item.template,
                        description=item.description,
                        success=item_result.success,
                        error=item_result.error,
                        output=item_result.output,
                        files=item_result.files,
                    )
                )

            # Collect files
            result.total_files.extend(item_result.files)

            # Handle errors
            if not item_result.success:
                result.errors.append(item_result.error or "Unknown error")

                if stop_on_error and not item_result.error_recoverable:
                    logger.error(
                        f"Stopping execution due to critical error in "
                        f"{item.template}: {item_result.error}"
                    )
                    result.success = False
                    break

            # Collect warnings
            result.warnings.extend(item_result.warnings)

            # Handle step-through
            if step_through:
                if not self._handle_step_through(item.description):
                    logger.info("Execution stopped by user during step-through")
                    break

        # Update overall success
        if result.items_failed > 0:
            result.success = False

        logger.info(result.summary)
        return result

    def _execute_item(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> ItemExecutionResult:
        """Execute a single checklist item with routing.

        Routes execution based on template type:
        - DETERMINISTIC_TEMPLATES: Execute CLI commands directly (no LLM)
        - LLM_GENERATED_TEMPLATES: Use LLM to generate code (if llm_client available)
        - Fallback: Use tool executor for unknown templates

        Args:
            item: Checklist item to execute
            context: User context

        Returns:
            ItemExecutionResult
        """
        logger.debug(f"Executing: {item.template} - {item.description}")

        try:
            # Route 1: Deterministic templates (CLI commands) - no LLM needed
            if item.template in DETERMINISTIC_TEMPLATES:
                logger.debug(f"Deterministic execution for {item.template}")
                return self._execute_deterministic(item, context)

            # Route 2: LLM-generated templates - use LLM for code generation
            if item.template in LLM_GENERATED_TEMPLATES and self.llm_client:
                logger.info(f"LLM code generation for {item.template}")
                return self._execute_with_llm(item, context)

            # Route 3: Fallback to tool execution (no LLM or unknown template)
            logger.debug(f"Fallback tool execution for {item.template}")
            return self._execute_via_tool(item, context)

        except Exception as e:
            logger.exception(f"Exception executing {item.template}")
            return ItemExecutionResult(
                template=item.template,
                params=item.params,
                description=item.description,
                success=False,
                error=str(e),
                error_recoverable=False,
            )

    def _execute_deterministic(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> ItemExecutionResult:
        """Execute a deterministic template (CLI command).

        These templates don't need LLM - they run predefined commands.

        Args:
            item: Checklist item to execute
            context: User context

        Returns:
            ItemExecutionResult
        """
        # Map template to tool name
        tool_name = TEMPLATE_TO_TOOL.get(item.template, item.template)

        # Build params for CLI execution
        params = self._build_params(item, context)

        logger.debug(f"Calling tool '{tool_name}' with params: {params}")

        # Execute the tool
        raw_result = self.tool_executor(tool_name, params)

        # Parse result
        result = self._parse_tool_result(item, raw_result)

        # Post-command file operations (cross-platform, avoids shell file writing)
        if result.success and item.template == "setup_prisma":
            self._write_prisma_singleton(context.project_dir)

        return result

    def _execute_via_tool(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> ItemExecutionResult:
        """Execute via tool executor (fallback when no LLM).

        Args:
            item: Checklist item to execute
            context: User context

        Returns:
            ItemExecutionResult
        """
        # Map template to tool name
        tool_name = TEMPLATE_TO_TOOL.get(item.template, item.template)

        # Build params with project_dir
        params = self._build_params(item, context)

        logger.debug(f"Calling tool '{tool_name}' with params: {params}")

        # Execute the tool
        raw_result = self.tool_executor(tool_name, params)

        # Parse result
        return self._parse_tool_result(item, raw_result)

    def _execute_with_llm(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> ItemExecutionResult:
        """Execute a checklist item using LLM code generation.

        This is the core of Phase 9 - LLM generates contextual code using
        templates as structural guidance.

        Args:
            item: Checklist item to execute
            context: User context

        Returns:
            ItemExecutionResult
        """
        logger.info(f"Generating code with LLM for {item.template}")

        try:
            # 1. Get template as guidance
            template_guidance = self._get_template_guidance(item)
            if not template_guidance:
                logger.warning(
                    f"No template guidance for {item.template}, falling back to tool"
                )
                return self._execute_via_tool(item, context)

            # 1b. Resolve field definitions for prompt + post-processing
            resolved_fields = self._resolve_fields(item, context)

            # 2. Build prompt
            prompt = self._build_code_generation_prompt(
                item, context, template_guidance, resolved_fields
            )

            # 3. Call LLM
            logger.debug(f"Sending prompt to LLM ({len(prompt)} chars)")

            file_path = self._determine_file_path(item, context)

            # Start file preview
            self.console.start_file_preview(file_path, max_lines=15)

            # Stream the response
            full_response = ""
            try:
                # Try streaming first if available
                if hasattr(self.llm_client, "send_stream"):
                    for chunk in self.llm_client.send_stream(prompt, timeout=1200):
                        if hasattr(chunk, "text"):
                            text = chunk.text
                            self.console.update_file_preview(text)
                            full_response += text

                    if full_response.strip():
                        generated_code = full_response
                    else:
                        raise ValueError("Empty streaming response")
                else:
                    # Fallback to non-streaming
                    response = self.llm_client.send(prompt, timeout=1200)
                    if hasattr(response, "text"):
                        generated_code = response.text
                    elif hasattr(response, "content"):
                        generated_code = response.content
                    else:
                        generated_code = str(response)

                    self.console.update_file_preview(generated_code)

            except Exception as e:
                # Fallback if streaming fails
                logger.warning(f"Streaming failed, falling back to standard send: {e}")
                response = self.llm_client.send(prompt, timeout=1200)
                if hasattr(response, "text"):
                    generated_code = response.text
                elif hasattr(response, "content"):
                    generated_code = response.content
                else:
                    generated_code = str(response)

                self.console.update_file_preview(generated_code)

            # Stop file preview
            self.console.stop_file_preview()

            # 4. Clean response (strip markdown if present)
            clean_code = self._clean_llm_response(generated_code)

            # 5. Validate generated code
            is_valid, issues, is_blocking = self._validate_generated_code(
                clean_code, item
            )
            if not is_valid:
                logger.warning(f"Validation issues for {item.template}: {issues}")

                # CRITICAL: Block file write for blocking errors (Issue #1002)
                # This prevents TypeScript code from being written to CSS files
                if is_blocking:
                    logger.error(
                        f"BLOCKING validation error for {item.template}: {issues}"
                    )
                    return ItemExecutionResult(
                        template=item.template,
                        params=item.params,
                        description=item.description,
                        success=False,
                        error=f"Content validation failed: {'; '.join(issues)}",
                        error_recoverable=True,  # Allow LLM retry with recovery
                    )
                # Non-blocking issues: log warning and continue (best effort)

            # 6. Write to file
            full_path = os.path.join(context.project_dir, file_path)

            # Create directory if needed
            os.makedirs(os.path.dirname(full_path), exist_ok=True)

            # Write the generated code
            with open(full_path, "w", encoding="utf-8") as f:
                f.write(clean_code)

            logger.info(f"Wrote LLM-generated code to {file_path}")

            generated_files = [file_path]

            return ItemExecutionResult(
                template=item.template,
                params=item.params,
                description=item.description,
                success=True,
                files=generated_files,
                warnings=issues if not is_valid else [],
            )

        except Exception as e:
            logger.exception(f"LLM generation failed for {item.template}")
            return ItemExecutionResult(
                template=item.template,
                params=item.params,
                description=item.description,
                success=False,
                error=str(e),
                error_recoverable=True,
            )

    def _build_code_generation_prompt(
        self,
        item: ChecklistItem,
        context: UserContext,
        template_guidance: str,
        fields_override: Optional[Dict[str, str]] = None,
    ) -> str:
        """Build prompt for LLM code generation.

        Args:
            item: Checklist item describing what to generate
            context: User context with project info
            template_guidance: Template structure as guidance

        Returns:
            Prompt string for LLM
        """
        # CSS templates need a different prompt - they should NOT generate TypeScript
        css_templates = {"setup_app_styling"}
        if item.template in css_templates:
            return self._build_css_generation_prompt(item, template_guidance)

        resource = item.params.get("resource", "item")
        variant = item.params.get("variant", "default")
        fields = (
            fields_override
            if fields_override is not None
            else item.params.get("fields", context.schema_fields or {})
        )

        # Determine file type and output format
        is_css_file = item.template == "setup_app_styling"
        file_type = "CSS" if is_css_file else "TypeScript/TSX"
        code_language = "css" if is_css_file else "typescript"
        start_instruction = (
            "Start immediately with @tailwind directives or CSS rules"
            if is_css_file
            else 'Start immediately with imports or "use client"'
        )

        # Get required classes for this variant
        required_classes = self._get_required_classes(item)
        required_classes_str = (
            ", ".join(f"`{cls}`" for cls in required_classes)
            if required_classes
            else "None specified"
        )

        # Build architecture rules - skip TypeScript-specific rules for CSS
        architecture_rules = ""
        if not is_css_file:
            architecture_rules = """## Architecture Rules
- Use Server Components by default for data fetching
- Add "use client" directive ONLY when using hooks, event handlers, or browser APIs
- Define explicit TypeScript types for all props and return values
- Use Prisma-generated types where applicable
- Never use `any` type

---"""

        # Add CSS-specific warning for CSS files
        css_warning = ""
        if is_css_file:
            css_warning = """## CRITICAL: CSS File Requirements

This is a CSS file (globals.css). You MUST generate ONLY CSS code:
- NO TypeScript/JavaScript code
- NO import statements
- NO export statements
- NO const/let/function declarations
- NO JSX/React components
- NO TypeScript interfaces or types
- ONLY CSS rules, @tailwind directives, @layer directives, and CSS selectors

---

"""

        return f"""You are an expert Next.js 14+ developer specializing in full-stack TypeScript applications.

{css_warning}## CRITICAL: Required CSS Classes

Your generated code MUST include these CSS classes:
{required_classes_str}

These classes are MANDATORY and will be validated. Code without them will fail validation.

---

## Task
Generate a {item.template} ({variant}) for the {resource} resource.

### Purpose
{item.description}

### User Request Context
{context.user_request}

### Parameters
{json.dumps(item.params, indent=2)}

### Data Model Fields
{json.dumps(fields, indent=2) if fields else "Not specified - use reasonable defaults based on resource name"}

---

{architecture_rules}## Design System Classes (Dark Theme)

**Containers**: `glass-card` (ALWAYS use for main content container - glassmorphism effect)
**Typography**: `page-title` (ALWAYS use for h1 headers - gradient text)
**Buttons**: `btn-primary` (blue gradient), `btn-secondary` (outline), `btn-danger` (red)
**Forms**: `input-field`, `select-field`, `textarea-field`, `label-text`, `form-group`
**Navigation**: `link-back` (for ← back links)
**Checkboxes**: `checkbox-modern` (for boolean fields)

Theme: Dark backgrounds (slate-900), white text, blue-500 accents, white/10 borders.

---

## Reference Pattern

Adapt this structural pattern. Replace placeholders with actual values for {resource}:

```{code_language}
{template_guidance}
```

---

## Output Format

Return ONLY raw {file_type} code:
- NO markdown code blocks (no ```)
- NO explanatory text before or after
- {start_instruction}
- MUST include all required CSS classes listed above"""

    def _build_css_generation_prompt(
        self,
        item: ChecklistItem,
        template_guidance: str,
    ) -> str:
        """Build prompt for CSS code generation.

        This is a specialized prompt for CSS files that ensures the LLM
        generates Tailwind CSS instead of TypeScript/JSX.

        Args:
            item: Checklist item describing what to generate
            template_guidance: CSS template as guidance

        Returns:
            Prompt string for LLM
        """
        return f"""You are an expert CSS developer specializing in Tailwind CSS.

## Task
Generate a Tailwind CSS stylesheet for: {item.description}

## Rules
- Return ONLY CSS code (Tailwind CSS with @apply directives is valid CSS)
- NO TypeScript, JavaScript, imports, or exports
- NO React components or JSX
- NO markdown code blocks
- Start with @tailwind directives

## Required Structure
The CSS must include:
1. @tailwind base, components, utilities directives
2. :root CSS variables for theming
3. @layer components with these classes using @apply:
   - .glass-card (glassmorphism container)
   - .page-title (gradient text heading)
   - .btn-primary, .btn-secondary, .btn-danger (buttons)
   - .input-field (form inputs)
   - .checkbox-modern (styled checkboxes)
   - .link-back (navigation links)

## Reference Pattern

Follow this Tailwind CSS template exactly:

{template_guidance}

## Output
Return raw CSS starting with @tailwind base;"""

    def _get_template_guidance(self, item: ChecklistItem) -> Optional[str]:
        """Get template content as guidance for LLM.

        The templates from code_patterns.py serve as structural guidance,
        not verbatim content to copy.

        Args:
            item: Checklist item with template and params

        Returns:
            Template string if found, None otherwise
        """
        # Import templates lazily to avoid circular imports
        try:
            from ..prompts.code_patterns import (
                API_ROUTE_DYNAMIC_DELETE,
                API_ROUTE_DYNAMIC_GET,
                API_ROUTE_DYNAMIC_PATCH,
                API_ROUTE_GET,
                API_ROUTE_POST,
                APP_GLOBALS_CSS,
                CLIENT_COMPONENT_FORM,
                CLIENT_COMPONENT_NEW_PAGE,
                SERVER_COMPONENT_DETAIL,
                SERVER_COMPONENT_LIST,
            )
        except ImportError:
            logger.warning("Could not import code_patterns templates")
            return None

        # Compose API route templates
        api_route_collection = f"""import {{ NextResponse }} from "next/server";
import {{ prisma }} from "@/lib/prisma";
import {{ z }} from "zod";

// Schema for validation
// IMPORTANT: Use z.coerce.date() for any date/datetime/timestamp fields
const {{Resource}}Schema = z.object({{
  // Example: publishedOn: z.coerce.date(), 
  // Define fields based on your data model
}});

{API_ROUTE_GET}

{API_ROUTE_POST}
"""

        api_route_item = f"""import {{ NextResponse }} from "next/server";
import {{ prisma }} from "@/lib/prisma";
import {{ z }} from "zod";

// Schema for update validation
// IMPORTANT: Use z.coerce.date() for any date/datetime/timestamp fields
const {{Resource}}UpdateSchema = z.object({{
  // Example: publishedOn: z.coerce.date().optional(),
  // Define update fields (all optional for PATCH)
}}).partial();

{API_ROUTE_DYNAMIC_GET}

{API_ROUTE_DYNAMIC_PATCH}

{API_ROUTE_DYNAMIC_DELETE}
"""

        # NOTE: Prisma model guidance removed - uses manage_data_model tool, not LLM

        # Landing page template guidance
        landing_page_guidance = """import Link from "next/link";

export default function Home() {
  return (
    <main className="min-h-screen">
      <div className="container mx-auto px-4 py-12 max-w-4xl">
        <h1 className="page-title mb-8">Welcome</h1>

        <div className="grid gap-6">
          <Link
            href="/{resource}s"
            className="glass-card p-6 block hover:border-indigo-500/50 transition-all duration-300 group"
          >
            <div className="flex items-center justify-between">
              <div>
                <h2 className="text-2xl font-semibold text-slate-100 mb-2 group-hover:text-indigo-400 transition-colors">
                  {Resource}s
                </h2>
                <p className="text-slate-400">Manage your {resource}s</p>
              </div>
              <svg className="w-6 h-6 text-slate-500 group-hover:text-indigo-400 group-hover:translate-x-1 transition-all" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
              </svg>
            </div>
          </Link>
        </div>
      </div>
    </main>
  );
}
"""

        # Mapping of template names to their guidance
        TEMPLATE_GUIDANCE = {
            "generate_react_component": {
                "list": SERVER_COMPONENT_LIST,
                "form": CLIENT_COMPONENT_FORM,
                "new": CLIENT_COMPONENT_NEW_PAGE,
                "detail": SERVER_COMPONENT_DETAIL,
            },
            "generate_api_route": {
                "collection": api_route_collection,
                "item": api_route_item,
            },
            # NOTE: generate_prisma_model removed - uses manage_data_model tool
            "setup_app_styling": {
                "default": APP_GLOBALS_CSS,
            },
            "update_landing_page": {
                "default": landing_page_guidance,
            },
        }

        template_name = item.template
        guidance_map = TEMPLATE_GUIDANCE.get(template_name)

        if guidance_map is None:
            return None

        if isinstance(guidance_map, str):
            return guidance_map

        # Get variant-specific guidance
        variant = item.params.get("variant", "default")
        route_type = item.params.get("type", "default")  # For API routes

        # Try variant first, then route_type, then default
        return (
            guidance_map.get(variant)
            or guidance_map.get(route_type)
            or guidance_map.get("default")
        )

    def _determine_file_path(
        self,
        item: ChecklistItem,
        context: UserContext,  # pylint: disable=unused-argument
    ) -> str:
        """Determine the output file path for generated code.

        Args:
            item: Checklist item with template and params
            context: User context (unused but kept for consistency)

        Returns:
            Relative file path from project root
        """
        template = item.template
        params = item.params
        resource = params.get("resource", "item")
        resource_cap = resource.capitalize()

        if template == "generate_react_component":
            variant = params.get("variant", "list")

            if variant == "list":
                return f"src/app/{resource}s/page.tsx"
            elif variant == "form":
                return f"src/components/{resource_cap}Form.tsx"
            elif variant == "new":
                return f"src/app/{resource}s/new/page.tsx"
            elif variant == "detail":
                return f"src/app/{resource}s/[id]/page.tsx"
            else:
                return f"src/components/{resource_cap}{variant.capitalize()}.tsx"

        elif template == "generate_api_route":
            route_type = params.get("type", "collection")
            if route_type == "item":
                return f"src/app/api/{resource}s/[id]/route.ts"
            else:
                return f"src/app/api/{resource}s/route.ts"

        # NOTE: generate_prisma_model removed - uses manage_data_model tool, not LLM

        elif template == "setup_app_styling":
            return "src/app/globals.css"

        elif template == "update_landing_page":
            return "src/app/page.tsx"

        # Default fallback
        return f"src/generated/{template}.tsx"

    def _resolve_fields(
        self, item: ChecklistItem, context: UserContext
    ) -> Dict[str, str]:
        """Resolve resource fields for code generation prompts."""
        params = item.params or {}

        def _normalize(fields: Dict[str, str]) -> Dict[str, str]:
            type_map = {
                "string": "string",
                "text": "string",
                "int": "number",
                "float": "float",
                "double": "float",
                "number": "number",
                "boolean": "boolean",
                "datetime": "datetime",
                "date": "date",
                "timestamp": "datetime",
                "email": "email",
                "url": "url",
            }
            normalized = {}
            for name, field_type in (fields or {}).items():
                mapped = type_map.get(field_type.lower(), "string")
                normalized[name] = mapped
            return normalized

        if "fields" in params and isinstance(params["fields"], dict):
            return _normalize(params["fields"])

        if context.schema_fields:
            return _normalize(context.schema_fields)

        resource = params.get("resource")
        if not resource:
            return {}

        try:
            from ..tools.web_dev_tools import read_prisma_model
        except ImportError:
            logger.debug("read_prisma_model unavailable for field resolution")
            return {}

        try:
            model_info = read_prisma_model(context.project_dir, resource.capitalize())
        except Exception as exc:  # noqa: BLE001
            logger.warning(f"Failed to read Prisma model for {resource}: {exc}")
            return {}

        if not model_info.get("success"):
            logger.debug(
                "Could not resolve Prisma fields for %s: %s",
                resource,
                model_info.get("error"),
            )
            return {}

        prisma_fields = model_info.get("fields", {})
        return _normalize(prisma_fields)

    def _clean_llm_response(self, response: str) -> str:
        """Clean LLM response by removing markdown artifacts.

        Args:
            response: Raw LLM response

        Returns:
            Cleaned code string
        """
        code = response.strip()

        # Remove markdown code blocks
        if code.startswith("```"):
            lines = code.split("\n")
            # Remove first line (```typescript or ```)
            lines = lines[1:]
            # Remove last line if it's closing ```
            if lines and lines[-1].strip() == "```":
                lines = lines[:-1]
            code = "\n".join(lines)

        # Also handle case where there might be text before code block
        if "```typescript" in code or "```tsx" in code:
            # Find start of code block
            for marker in ["```typescript", "```tsx", "```"]:
                if marker in code:
                    start = code.find(marker)
                    end = code.find("```", start + len(marker))
                    if end > start:
                        code = code[start + len(marker) : end]
                        break

        return code.strip()

    def _get_required_classes(self, item: ChecklistItem) -> List[str]:
        """Get list of required CSS classes for a checklist item.

        Args:
            item: Checklist item with template and variant info

        Returns:
            List of required CSS class names
        """
        metadata = TEMPLATE_METADATA.get(item.template, {})
        variant = item.params.get("variant", "default")
        route_type = item.params.get("type", "default")

        # Try variant, then route_type, then default
        variant_meta = (
            metadata.get(variant)
            or metadata.get(route_type)
            or metadata.get("default")
            or {}
        )

        return variant_meta.get("expected_classes", [])

    def _validate_generated_code(
        self,
        code: str,
        item: ChecklistItem,
    ) -> tuple[bool, List[str], bool]:
        """Validate generated code meets requirements.

        Args:
            code: Generated code to validate
            item: Checklist item with template info

        Returns:
            Tuple of (is_valid, list_of_issues, is_blocking)
            - is_valid: True if no issues found
            - list_of_issues: List of validation issue messages
            - is_blocking: True if issues should prevent file write (CRITICAL errors)
        """
        issues = []
        is_blocking = False

        # Check for markdown artifacts that slipped through
        if code.strip().startswith("```"):
            issues.append("Code still contains markdown block markers")

        # CRITICAL: For CSS files (setup_app_styling), check for TypeScript content
        # This catches Issue #1002 where CSS files contain TypeScript code
        if item.template == "setup_app_styling":
            css_validation = self._validate_css_content_inline(code)
            if css_validation["errors"]:
                issues.extend(css_validation["errors"])
                is_blocking = True  # TypeScript in CSS is a BLOCKING error

        # Get metadata for this template
        metadata = TEMPLATE_METADATA.get(item.template, {})
        variant = item.params.get("variant", "default")
        route_type = item.params.get("type", "default")

        # Try variant, then route_type, then default
        variant_meta = (
            metadata.get(variant)
            or metadata.get(route_type)
            or metadata.get("default")
            or {}
        )

        # Check for expected CSS classes (for UI components)
        expected_classes = variant_meta.get("expected_classes", [])
        for cls in expected_classes:
            if cls not in code:
                issues.append(f"Missing expected class: {cls}")

        # Check for 'use client' when needed
        if variant_meta.get("requires_client"):
            if '"use client"' not in code and "'use client'" not in code:
                issues.append("Missing 'use client' directive for client component")

        # Check for basic TypeScript syntax
        if item.template == "generate_react_component":
            if "export default" not in code and "export function" not in code:
                issues.append("Missing export statement")

        return len(issues) == 0, issues, is_blocking

    def _validate_css_content_inline(self, content: str) -> Dict[str, Any]:
        """Validate CSS content for TypeScript/JavaScript code (Issue #1002).

        This is an inline version of the CSS validation for use during LLM
        code generation. It detects when the LLM accidentally generates
        TypeScript/JSX code instead of CSS.

        Args:
            content: File content to validate

        Returns:
            Dictionary with errors (blocking) and warnings
        """
        import re

        errors = []
        warnings = []

        # CRITICAL: Detect TypeScript/JavaScript code in CSS files
        # These patterns indicate wrong file content - always invalid
        typescript_indicators = [
            (r"^\s*import\s+.*from", "import statement"),
            (r"^\s*export\s+(default|const|function|class|async)", "export statement"),
            (r'"use client"|\'use client\'', "React client directive"),
            (r"^\s*interface\s+\w+", "TypeScript interface"),
            (r"^\s*type\s+\w+\s*=", "TypeScript type alias"),
            (r"^\s*const\s+\w+\s*[=:]", "const declaration"),
            (r"^\s*let\s+\w+\s*[=:]", "let declaration"),
            (r"^\s*function\s+\w+", "function declaration"),
            (r"^\s*async\s+function", "async function"),
            (r"<[A-Z][a-zA-Z]*[\s/>]", "JSX component tag"),
            (r"useState|useEffect|useRouter|usePathname", "React hook"),
        ]

        for pattern, description in typescript_indicators:
            if re.search(pattern, content, re.MULTILINE):
                errors.append(
                    f"CRITICAL - CSS file contains {description}. "
                    f"This is TypeScript/JSX code, not CSS."
                )

        # Check for balanced braces
        if content.count("{") != content.count("}"):
            errors.append("Mismatched braces in CSS")

        # Check for Tailwind directives
        has_tailwind = "@tailwind" in content or '@import "tailwindcss' in content
        if not has_tailwind and len(content.strip()) > 50:
            warnings.append(
                "Missing Tailwind directives (@tailwind base/components/utilities)"
            )

        return {
            "errors": errors,
            "warnings": warnings,
            "is_valid": len(errors) == 0,
        }

    def _execute_item_with_recovery(
        self,
        item: ChecklistItem,
        context: UserContext,
        max_attempts: int = 3,
    ) -> ItemExecutionResult:
        """Execute a checklist item with error recovery.

        Uses the three-tier recovery strategy via ErrorHandler:
        1. RETRY: Simple retry (transient errors)
        2. FIX_AND_RETRY: LLM fixes code then retry
        3. ESCALATE: LLM rewrites from scratch
        4. ABORT: Give up after max attempts

        Args:
            item: Checklist item to execute
            context: User context
            max_attempts: Maximum recovery attempts (default 3)

        Returns:
            ItemExecutionResult from execution (or recovery attempts)
        """
        last_result = None

        for attempt in range(max_attempts):
            try:
                # Execute the item
                result = self._execute_item(item, context)
                last_result = result

                if result.success:
                    # Reset retry count on success
                    if self.error_handler:
                        self.error_handler.reset_retry_count(item.template)
                    return result

                # No error handler - return failure immediately
                if not self.error_handler:
                    logger.warning(
                        f"No error handler available for {item.template}, "
                        f"cannot retry: {result.error}"
                    )
                    return result

                # Last attempt - return failure
                if attempt >= max_attempts - 1:
                    logger.error(
                        f"Max attempts ({max_attempts}) exceeded for {item.template}"
                    )
                    return result

                # Handle failure with error handler
                logger.info(
                    f"Attempting recovery for {item.template} "
                    f"(attempt {attempt + 1}/{max_attempts}): {result.error}"
                )

                action, fix_info = self.error_handler.handle_error(
                    item.template,
                    result.error or "Unknown error",
                    {
                        "code": "",  # Tool output doesn't include code
                        "project_dir": context.project_dir,
                    },
                )

                if action == RecoveryAction.ABORT:
                    logger.error(f"Recovery aborted for {item.template}")
                    return result

                if action == RecoveryAction.RETRY:
                    logger.info(
                        f"Retrying {item.template} "
                        f"(attempt {attempt + 2}/{max_attempts})"
                    )
                    continue

                if action in (RecoveryAction.FIX_AND_RETRY, RecoveryAction.ESCALATE):
                    if fix_info:
                        logger.info(
                            f"Fix applied for {item.template}: {fix_info[:100]}..."
                        )
                    logger.info(
                        f"Retrying {item.template} after fix "
                        f"(attempt {attempt + 2}/{max_attempts})"
                    )
                    continue

            except Exception as e:
                logger.exception(
                    f"Exception in {item.template} (attempt {attempt + 1})"
                )
                last_result = ItemExecutionResult(
                    template=item.template,
                    params=item.params,
                    description=item.description,
                    success=False,
                    error=str(e),
                    error_recoverable=True,
                )

                # Last attempt - return exception result
                if attempt >= max_attempts - 1:
                    last_result.error_recoverable = False
                    return last_result

        # Should not reach here, but return last result just in case
        if last_result:
            return last_result

        return ItemExecutionResult(
            template=item.template,
            params=item.params,
            description=item.description,
            success=False,
            error=f"Max attempts ({max_attempts}) exceeded",
            error_recoverable=False,
        )

    def _build_params(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> Dict[str, Any]:
        """Build tool parameters from checklist item and context.

        Args:
            item: Checklist item
            context: User context

        Returns:
            Dictionary of tool parameters
        """
        params = dict(item.params)
        tool_name = TEMPLATE_TO_TOOL.get(item.template, item.template)

        # Handle CLI command templates specially
        if item.template == "create_next_app":
            # Convert to run_cli_command format
            return {
                "command": (
                    f"npx -y create-next-app@{NEXTJS_VERSION} . "
                    "--typescript --tailwind --eslint --app --src-dir --import-alias '@/*' --yes"
                ),
                "working_dir": context.project_dir,
                "timeout": 1200,
            }

        if item.template == "run_tests":
            # Convert to run_cli_command format
            return {
                "command": "npm test",
                "working_dir": context.project_dir,
                "timeout": 1200,
            }

        if item.template == "prisma_db_sync":
            # Generate Prisma client and push schema to database
            # This MUST run after generate_prisma_model and before API routes
            return {
                "command": "npx -y prisma generate && npx -y prisma db push",
                "working_dir": context.project_dir,
                "timeout": 1200,
            }

        # Handle setup_prisma specially - needs to initialize Prisma first
        if item.template == "setup_prisma":
            return self._build_setup_prisma_params(item, context)

        # Handle generate_react_component specially - needs component_name derivation
        if item.template == "generate_react_component":
            return self._build_react_component_params(item, context)

        # Add project_dir only if tool expects it
        if "project_dir" not in params and self._tool_accepts_parameter(
            tool_name, "project_dir"
        ):
            params["project_dir"] = context.project_dir

        # Map checklist param names to tool param names
        param_mapping = {
            "resource": "resource_name",  # generate_api_route -> manage_api_endpoint
            "model_name": "model_name",  # stays the same
            "variant": "variant",  # stays the same
        }

        # Apply mappings
        for checklist_name, tool_name in param_mapping.items():
            if checklist_name in params and checklist_name != tool_name:
                params[tool_name] = params.pop(checklist_name)

        # Handle specific template parameters
        template_def = get_template(item.template)
        if template_def:
            # Add entity name for data models
            if item.template == "generate_prisma_model" and "model_name" in params:
                context.entity_name = params["model_name"]

            # Handle API route type
            if item.template == "generate_api_route":
                route_type = params.pop("type", "collection")
                if route_type == "item":
                    # For item routes, set operations appropriately
                    if "operations" not in params:
                        params["operations"] = ["GET", "PATCH", "DELETE"]

        return params

    def _build_setup_prisma_params(
        self,
        item: ChecklistItem,  # pylint: disable=unused-argument
        context: UserContext,
    ) -> Dict[str, Any]:
        """Build parameters for Prisma initialization.

        The setup_prisma template needs to:
        1. Initialize Prisma with SQLite (npx prisma init)
        2. Create the singleton file (src/lib/prisma.ts)

        The CLI commands run via shell, but the singleton file is written
        via Python's pathlib in _execute_deterministic() for cross-platform
        compatibility (Windows doesn't support Unix shell file operations).

        Args:
            item: Checklist item (unused, kept for consistency)
            context: User context

        Returns:
            Dictionary of tool parameters for run_cli_command
        """
        # Only run CLI commands - file writing is handled separately in
        # _execute_deterministic() via _write_prisma_singleton() for
        # cross-platform compatibility (mkdir -p and echo don't work on Windows)
        command = (
            "npm install prisma@5 @prisma/client@5 zod && "
            "npx -y prisma init --datasource-provider sqlite"
        )

        return {
            "command": command,
            "working_dir": context.project_dir,
            "timeout": 1200,
        }

    def _write_prisma_singleton(self, project_dir: str) -> None:
        """Write Prisma singleton file using cross-platform Python.

        This method is called after setup_prisma CLI commands succeed.
        We use Python's pathlib instead of shell commands (mkdir -p, echo)
        because those Unix commands don't work on Windows.

        Args:
            project_dir: Project root directory
        """
        from pathlib import Path

        singleton_content = """import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
"""
        lib_dir = Path(project_dir) / "src" / "lib"
        lib_dir.mkdir(parents=True, exist_ok=True)
        singleton_file = lib_dir / "prisma.ts"
        singleton_file.write_text(singleton_content)
        logger.debug(f"Created Prisma singleton at {singleton_file}")

    def _build_react_component_params(
        self,
        item: ChecklistItem,
        context: UserContext,
    ) -> Dict[str, Any]:
        """Build parameters for manage_react_component tool.

        The template catalog defines:
        - resource: Resource name (lowercase, singular)
        - variant: Component variant (list|form|new|detail|actions)
        - with_checkboxes: Boolean (optional, not supported by tool)

        The tool expects:
        - project_dir: Path to project
        - component_name: Component name (e.g., "TodoList", "UserForm")
        - component_type: "server" or "client"
        - resource_name: Associated resource
        - fields: Resource fields (optional)
        - variant: Component variant

        Args:
            item: Checklist item with template params
            context: User context

        Returns:
            Dictionary of tool parameters
        """
        template_params = dict(item.params)

        # Extract resource and variant
        resource = template_params.get("resource", "")
        variant = template_params.get("variant", "list")

        # Generate component_name from resource + variant
        # e.g., "todo" + "list" -> "TodoList"
        resource_capitalized = resource.capitalize() if resource else "Item"
        variant_capitalized = variant.capitalize() if variant else "List"

        # Build component name based on variant
        if variant == "list":
            component_name = f"{resource_capitalized}List"
        elif variant == "form":
            component_name = f"{resource_capitalized}Form"
        elif variant == "new":
            component_name = f"New{resource_capitalized}"
        elif variant == "detail":
            component_name = f"{resource_capitalized}Detail"
        elif variant == "actions":
            component_name = f"{resource_capitalized}Actions"
        else:
            component_name = f"{resource_capitalized}{variant_capitalized}"

        # Determine component_type based on variant
        # list pages are server components, forms and interactive pages are client
        if variant in ("list",):
            component_type = "server"
        else:
            component_type = "client"

        # Build the actual tool params
        tool_params = {
            "project_dir": context.project_dir,
            "component_name": component_name,
            "component_type": component_type,
            "resource_name": resource,
            "variant": variant,
        }

        # Add fields from context if available
        if context.schema_fields:
            tool_params["fields"] = context.schema_fields

        # Note: with_checkboxes is NOT passed - tool doesn't support it
        # The variant and resource determine the component behavior

        return tool_params

    def _parse_tool_result(
        self,
        item: ChecklistItem,
        raw_result: Any,
    ) -> ItemExecutionResult:
        """Parse raw tool result into ItemExecutionResult.

        Args:
            item: Original checklist item
            raw_result: Raw result from tool execution

        Returns:
            ItemExecutionResult
        """
        # Handle different result types
        if isinstance(raw_result, StepResult):
            return ItemExecutionResult(
                template=item.template,
                params=item.params,
                description=item.description,
                success=raw_result.success,
                files=raw_result.output.get("files", []),
                warnings=raw_result.output.get("warnings", []),
                error=raw_result.error_message,
                error_recoverable=raw_result.retryable,
                output=raw_result.output,
            )

        if isinstance(raw_result, dict):
            success = raw_result.get("success", True)
            return ItemExecutionResult(
                template=item.template,
                params=item.params,
                description=item.description,
                success=success,
                files=raw_result.get("files", []),
                warnings=raw_result.get("warnings", []),
                error=raw_result.get("error"),
                error_recoverable=raw_result.get("retryable", True),
                output=raw_result,
            )

        # Unknown result type - treat as success if truthy
        return ItemExecutionResult(
            template=item.template,
            params=item.params,
            description=item.description,
            success=bool(raw_result),
            output={"raw": raw_result},
        )

    def _report_progress(self, description: str, current: int, total: int) -> None:
        """Report progress via callback if available.

        Args:
            description: Current item description
            current: Current item number
            total: Total items
        """
        # Log at debug level to avoid duplicate console output (checklist state is already printed)
        logger.debug(f"[{current}/{total}] {description}")
        if self.progress_callback:
            self.progress_callback(description, current, total)

    def _handle_step_through(self, description: str) -> bool:
        """Handle step-through pause.

        Args:
            description: Description of the completed step

        Returns:
            True to continue, False to stop
        """
        # Check for TTY to avoid hanging in non-interactive modes
        if not sys.stdin or not sys.stdin.isatty():
            # In non-interactive mode, log and continue
            logger.debug(
                f"Step-through enabled but no TTY. Continuing after: {description}"
            )
            return True

        self.console.print_step_paused(description)

        try:
            response = input("> ").strip().lower()
            if response in ["n", "no", "q", "quit", "exit"]:
                return False
            return True
        except (EOFError, KeyboardInterrupt):
            return False
