"""PlanningEngine — orchestrates prompt assembly + LLM call → structured Plan.

DEPRECATED: Superseded by runtime/agent/loop.py (agent loop pattern).
The model IS the planner — no static plan generation needed.
Kept for reference only. Will be removed in a follow-up PR.
"""

import json
import re
from pathlib import Path

import structlog

from fliiq.runtime.llm.providers import BaseLLM
from fliiq.runtime.planning.domain_detector import detect_domain
from fliiq.runtime.planning.models import Plan, PlanStep
from fliiq.runtime.planning.playbook_loader import load_playbook, load_soul
from fliiq.runtime.planning.prompt_assembly import assemble_system_prompt

log = structlog.get_logger()

_PLAN_INSTRUCTION = """\
You are generating a structured plan. Respond with ONLY valid JSON (no markdown fencing, no explanation).

Format:
{
  "goal": "one-sentence goal statement",
  "steps": [
    {"title": "step title", "description": "what to do", "skill": null, "dependencies": []}
  ]
}

Rules:
- Each step should be concrete and actionable
- Use "skill" field if a known skill should execute the step (null otherwise)
- "dependencies" lists step indices (0-based) that must complete first
- Keep steps focused — one action per step
- 3-8 steps typical for most tasks"""


def _extract_json(text: str) -> dict:
    """Extract JSON from LLM response. Tries direct parse, then fenced block fallback."""
    # Direct parse
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass

    # Fenced code block fallback
    match = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", text, re.DOTALL)
    if match:
        try:
            return json.loads(match.group(1))
        except json.JSONDecodeError:
            pass

    raise ValueError(f"Could not extract valid JSON from LLM response: {text[:200]}...")


class PlanningEngine:
    def __init__(self, llm: BaseLLM, project_root: Path | None = None):
        self._llm = llm
        self._project_root = project_root

    async def generate_plan(self, prompt: str) -> Plan:
        # Detect domain and load relevant playbook
        domains = detect_domain(prompt, working_dir=self._project_root, project_root=self._project_root)
        log.debug("detected_domains", domains=domains)

        playbook_content = None
        for domain in domains:
            pb = load_playbook(domain, project_root=self._project_root)
            if pb:
                playbook_content = pb
                break  # Use first matching playbook

        # Load SOUL
        soul_content, user_soul = load_soul(project_root=self._project_root)

        # Assemble system prompt
        system = assemble_system_prompt(
            soul=soul_content,
            user_soul=user_soul,
            playbook=playbook_content,
        )

        # Add plan instruction to system prompt
        system = f"{system}\n\n{_PLAN_INSTRUCTION}"

        # Call LLM
        messages = [{"role": "user", "content": prompt}]
        response = await self._llm.generate(messages, system=system)
        log.debug("llm_response", provider=response.provider, model=response.model)

        # Parse response into Plan
        raw = _extract_json(response.content)
        steps = [
            PlanStep(
                title=s["title"],
                description=s["description"],
                skill=s.get("skill"),
                dependencies=[str(d) for d in s.get("dependencies", [])],
            )
            for s in raw["steps"]
        ]

        return Plan(
            prompt=prompt,
            goal=raw["goal"],
            steps=steps,
            metadata={"provider": response.provider, "model": response.model},
        )
