from pathlib import Path
from typing import Callable, List, Optional

from zrb.context.any_context import AnyContext
from zrb.llm.skill.manager import Skill, SkillManager
from zrb.util.markdown import make_markdown_section


def create_claude_skills_prompt(
    skill_manager: SkillManager,
    active_skills: Optional[List[str]] = None,
    include_claude_skills: bool = True,
):
    def claude_compatibility(
        ctx: AnyContext,
        current_prompt: str,
        next_handler: Callable[[AnyContext, str], str],
    ) -> str:
        search_dirs = _get_search_directories()
        additional_context = []

        # 1. Available Claude Skills
        skills_section = _get_skills_section(
            skill_manager,
            search_dirs,
            active_skills=active_skills,
            include_claude_skills=include_claude_skills,
        )
        if skills_section:
            additional_context.append(skills_section)

        new_section = "\n\n".join(additional_context)
        return next_handler(ctx, f"{current_prompt}\n\n{new_section}")

    return claude_compatibility


def create_project_context_prompt():
    def project_context(
        ctx: AnyContext,
        current_prompt: str,
        next_handler: Callable[[AnyContext, str], str],
    ) -> str:
        search_dirs = _get_search_directories()
        found_files = []

        for filename in ["CLAUDE.md", "AGENTS.md", "README.md", "GEMINI.md"]:
            for directory in search_dirs:
                file_path = directory / filename
                if file_path.exists() and file_path.is_file():
                    # Check if file has content
                    try:
                        with open(file_path, "r", encoding="utf-8") as f:
                            content = f.read().strip()
                            status = (
                                "exists (has content)" if content else "exists (empty)"
                            )
                    except Exception:
                        status = "exists (unreadable)"

                    found_files.append(f"- `{file_path}` ({status})")
                    break  # Only list first occurrence in search hierarchy

        if found_files:
            context_message = (
                "## Project Documentation Guidelines\n\n"
                "The following project documentation files are available:\n\n"
                + "\n".join(found_files)
                + "\n\n"
                "**SMART DOCUMENTATION USAGE RULES:**\n\n"
                "1. **READ WHEN WORKING ON PROJECT:** Only and always load project documentation "
                "when you need to do anything with the current project. This includes:\n"
                "   - Modifying code or project structure\n"
                "   - Adding tests or changing dependencies\n"
                "   - Making architectural decisions\n"
                "   - Understanding project conventions or patterns\n\n"
                "2. **DO NOT READ when:**\n"
                "   - Having general conversation (e.g., 'what time is it?')\n"
                "   - Answering questions unrelated to project work\n"
                "   - When documentation is not relevant to the current task\n\n"
                "3. **DOCUMENTATION HIERARCHY (TRUTH SOURCE ORDER):**\n"
                "   - **AGENTS.md:** Project laws - OVERRIDES all other documentation (AI Agent documentation)\n"
                "   - **CLAUDE.md/GEMINI.md:** Same as AGENTS.md but built with Claude/Gemini in mind\n"
                "   - **README.md:** For humans, but LLM should read with this consideration\n"
                "   - **NEVER** assume project conventions without explicit verification from these files\n\n"
                "4. **ACTIVE READING REQUIRED:** If documentation is not in system prompt and you need "
                "to work with the project, you MUST use `Read` tool to load it. Documentation must "
                "always be loaded when needed to work with the current project.\n\n"
                "5. **VERIFICATION & CITATION:** After reading project documentation, you should "
                "reference which documentation informed your approach when relevant."
            )
            return next_handler(
                ctx,
                f"{current_prompt}\n\n{make_markdown_section('Project Documentation Guidelines', context_message)}",
            )
        else:
            # No documentation found
            return next_handler(ctx, current_prompt)

    return project_context


def _get_search_directories() -> list[Path]:
    search_dirs: list[Path] = []
    # 1. User global config (~/.claude)
    try:
        home = Path.home()
        search_dirs.append(home / ".claude")
    except Exception:
        pass

    # 2. Project directories (Root -> ... -> CWD)
    try:
        cwd = Path.cwd()
        # Parents returns [parent, grandparent...]. We want reversed (Root first)
        # This allows specific configs (closer to CWD) to override general ones
        project_dirs = list(cwd.parents)[::-1] + [cwd]
        search_dirs.extend(project_dirs)
    except Exception:
        pass
    return search_dirs


def _get_skills_section(
    skill_manager: SkillManager,
    search_dirs: list[Path],
    active_skills: Optional[List[str]] = None,
    include_claude_skills: bool = True,
) -> Optional[str]:
    # Use SkillManager's built-in search directories logic
    skills = skill_manager.scan(search_dirs=skill_manager.get_search_directories())
    if not skills:
        return None

    skills_context = []

    # Add active skills first (if any) with their full content
    if active_skills:
        skills_context.append("## Active Skills (Fully Loaded)")
        for skill_name in active_skills:
            skill_obj = skill_manager.get_skill(skill_name)

            if skill_obj and skill_obj.model_invocable:
                if not include_claude_skills and not skill_name.startswith(
                    "core_mandate_"
                ):
                    continue
                # Get the full skill content
                skill_content = skill_manager.get_skill_content(skill_name)
                if skill_content:
                    skills_context.append(f"### {skill_name}")
                    skills_context.append(skill_content)
                else:
                    # Fallback to description if content can't be loaded
                    skills_context.append(f"- {skill_name}: {skill_obj.description}")
        skills_context.append("")  # Add empty line for separation

    # Add available skills (just metadata)
    skills_context.append("## Available Skills")
    skills_context.append(
        "You MUST ALWAYS use 'ActivateSkill' to load instructions for a skill before execution."
    )
    for skill in skills:
        if skill.model_invocable:
            if not include_claude_skills and not skill.name.startswith("core_mandate_"):
                continue
            # Skip skills that are already active
            if active_skills and skill.name in active_skills:
                continue
            skills_context.append(f"- {skill.name}: {skill.description}")

    return make_markdown_section(
        "Available Skills (Claude Skills)", "\n".join(skills_context)
    )
