"""Plan task node for executor graph.

Phase 2.4.2: Pre-execution planning to identify files, dependencies, risks.

This node runs between pick_task and build_context when --enable-planning
is set. It uses an LLM to analyze the task and generate a structured
execution plan that informs context building.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from ai_infra.executor.state import ExecutorGraphState
from ai_infra.logging import get_logger

if TYPE_CHECKING:
    from ai_infra.agent import Agent

logger = get_logger("executor.nodes.plan")


# =============================================================================
# Phase 2.4.2: Execution Plan Data Model
# =============================================================================


@dataclass
class ExecutionPlan:
    """Structured execution plan for a task.

    Phase 2.4.2: Generated by plan_task_node to inform context building.

    Attributes:
        likely_files: Files likely to be created or modified.
        dependencies: External dependencies/imports that may be needed.
        risks: Potential risks or edge cases to consider.
        approach: Brief description of suggested approach (2-3 sentences).
        complexity: Estimated complexity (low/medium/high).
    """

    likely_files: list[str] = field(default_factory=list)
    dependencies: list[str] = field(default_factory=list)
    risks: list[str] = field(default_factory=list)
    approach: str = ""
    complexity: str = "medium"

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for state storage."""
        return {
            "likely_files": self.likely_files,
            "dependencies": self.dependencies,
            "risks": self.risks,
            "approach": self.approach,
            "complexity": self.complexity,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ExecutionPlan:
        """Create from dictionary."""
        return cls(
            likely_files=data.get("likely_files", []),
            dependencies=data.get("dependencies", []),
            risks=data.get("risks", []),
            approach=data.get("approach", ""),
            complexity=data.get("complexity", "medium"),
        )


# =============================================================================
# Planning Prompt
# =============================================================================

PLANNING_PROMPT = """Analyze this task and create an execution plan.

## Task
**Title**: {title}
**Description**: {description}
**File Hints**: {file_hints}

## Instructions

Analyze the task and provide a structured plan with:

1. **Files to Modify/Create**: List specific file paths likely to be involved.
   Think about: source files, test files, config files, documentation.

2. **Dependencies**: List any external packages or imports that may be needed.
   Only include new dependencies not already in the project.

3. **Risks and Edge Cases**: List potential issues to watch for.
   Consider: breaking changes, edge cases, compatibility issues.

4. **Approach**: Describe the implementation approach in 2-3 sentences.
   Be specific about the key steps.

5. **Complexity**: Estimate as LOW, MEDIUM, or HIGH based on:
   - LOW: Simple change, 1-2 files, no new dependencies
   - MEDIUM: Moderate change, 3-5 files, may need refactoring
   - HIGH: Complex change, 5+ files, new patterns or architecture

## Response Format

Respond with exactly this format:

FILES:
- path/to/file1.py
- path/to/file2.py

DEPENDENCIES:
- package1
- package2

RISKS:
- Risk description 1
- Risk description 2

APPROACH:
Description of the implementation approach in 2-3 sentences.

COMPLEXITY: MEDIUM
"""


def _parse_planning_response(response: str) -> ExecutionPlan:
    """Parse LLM planning response into ExecutionPlan.

    Args:
        response: Raw LLM response text.

    Returns:
        Structured ExecutionPlan.
    """
    import re

    plan = ExecutionPlan()

    # Parse FILES section
    files_match = re.search(
        r"FILES:\s*\n((?:[-*]\s*.+\n?)+)",
        response,
        re.IGNORECASE,
    )
    if files_match:
        files_text = files_match.group(1)
        plan.likely_files = [
            line.strip().lstrip("-* ")
            for line in files_text.strip().split("\n")
            if line.strip() and line.strip().lstrip("-* ")
        ]

    # Parse DEPENDENCIES section
    deps_match = re.search(
        r"DEPENDENCIES:\s*\n((?:[-*]\s*.+\n?)+)",
        response,
        re.IGNORECASE,
    )
    if deps_match:
        deps_text = deps_match.group(1)
        plan.dependencies = [
            line.strip().lstrip("-* ")
            for line in deps_text.strip().split("\n")
            if line.strip() and line.strip().lstrip("-* ") and "none" not in line.lower()
        ]

    # Parse RISKS section
    risks_match = re.search(
        r"RISKS:\s*\n((?:[-*]\s*.+\n?)+)",
        response,
        re.IGNORECASE,
    )
    if risks_match:
        risks_text = risks_match.group(1)
        plan.risks = [
            line.strip().lstrip("-* ")
            for line in risks_text.strip().split("\n")
            if line.strip() and line.strip().lstrip("-* ") and "none" not in line.lower()
        ]

    # Parse APPROACH section
    approach_match = re.search(
        r"APPROACH:\s*\n(.+?)(?:\n\n|COMPLEXITY:|$)",
        response,
        re.IGNORECASE | re.DOTALL,
    )
    if approach_match:
        plan.approach = approach_match.group(1).strip()

    # Parse COMPLEXITY
    complexity_match = re.search(
        r"COMPLEXITY:\s*(LOW|MEDIUM|HIGH)",
        response,
        re.IGNORECASE,
    )
    if complexity_match:
        plan.complexity = complexity_match.group(1).lower()

    return plan


# =============================================================================
# Plan Task Node
# =============================================================================


async def plan_task_node(
    state: ExecutorGraphState,
    *,
    planner_agent: Agent | None = None,
) -> ExecutorGraphState:
    """Plan execution strategy before building context.

    Phase 2.4.2: Pre-execution planning to identify files, dependencies, risks.

    This node:
    1. Gets current_task from state
    2. Uses LLM to analyze task and generate execution plan
    3. Returns structured plan for context building to use

    The plan includes:
    - likely_files: Files to be modified/created
    - dependencies: New dependencies needed
    - risks: Potential edge cases
    - approach: Implementation strategy
    - complexity: LOW/MEDIUM/HIGH estimate

    Args:
        state: Current graph state with current_task.
        planner_agent: Agent for planning (can use smaller/faster model).

    Returns:
        Updated state with task_plan dict.
    """
    current_task = state.get("current_task")
    task_id = str(current_task.id) if current_task else "unknown"

    if current_task is None:
        logger.warning("plan_task called without current_task")
        return {
            **state,
            "task_plan": None,
        }

    if planner_agent is None:
        logger.warning(f"No planner agent provided for task [{task_id}], skipping planning")
        return {
            **state,
            "task_plan": None,
        }

    logger.info(f"Planning task [{task_id}]: {current_task.title}")

    try:
        # Build planning prompt
        prompt = PLANNING_PROMPT.format(
            title=current_task.title,
            description=current_task.description or "(no description)",
            file_hints=", ".join(current_task.file_hints) if current_task.file_hints else "(none)",
        )

        # Get planning response from LLM
        result = await planner_agent.arun(prompt)
        response_text = str(result)

        # Parse response into structured plan
        plan = _parse_planning_response(response_text)

        logger.info(
            f"Generated plan for task [{task_id}]: "
            f"{len(plan.likely_files)} files, "
            f"{len(plan.dependencies)} deps, "
            f"complexity={plan.complexity}"
        )

        return {
            **state,
            "task_plan": plan.to_dict(),
        }

    except Exception as e:
        logger.warning(f"Planning failed for task [{task_id}]: {e}")
        # Planning is optional - continue without plan
        return {
            **state,
            "task_plan": None,
        }
